#include "SUDSDialogue.h" #include "SUDSLibrary.h" #include "SUDSMessageLogger.h" #include "SUDSScript.h" #include "SUDSScriptImporter.h" #include "TestUtils.h" #include "Misc/AutomationTest.h" UE_DISABLE_OPTIMIZATION const FString GotoGosubInput = R"RAWSUD( Player: Hello there NPC: Hello :choice * Actually bye NPC: How rude [goto end] * Reused NPC: This is going to re-used dialogue [gosub subroutine] NPC: And now we're back [goto goodbye] * Gosub choice NPC: This is going to return to the choice [gosub subroutine] * Choice After NPC: It's a choice after a gosub! [goto goodbye] * Choice After 2 NPC: Not really much difference eh [goto goodbye] * Gosub choice via goto NPC: This is going to return to the choice via goto [gosub subroutine] [goto choice] :subroutine Player: Some reused discussion NPC: Yep, sure is [return] :goodbye NPC: Bye! )RAWSUD"; IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestGotoGosub, "SUDSTest.TestGotoGosub", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestGotoGosub::RunTest(const FString& Parameters) { FSUDSMessageLogger Logger(false); FSUDSScriptImporter Importer; TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GotoGosubInput), GotoGosubInput.Len(), "GotoGosubInput", &Logger, true)); auto Script = NewObject(GetTransientPackage(), "Test"); const ScopedStringTableHolder StringTableHolder; Importer.PopulateAsset(Script, StringTableHolder.StringTable); // Script shouldn't be the owner of the dialogue but it's the only UObject we've got right now so why not auto Dlg = USUDSLibrary::CreateDialogue(Script, Script); Dlg->Start(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Hello"); TestEqual("Choices", Dlg->GetNumberOfChoices(), 4); TestEqual("Choice 0 text", Dlg->GetChoiceText(0).ToString(), "Actually bye"); TestTrue("Choose 0", Dlg->Choose(0)); TestDialogueText(this, "Next", Dlg, "NPC", "How rude"); TestFalse("End", Dlg->Continue()); Dlg->Restart(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Hello"); TestTrue("Choose 1", Dlg->Choose(1)); TestDialogueText(this, "Next", Dlg, "NPC", "This is going to re-used dialogue"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "Player", "Some reused discussion"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Yep, sure is"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "And now we're back"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Bye!"); TestFalse("Continue", Dlg->Continue()); Dlg->Restart(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Hello"); TestTrue("Choose 2", Dlg->Choose(2)); TestDialogueText(this, "Next", Dlg, "NPC", "This is going to return to the choice"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "Player", "Some reused discussion"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Yep, sure is"); // Should have a choice at the end of it this time TestEqual("Choices", Dlg->GetNumberOfChoices(), 2); TestEqual("Choice 0 text", Dlg->GetChoiceText(0).ToString(), "Choice After"); TestEqual("Choice 1 text", Dlg->GetChoiceText(1).ToString(), "Choice After 2"); TestTrue("Choose 1", Dlg->Choose(1)); TestDialogueText(this, "Next", Dlg, "NPC", "Not really much difference eh"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Bye!"); TestFalse("Continue", Dlg->Continue()); Dlg->Restart(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Hello"); TestTrue("Choose 2", Dlg->Choose(3)); TestDialogueText(this, "Next", Dlg, "NPC", "This is going to return to the choice via goto"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "Player", "Some reused discussion"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Next", Dlg, "NPC", "Yep, sure is"); // Should have a choice at the end of it this time TestEqual("Choices", Dlg->GetNumberOfChoices(), 4); TestEqual("Choice 0 text", Dlg->GetChoiceText(0).ToString(), "Actually bye"); TestTrue("Choose 0", Dlg->Choose(0)); TestDialogueText(this, "Next", Dlg, "NPC", "How rude"); TestFalse("End", Dlg->Continue()); Script->MarkAsGarbage(); return true; } const FString NestedGosubInput = R"RAWSUD( Player: Hello there [gosub sub1] NPC: Back at level 0 [goto goodbye] :sub1 Player: This is level 1 [gosub sub2_1] [gosub sub2_2] Player: End of level 1 [return] :sub2_1 Player: This is level 2, sub 1 [return] :sub2_2 Player: This is level 2, sub 2 NPC: We have to go deeper [gosub sub3] Player: End of level 2, sub 2 [return] :sub3 Player: This is level 3 [return] :goodbye NPC: Bye! )RAWSUD"; IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestNestedGosub, "SUDSTest.TestNestedGosub", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestNestedGosub::RunTest(const FString& Parameters) { FSUDSMessageLogger Logger(false); FSUDSScriptImporter Importer; TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(NestedGosubInput), NestedGosubInput.Len(), "NestedGosubInput", &Logger, true)); auto Script = NewObject(GetTransientPackage(), "Test"); const ScopedStringTableHolder StringTableHolder; Importer.PopulateAsset(Script, StringTableHolder.StringTable); // Script shouldn't be the owner of the dialogue but it's the only UObject we've got right now so why not auto Dlg = USUDSLibrary::CreateDialogue(Script, Script); Dlg->Start(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "Player", "This is level 1"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "Player", "This is level 2, sub 1"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "Player", "This is level 2, sub 2"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "NPC", "We have to go deeper"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "Player", "This is level 3"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "Player", "End of level 2, sub 2"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "Player", "End of level 1"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Sub", Dlg, "NPC", "Back at level 0"); TestTrue("Continue", Dlg->Continue()); TestDialogueText(this, "Bye", Dlg, "NPC", "Bye!"); TestFalse("Continue", Dlg->Continue()); Script->MarkAsGarbage(); return true; } const FString GotoBetweenSpeakerAndChoiceInput = R"RAWSUD( Player: Hello there [if {SkipChoices}] [goto end] [endif] * Option A * Option B )RAWSUD"; IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestGotoBetweenSpeakerAndChoice1, "SUDSTest.TestGotoBetweenSpeakerAndChoice1", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestGotoBetweenSpeakerAndChoice1::RunTest(const FString& Parameters) { FSUDSMessageLogger Logger(false); FSUDSScriptImporter Importer; TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GotoBetweenSpeakerAndChoiceInput), GotoBetweenSpeakerAndChoiceInput.Len(), "GotoBetweenSpeakerAndChoiceInput", &Logger, true)); auto Script = NewObject(GetTransientPackage(), "Test"); const ScopedStringTableHolder StringTableHolder; Importer.PopulateAsset(Script, StringTableHolder.StringTable); // Script shouldn't be the owner of the dialogue but it's the only UObject we've got right now so why not auto Dlg = USUDSLibrary::CreateDialogue(Script, Script); Dlg->Start(); if (TestEqual("Choice Count", Dlg->GetNumberOfChoices(), 2)) { TestEqual("Choice 1", Dlg->GetChoiceText(0).ToString(), "Option A"); TestEqual("Choice 2", Dlg->GetChoiceText(1).ToString(), "Option B"); } Script->MarkAsGarbage(); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestGotoBetweenSpeakerAndChoice2, "SUDSTest.TestGotoBetweenSpeakerAndChoice2", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestGotoBetweenSpeakerAndChoice2::RunTest(const FString& Parameters) { FSUDSMessageLogger Logger(false); FSUDSScriptImporter Importer; TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GotoBetweenSpeakerAndChoiceInput), GotoBetweenSpeakerAndChoiceInput.Len(), "GotoBetweenSpeakerAndChoiceInput", &Logger, true)); auto Script = NewObject(GetTransientPackage(), "Test"); const ScopedStringTableHolder StringTableHolder; Importer.PopulateAsset(Script, StringTableHolder.StringTable); // Script shouldn't be the owner of the dialogue but it's the only UObject we've got right now so why not auto Dlg = USUDSLibrary::CreateDialogue(Script, Script); Dlg->SetVariableBoolean("SkipChoices", true); Dlg->Start(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Plain continue", Dlg->IsSimpleContinue()); Script->MarkAsGarbage(); return true; } const FString GosubBetweenSpeakerAndChoiceInput1 = R"RAWSUD( Player: Hello there [gosub MaybeSkipChoices] * Option A * Option B [goto end] :MaybeSkipChoices [if {SkipChoices}] [goto end] [endif] [return] )RAWSUD"; const FString GosubBetweenSpeakerAndChoiceInput2 = R"RAWSUD( Player: Hello there [gosub PrintDebug] [gosub MaybeSkipChoices] * Option A * Option B [goto end] :PrintDebug Debug: SkipChoices is {SkipChoices} [return] :MaybeSkipChoices [if {SkipChoices}] [goto end] [endif] Speaker: Another sentence. [return] )RAWSUD"; IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestGosubBetweenSpeakerAndChoice1, "SUDSTest.TestGosubBetweenSpeakerAndChoice1", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestGosubBetweenSpeakerAndChoice1::RunTest(const FString& Parameters) { FSUDSMessageLogger Logger(false); FSUDSScriptImporter Importer; TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GosubBetweenSpeakerAndChoiceInput1), GosubBetweenSpeakerAndChoiceInput1.Len(), "GosubBetweenSpeakerAndChoiceInput1", &Logger, true)); auto Script = NewObject(GetTransientPackage(), "Test"); const ScopedStringTableHolder StringTableHolder; Importer.PopulateAsset(Script, StringTableHolder.StringTable); // Script shouldn't be the owner of the dialogue but it's the only UObject we've got right now so why not auto Dlg = USUDSLibrary::CreateDialogue(Script, Script); Dlg->Start(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); if (TestEqual("Choice Count", Dlg->GetNumberOfChoices(), 2)) { TestEqual("Choice 1", Dlg->GetChoiceText(0).ToString(), "Option A"); TestEqual("Choice 2", Dlg->GetChoiceText(1).ToString(), "Option B"); } // Now test the true case Dlg->SetVariableBoolean("SkipChoices", true); Dlg->Restart(false); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Plain continue", Dlg->IsSimpleContinue()); Script->MarkAsGarbage(); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestGosubBetweenSpeakerAndChoice2, "SUDSTest.TestGosubBetweenSpeakerAndChoice2", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::ProductFilter) bool FTestGosubBetweenSpeakerAndChoice2::RunTest(const FString& Parameters) { FSUDSMessageLogger Logger(false); FSUDSScriptImporter Importer; TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GosubBetweenSpeakerAndChoiceInput2), GosubBetweenSpeakerAndChoiceInput2.Len(), "GosubBetweenSpeakerAndChoiceInput2", &Logger, true)); auto Script = NewObject(GetTransientPackage(), "Test"); const ScopedStringTableHolder StringTableHolder; Importer.PopulateAsset(Script, StringTableHolder.StringTable); // Script shouldn't be the owner of the dialogue but it's the only UObject we've got right now so why not auto Dlg = USUDSLibrary::CreateDialogue(Script, Script); Dlg->Start(); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); // in this case, there's another speaker line in both nested gosubs so the choices are actually associated with the // last of those. TestTrue("Plain continue", Dlg->Continue()); TestDialogueText(this, "Gosub 1 speaker node", Dlg, "Debug", "SkipChoices is {SkipChoices}"); TestTrue("Plain continue", Dlg->Continue()); TestDialogueText(this, "Gosub 2 speaker node", Dlg, "Speaker", "Another sentence."); if (TestEqual("Choice Count", Dlg->GetNumberOfChoices(), 2)) { TestEqual("Choice 1", Dlg->GetChoiceText(0).ToString(), "Option A"); TestEqual("Choice 2", Dlg->GetChoiceText(1).ToString(), "Option B"); } // Now test the true case Dlg->SetVariableBoolean("SkipChoices", true); Dlg->Restart(false); TestDialogueText(this, "Start node", Dlg, "Player", "Hello there"); TestTrue("Plain continue", Dlg->Continue()); TestDialogueText(this, "Gosub 1 speaker node", Dlg, "Debug", "SkipChoices is 1"); TestTrue("Plain continue", Dlg->IsSimpleContinue()); Script->MarkAsGarbage(); return true; } UE_ENABLE_OPTIMIZATION