Files
UnrealEngine/Engine/Plugins/Runtime/GameplayInteractions/Source/GameplayInteractionsModule/Private/GameplayInteractionContext.cpp
2025-05-18 13:04:45 +08:00

252 lines
8.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameplayInteractionContext.h"
#include "GameplayInteractionSmartObjectBehaviorDefinition.h"
#include "Engine/World.h"
#include "StateTreeExecutionContext.h"
#include "VisualLogger/VisualLogger.h"
#include "GameplayInteractionStateTreeSchema.h"
#include "SmartObjectSubsystem.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameplayInteractionContext)
bool FGameplayInteractionContext::Activate(const UGameplayInteractionSmartObjectBehaviorDefinition& InDefinition)
{
Definition = &InDefinition;
check(Definition);
const FStateTreeReference& StateTreeReference = Definition->StateTreeReference;
const UStateTree* StateTree = StateTreeReference.GetStateTree();
if (!IsValid())
{
UE_LOG(LogGameplayInteractions, Error, TEXT("Failed to activate interaction. Context is not properly setup."));
return false;
}
if (StateTree == nullptr)
{
UE_VLOG_UELOG(ContextActor, LogGameplayInteractions, Error,
TEXT("Failed to activate interaction for %s. Definition %s doesn't point to a valid StateTree asset."),
*GetNameSafe(ContextActor),
*Definition.GetFullName());
return false;
}
FStateTreeExecutionContext StateTreeContext(*ContextActor, *StateTree, StateTreeInstanceData);
if (!StateTreeContext.IsValid())
{
UE_VLOG_UELOG(ContextActor, LogGameplayInteractions, Error,
TEXT("Failed to activate interaction for %s. Unable to initialize StateTree execution context for StateTree asset: %s."),
*GetNameSafe(ContextActor),
*StateTree->GetFullName());
return false;
}
if (!ValidateSchema(StateTreeContext))
{
return false;
}
if (!SetContextRequirements(StateTreeContext))
{
UE_VLOG_UELOG(ContextActor, LogGameplayInteractions, Error,
TEXT("Failed to activate interaction for %s. Unable to provide all external data views for StateTree asset: %s."),
*GetNameSafe(ContextActor),
*StateTree->GetFullName());
return false;
}
// Set slot user actor
USmartObjectSubsystem* SmartObjectSubsystem = USmartObjectSubsystem::GetCurrent(ContextActor->GetWorld());
check(SmartObjectSubsystem);
SmartObjectSubsystem->MutateSlotData(ClaimedHandle.SlotHandle, [this, SmartObjectSubsystem](const FSmartObjectSlotView& SlotView)
{
if (FGameplayInteractionSlotUserData* UserData = SlotView.GetMutableStateDataPtr<FGameplayInteractionSlotUserData>())
{
UserData->UserActor = ContextActor;
}
else
{
SmartObjectSubsystem->AddSlotData(ClaimedHandle, FConstStructView::Make(FGameplayInteractionSlotUserData(ContextActor)));
}
});
// Start State Tree
StateTreeContext.Start(&StateTreeReference.GetParameters());
return true;
}
bool FGameplayInteractionContext::Tick(const float DeltaTime)
{
if (Definition == nullptr || !IsValid())
{
return false;
}
const FStateTreeReference& StateTreeReference = Definition->StateTreeReference;
const UStateTree* StateTree = StateTreeReference.GetStateTree();
if (StateTree == nullptr)
{
return false;
}
FStateTreeExecutionContext StateTreeContext(*ContextActor, *StateTree, StateTreeInstanceData);
EStateTreeRunStatus RunStatus = EStateTreeRunStatus::Unset;
if (SetContextRequirements(StateTreeContext))
{
RunStatus = StateTreeContext.Tick(DeltaTime);
}
LastRunStatus = RunStatus;
return RunStatus == EStateTreeRunStatus::Running;
}
void FGameplayInteractionContext::Deactivate()
{
if (Definition == nullptr || ContextActor == nullptr)
{
return;
}
const FStateTreeReference& StateTreeReference = Definition->StateTreeReference;
const UStateTree* StateTree = StateTreeReference.GetStateTree();
if (StateTree == nullptr)
{
UE_VLOG_UELOG(ContextActor, LogGameplayInteractions, Error,
TEXT("Failed to deactivate interaction for %s. Definition %s doesn't point to a valid StateTree asset."),
*GetNameSafe(ContextActor),
*Definition.GetFullName());
return;
}
FStateTreeExecutionContext StateTreeContext(ContextActor, StateTree, StateTreeInstanceData);
if (SetContextRequirements(StateTreeContext))
{
StateTreeContext.Stop();
}
// Remove user
const USmartObjectSubsystem* SmartObjectSubsystem = USmartObjectSubsystem::GetCurrent(ContextActor->GetWorld());
check(SmartObjectSubsystem);
SmartObjectSubsystem->MutateSlotData(ClaimedHandle.SlotHandle, [](const FSmartObjectSlotView& SlotView)
{
if (FGameplayInteractionSlotUserData* UserData = SlotView.GetMutableStateDataPtr<FGameplayInteractionSlotUserData>())
{
UserData->UserActor = nullptr;
}
});
}
void FGameplayInteractionContext::SendEvent(const FGameplayTag Tag, const FConstStructView Payload, const FName Origin)
{
if (Definition == nullptr || !IsValid())
{
return;
}
const FStateTreeReference& StateTreeReference = Definition->StateTreeReference;
const UStateTree* StateTree = StateTreeReference.GetStateTree();
if (!StateTree)
{
return;
}
FStateTreeMinimalExecutionContext StateTreeContext(ContextActor, StateTree, StateTreeInstanceData);
StateTreeContext.SendEvent(Tag, Payload, Origin);
}
bool FGameplayInteractionContext::ValidateSchema(const FStateTreeExecutionContext& StateTreeContext) const
{
// Ensure that the actor and smart object match the schema.
const UGameplayInteractionStateTreeSchema* Schema = Cast<UGameplayInteractionStateTreeSchema>(StateTreeContext.GetStateTree()->GetSchema());
if (Schema == nullptr)
{
UE_VLOG_UELOG(ContextActor, LogGameplayInteractions, Error,
TEXT("Failed to activate interaction for %s. Expecting %s schema for StateTree asset: %s."),
*GetNameSafe(ContextActor),
*GetNameSafe(UGameplayInteractionStateTreeSchema::StaticClass()),
*GetFullNameSafe(StateTreeContext.GetStateTree()));
return false;
}
if (!ContextActor || !ContextActor->IsA(Schema->GetContextActorClass()))
{
UE_VLOG_UELOG(ContextActor, LogGameplayInteractions, Error,
TEXT("Failed to activate interaction for %s. Expecting Actor to be of type %s (found %s) for StateTree asset: %s."),
*GetNameSafe(ContextActor),
*GetNameSafe(Schema->GetContextActorClass()),
*GetNameSafe(ContextActor ? ContextActor->GetClass() : nullptr),
*GetFullNameSafe(StateTreeContext.GetStateTree()));
return false;
}
if (!SmartObjectActor || !SmartObjectActor->IsA(Schema->GetSmartObjectActorClass()))
{
UE_VLOG_UELOG(ContextActor, LogGameplayInteractions, Error,
TEXT("Failed to activate interaction for %s. SmartObject Actor to be of type %s (found %s) for StateTree asset: %s."),
*GetNameSafe(ContextActor),
*GetNameSafe(Schema->GetSmartObjectActorClass()),
*GetNameSafe(SmartObjectActor ? SmartObjectActor->GetClass() : nullptr),
*GetFullNameSafe(StateTreeContext.GetStateTree()));
return false;
}
return true;
}
bool FGameplayInteractionContext::SetContextRequirements(FStateTreeExecutionContext& StateTreeContext)
{
if (!StateTreeContext.IsValid())
{
return false;
}
if (!::IsValid(Definition))
{
return false;
}
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::ContextActor, FStateTreeDataView(ContextActor));
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::SmartObjectActor, FStateTreeDataView(SmartObjectActor));
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::SmartObjectClaimedHandle, FStateTreeDataView(FStructView::Make(ClaimedHandle)));
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::SlotEntranceHandle, FStateTreeDataView(FStructView::Make(SlotEntranceHandle)));
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::AbortContext, FStateTreeDataView(FStructView::Make(AbortContext)));
checkf(ContextActor != nullptr, TEXT("Should never reach this point with an invalid ContextActor since it is required to get a valid StateTreeContext."));
const UWorld* World = ContextActor->GetWorld();
StateTreeContext.SetCollectExternalDataCallback(FOnCollectStateTreeExternalData::CreateLambda(
[World, ContextActor = ContextActor]
(const FStateTreeExecutionContext& Context, const UStateTree* StateTree, TArrayView<const FStateTreeExternalDataDesc> ExternalDescs, TArrayView<FStateTreeDataView> OutDataViews)
{
check(ExternalDescs.Num() == OutDataViews.Num());
for (int32 Index = 0; Index < ExternalDescs.Num(); Index++)
{
const FStateTreeExternalDataDesc& Desc = ExternalDescs[Index];
if (Desc.Struct != nullptr)
{
if (World != nullptr && Desc.Struct->IsChildOf(UWorldSubsystem::StaticClass()))
{
UWorldSubsystem* Subsystem = World->GetSubsystemBase(Cast<UClass>(const_cast<UStruct*>(ToRawPtr(Desc.Struct))));
OutDataViews[Index] = FStateTreeDataView(Subsystem);
}
else if (Desc.Struct->IsChildOf(AActor::StaticClass()))
{
OutDataViews[Index] = FStateTreeDataView(ContextActor);
}
}
}
return true;
})
);
return StateTreeContext.AreContextDataViewsValid();
}