Files
ProjectEleri/Plugins/SUDS/Source/SUDSTest/Private/TestConditionals.cpp

1045 lines
47 KiB
C++

#include "SUDSLibrary.h"
#include "SUDSMessageLogger.h"
#include "SUDSScript.h"
#include "SUDSScriptImporter.h"
#include "TestUtils.h"
UE_DISABLE_OPTIMIZATION
const FString BasicConditionalInput = R"RAWSUD(
Player: Hello
[if {x} == 1]
NPC: Reply when x == 1
[if {y} > 0]
Player: Player text when x ==1 and y > 0
[endif]
[elseif {x} > 1]
NPC: Reply when x > 1
[else]
NPC: Reply when x is something else
[endif]
[if {z}]
Player: the end is true
[else]
Player: the end is false
[endif]
NPC: OK
)RAWSUD";
const FString ConditionalChoiceInput = R"RAWSUD(
# First test has a regular choice first before conditional
NPC: Hello
* First choice
Player: I took the 1.1 choice
[if {y} == 2]
* Second choice (conditional)
Player: I took the 1.2 choice
* Third choice (conditional)
Player: I took the 1.3 choice
[else]
* Second Alt Choice
Player: I took the alt 1.2 choice
[endif]
* Common last choice
Player: I took the 1.4 choice
# Second test has conditional choice as the first one
NPC: OK next question
[if {y} == 0]
* First conditional choice
Player: I took the 2.1 choice
[elseif {y} == 1]
* Second conditional choice
Player: I took the 2.2 choice
[if {q} == 10]
* Nested conditional choice
Player: I took the 2.2.1 choice
[endif]
[else]
* Third conditional choice
Player: I took the 2.3 choice
[endif]
* Final common choice
Player: I took the 2.4 choice
NPC: Bye
)RAWSUD";
const FString SiblingConditionalChoiceInput = R"RAWSUD(
NPC: Hello
[if {x} == 0]
* First choice
Player: I took the 1.1 choice
[endif]
[if {y} == 1]
* Second choice (conditional)
Player: I took the 1.2 choice
[endif]
[if {z} == 1]
* Third choice (conditional)
Player: I took the 1.3 choice
[endif]
* Second Alt Choice
NPC: End
)RAWSUD";
const FString SiblingConditionalChoiceWithElseInput = R"RAWSUD(
NPC: Hello
[if {x} == 0]
* First choice
Player: I took the 1.1 choice
[elseif {SomeBool}]
* First alt choice elseif
Player: I took the 1.1 elseif choice
[else]
* First alt choice
Player: I took the 1.1b choice
[endif]
[if {y} == 1]
* Second choice (conditional)
Player: I took the 1.2 choice
[elseif {SomeBool}]
* Second alt choice elseif
Player: I took the 1.2 elseif choice
[else]
* Second alt choice
Player: I took the 1.2b choice
[endif]
[if {z} == 1]
* Third choice (conditional)
Player: I took the 1.3 choice
[elseif {SomeBool}]
* Third alt choice elseif
Player: I took the 1.3 elseif choice
[else]
* Third alt choice
Player: I took the 1.3b choice
[endif]
NPC: End
)RAWSUD";
const FString MixedChoiceAndBranchInput = R"RAWSUD(
[if {alreadyvisited}]
[goto secondvisit]
[endif]
NPC: Hello
* First choice
Player: I took the 1.1 choice
[if {x} > 0]
* Second choice (conditional)
Player: I took the 1.2 choice
* Third choice (conditional)
Player: I took the 1.3 choice
[else]
* Second Alt Choice
Player: I took the alt 1.2 choice
[endif]
* Common last choice
Player: I took the 1.4 choice
[goto goodbye]
:secondvisit
NPC: Hello again
[if {y} < 0]
Player: Y is less than 0
NPC: How interesting
* You don't sound that interested
NPC: Well I was trying to be nice
[if {x} == 0]
* Also x is zero
NPC: Fascinating
[endif]
* Well, better be off
[goto goodbye]
[elseif {y} == 0]
Player: Y is zero
NPC: So is my interest level
Player: Rude
[else]
Player: Who knows what Y is anyway
[if {ponderous}]
* Who knows what anything is?
NPC: Get out
[endif]
[if {y} > 5]
* It's more than I can count on one hand
NPC: Well, that helps
[else]
* It's kind of small though
NPC: Much like your brain
[endif]
* I'm done with this
[endif]
Player: This is some fallthrough text
:goodbye
NPC: Bye
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestBasicConditionals,
"SUDSTest.TestBasicConditionals",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestBasicConditionals::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(BasicConditionalInput), BasicConditionalInput.Len(), "BasicConditionalInput", &Logger, true));
// Test the content of the parsing
auto NextNode = Importer.GetNode(0);
if (!TestNotNull("Root node should exist", NextNode))
return false;
TestParsedText(this, "First node", NextNode, "Player", "Hello");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
if (TestParsedSelect(this, "First Select node", NextNode, 3))
{
auto SelectNode = NextNode;
TestParsedSelectEdge(this, "First select edge 1 (if)", SelectNode, 0, "{x} == 1", Importer, &NextNode);
TestParsedText(this, "Nested node 1", NextNode, "NPC", "Reply when x == 1");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
// Note: even though this is a single "if", there is an implicit "else" edge created
if (TestParsedSelect(this, "Nested Select node", NextNode, 2))
{
// Nested select
auto SelectNode2 = NextNode;
TestParsedSelectEdge(this, "Nested select edge 1", SelectNode2, 0, "{y} > 0", Importer, &NextNode);
TestParsedText(this, "Nested node edge 1", NextNode, "Player", "Player text when x ==1 and y > 0");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
if (TestParsedSelect(this, "Fallthrough select", NextNode, 2))
{
auto SelectNode3 = NextNode;
TestParsedSelectEdge(this, "Final select edge 1", SelectNode3, 0, "{z}", Importer, &NextNode);
TestParsedText(this, "Final select edge 1 text", NextNode, "Player", "the end is true");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Final fallthrough", NextNode, "NPC", "OK");
TestParsedSelectEdge(this, "Final select edge 2", SelectNode3, 1, "", Importer, &NextNode);
TestParsedText(this, "Final select edge 2 text", NextNode, "Player", "the end is false");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Final fallthrough", NextNode, "NPC", "OK");
}
// Go back to the nested select
// This "else" edge should have been created automatically to fall through
TestParsedSelectEdge(this, "Nested select edge 2", SelectNode2, 1, "", Importer, &NextNode);
// Just test it gets to the fallthrough, we've already tested the continuation from there
TestParsedSelect(this, "Fallthrough select", NextNode, 2);
}
TestParsedSelectEdge(this, "First select edge 2 (elseif)", SelectNode, 1, "{x} > 1", Importer, &NextNode);
TestParsedText(this, "Select node 2", NextNode, "NPC", "Reply when x > 1");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
// Just test it gets to the fallthrough, we've already tested the continuation from there
TestParsedSelect(this, "Fallthrough select", NextNode, 2);
TestParsedSelectEdge(this, "First select edge 2 (else)", SelectNode, 2, "", Importer, &NextNode);
TestParsedText(this, "Select node 3", NextNode, "NPC", "Reply when x is something else");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
// Just test it gets to the fallthrough, we've already tested the continuation from there
TestParsedSelect(this, "Fallthrough select", NextNode, 2);
}
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestSiblingConditionals,
"SUDSTest.TestSiblingConditionals",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestSiblingConditionals::RunTest(const FString& Parameters)
{
FSUDSScriptImporter Importer;
FSUDSMessageLogger Logger(false);
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(SiblingConditionalChoiceInput), SiblingConditionalChoiceInput.Len(), "SiblingConditionalChoiceInput", &Logger, true));
// Test the content of the parsing
auto NextNode = Importer.GetNode(0);
if (!TestNotNull("Root node should exist", NextNode))
return false;
TestParsedText(this, "Text node", NextNode, "NPC", "Hello");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
if (TestParsedChoice(this, "Root Choice", NextNode, 4))
{
auto RootChoice = NextNode;
// Sibling choices all get their own nodes
// Bit wasteful to have C -> S -> C -> Text instead of just C -> T but you never know how many choices would
// be in each select level
TestParsedChoiceEdge(this, "Choice 0", RootChoice, 0, "", Importer, &NextNode);
if (TestParsedSelect(this, "Select 0", NextNode, 1))
{
TestParsedSelectEdge(this, "Select 0", NextNode, 0, "{x} == 0", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 0 sub", NextNode, 0, "First choice", Importer, &NextNode);
TestParsedText(this, "Text 0", NextNode, "Player", "I took the 1.1 choice");
}
TestParsedChoiceEdge(this, "Choice 1", RootChoice, 1, "", Importer, &NextNode);
if (TestParsedSelect(this, "Select 1", NextNode, 1))
{
TestParsedSelectEdge(this, "Select 1", NextNode, 0, "{y} == 1", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 1 sub", NextNode, 0, "Second choice (conditional)", Importer, &NextNode);
TestParsedText(this, "Text 1", NextNode, "Player", "I took the 1.2 choice");
}
TestParsedChoiceEdge(this, "Choice 2", RootChoice, 2, "", Importer, &NextNode);
if (TestParsedSelect(this, "Select 2", NextNode, 1))
{
TestParsedSelectEdge(this, "Select 2", NextNode, 0, "{z} == 1", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 2 sub", NextNode, 0, "Third choice (conditional)", Importer, &NextNode);
TestParsedText(this, "Text 1", NextNode, "Player", "I took the 1.3 choice");
}
// Dangling choice with no condition
TestParsedChoiceEdge(this, "Choice 3", RootChoice, 3, "Second Alt Choice", Importer, &NextNode);
}
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestSiblingConditionalsWithElse,
"SUDSTest.TestSiblingConditionalsWithElse",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestSiblingConditionalsWithElse::RunTest(const FString& Parameters)
{
FSUDSScriptImporter Importer;
FSUDSMessageLogger Logger(false);
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(SiblingConditionalChoiceWithElseInput), SiblingConditionalChoiceWithElseInput.Len(), "SiblingConditionalChoiceWithElseInput", &Logger, true));
// Test the content of the parsing
auto NextNode = Importer.GetNode(0);
if (!TestNotNull("Root node should exist", NextNode))
return false;
TestParsedText(this, "Text node", NextNode, "NPC", "Hello");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
if (TestParsedChoice(this, "Root Choice", NextNode, 3))
{
auto RootChoice = NextNode;
// Sibling choices all get their own nodes
// Bit wasteful to have C -> S -> C -> Text instead of just C -> T but you never know how many choices would
// be in each select level
TestParsedChoiceEdge(this, "Choice 0", RootChoice, 0, "", Importer, &NextNode);
if (TestParsedSelect(this, "Select 0", NextNode, 3))
{
const FSUDSParsedNode* SelectNode = NextNode;
TestParsedSelectEdge(this, "Select 0", SelectNode, 0, "{x} == 0", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 0 sub", NextNode, 0, "First choice", Importer, &NextNode);
TestParsedText(this, "Text 0", NextNode, "Player", "I took the 1.1 choice");
// It's key to have the same "elseif {SomeBool}" for every sibling "if" to prove that the elseifs don't get merged
// just because their own conditions are the same. They're different because they're dependent on their own ifs not
// being true.
TestParsedSelectEdge(this, "Select 0 ELSEIF", SelectNode, 1, "{SomeBool}", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 0 sub ELSEIF", NextNode, 0, "First alt choice elseif", Importer, &NextNode);
TestParsedText(this, "Text 0 ELSEIF", NextNode, "Player", "I took the 1.1 elseif choice");
TestParsedSelectEdge(this, "Select 0 ELSE", SelectNode, 2, "", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 0 sub ELSE", NextNode, 0, "First alt choice", Importer, &NextNode);
TestParsedText(this, "Text 0 ELSE", NextNode, "Player", "I took the 1.1b choice");
}
TestParsedChoiceEdge(this, "Choice 1", RootChoice, 1, "", Importer, &NextNode);
if (TestParsedSelect(this, "Select 1", NextNode, 3))
{
const FSUDSParsedNode* SelectNode = NextNode;
TestParsedSelectEdge(this, "Select 1", SelectNode, 0, "{y} == 1", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 1 sub", NextNode, 0, "Second choice (conditional)", Importer, &NextNode);
TestParsedText(this, "Text 1", NextNode, "Player", "I took the 1.2 choice");
TestParsedSelectEdge(this, "Select 1 ELSEIF", SelectNode, 1, "{SomeBool}", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 1 sub ELSEIF", NextNode, 0, "Second alt choice elseif", Importer, &NextNode);
TestParsedText(this, "Text 1 ELSEIF", NextNode, "Player", "I took the 1.2 elseif choice");
TestParsedSelectEdge(this, "Select 1 ELSE", SelectNode, 2, "", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 1 sub ELSE", NextNode, 0, "Second alt choice", Importer, &NextNode);
TestParsedText(this, "Text 1 ELSE", NextNode, "Player", "I took the 1.2b choice");
}
TestParsedChoiceEdge(this, "Choice 2", RootChoice, 2, "", Importer, &NextNode);
if (TestParsedSelect(this, "Select 2", NextNode, 3))
{
const FSUDSParsedNode* SelectNode = NextNode;
TestParsedSelectEdge(this, "Select 2", SelectNode, 0, "{z} == 1", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 2 sub", NextNode, 0, "Third choice (conditional)", Importer, &NextNode);
TestParsedText(this, "Text 2", NextNode, "Player", "I took the 1.3 choice");
TestParsedSelectEdge(this, "Select 2 ELSEIF", SelectNode, 1, "{SomeBool}", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 2 sub ELSEIF", NextNode, 0, "Third alt choice elseif", Importer, &NextNode);
TestParsedText(this, "Text 2 ELSE", NextNode, "Player", "I took the 1.3 elseif choice");
TestParsedSelectEdge(this, "Select 2 ELSE", SelectNode, 2, "", Importer, &NextNode);
TestParsedChoiceEdge(this, "Choice 2 sub ELSE", NextNode, 0, "Third alt choice", Importer, &NextNode);
TestParsedText(this, "Text 2 ELSE", NextNode, "Player", "I took the 1.3b choice");
}
// Dangling choice with no condition
TestParsedChoiceEdge(this, "Choice 3", RootChoice, 3, "Second Alt Choice", Importer, &NextNode);
}
// Test running
auto Script = NewObject<USUDSScript>(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);
// For the first run, do not set any state
Dlg->Start();
TestDialogueText(this, "First node", Dlg, "NPC", "Hello");
if (TestEqual("Choice count", Dlg->GetNumberOfChoices(), 3))
{
TestEqual("Choice 0", Dlg->GetChoiceText(0).ToString(), TEXT("First choice"));
TestEqual("Choice 1", Dlg->GetChoiceText(1).ToString(), TEXT("Second alt choice"));
TestEqual("Choice 2", Dlg->GetChoiceText(2).ToString(), TEXT("Third alt choice"));
}
// restart, set some variables to alter the path
Dlg->SetVariableInt("x", 1);
Dlg->SetVariableInt("y", 1);
Dlg->SetVariableInt("z", 0);
Dlg->Restart(false);
TestDialogueText(this, "First node", Dlg, "NPC", "Hello");
if (TestEqual("Choice count", Dlg->GetNumberOfChoices(), 3))
{
TestEqual("Choice 0", Dlg->GetChoiceText(0).ToString(), TEXT("First alt choice"));
TestEqual("Choice 1", Dlg->GetChoiceText(1).ToString(), TEXT("Second choice (conditional)"));
TestEqual("Choice 2", Dlg->GetChoiceText(2).ToString(), TEXT("Third alt choice"));
}
// restart, set some variables to alter the path
Dlg->SetVariableBoolean("SomeBool", true);
Dlg->SetVariableInt("x", 1);
Dlg->SetVariableInt("y", 0);
Dlg->SetVariableInt("z", 1);
Dlg->Restart(false);
TestDialogueText(this, "First node", Dlg, "NPC", "Hello");
if (TestEqual("Choice count", Dlg->GetNumberOfChoices(), 3))
{
TestEqual("Choice 0", Dlg->GetChoiceText(0).ToString(), TEXT("First alt choice elseif"));
TestEqual("Choice 1", Dlg->GetChoiceText(1).ToString(), TEXT("Second alt choice elseif"));
TestEqual("Choice 2", Dlg->GetChoiceText(2).ToString(), TEXT("Third choice (conditional)"));
}
Script->MarkAsGarbage();
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestRunningBasicConditionals,
"SUDSTest.TestRunningBasicConditionals",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestRunningBasicConditionals::RunTest(const FString& Parameters)
{
FSUDSScriptImporter Importer;
FSUDSMessageLogger Logger(false);
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(BasicConditionalInput), BasicConditionalInput.Len(), "BasicConditionalInput", &Logger, true));
auto Script = NewObject<USUDSScript>(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);
// For the first run, do not set any state
Dlg->Start();
TestDialogueText(this, "First node", Dlg, "Player", "Hello");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "NPC", "Reply when x is something else");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "Player", "the end is false");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "NPC", "OK");
TestFalse("Continue", Dlg->Continue());
TestTrue("End", Dlg->IsEnded());
// restart, set some variables to alter the path
Dlg->Restart(true);
Dlg->SetVariableInt("x", 1);
Dlg->SetVariableInt("y", -1);
Dlg->SetVariableBoolean("z", true);
TestDialogueText(this, "First node", Dlg, "Player", "Hello");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "NPC", "Reply when x == 1");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "Player", "the end is true");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "NPC", "OK");
TestFalse("Continue", Dlg->Continue());
TestTrue("End", Dlg->IsEnded());
// restart, set some variables to alter the path
Dlg->Restart(true);
Dlg->SetVariableInt("x", 1);
Dlg->SetVariableInt("y", 10);
Dlg->SetVariableBoolean("z", false);
TestDialogueText(this, "First node", Dlg, "Player", "Hello");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "NPC", "Reply when x == 1");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "Player", "Player text when x ==1 and y > 0");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "Player", "the end is false");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text", Dlg, "NPC", "OK");
TestFalse("Continue", Dlg->Continue());
TestTrue("End", Dlg->IsEnded());
Script->MarkAsGarbage();
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestConditionalChoices,
"SUDSTest.TestConditionalChoices",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestConditionalChoices::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(ConditionalChoiceInput), ConditionalChoiceInput.Len(), "ConditionalChoiceInput", &Logger, true));
// Test the content of the parsing
auto NextNode = Importer.GetNode(0);
if (!TestNotNull("Root node should exist", NextNode))
return false;
TestParsedText(this, "First node", NextNode, "NPC", "Hello");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
// This will have one choice edge for the unconditional choice, then an edge to the select node
if (TestParsedChoice(this, "First choice node", NextNode, 3))
{
auto ChoiceNode = NextNode;
TestParsedChoiceEdge(this, "First choice edge 1", ChoiceNode, 0, "First choice", Importer, &NextNode);
TestParsedText(this, "First choice edge 1 text", NextNode, "Player", "I took the 1.1 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Fallthrough to next question", NextNode, "NPC", "OK next question");
// Second edge has no text because it's going to a select for other choices
TestParsedChoiceEdge(this, "First choice edge 2", ChoiceNode, 1, "", Importer, &NextNode);
// 2 edges because only if/else
if (TestParsedSelect(this, "First choice edge 2 should link to select", NextNode, 2))
{
auto SelectNode = NextNode;
TestParsedSelectEdge(this, "Select edge 0", SelectNode, 0, "{y} == 2", Importer, &NextNode);
// 2 choices in this if clause
if (TestParsedChoice(this, "Choice node under first if", NextNode, 2))
{
auto Choice2 = NextNode;
TestParsedChoiceEdge(this, "y == 2 choice edge 1", Choice2, 0, "Second choice (conditional)", Importer, &NextNode);
TestParsedText(this, "y == 2 choice edge 1 text", NextNode, "Player", "I took the 1.2 choice");
// This should fall through to text node after the choices
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Fallthrough to next text", NextNode, "NPC", "OK next question");
TestParsedChoiceEdge(this, "y == 2 choice edge 2", Choice2, 1, "Third choice (conditional)", Importer, &NextNode);
TestParsedText(this, "y == 2 choice edge 2 text", NextNode, "Player", "I took the 1.3 choice");
// This should fall through to text node after the choices
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Fallthrough to next text", NextNode, "NPC", "OK next question");
}
TestParsedSelectEdge(this, "Select edge 1 (else)", SelectNode, 1, "", Importer, &NextNode);
// 1 choice under this else section
if (TestParsedChoice(this, "Choice node under first else", NextNode, 1))
{
auto Choice3 = NextNode;
TestParsedChoiceEdge(this, "y == 2 choice edge 1", Choice3, 0, "Second Alt Choice", Importer, &NextNode);
TestParsedText(this, "Else edge text", NextNode, "Player", "I took the alt 1.2 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Fallthrough to next question", NextNode, "NPC", "OK next question");
}
// Final fallthrough choice, unconditional
TestParsedChoiceEdge(this, "First choice edge 3", ChoiceNode, 2, "Common last choice", Importer, &NextNode);
TestParsedText(this, "Fallthrough to next text", NextNode, "Player", "I took the 1.4 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Fallthrough to next question", NextNode, "NPC", "OK next question");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
// This time the first choice is already in the select, BUT we should have inserted an extra choice anyway
// Should be 2 edges because the last common choice should have attached itself
if (TestParsedChoice(this, "Inserted choice before select", NextNode, 2))
{
auto InsertedChoice = NextNode;
TestParsedChoiceEdge(this, "Inserted choice edge 1", InsertedChoice, 0, "", Importer, &NextNode);
// There's an if, elseif, and else edge
if (TestParsedSelect(this, "Select node under next question", NextNode, 3))
{
auto SelectNode2 = NextNode;
TestParsedSelectEdge(this, "Select edge 0", SelectNode2, 0, "{y} == 0", Importer, &NextNode);
// Choice node under if, only 1 in this section
if (TestParsedChoice(this, "Choice node under select", NextNode, 1))
{
TestParsedChoiceEdge(this, "First choice edge", NextNode, 0, "First conditional choice", Importer, &NextNode);
TestParsedText(this, "First choice text", NextNode, "Player", "I took the 2.1 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Final fallthrough", NextNode, "NPC", "Bye");
}
TestParsedSelectEdge(this, "Select edge 1", SelectNode2, 1, "{y} == 1", Importer, &NextNode);
// Choice node under elseif, only 1 in this section
if (TestParsedChoice(this, "Choice node under select 2", NextNode, 2))
{
auto Choice4 = NextNode;
TestParsedChoiceEdge(this, "Second choice edge 1", Choice4, 0, "Second conditional choice", Importer, &NextNode);
TestParsedText(this, "First choice text", NextNode, "Player", "I took the 2.2 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Final fallthrough", NextNode, "NPC", "Bye");
// Next edge goes to a select
TestParsedChoiceEdge(this, "Second choice edge 2", Choice4, 1, "", Importer, &NextNode);
// There is only an "if" in this nested select. No implicit "else" edge should exist because it's a choice node
if (TestParsedSelect(this, "Select node under next question", NextNode, 1))
{
TestParsedSelectEdge(this, "Nested select edge 1", NextNode, 0, "{q} == 10", Importer, &NextNode);
if (TestParsedChoice(this, "Choice node under nested select", NextNode, 1))
{
TestParsedChoiceEdge(this, "Nested choice edge 1", NextNode, 0, "Nested conditional choice", Importer, &NextNode);
TestParsedText(this, "First choice text", NextNode, "Player", "I took the 2.2.1 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Final fallthrough", NextNode, "NPC", "Bye");
}
}
}
TestParsedSelectEdge(this, "Select edge 2", SelectNode2, 2, "", Importer, &NextNode);
// Choice node under elseif, only 1 in this section
if (TestParsedChoice(this, "Choice node under select 3", NextNode, 1))
{
TestParsedChoiceEdge(this, "Choice under select 3 edge", NextNode, 0, "Third conditional choice", Importer, &NextNode);
TestParsedText(this, "First choice text", NextNode, "Player", "I took the 2.3 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Final fallthrough", NextNode, "NPC", "Bye");
}
}
// Finally the last catch-all choice which attached itself to the choice node we created above the select
TestParsedChoiceEdge(this, "Inserted choice edge 2", InsertedChoice, 1, "Final common choice", Importer, &NextNode);
TestParsedText(this, "First choice text", NextNode, "Player", "I took the 2.4 choice");
TestGetParsedNextNode(this, "Get next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Final fallthrough", NextNode, "NPC", "Bye");
}
}
}
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestMixedChoiceAndBranch,
"SUDSTest.TestMixedChoiceAndBranch",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestMixedChoiceAndBranch::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(MixedChoiceAndBranchInput), MixedChoiceAndBranchInput.Len(), "MixedChoiceAndBranchInput", &Logger, true));
auto Script = NewObject<USUDSScript>(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);
// For the first run, do not set any state
Dlg->Start();
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello");
TestEqual("Num choices", Dlg->GetNumberOfChoices(), 3);
TestEqual("Num choices", Dlg->GetChoiceText(0).ToString(), "First choice");
TestEqual("Num choices", Dlg->GetChoiceText(1).ToString(), "Second Alt Choice");
TestEqual("Num choices", Dlg->GetChoiceText(2).ToString(), "Common last choice");
TestTrue("Choose", Dlg->Choose(0));
TestDialogueText(this, "Text node", Dlg, "Player", "I took the 1.1 choice");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
// Choices are on first node so need to set before restart
Dlg->SetVariableInt("x", 10);
Dlg->Restart(false);
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello");
if (TestEqual("Num choices", Dlg->GetNumberOfChoices(), 4))
{
TestEqual("ChoiceText", Dlg->GetChoiceText(0).ToString(), "First choice");
TestEqual("ChoiceText", Dlg->GetChoiceText(1).ToString(), "Second choice (conditional)");
TestEqual("ChoiceText", Dlg->GetChoiceText(2).ToString(), "Third choice (conditional)");
TestEqual("ChoiceText", Dlg->GetChoiceText(3).ToString(), "Common last choice");
TestTrue("Choose", Dlg->Choose(2));
TestDialogueText(this, "Text node", Dlg, "Player", "I took the 1.3 choice");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
}
Dlg->SetVariableBoolean("alreadyvisited", true);
Dlg->SetVariableInt("y", -1);
Dlg->Restart(false);
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello again");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "Y is less than 0");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "How interesting");
if (TestEqual("Num choices", Dlg->GetNumberOfChoices(), 2))
{
TestEqual("Choice text", Dlg->GetChoiceText(0).ToString(), "You don't sound that interested");
TestEqual("Choice text", Dlg->GetChoiceText(1).ToString(), "Well, better be off");
TestTrue("Choose", Dlg->Choose(1));
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
}
Dlg->SetVariableBoolean("alreadyvisited", true);
Dlg->SetVariableInt("y", -1);
Dlg->SetVariableInt("x", 0);
Dlg->Restart(false);
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello again");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "Y is less than 0");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "How interesting");
if (TestEqual("Num choices", Dlg->GetNumberOfChoices(), 3))
{
TestEqual("Choice text", Dlg->GetChoiceText(0).ToString(), "You don't sound that interested");
TestEqual("Choice text", Dlg->GetChoiceText(1).ToString(), "Also x is zero");
TestEqual("Choice text", Dlg->GetChoiceText(2).ToString(), "Well, better be off");
TestTrue("Choose", Dlg->Choose(1));
TestDialogueText(this, "Text node", Dlg, "NPC", "Fascinating");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "This is some fallthrough text");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
}
Dlg->SetVariableBoolean("alreadyvisited", true);
Dlg->SetVariableInt("y", 123);
Dlg->SetVariableBoolean("ponderous", true);
Dlg->Restart(false);
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello again");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "Who knows what Y is anyway");
if (TestEqual("Num choices", Dlg->GetNumberOfChoices(), 3))
{
TestEqual("Choice text", Dlg->GetChoiceText(0).ToString(), "Who knows what anything is?");
TestEqual("Choice text", Dlg->GetChoiceText(1).ToString(), "It's more than I can count on one hand");
TestEqual("Choice text", Dlg->GetChoiceText(2).ToString(), "I'm done with this");
TestTrue("Choose", Dlg->Choose(0));
TestDialogueText(this, "Text node", Dlg, "NPC", "Get out");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "This is some fallthrough text");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
}
Dlg->SetVariableBoolean("alreadyvisited", true);
Dlg->SetVariableInt("y", 4);
Dlg->SetVariableBoolean("ponderous", true);
Dlg->Restart(false);
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello again");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "Who knows what Y is anyway");
if (TestEqual("Num choices", Dlg->GetNumberOfChoices(), 3))
{
TestEqual("Choice text", Dlg->GetChoiceText(0).ToString(), "Who knows what anything is?");
TestEqual("Choice text", Dlg->GetChoiceText(1).ToString(), "It's kind of small though");
TestEqual("Choice text", Dlg->GetChoiceText(2).ToString(), "I'm done with this");
TestTrue("Choose", Dlg->Choose(2));
TestDialogueText(this, "Text node", Dlg, "Player", "This is some fallthrough text");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
}
Script->MarkAsGarbage();
return true;
}
const FString ChoiceAfterConditionals = R"RAWSUD(
NPC: Hello
[if {AltPath}]
Player: This is the alternate path before choice
[else]
Player: This is the main path
[endif]
* Choice 1
NPC: Choice 1 after conditional
* Choice 2
NPC: Choice 2 after conditional
NPC: Bye
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestChoiceAfterConditionals,
"SUDSTest.TestChoiceAfterConditionals",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestChoiceAfterConditionals::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(ChoiceAfterConditionals), ChoiceAfterConditionals.Len(), "ChoiceAfterConditionals", &Logger, true));
auto Script = NewObject<USUDSScript>(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);
// For the first run, do not set any state
Dlg->Start();
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "This is the main path");
TestEqual("Number of choices", Dlg->GetNumberOfChoices(), 2);
TestEqual("Choice text", Dlg->GetChoiceText(0).ToString(), "Choice 1");
TestEqual("Choice text", Dlg->GetChoiceText(1).ToString(), "Choice 2");
TestTrue("Choose", Dlg->Choose(0));
TestDialogueText(this, "Text node", Dlg, "NPC", "Choice 1 after conditional");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
Script->MarkAsGarbage();
return true;
}
const FString ChoiceAfterNestedConditionals = R"RAWSUD(
NPC: Hello
[if {AltPath}]
Player: This is the alternate path before choice
[else]
Player: This is the main path
[if {AltPath2}]
Player: An alt-alt path
[endif]
[endif]
:choices
* Choice 1
NPC: Choice 1 after conditional
* Choice 2
NPC: Choice 2 after conditional
NPC: Bye
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestChoiceAfterNestedConditionals,
"SUDSTest.TestChoiceAfterNestedConditionals",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestChoiceAfterNestedConditionals::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(ChoiceAfterNestedConditionals), ChoiceAfterNestedConditionals.Len(), "ChoiceAfterNestedConditionals", &Logger, true));
auto Script = NewObject<USUDSScript>(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);
// For the first run, do not set any state
Dlg->Start();
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "Player", "This is the main path");
TestEqual("Number of choices", Dlg->GetNumberOfChoices(), 2);
TestEqual("Choice text", Dlg->GetChoiceText(0).ToString(), "Choice 1");
TestEqual("Choice text", Dlg->GetChoiceText(1).ToString(), "Choice 2");
TestTrue("Choose", Dlg->Choose(0));
TestDialogueText(this, "Text node", Dlg, "NPC", "Choice 1 after conditional");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Bye");
Script->MarkAsGarbage();
return true;
}
const FString VarsSetBetweenTextAndChoiceChoice = R"RAWSUD(
# prove that the set node is run before the choices are evaluated even though it's after the text node
NPC: Hello
[set AltPath true]
[if {AltPath}]
* Alt Choice
Player: This is the alternate path choice
[else]
* Main Choice
Player: Main path choice
[endif]
* Common Choice
Player: Common choice here
NPC: Bye
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FVarsSetBetweenTextAndChoiceChoice,
"SUDSTest.VarsSetBetweenTextAndChoiceChoice",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FVarsSetBetweenTextAndChoiceChoice::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(VarsSetBetweenTextAndChoiceChoice), VarsSetBetweenTextAndChoiceChoice.Len(), "VarsSetBetweenTextAndChoiceChoice", &Logger, true));
auto Script = NewObject<USUDSScript>(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);
// For the first run, do not set any state
Dlg->Start();
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello");
TestEqual("Number of choices", Dlg->GetNumberOfChoices(), 2);
TestEqual("Choice text", Dlg->GetChoiceText(0).ToString(), "Alt Choice");
TestEqual("Choice text", Dlg->GetChoiceText(1).ToString(), "Common Choice");
Script->MarkAsGarbage();
return true;
}
const FString MultipleOptionalChoicesWithLinesBetweenTextAndChoice = R"RAWSUD(
NPC: Hello
NPC: Testing
# This event being here could mess up multiple sibling optional choices
[event SomeEvent]
* Choice 1?
Player: Choice 1
[if {Opt2}]
* Optional Choice 2?
Player: Choice 2
[endif]
[if {Opt3}]
* Optional Choice 3?
Player: Choice 3
[endif]
* Back
Player: going back
NPC: Fallthrough
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestMultipleOptionalChoicesWithLinesBetweenTextAndChoice,
"SUDSTest.TestMultipleOptionalChoicesWithLinesBetweenTextAndChoice",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestMultipleOptionalChoicesWithLinesBetweenTextAndChoice::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(MultipleOptionalChoicesWithLinesBetweenTextAndChoice), MultipleOptionalChoicesWithLinesBetweenTextAndChoice.Len(), "MultipleOptionalChoicesWithLinesBetweenTextAndChoice", &Logger, true));
auto Script = NewObject<USUDSScript>(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);
// For the first run, do not set any state
Dlg->Start();
TestDialogueText(this, "Text node", Dlg, "NPC", "Hello");
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Testing");
TestEqual("Number of choices", Dlg->GetNumberOfChoices(), 2);
TestEqual("Choice text 0", Dlg->GetChoiceText(0).ToString(), "Choice 1?");
TestEqual("Choice text 1", Dlg->GetChoiceText(1).ToString(), "Back");
Dlg->Restart(true);
Dlg->SetVariableBoolean("Opt2", true);
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Testing");
TestEqual("Number of choices", Dlg->GetNumberOfChoices(), 3);
TestEqual("Choice text 0", Dlg->GetChoiceText(0).ToString(), "Choice 1?");
TestEqual("Choice text 1", Dlg->GetChoiceText(1).ToString(), "Optional Choice 2?");
TestEqual("Choice text 2", Dlg->GetChoiceText(2).ToString(), "Back");
Dlg->Restart(true);
Dlg->SetVariableBoolean("Opt3", true);
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Testing");
TestEqual("Number of choices", Dlg->GetNumberOfChoices(), 3);
TestEqual("Choice text 0", Dlg->GetChoiceText(0).ToString(), "Choice 1?");
TestEqual("Choice text 1", Dlg->GetChoiceText(1).ToString(), "Optional Choice 3?");
TestEqual("Choice text 2", Dlg->GetChoiceText(2).ToString(), "Back");
Dlg->Restart(true);
Dlg->SetVariableBoolean("Opt2", true);
Dlg->SetVariableBoolean("Opt3", true);
TestTrue("Continue", Dlg->Continue());
TestDialogueText(this, "Text node", Dlg, "NPC", "Testing");
TestEqual("Number of choices", Dlg->GetNumberOfChoices(), 4);
TestEqual("Choice text 0", Dlg->GetChoiceText(0).ToString(), "Choice 1?");
TestEqual("Choice text 1", Dlg->GetChoiceText(1).ToString(), "Optional Choice 2?");
TestEqual("Choice text 2", Dlg->GetChoiceText(2).ToString(), "Optional Choice 3?");
TestEqual("Choice text 3", Dlg->GetChoiceText(3).ToString(), "Back");
Script->MarkAsGarbage();
return true;
}
UE_ENABLE_OPTIMIZATION