// 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()) { 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()) { 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(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 ExternalDescs, TArrayView 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(const_cast(ToRawPtr(Desc.Struct)))); OutDataViews[Index] = FStateTreeDataView(Subsystem); } else if (Desc.Struct->IsChildOf(AActor::StaticClass())) { OutDataViews[Index] = FStateTreeDataView(ContextActor); } } } return true; }) ); return StateTreeContext.AreContextDataViewsValid(); }