// Copyright Epic Games, Inc. All Rights Reserved. #include "StateTreeTest.h" #include "StateTreeTestBase.h" #include "StateTreeTestTypes.h" #include "StateTreeCompilerLog.h" #include "StateTreeEditorData.h" #include "StateTreeCompiler.h" #include "Conditions/StateTreeCommonConditions.h" #define LOCTEXT_NAMESPACE "AITestSuite_StateTreeTest" UE_DISABLE_OPTIMIZATION_SHIP namespace UE::StateTree::Tests { struct FStateTreeTest_TransitionPriority : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); /* - Root - State1 : Task1 -> Succeeded - State1A : Task1A -> Next - State1B : Task1B -> Next - State1C : Task1C Task1A completed first, transitioning to State1B. Task1, Task1B, and Task1C complete at the same time, we should take the transition on the first completed state (State1). */ UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1"))); UStateTreeState& State1A = State1.AddChildState(FName(TEXT("State1A"))); UStateTreeState& State1B = State1.AddChildState(FName(TEXT("State1B"))); UStateTreeState& State1C = State1.AddChildState(FName(TEXT("State1C"))); auto& Task1 = State1.AddTask(FName(TEXT("Task1"))); Task1.GetNode().TicksToCompletion = 2; State1.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded); auto& Task1A = State1A.AddTask(FName(TEXT("Task1A"))); Task1A.GetNode().TicksToCompletion = 1; State1A.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState); auto& Task1B = State1B.AddTask(FName(TEXT("Task1B"))); Task1B.GetNode().TicksToCompletion = 2; State1B.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState); auto& Task1C = State1C.AddTask(FName(TEXT("Task1C"))); Task1C.GetNode().TicksToCompletion = 2; FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); const TCHAR* TickStr(TEXT("Tick")); const TCHAR* EnterStateStr(TEXT("EnterState")); const TCHAR* ExitStateStr(TEXT("ExitState")); const TCHAR* StateCompletedStr(TEXT("StateCompleted")); // Start and enter state Status = Exec.Start(); AITEST_TRUE(TEXT("StateTree Task1 should enter state"), Exec.Expect(Task1.GetName(), EnterStateStr)); AITEST_TRUE(TEXT("StateTree Task1A should enter state"), Exec.Expect(Task1A.GetName(), EnterStateStr)); Exec.LogClear(); // Transition from Task1A to Task1B Status = Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task1A should complete"), Exec.Expect(Task1A.GetName(), StateCompletedStr)); AITEST_TRUE(TEXT("StateTree Task1B should enter state"), Exec.Expect(Task1B.GetName(), EnterStateStr)); Exec.LogClear(); // Task1 completes, and we should take State1 transition. Status = Exec.Tick(0.1f); AITEST_TRUE("StateTree Task1 should complete", Exec.Expect(Task1.GetName(), StateCompletedStr)); AITEST_EQUAL(TEXT("Tree execution should stop on success"), Status, EStateTreeRunStatus::Succeeded); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionPriority, "System.StateTree.Transition.Priority"); struct FStateTreeTest_TransitionPriorityEnterState : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& State0 = Root.AddChildState(FName(TEXT("State0"))); UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1"))); UStateTreeState& State1A = State1.AddChildState(FName(TEXT("State1A"))); UStateTreeState& State2 = Root.AddChildState(FName(TEXT("State2"))); UStateTreeState& State3 = Root.AddChildState(FName(TEXT("State3"))); auto& Task0 = State0.AddTask(FName(TEXT("Task0"))); State0.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &State1); auto& Task1 = State1.AddTask(FName(TEXT("Task1"))); Task1.GetNode().EnterStateResult = EStateTreeRunStatus::Failed; State1.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &State2); auto& Task1A = State1A.AddTask(FName(TEXT("Task1A"))); State1A.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &State3); auto& Task2 = State2.AddTask(FName(TEXT("Task2"))); State2.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded); auto& Task3 = State3.AddTask(FName(TEXT("Task3"))); State3.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); const TCHAR* TickStr(TEXT("Tick")); const TCHAR* EnterStateStr(TEXT("EnterState")); const TCHAR* ExitStateStr(TEXT("ExitState")); const TCHAR* StateCompletedStr(TEXT("StateCompleted")); // Start and enter state Status = Exec.Start(); AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr)); Exec.LogClear(); // Transition from State0 to State1, it should fail (Task1), and the transition on State1->State2 (and not State1A->State3) Status = Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task0 should complete"), Exec.Expect(Task0.GetName(), StateCompletedStr)); AITEST_TRUE(TEXT("StateTree Task2 should enter state"), Exec.Expect(Task2.GetName(), EnterStateStr)); AITEST_FALSE(TEXT("StateTree Task3 should not enter state"), Exec.Expect(Task3.GetName(), EnterStateStr)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionPriorityEnterState, "System.StateTree.Transition.PriorityEnterState"); struct FStateTreeTest_TransitionNextSelectableState : FStateTreeTestBase { virtual bool InstantTest() override { // Root // State0: OnCompleted->Nextselectable // State1: EnterCondition fails // State2: EnterCondition fails // State3: EnterCondition successed. OnCompleted->Succeeded UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& State0 = Root.AddChildState(FName(TEXT("State0"))); UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1"))); UStateTreeState& State2 = Root.AddChildState(FName(TEXT("State2"))); UStateTreeState& State3 = Root.AddChildState(FName(TEXT("State3"))); auto& EvalA = EditorData.AddEvaluator(); EvalA.GetInstanceData().bBoolA = true; auto& Task0 = State0.AddTask(FName(TEXT("Task0"))); State0.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextSelectableState); // Add Task 1 with Condition that will always fail auto& Task1 = State1.AddTask(FName(TEXT("Task1"))); auto& BoolCond1 = State1.AddEnterCondition(); EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), BoolCond1, TEXT("bLeft")); BoolCond1.GetInstanceData().bRight = !EvalA.GetInstanceData().bBoolA; // Add Task 2 with Condition that will always fail auto& Task2 = State2.AddTask(FName(TEXT("Task2"))); auto& BoolCond2 = State2.AddEnterCondition(); EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), BoolCond2, TEXT("bLeft")); BoolCond2.GetInstanceData().bRight = !EvalA.GetInstanceData().bBoolA; // Add Task 3 with Condition that will always succeed auto& Task3 = State3.AddTask(FName(TEXT("Task3"))); auto& BoolCond3 = State3.AddEnterCondition(); State3.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::Succeeded); EditorData.AddPropertyBinding(EvalA, TEXT("bBoolA"), BoolCond3, TEXT("bLeft")); BoolCond3.GetInstanceData().bRight = EvalA.GetInstanceData().bBoolA; FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); const TCHAR* TickStr = TEXT("Tick"); const TCHAR* EnterStateStr = TEXT("EnterState"); const TCHAR* ExitStateStr = TEXT("ExitState"); const TCHAR* StateCompletedStr = TEXT("StateCompleted"); // Start and enter state Exec.Start(); AITEST_TRUE(TEXT("Should be in active state"), Exec.ExpectInActiveStates("Root", "State0")); AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr)); Exec.LogClear(); // Transition from State0 and tries to select State1. It should fail (Task1 and Tasks2) and because transition is set to "Next Selectable", it should now select Task 3 and Enter State Exec.Tick(0.1f); AITEST_TRUE(TEXT("Should be in active state"), Exec.ExpectInActiveStates("Root", "State3")); AITEST_TRUE(TEXT("StateTree Task0 should complete"), Exec.Expect(Task0.GetName(), StateCompletedStr)); AITEST_FALSE(TEXT("StateTree Task1 should not enter state"), Exec.Expect(Task1.GetName(), EnterStateStr)); AITEST_FALSE(TEXT("StateTree Task2 should not enter state"), Exec.Expect(Task2.GetName(), EnterStateStr)); AITEST_TRUE(TEXT("StateTree Task3 should enter state"), Exec.Expect(Task3.GetName(), EnterStateStr)); Exec.LogClear(); // Complete Task3 EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should succeed"), Status, EStateTreeRunStatus::Succeeded); AITEST_TRUE(TEXT("Should be in active state"), Exec.GetActiveStateNames().Num() == 0); AITEST_TRUE(TEXT("StateTree Task3 should complete"), Exec.Expect(Task3.GetName(), StateCompletedStr)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionNextSelectableState, "System.StateTree.Transition.NextSelectableState"); struct FStateTreeTest_TransitionNextWithParentData : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& State0 = Root.AddChildState(FName(TEXT("State0"))); UStateTreeState& State1 = Root.AddChildState(FName(TEXT("State1"))); UStateTreeState& State1A = State1.AddChildState(FName(TEXT("State1A"))); auto& RootTask = Root.AddTask(FName(TEXT("RootTask"))); RootTask.GetInstanceData().bBoolB = true; auto& Task0 = State0.AddTask(FName(TEXT("Task0"))); State0.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::NextState); auto& Task1A = State1A.AddTask(FName(TEXT("Task1A"))); auto& BoolCond1 = State1A.AddEnterCondition(); EditorData.AddPropertyBinding(RootTask, TEXT("bBoolB"), BoolCond1, TEXT("bLeft")); BoolCond1.GetInstanceData().bRight = true; FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); const TCHAR* TickStr(TEXT("Tick")); const TCHAR* EnterStateStr(TEXT("EnterState")); const TCHAR* ExitStateStr(TEXT("ExitState")); const TCHAR* StateCompletedStr(TEXT("StateCompleted")); // Start and enter state Exec.Start(); AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr)); Exec.LogClear(); // Transition from State0 and tries to select State1. // This tests that data from current shared active states (Root) is available during state selection. Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task0 should complete"), Exec.Expect(Task0.GetName(), StateCompletedStr)); AITEST_TRUE(TEXT("StateTree Task1A should enter state"), Exec.Expect(Task1A.GetName(), EnterStateStr)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionNextWithParentData, "System.StateTree.Transition.NextWithParentData"); struct FStateTreeTest_TransitionGlobalDataView : FStateTreeTestBase { // Tests that the global eval and task dataviews are kept up to date when transitioning from virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A"))); UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B"))); auto& EvalA = EditorData.AddEvaluator(FName(TEXT("Eval"))); EvalA.GetInstanceData().IntA = 42; auto& GlobalTask = EditorData.AddGlobalTask(FName(TEXT("Global"))); GlobalTask.GetInstanceData().Value = 123; // State A auto& Task0 = StateA.AddTask(FName(TEXT("Task0"))); StateA.AddTransition(EStateTreeTransitionTrigger::OnStateCompleted, EStateTreeTransitionType::GotoState, &StateB); // State B auto& Task1 = StateB.AddTask(FName(TEXT("Task1"))); EditorData.AddPropertyBinding(EvalA, TEXT("IntA"), Task1, TEXT("Value")); auto& Task2 = StateB.AddTask(FName(TEXT("Task2"))); EditorData.AddPropertyBinding(GlobalTask, TEXT("Value"), Task2, TEXT("Value")); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); const TCHAR* EnterStateStr(TEXT("EnterState")); const TCHAR* EnterState42Str(TEXT("EnterState42")); const TCHAR* EnterState123Str(TEXT("EnterState123")); // Start and enter state Status = Exec.Start(); AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr)); Exec.LogClear(); // Transition from StateA to StateB, Task0 should enter state with evaluator value copied. Status = Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task0 should enter state with value 42"), Exec.Expect(Task1.GetName(), EnterState42Str)); AITEST_TRUE(TEXT("StateTree Task1 should enter state with value 123"), Exec.Expect(Task2.GetName(), EnterState123Str)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionGlobalDataView, "System.StateTree.Transition.GlobalDataView"); struct FStateTreeTest_TransitionDelay : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); const FGameplayTag Tag = GetTestTag1(); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A"))); UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B"))); // State A auto& Task0 = StateA.AddTask(FName(TEXT("Task0"))); Task0.GetNode().TicksToCompletion = 100; FStateTreeTransition& Transition = StateA.AddTransition(EStateTreeTransitionTrigger::OnEvent, EStateTreeTransitionType::GotoState, &StateB); Transition.bDelayTransition = true; Transition.DelayDuration = 0.15f; Transition.DelayRandomVariance = 0.0f; Transition.RequiredEvent.Tag = Tag; // State B auto& Task1 = StateB.AddTask(FName(TEXT("Task1"))); Task1.GetNode().TicksToCompletion = 100; FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); const TCHAR* TickStr(TEXT("Tick")); const TCHAR* EnterStateStr(TEXT("EnterState")); const TCHAR* ExitStateStr(TEXT("ExitState")); const TCHAR* StateCompletedStr(TEXT("StateCompleted")); // Start and enter state Status = Exec.Start(); AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr)); Exec.LogClear(); // This should cause delayed transition. Exec.SendEvent(Tag); Status = Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task0 should tick"), Exec.Expect(Task0.GetName(), TickStr)); Exec.LogClear(); // Should have execution frames AITEST_TRUE(TEXT("Should have active frames"), InstanceData.GetExecutionState()->ActiveFrames.Num() > 0); // Should have delayed transitions const int32 NumDelayedTransitions0 = InstanceData.GetExecutionState()->DelayedTransitions.Num(); AITEST_EQUAL(TEXT("Should have a delayed transition"), NumDelayedTransitions0, 1); // Tick and expect a delayed transition. Status = Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task0 should tick"), Exec.Expect(Task0.GetName(), TickStr)); Exec.LogClear(); const int32 NumDelayedTransitions1 = InstanceData.GetExecutionState()->DelayedTransitions.Num(); AITEST_EQUAL(TEXT("Should have a delayed transition"), NumDelayedTransitions1, 1); // Should complete delayed transition. Status = Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task0 should exit state"), Exec.Expect(Task0.GetName(), ExitStateStr)); AITEST_TRUE(TEXT("StateTree Task1 should enter state"), Exec.Expect(Task1.GetName(), EnterStateStr)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionDelay, "System.StateTree.Transition.Delay"); struct FStateTreeTest_TransitionDelayZero : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); const FGameplayTag Tag = GetTestTag1(); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A"))); UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B"))); // State A auto& Task0 = StateA.AddTask(FName(TEXT("Task0"))); Task0.GetNode().TicksToCompletion = 100; FStateTreeTransition& Transition = StateA.AddTransition(EStateTreeTransitionTrigger::OnEvent, EStateTreeTransitionType::GotoState, &StateB); Transition.bDelayTransition = true; Transition.DelayDuration = 0.0f; Transition.DelayRandomVariance = 0.0f; Transition.RequiredEvent.Tag = Tag; // State B auto& Task1 = StateB.AddTask(FName(TEXT("Task1"))); Task1.GetNode().TicksToCompletion = 100; FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); const TCHAR* TickStr(TEXT("Tick")); const TCHAR* EnterStateStr(TEXT("EnterState")); const TCHAR* ExitStateStr(TEXT("ExitState")); const TCHAR* StateCompletedStr(TEXT("StateCompleted")); // Start and enter state Status = Exec.Start(); AITEST_TRUE(TEXT("StateTree Task0 should enter state"), Exec.Expect(Task0.GetName(), EnterStateStr)); Exec.LogClear(); // This should cause delayed transition. Because the time is 0, it should happen immediately. Exec.SendEvent(Tag); Status = Exec.Tick(0.1f); AITEST_TRUE(TEXT("StateTree Task0 should exit state"), Exec.Expect(Task0.GetName(), ExitStateStr)); AITEST_TRUE(TEXT("StateTree Task1 should enter state"), Exec.Expect(Task1.GetName(), EnterStateStr)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_TransitionDelayZero, "System.StateTree.Transition.DelayZero"); struct FStateTreeTest_PassingTransitionEventToStateSelection : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); FPropertyBindingPath PathToPayloadMember; { const bool bParseResult = PathToPayloadMember.FromString(TEXT("Payload.A")); AITEST_TRUE(TEXT("Parsing path should succeeed"), bParseResult); FStateTreeEvent EventWithPayload; EventWithPayload.Payload = FInstancedStruct::Make(); const bool bUpdateSegments = PathToPayloadMember.UpdateSegmentsFromValue(FStateTreeDataView(FStructView::Make(EventWithPayload))); AITEST_TRUE(TEXT("Updating segments should succeeed"), bUpdateSegments); } // This state shouldn't be selected, because transition's condition and state's enter condition exlude each other. UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A"))); StateA.bHasRequiredEventToEnter = true; StateA.RequiredEventToEnter.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct(); auto& TaskA = StateA.AddTask(FName(TEXT("TaskA"))); TStateTreeEditorNode& AIntCond = StateA.AddEnterCondition(EGenericAICheck::Equal); AIntCond.GetInstanceData().Right = 0; EditorData.AddPropertyBinding( FPropertyBindingPath(StateA.GetEventID(), PathToPayloadMember.GetSegments()), FPropertyBindingPath(AIntCond.ID, TEXT("Left"))); // This state should be selected as the sent event fullfils both transition's condition and state's enter condition. UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B"))); StateB.bHasRequiredEventToEnter = true; StateB.RequiredEventToEnter.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct(); auto& TaskB = StateB.AddTask(FName(TEXT("TaskB"))); // Test copying data from the state event. The condition properties are copied from temp instance data during selection, this gets copied from active instance data. TaskB.GetInstanceData().Value = -1; // Initially -1, expected to be overridden by property binding below. EditorData.AddPropertyBinding( FPropertyBindingPath(StateB.GetEventID(), PathToPayloadMember.GetSegments()), FPropertyBindingPath(TaskB.ID, TEXT("Value"))); TStateTreeEditorNode& BIntCond = StateB.AddEnterCondition(EGenericAICheck::Equal); BIntCond.GetInstanceData().Right = 1; EditorData.AddPropertyBinding( FPropertyBindingPath(StateB.GetEventID(), PathToPayloadMember.GetSegments()), FPropertyBindingPath(BIntCond.ID, TEXT("Left"))); // This state should be selected only initially when there's not event in the queue. UStateTreeState& StateInitial = Root.AddChildState(FName(TEXT("Initial"))); auto& TaskInitial = StateInitial.AddTask(FName(TEXT("TaskInitial"))); // Transition from Initial -> StateA FStateTreeTransition& TransA = StateInitial.AddTransition(EStateTreeTransitionTrigger::OnEvent, FGameplayTag(), EStateTreeTransitionType::GotoState, &StateA); TransA.RequiredEvent.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct(); TStateTreeEditorNode& TransAIntCond = TransA.AddCondition(EGenericAICheck::Equal); TransAIntCond.GetInstanceData().Right = 1; EditorData.AddPropertyBinding( FPropertyBindingPath(TransA.GetEventID(), PathToPayloadMember.GetSegments()), FPropertyBindingPath(TransAIntCond.ID, TEXT("Left"))); // Transition from Initial -> StateB FStateTreeTransition& TransB = StateInitial.AddTransition(EStateTreeTransitionTrigger::OnEvent, FGameplayTag(), EStateTreeTransitionType::GotoState, &StateB); TransB.RequiredEvent.PayloadStruct = FStateTreeTest_PropertyStructA::StaticStruct(); TStateTreeEditorNode& TransBIntCond = TransB.AddCondition(EGenericAICheck::Equal); TransBIntCond.GetInstanceData().Right = 1; EditorData.AddPropertyBinding( FPropertyBindingPath(TransB.GetEventID(), PathToPayloadMember.GetSegments()), FPropertyBindingPath(TransBIntCond.ID, TEXT("Left"))); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); const TCHAR* EnterStateStr(TEXT("EnterState")); Status = Exec.Start(); AITEST_TRUE(TEXT("StateTree TaskInitial should enter state"), Exec.Expect(TaskInitial.GetName(), EnterStateStr)); Exec.LogClear(); // The conditions test for payload Value=1, the first event should not trigger transition. Exec.SendEvent(GetTestTag1(), FConstStructView::Make(FStateTreeTest_PropertyStructA{0})); Exec.SendEvent(GetTestTag1(), FConstStructView::Make(FStateTreeTest_PropertyStructA{1})); Status = Exec.Tick(0.1f); AITEST_FALSE(TEXT("StateTree TaskA should not enter state"), Exec.Expect(TaskA.GetName(), EnterStateStr)); AITEST_TRUE(TEXT("StateTree TaskB should enter state"), Exec.Expect(TaskB.GetName(), TEXT("EnterState1"))); // TaskB decorates "EnterState" with value from the payload. Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_PassingTransitionEventToStateSelection, "System.StateTree.Transition.PassingTransitionEventToStateSelection"); struct FStateTreeTest_FollowTransitions : FStateTreeTestBase { virtual bool InstantTest() override { //Root // Trans // A // B // C UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData); RootPropertyBag.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32); RootPropertyBag.SetValueInt32(FName(TEXT("Int")), 1); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateTrans = Root.AddChildState(FName(TEXT("Trans"))); UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A"))); UStateTreeState& StateB = Root.AddChildState(FName(TEXT("B"))); UStateTreeState& StateC = Root.AddChildState(FName(TEXT("C"))); // Root // Trans { StateTrans.SelectionBehavior = EStateTreeStateSelectionBehavior::TryFollowTransitions; { // This transition should be skipped due to the condition FStateTreeTransition& TransA = StateTrans.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateA); TStateTreeEditorNode& TransIntCond = TransA.AddCondition(EGenericAICheck::Equal); TransIntCond.GetInstanceData().Right = 0; EditorData.AddPropertyBinding( FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")), FPropertyBindingPath(TransIntCond.ID, TEXT("Left"))); } { // This transition leads to selection, but will be overridden. FStateTreeTransition& TransB = StateTrans.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateB); TransB.Priority = EStateTreeTransitionPriority::Normal; TStateTreeEditorNode& TransIntCond = TransB.AddCondition(EGenericAICheck::Equal); TransIntCond.GetInstanceData().Right = 1; EditorData.AddPropertyBinding( FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")), FPropertyBindingPath(TransIntCond.ID, TEXT("Left"))); } { // This transition is selected, should override previous one due to priority. FStateTreeTransition& TransC = StateTrans.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateC); TransC.Priority = EStateTreeTransitionPriority::High; TStateTreeEditorNode& TransIntCond = TransC.AddCondition(EGenericAICheck::Equal); TransIntCond.GetInstanceData().Right = 1; EditorData.AddPropertyBinding( FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")), FPropertyBindingPath(TransIntCond.ID, TEXT("Left"))); } } auto& TaskA = StateA.AddTask(FName(TEXT("TaskA"))); auto& TaskB = StateB.AddTask(FName(TEXT("TaskB"))); auto& TaskC = StateC.AddTask(FName(TEXT("TaskC"))); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); const TCHAR* TickStr = TEXT("Tick"); const TCHAR* EnterStateStr = TEXT("EnterState"); const TCHAR* ExitStateStr = TEXT("ExitState"); Status = Exec.Start(); AITEST_FALSE(TEXT("StateTree TaskA should not enter state"), Exec.Expect(TaskA.GetName(), EnterStateStr)); AITEST_FALSE(TEXT("StateTree TaskB should not enter state"), Exec.Expect(TaskB.GetName(), EnterStateStr)); AITEST_TRUE(TEXT("StateTree TaskC should enter state"), Exec.Expect(TaskC.GetName(), EnterStateStr)); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_FollowTransitions, "System.StateTree.Transition.FollowTransitions"); struct FStateTreeTest_InfiniteLoop : FStateTreeTestBase { virtual bool InstantTest() override { UStateTree& StateTree = NewStateTree(); UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData); RootPropertyBag.AddProperty(FName(TEXT("Int")), EPropertyBagPropertyType::Int32); RootPropertyBag.SetValueInt32(FName(TEXT("Int")), 1); UStateTreeState& Root = EditorData.AddSubTree(FName(TEXT("Root"))); UStateTreeState& StateA = Root.AddChildState(FName(TEXT("A"))); UStateTreeState& StateB = StateA.AddChildState(FName(TEXT("B"))); // Root // State A { StateA.SelectionBehavior = EStateTreeStateSelectionBehavior::TryFollowTransitions; { // A -> B FStateTreeTransition& Trans = StateA.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateB); TStateTreeEditorNode& TransIntCond = Trans.AddCondition(EGenericAICheck::Equal); TransIntCond.GetInstanceData().Right = 1; EditorData.AddPropertyBinding( FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")), FPropertyBindingPath(TransIntCond.ID, TEXT("Left"))); } } // State B { StateB.SelectionBehavior = EStateTreeStateSelectionBehavior::TryFollowTransitions; { // B -> A FStateTreeTransition& Trans = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &StateA); TStateTreeEditorNode& TransIntCond = Trans.AddCondition(EGenericAICheck::Equal); TransIntCond.GetInstanceData().Right = 1; EditorData.AddPropertyBinding( FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Int")), FPropertyBindingPath(TransIntCond.ID, TEXT("Left"))); } } auto& TaskA = StateA.AddTask(FName(TEXT("TaskA"))); auto& TaskB = StateB.AddTask(FName(TEXT("TaskB"))); FStateTreeCompilerLog Log; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); const TCHAR* TickStr = TEXT("Tick"); const TCHAR* EnterStateStr = TEXT("EnterState"); const TCHAR* ExitStateStr = TEXT("ExitState"); GetTestRunner().AddExpectedError(TEXT("Loop detected when trying to select state"), EAutomationExpectedErrorFlags::Contains, 1); GetTestRunner().AddExpectedError(TEXT("Failed to select initial state"), EAutomationExpectedErrorFlags::Contains, 1); Status = Exec.Start(); AITEST_EQUAL(TEXT("Start should fail"), Status, EStateTreeRunStatus::Failed); Exec.LogClear(); Exec.Stop(); return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_InfiniteLoop, "System.StateTree.Transition.InfiniteLoop"); struct FStateTreeTest_RegularTransitions : FStateTreeTestBase { virtual bool InstantTest() override { //Tree 1 // Global task and parameter // RootA // StateB -> Next // StateC -> Next // StateD -> Next // StateE -> Next // StateF -> Next // StateG -> Succeeded FStateTreeCompilerLog Log; // Main asset UStateTree& StateTree = NewStateTree(); { UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); FGuid RootParameter_ValueID; { // Parameters FInstancedPropertyBag& RootPropertyBag = GetRootPropertyBag(EditorData); RootPropertyBag.AddProperty("Value", EPropertyBagPropertyType::Int32); RootPropertyBag.SetValueInt32("Value", -111); RootParameter_ValueID = RootPropertyBag.FindPropertyDescByName("Value")->ID; TStateTreeEditorNode& GlobalTask = EditorData.AddGlobalTask("GlobalTask"); GlobalTask.GetInstanceData().Value = -1; EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(GlobalTask.ID, TEXT("Value"))); } UStateTreeState& Root = EditorData.AddSubTree("RootA"); { TStateTreeEditorNode& Task = Root.AddTask("TaskA"); Task.GetInstanceData().Value = -1; EditorData.AddPropertyBinding(FPropertyBindingPath(EditorData.GetRootParametersGuid(), TEXT("Value")), FPropertyBindingPath(Task.ID, TEXT("Value"))); } { UStateTreeState& StateB = Root.AddChildState("StateB", EStateTreeStateType::State); TStateTreeEditorNode& Task = StateB.AddTask("TaskB"); Task.GetInstanceData().Value = 1; FStateTreeTransition& Transition = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::NextState); Transition.bDelayTransition = true; Transition.DelayDuration = 1.0; } { UStateTreeState& StateB = Root.AddChildState("StateC", EStateTreeStateType::State); TStateTreeEditorNode& Task = StateB.AddTask("TaskC"); Task.GetInstanceData().Value = 2; FStateTreeTransition& Transition = StateB.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::NextState); Transition.bDelayTransition = true; Transition.DelayDuration = 1.0; } { UStateTreeState& StateD = Root.AddChildState("StateD", EStateTreeStateType::State); TStateTreeEditorNode& Task = StateD.AddTask("TaskD"); Task.GetInstanceData().Value = 3; FStateTreeTransition& Transition = StateD.AddTransition(EStateTreeTransitionTrigger::OnTick, EStateTreeTransitionType::GotoState, &Root); Transition.bDelayTransition = true; Transition.DelayDuration = 1.0; } FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE(TEXT("StateTree should get compiled"), bResult); } const TCHAR* EnterStateStr(TEXT("EnterState")); const TCHAR* ExitStateStr(TEXT("ExitState")); { EStateTreeRunStatus Status = EStateTreeRunStatus::Unset; FStateTreeInstanceData InstanceData; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE(TEXT("StateTree should init"), bInitSucceeded); FInstancedPropertyBag Parameters; Parameters.MigrateToNewBagInstance(StateTree.GetDefaultParameters()); Parameters.SetValueInt32("Value", 111); Status = Exec.Start(&Parameters); AITEST_EQUAL(TEXT("Start should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should enter Global tasks"), Exec.Expect("GlobalTask", TEXT("EnterState111"))); AITEST_TRUE(TEXT("Start should enter StateA"), Exec.Expect("TaskA", TEXT("EnterState111"))); AITEST_TRUE(TEXT("Start should enter StateB"), Exec.Expect("TaskB", TEXT("EnterState1"))); Exec.LogClear(); Status = Exec.Tick(1.5f); // over tick, should trigger AITEST_EQUAL(TEXT("1st Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("1st Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111"))); AITEST_TRUE(TEXT("1st Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111"))); AITEST_TRUE(TEXT("1st Tick should tick StateB"), Exec.Expect("TaskB", TEXT("Tick1"))); Exec.LogClear(); // B go to C Status = Exec.Tick(1.0f); AITEST_EQUAL(TEXT("2nd Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("2nd Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111"))); AITEST_TRUE(TEXT("2nd Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111"))); AITEST_TRUE(TEXT("2nd Tick should tick the StateB"), Exec.Expect("TaskB", TEXT("Tick1"))); AITEST_TRUE(TEXT("2nd Tick should exit the StateB"), Exec.Expect("TaskB", TEXT("ExitState1"))); AITEST_TRUE(TEXT("2nd Tick should enter the StateC"), Exec.Expect("TaskC", TEXT("EnterState2"))); AITEST_FALSE(TEXT("2nd Tick should not exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState111"))); AITEST_FALSE(TEXT("2nd Tick should not exit the Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111"))); Exec.LogClear(); Status = Exec.Tick(1.0f); AITEST_EQUAL(TEXT("3rd Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("3rd Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111"))); AITEST_TRUE(TEXT("3rd Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111"))); AITEST_TRUE(TEXT("3rd Tick should tick StateC"), Exec.Expect("TaskC", TEXT("Tick2"))); AITEST_FALSE(TEXT("3th Tick should not exit StateC"), Exec.Expect("TaskD", TEXT("ExitState2"))); Exec.LogClear(); // C go to D Status = Exec.Tick(1.0f); AITEST_EQUAL(TEXT("4th Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("4th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111"))); AITEST_TRUE(TEXT("4th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111"))); AITEST_TRUE(TEXT("4th Tick should tick the StateC"), Exec.Expect("TaskC", TEXT("Tick2"))); AITEST_TRUE(TEXT("4th Tick should exit the StateC"), Exec.Expect("TaskC", TEXT("ExitState2"))); AITEST_TRUE(TEXT("4th Tick should enter the StateD"), Exec.Expect("TaskD", TEXT("EnterState3"))); AITEST_FALSE(TEXT("4th Tick should not exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState111"))); AITEST_FALSE(TEXT("4th Tick should not exit the Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111"))); Exec.LogClear(); Status = Exec.Tick(0.001f); AITEST_EQUAL(TEXT("5th Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("5th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111"))); AITEST_TRUE(TEXT("5th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111"))); AITEST_TRUE(TEXT("5th Tick should tick StateD"), Exec.Expect("TaskD", TEXT("Tick3"))); AITEST_FALSE(TEXT("5th Tick should not exit StateD"), Exec.Expect("TaskD", TEXT("ExitState3"))); Exec.LogClear(); // D go to root Status = Exec.Tick(1.0f); AITEST_EQUAL(TEXT("6th Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("6th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111"))); AITEST_TRUE(TEXT("6th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111"))); AITEST_TRUE(TEXT("6th Tick should tick StateD"), Exec.Expect("TaskD", TEXT("Tick3"))); AITEST_TRUE(TEXT("6th Tick should exit the StateD"), Exec.Expect("TaskD", TEXT("ExitState3"))); AITEST_FALSE(TEXT("6th Tick should not exit the Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111"))); AITEST_FALSE(TEXT("6th Tick should not enter the Global tasks"), Exec.Expect("GlobalTask", TEXT("EnterState111"))); AITEST_TRUE(TEXT("6th Tick should exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState=Sustained"))); AITEST_TRUE(TEXT("6th Tick should enter the StateA"), Exec.Expect("TaskA", TEXT("EnterState=Sustained"))); AITEST_TRUE(TEXT("6th Tick should enter the StateB"), Exec.Expect("TaskB", TEXT("EnterState1"))); AITEST_TRUE(TEXT("6th Tick should enter the StateB"), Exec.Expect("TaskB", TEXT("EnterState=Changed"))); Exec.LogClear(); Status = Exec.Tick(1.0f); AITEST_EQUAL(TEXT("7th Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("7th Tick should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("Tick111"))); AITEST_TRUE(TEXT("7th Tick should tick StateA"), Exec.Expect("TaskA", TEXT("Tick111"))); AITEST_TRUE(TEXT("7th Tick should tick StateB"), Exec.Expect("TaskB", TEXT("Tick1"))); Exec.LogClear(); Exec.Stop(); AITEST_TRUE(TEXT("Stop Tick should exit the StateB"), Exec.Expect("TaskB", TEXT("ExitState1"))); AITEST_TRUE(TEXT("Stop Tick should exit the StateA"), Exec.Expect("TaskA", TEXT("ExitState111"))); AITEST_TRUE(TEXT("Stop should tick Global tasks"), Exec.Expect("GlobalTask", TEXT("ExitState111"))); Exec.LogClear(); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_RegularTransitions, "System.StateTree.Transition.RegularTransitions"); struct FStateTreeTest_RequestTransition : FStateTreeTestBase { virtual bool InstantTest() override { //Tree 1 //RootA // StateB // StateC // StateD // StateE // StateF // StateI // StateJ // StateX // StateY //RootO // StateP FStateTreeCompilerLog Log; enum class ECustomFunctionToRun { None, TransitionD_To_E, TransitionB_To_I, TransitionB_To_C, TransitionC_To_B, TransitionB_To_X, TransitionB_To_B, TransitionB_To_P, } TransitionToExecute = ECustomFunctionToRun::None; struct FStateHandle { FStateTreeStateHandle B; FStateTreeStateHandle C; FStateTreeStateHandle E; FStateTreeStateHandle I; FStateTreeStateHandle X; FStateTreeStateHandle P; FGuid B_ID; FGuid C_ID; FGuid E_ID; FGuid I_ID; FGuid X_ID; FGuid P_ID; bool bFinishTasksB = false; bool bFinishTasksC = false; bool bFinishTasksD = false; } AllStateHandle; // Main asset UStateTree& StateTree = NewStateTree(); { UStateTreeEditorData& EditorData = *Cast(StateTree.EditorData); UStateTreeState& StateA = EditorData.AddSubTree("StateA"); StateA.AddTask("StateATask"); UStateTreeState& StateB = StateA.AddChildState("StateB", EStateTreeStateType::State); FTestTask_PrintValue& TaskB = StateB.AddTask("StateBTask").GetNode(); AllStateHandle.B_ID = StateB.ID; UStateTreeState& StateC = StateB.AddChildState("StateC", EStateTreeStateType::State); FTestTask_PrintValue& TaskC = StateC.AddTask("StateCTask").GetNode(); AllStateHandle.C_ID = StateC.ID; UStateTreeState& StateD = StateC.AddChildState("StateD", EStateTreeStateType::State); FTestTask_PrintValue& TaskD = StateD.AddTask("StateDTask").GetNode(); StateD.SelectionBehavior = EStateTreeStateSelectionBehavior::TryEnterState; UStateTreeState& StateE = StateD.AddChildState("StateE", EStateTreeStateType::State); StateE.AddTask("StateETask"); AllStateHandle.E_ID = StateE.ID; UStateTreeState& StateF = StateE.AddChildState("StateF", EStateTreeStateType::State); StateF.AddTask("StateFTask"); UStateTreeState& StateI = StateB.AddChildState("StateI", EStateTreeStateType::State); StateI.AddTask("StateITask"); AllStateHandle.I_ID = StateI.ID; UStateTreeState& StateJ = StateI.AddChildState("StateJ", EStateTreeStateType::State); StateJ.AddTask("StateJTask"); UStateTreeState& StateX = StateA.AddChildState("StateX", EStateTreeStateType::State); StateX.AddTask("StateXTask"); AllStateHandle.X_ID = StateX.ID; UStateTreeState& StateY = StateX.AddChildState("StateY", EStateTreeStateType::State); StateY.AddTask("StateYTask"); UStateTreeState& StateO = EditorData.AddSubTree("StateO"); StateO.AddTask("StateOTask"); UStateTreeState& StateP = StateO.AddChildState("StateP", EStateTreeStateType::State); StateP.AddTask("StatePTask"); AllStateHandle.P_ID = StateP.ID; TaskB.CustomTickFunc = [&TransitionToExecute, &AllStateHandle] (FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_I) { Context.RequestTransition(AllStateHandle.I); } if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_C) { Context.RequestTransition(AllStateHandle.C); } if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_X) { Context.RequestTransition(AllStateHandle.X); } if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_B) { Context.RequestTransition(AllStateHandle.B); } if (TransitionToExecute == ECustomFunctionToRun::TransitionB_To_P) { Context.RequestTransition(AllStateHandle.P); } if (AllStateHandle.bFinishTasksB) { Context.FinishTask(*Task, EStateTreeFinishTaskType::Succeeded); } }; TaskC.CustomTickFunc = [&TransitionToExecute, &AllStateHandle] (FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { if (TransitionToExecute == ECustomFunctionToRun::TransitionC_To_B) { Context.RequestTransition(AllStateHandle.B); } if (AllStateHandle.bFinishTasksC) { Context.FinishTask(*Task, EStateTreeFinishTaskType::Succeeded); } }; TaskD.CustomTickFunc = [&TransitionToExecute, &AllStateHandle] (FStateTreeExecutionContext& Context, const FTestTask_PrintValue* Task) { if (TransitionToExecute == ECustomFunctionToRun::TransitionD_To_E) { Context.RequestTransition(AllStateHandle.E); } if (AllStateHandle.bFinishTasksD) { Context.FinishTask(*Task, EStateTreeFinishTaskType::Succeeded); } }; FStateTreeCompiler Compiler(Log); const bool bResult = Compiler.Compile(StateTree); AITEST_TRUE("StateTree should get compiled", bResult); AllStateHandle.B = StateTree.GetStateHandleFromId(AllStateHandle.B_ID); AllStateHandle.C = StateTree.GetStateHandleFromId(AllStateHandle.C_ID); AllStateHandle.E = StateTree.GetStateHandleFromId(AllStateHandle.E_ID); AllStateHandle.I = StateTree.GetStateHandleFromId(AllStateHandle.I_ID); AllStateHandle.X = StateTree.GetStateHandleFromId(AllStateHandle.X_ID); AllStateHandle.P = StateTree.GetStateHandleFromId(AllStateHandle.P_ID); } FStateTreeInstanceData InstanceData; { FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const bool bInitSucceeded = Exec.IsValid(); AITEST_TRUE("StateTree should init", bInitSucceeded); } auto ResetInstanceData = [this, &StateTree, &InstanceData]() { { FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); EStateTreeRunStatus Status = Exec.Stop(); AITEST_EQUAL(TEXT("Should stop"), Status, EStateTreeRunStatus::Stopped); } InstanceData = FStateTreeInstanceData(); { FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Start(); AITEST_EQUAL(TEXT("State should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD")); } return true; }; { FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Start(); AITEST_EQUAL("Start should complete with Running", Status, EStateTreeRunStatus::Running); AITEST_TRUE("Start should enter Global tasks", Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Start StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Start StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Start StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Start StateD"), LogOrder); AITEST_FALSE(TEXT("Tick StateA"), Exec.Expect("StateATask", TEXT("Tick0"))); AITEST_FALSE(TEXT("Tick StateB"), Exec.Expect("StateBTask", TEXT("Tick0"))); AITEST_FALSE(TEXT("Tick StateC"), Exec.Expect("StateCTask", TEXT("Tick0"))); AITEST_FALSE(TEXT("Tick StateD"), Exec.Expect("StateDTask", TEXT("Tick0"))); AITEST_FALSE(TEXT("Start StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Start StateB"), Exec.Expect("StateBTask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Start StateC"), Exec.Expect("StateCTask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Start StateD"), Exec.Expect("StateDTask", TEXT("ExitState0"))); Exec.LogClear(); } { FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL("1st Tick should complete with Running", Status, EStateTreeRunStatus::Running); AITEST_TRUE("1st Tick no transition", Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateC"), Exec.Expect("StateCTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateD"), Exec.Expect("StateDTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateC"), Exec.Expect("StateCTask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateD"), Exec.Expect("StateDTask", TEXT("ExitState0"))); Exec.LogClear(); } { TransitionToExecute = ECustomFunctionToRun::TransitionD_To_E; FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("2nd Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("2nd tick sold be in new state"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD", "StateE", "StateF")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); LogOrder = LogOrder.Then("StateETask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateE"), LogOrder); LogOrder = LogOrder.Then("StateFTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateF"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateC"), Exec.Expect("StateCTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateD"), Exec.Expect("StateDTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateC"), Exec.Expect("StateCTask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateD"), Exec.Expect("StateDTask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } // Same transition but with a completed D { TransitionToExecute = ECustomFunctionToRun::TransitionD_To_E; AllStateHandle.bFinishTasksD = true; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD", "StateE", "StateF")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); //@TODO State D completed. It should be a new state. //LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed")); //AITEST_TRUE(TEXT("Exit StateD"), LogOrder); //@TODO State D completed. It should be a new state. //LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Changed")); //AITEST_TRUE(TEXT("Enter StateD"), LogOrder); LogOrder = LogOrder.Then("StateETask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateE"), LogOrder); LogOrder = LogOrder.Then("StateFTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateF"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateC"), Exec.Expect("StateCTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateC"), Exec.Expect("StateCTask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; AllStateHandle.bFinishTasksD = false; } { TransitionToExecute = ECustomFunctionToRun::TransitionB_To_I; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateI", "StateJ")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateD"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateC"), LogOrder); LogOrder = LogOrder.Then("StateITask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateI"), LogOrder); LogOrder = LogOrder.Then("StateJTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateJ"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } { TransitionToExecute = ECustomFunctionToRun::TransitionB_To_C; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateD"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateC"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateD"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Enter StateB"), Exec.Expect("StateBTask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); AITEST_FALSE(TEXT("Exit StateB"), Exec.Expect("StateBTask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } { TransitionToExecute = ECustomFunctionToRun::TransitionC_To_B; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateD"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateC"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateB"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateD"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } { TransitionToExecute = ECustomFunctionToRun::TransitionC_To_B; AllStateHandle.bFinishTasksC = true; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); //@TODO State C completed. It should change for another state //LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed")); //AITEST_TRUE(TEXT("Exit StateD"), LogOrder); //@TODO State C completed. It should change for another state //LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed")); //AITEST_TRUE(TEXT("Exit StateC"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateB"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateB"), LogOrder); //@TODO State C completed. It should be a new state. //LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Changed")); //AITEST_TRUE(TEXT("Enter StateC"), LogOrder); //@TODO State C completed. It should be a new state. //LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Changed")); //AITEST_TRUE(TEXT("Enter StateD"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } { TransitionToExecute = ECustomFunctionToRun::TransitionB_To_X; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateX", "StateY")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateD"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateC"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateB"), LogOrder); LogOrder = LogOrder.Then("StateXTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateX"), LogOrder); LogOrder = LogOrder.Then("StateYTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateY"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } { TransitionToExecute = ECustomFunctionToRun::TransitionB_To_B; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateA", "StateB", "StateC", "StateD")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateD"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateC"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Sustained")); AITEST_TRUE(TEXT("Exit StateB"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("EnterState=Sustained")); AITEST_TRUE(TEXT("Enter StateD"), LogOrder); AITEST_FALSE(TEXT("Enter StateA"), Exec.Expect("StateATask", TEXT("EnterState0"))); AITEST_FALSE(TEXT("Exit StateA"), Exec.Expect("StateATask", TEXT("ExitState0"))); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } { TransitionToExecute = ECustomFunctionToRun::TransitionB_To_P; if (!ResetInstanceData()) { return false; } FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); const EStateTreeRunStatus Status = Exec.Tick(0.1f); AITEST_EQUAL(TEXT("Tick should complete with Running"), Status, EStateTreeRunStatus::Running); AITEST_TRUE(TEXT("Start should"), Exec.ExpectInActiveStates("StateO", "StateP")); FTestStateTreeExecutionContext::FLogOrder LogOrder = Exec.Expect("StateATask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateA"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateB"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateC"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("Tick0")); AITEST_TRUE(TEXT("Tick StateD"), LogOrder); LogOrder = LogOrder.Then("StateDTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateD"), LogOrder); LogOrder = LogOrder.Then("StateCTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateC"), LogOrder); LogOrder = LogOrder.Then("StateBTask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateB"), LogOrder); LogOrder = LogOrder.Then("StateATask", TEXT("ExitState=Changed")); AITEST_TRUE(TEXT("Exit StateA"), LogOrder); LogOrder = LogOrder.Then("StateOTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateO"), LogOrder); LogOrder = LogOrder.Then("StatePTask", TEXT("EnterState=Changed")); AITEST_TRUE(TEXT("Enter StateP"), LogOrder); Exec.LogClear(); TransitionToExecute = ECustomFunctionToRun::None; } { FTestStateTreeExecutionContext Exec(StateTree, StateTree, InstanceData); EStateTreeRunStatus Status = Exec.Stop(); AITEST_EQUAL(TEXT("Should stop"), Status, EStateTreeRunStatus::Stopped); } return true; } }; IMPLEMENT_AI_INSTANT_TEST(FStateTreeTest_RequestTransition, "System.StateTree.Transition.RequestTransition"); } // namespace UE::StateTree::Tests UE_ENABLE_OPTIMIZATION_SHIP #undef LOCTEXT_NAMESPACE