From ce2ac8ba1c77cd7c898090c3c623055e743e0acb Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Tue, 25 Apr 2023 20:33:13 -0700
Subject: [PATCH] Add choice and choice-set event parsing

---
 assets/games/liminal/test.xml                 | 84 +++++++++++++---
 src/dawntools/vnscenetool/VNSceneGen.cpp      | 19 ++++
 .../vnscenetool/events/CMakeLists.txt         |  2 +
 .../events/VNChoiceEventParser.cpp            | 95 +++++++++++++++++++
 .../events/VNChoiceEventParser.hpp            | 44 +++++++++
 .../events/VNChoiceSetEventParser.cpp         | 27 ++++++
 .../events/VNChoiceSetEventParser.hpp         | 26 +++++
 .../events/VNSceneEventsParser.cpp            | 10 ++
 .../events/VNSceneEventsParser.hpp            |  8 +-
 .../vnscenetool/events/VNTextEventParser.cpp  | 26 ++++-
 .../vnscenetool/events/VNTextEventParser.hpp  | 12 +++
 11 files changed, 336 insertions(+), 17 deletions(-)
 create mode 100644 src/dawntools/vnscenetool/events/VNChoiceEventParser.cpp
 create mode 100644 src/dawntools/vnscenetool/events/VNChoiceEventParser.hpp
 create mode 100644 src/dawntools/vnscenetool/events/VNChoiceSetEventParser.cpp
 create mode 100644 src/dawntools/vnscenetool/events/VNChoiceSetEventParser.hpp

diff --git a/assets/games/liminal/test.xml b/assets/games/liminal/test.xml
index f70a0f40..c48ccfde 100644
--- a/assets/games/liminal/test.xml
+++ b/assets/games/liminal/test.xml
@@ -1,16 +1,21 @@
 <vnscene name="ExampleScene">
-  <item ref="cube" prefab="prefabs/SimpleSpinningCubePrefab" />
-  
+  <item ref="eth" prefab="prefabs/SimpleSpinningCubePrefab" />
+  <item ref="craig" prefab="prefabs/SimpleSpinningCubePrefab" />
+  <item ref="bg" prefab="prefabs/SimpleSpinningCubePrefab" />
+  <item ref="square" prefab="prefabs/SimpleSpinningCubePrefab" />
+
   <events>
     <!-- Set the default values -->
-    <position item="cube" x="0" y="0" z="0" />
+    <position item="bg" x="0" y="0" z="0"  />
+    <position item="craig" x="0" y="0" z="1" />
+    <position item="eth" x="0" y="0" z="1" />
+    <position item="square" x="0" y="0" z="10" />
 
-    <set property="cube->material->color" value="COLOR_BLACK" type="struct Color" />
-    <set property="cube->material->color.a" value="0" type="uint8_t" />
-    
+    <set property="square->material->color" value="COLOR_BLACK" type="struct Color" />
+    <set property="craig->material->color.a" value="0" type="uint8_t" />
 
     <!-- Fade out the black overlay -->
-    <set property="cube->material->color.a" to="0" duration="1" curve="easeOut" type="uint8_t" />
+    <set property="square->material->color.a" to="0" duration="1" curve="easeOut" type="uint8_t" />
     
     <!-- Example Text, also showing how in future we can support multiple languages -->
     <text character="eth">
@@ -18,9 +23,7 @@
     </text>
     
     <!-- Fade in craig -->
-    <marker name="craigCool" />
-
-    <set property="cube->material->color.a" to="255" duration="1" curve="easeOut" type="uint8_t" />
+    <set property="craig->material->color.a" to="255" duration="1" curve="easeOut" type="uint8_t" />
     <text character="craig">
       <string lang="en">Hi, I'm Craig.</string>
     </text>
@@ -31,14 +34,67 @@
       at the same time. This will make craig exit screen left.
     -->
     <parallel>
-      <set property="cube->material->color.a" to="0" duration="1" curve="easeOut" type="uint8_t" />
-      <position item="cube" x="-2" duration="1" curve="easeOut" />
+      <set property="craig->material->color.a" to="0" duration="1" curve="easeOut" type="uint8_t" />
+      <position item="craig" x="-2" duration="1" curve="easeOut" />
     </parallel>
 
     <!-- Now Craig is gone, so sad, let's now fade the screen out and present some choices -->
-    <set property="cube->material->color.a" to="255" duration="1" curve="easeOut" type="uint8_t" />
+    <set property="square->material->color.a" to="255" duration="1" curve="easeOut" type="uint8_t" />
 
     <!-- Here I am creating a marker -->
-    <goto-marker name="craigCool" />
+    <marker name="craigCool" />
+
+    <choices character="eth" key="isCraigCool">
+      <title>
+        <string lang="en">Do you think Craig is cool?</string>
+      </title>
+      <choice value="no">
+        <string lang="en">Yes</string>
+      </choice>
+      <choice value="yes">
+        <string lang="en">No</string>
+      </choice>
+      <choice value="maybe">
+        <string lang="en">Maybe</string>
+      </choice>
+    </choices>
+
+    <!--
+      Now we have a choice, we can use the key to get the value of the choice
+      and do something with it. In this case, we'll just show a different
+      message depending on the choice.
+
+      If the user selects "Maybe", we basically just display a message and then
+      go back to the choices indefinitely.
+    -->
+    <!-- <if key="isCraigCool" value="maybe">
+      <text character="eth">
+        <string lang="en">Are you unsure?</string>
+      </text>
+      <goto-marker name="craigCool" />
+    </if>
+
+    <if key="isCraigCool" value="yes">
+      <text character="eth">
+        <string lang="en">I agree, Craig is cool.</string>
+      </text>
+    </if>
+    <if key="isCraigCool" value="no">
+      <text character="eth">
+        <string lang="en">I disagree, Craig is cool.</string>
+      </text>
+    </if> -->
+    
+    <!-- We can also modify values/set values too. -->
+    <choice-set key="isCraigCool" value="yes" />
+
+    <!-- We can also wait some time -->
+    <wait duration="1" />
+
+    <!--
+      Now change scenes. Changing scenes is handled in-engine to stop the game
+      crashing from a lack of memory.
+    -->
+    <!-- <scene-change name="scenes/ExampleScene2" /> -->
   </events>
 </vnscene>
\ No newline at end of file
diff --git a/src/dawntools/vnscenetool/VNSceneGen.cpp b/src/dawntools/vnscenetool/VNSceneGen.cpp
index e1327c40..c8b9bd22 100644
--- a/src/dawntools/vnscenetool/VNSceneGen.cpp
+++ b/src/dawntools/vnscenetool/VNSceneGen.cpp
@@ -83,6 +83,25 @@ void VNSceneGen::test(
       line(&afterLines, eventName + "->then(marker_" + event->gotoMarker.name + ");", "");
       break;
 
+    case VN_SCENE_EVENT_TYPE_CHOICES: {
+      initType = "VNChoiceEvent";
+      toInclude = "games/vn/events/VNChoiceEvent.hpp";
+      line(&afterLines, eventName + "->key = \"" + event->choices.key+ "\";", "");
+      line(&afterLines, eventName + "->text = \"" + event->choices.titles.begin()->text + "\";", "");
+      auto itChoices = event->choices.choices.begin();
+      while(itChoices != event->choices.choices.end()) {
+        line(&afterLines, eventName + "->choices[\"" + itChoices->value + "\"] = \"" + itChoices->texts.begin()->text + "\";", "");
+        ++itChoices;
+      }
+      break;
+    }
+
+    case VN_SCENE_EVENT_TYPE_CHOICE_SET:
+      initType = "VNChoiceSetEvent";
+      toInclude = "games/vn/events/VNChoiceSetEvent.hpp";
+      line(&afterLines, eventName + "->key = \"" + event->choiceSet.key + "\";", "");
+      line(&afterLines, eventName + "->value = \"" + event->choiceSet.value + "\";", "");
+      break;
 
     default:
       std::cout << "Unknown event type: " << event->type << std::endl;
diff --git a/src/dawntools/vnscenetool/events/CMakeLists.txt b/src/dawntools/vnscenetool/events/CMakeLists.txt
index 6689ff35..166bf999 100644
--- a/src/dawntools/vnscenetool/events/CMakeLists.txt
+++ b/src/dawntools/vnscenetool/events/CMakeLists.txt
@@ -14,4 +14,6 @@ target_sources(vnscenetool
     VNWaitEventParser.cpp
     VNParallelEventParser.cpp
     VNGoToMarkerEventParser.cpp
+    VNChoiceEventParser.cpp
+    VNChoiceSetEventParser.cpp
 )
\ No newline at end of file
diff --git a/src/dawntools/vnscenetool/events/VNChoiceEventParser.cpp b/src/dawntools/vnscenetool/events/VNChoiceEventParser.cpp
new file mode 100644
index 00000000..470f9f51
--- /dev/null
+++ b/src/dawntools/vnscenetool/events/VNChoiceEventParser.cpp
@@ -0,0 +1,95 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "VNChoiceEventParser.hpp"
+
+using namespace Dawn;
+
+std::vector<std::string> VNChoiceParser::getRequiredAttributes() {
+  return { "value" };
+}
+
+std::map<std::string, std::string> VNChoiceParser::getOptionalAttributes() {
+  return { };
+}
+
+int32_t VNChoiceParser::onParse(
+  Xml *node,
+  std::map<std::string, std::string> values,
+  struct VNChoice *out,
+  std::string *error
+) {
+  int32_t ret;
+  auto itChildren = node->children.begin();
+  while(itChildren != node->children.end()) {
+    Xml *child = *itChildren;
+
+    // Parse strings
+    if(child->node == "string") {
+      VNText text;
+      ret = (VNTextParser()).parse(child, &text, error);
+      if(ret != 0) return ret;
+      out->texts.push_back(text);
+    } else {
+      *error = "Unknown child node '" + child->node + "'";
+      return -1;
+    }
+
+    itChildren++;
+  }
+
+  out->value = values["value"];
+  return 0;
+}
+
+// // // // // // // // // // // // // // // // // // // // // // // // // // //
+
+std::vector<std::string> VNChoicesEventParser::getRequiredAttributes() {
+  return { "key" };
+}
+
+std::map<std::string, std::string> VNChoicesEventParser::getOptionalAttributes() {
+  return { };
+}
+
+int32_t VNChoicesEventParser::onParse(
+  Xml *node,
+  std::map<std::string, std::string> values,
+  struct VNChoiceEvent *out,
+  std::string *error
+) {
+  int32_t ret;
+  auto itChildren = node->children.begin();
+  while(itChildren != node->children.end()) {
+    Xml *child = *itChildren;
+
+    // Parse strings
+    if(child->node == "title") {
+      auto itChildren2 = child->children.begin();
+      while(itChildren2 != child->children.end()) {
+        VNText text;
+        ret = (VNTextParser()).parse(*itChildren2, &text, error);
+        if(ret != 0) return ret;
+        out->titles.push_back(text);
+        ++itChildren2;
+      }
+
+    } else if(child->node == "choice") {
+      VNChoice choice;
+      ret = (VNChoiceParser()).parse(child, &choice, error);
+      if(ret != 0) return ret;
+      out->choices.push_back(choice);
+
+    } else {
+      *error = "Unknown child node '" + child->node + "'";
+      return -1;
+    }
+
+    itChildren++;
+  }
+
+  out->key = values["key"];
+  return 0;
+}
\ No newline at end of file
diff --git a/src/dawntools/vnscenetool/events/VNChoiceEventParser.hpp b/src/dawntools/vnscenetool/events/VNChoiceEventParser.hpp
new file mode 100644
index 00000000..f06d9d63
--- /dev/null
+++ b/src/dawntools/vnscenetool/events/VNChoiceEventParser.hpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "VNTextEventParser.hpp"
+
+namespace Dawn {
+  struct VNChoice {
+    std::vector<struct VNText> texts;
+    std::string value;
+  };
+
+  struct VNChoiceEvent {
+    std::vector<struct VNText> titles;
+    std::vector<struct VNChoice> choices;
+    std::string key;
+  };
+
+  class VNChoiceParser : public XmlParser<struct VNChoice> {
+    protected:
+      std::vector<std::string> getRequiredAttributes() override;
+      std::map<std::string, std::string> getOptionalAttributes() override;
+      int32_t onParse(
+        Xml *node,
+        std::map<std::string, std::string> values,
+        struct VNChoice *out,
+        std::string *error
+      ) override;
+  };
+
+  class VNChoicesEventParser : public XmlParser<struct VNChoiceEvent> {
+    protected:
+      std::vector<std::string> getRequiredAttributes() override;
+      std::map<std::string, std::string> getOptionalAttributes() override;
+      int32_t onParse(
+        Xml *node,
+        std::map<std::string, std::string> values,
+        struct VNChoiceEvent *out,
+        std::string *error
+      ) override;
+  };
+}
\ No newline at end of file
diff --git a/src/dawntools/vnscenetool/events/VNChoiceSetEventParser.cpp b/src/dawntools/vnscenetool/events/VNChoiceSetEventParser.cpp
new file mode 100644
index 00000000..14a78383
--- /dev/null
+++ b/src/dawntools/vnscenetool/events/VNChoiceSetEventParser.cpp
@@ -0,0 +1,27 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#include "VNChoiceSetEventParser.hpp"
+
+using namespace Dawn;
+
+std::vector<std::string> VNChoiceSetEventParser::getRequiredAttributes() {
+  return { "key", "value" };
+}
+
+std::map<std::string, std::string> VNChoiceSetEventParser::getOptionalAttributes() {
+  return {};
+}
+
+int32_t VNChoiceSetEventParser::onParse(
+  Xml *node,
+  std::map<std::string, std::string> values,
+  struct VNChoiceSetEvent *out,
+  std::string *error
+) {
+  out->key = values["key"];
+  out->value = values["value"];
+  return 0;
+}
\ No newline at end of file
diff --git a/src/dawntools/vnscenetool/events/VNChoiceSetEventParser.hpp b/src/dawntools/vnscenetool/events/VNChoiceSetEventParser.hpp
new file mode 100644
index 00000000..0691c9b5
--- /dev/null
+++ b/src/dawntools/vnscenetool/events/VNChoiceSetEventParser.hpp
@@ -0,0 +1,26 @@
+// Copyright (c) 2023 Dominic Masters
+// 
+// This software is released under the MIT License.
+// https://opensource.org/licenses/MIT
+
+#pragma once
+#include "util/XmlParser.hpp"
+
+namespace Dawn {
+  struct VNChoiceSetEvent {
+    std::string key;
+    std::string value;
+  };
+
+  class VNChoiceSetEventParser : public XmlParser<VNChoiceSetEvent> {
+    protected:
+      std::vector<std::string> getRequiredAttributes() override;
+      std::map<std::string, std::string> getOptionalAttributes() override;
+      int32_t onParse(
+        Xml *node,
+        std::map<std::string, std::string> values,
+        struct VNChoiceSetEvent *out,
+        std::string *error
+      ) override;
+  };
+}
\ No newline at end of file
diff --git a/src/dawntools/vnscenetool/events/VNSceneEventsParser.cpp b/src/dawntools/vnscenetool/events/VNSceneEventsParser.cpp
index ba3ebeb1..9fbb79e8 100644
--- a/src/dawntools/vnscenetool/events/VNSceneEventsParser.cpp
+++ b/src/dawntools/vnscenetool/events/VNSceneEventsParser.cpp
@@ -65,6 +65,16 @@ int32_t VNSceneEventsParser::onParse(
       ret = (VNGoToMarkerEventParser()).parse(child, &event.gotoMarker, error);
       if(ret != 0) return ret;
 
+    } else if(child->node == "choices") {
+      event.type = VN_SCENE_EVENT_TYPE_CHOICES;
+      ret = (VNChoicesEventParser()).parse(child, &event.choices, error);
+      if(ret != 0) return ret;
+
+    } else if(child->node == "choice-set") {
+      event.type = VN_SCENE_EVENT_TYPE_CHOICE_SET;
+      ret = (VNChoiceSetEventParser()).parse(child, &event.choiceSet, error);
+      if(ret != 0) return ret;
+
     } else {
       *error = "Unknown child node '" + child->node + "'";
       return -1;
diff --git a/src/dawntools/vnscenetool/events/VNSceneEventsParser.hpp b/src/dawntools/vnscenetool/events/VNSceneEventsParser.hpp
index 38e9d31f..5ec10dcd 100644
--- a/src/dawntools/vnscenetool/events/VNSceneEventsParser.hpp
+++ b/src/dawntools/vnscenetool/events/VNSceneEventsParser.hpp
@@ -11,6 +11,8 @@
 #include "VNParallelEventParser.hpp"
 #include "VNMarkerParser.hpp"
 #include "VNGoToMarkerEventParser.hpp"
+#include "VNChoiceEventParser.hpp"
+#include "VNChoiceSetEventParser.hpp"
 
 namespace Dawn {
   struct VNSceneEvent;
@@ -26,7 +28,9 @@ namespace Dawn {
     VN_SCENE_EVENT_TYPE_WAIT,
     VN_SCENE_EVENT_TYPE_PARALLEL,
     VN_SCENE_EVENT_TYPE_MARKER,
-    VN_SCENE_EVENT_TYPE_GOTO_MARKER
+    VN_SCENE_EVENT_TYPE_GOTO_MARKER,
+    VN_SCENE_EVENT_TYPE_CHOICES,
+    VN_SCENE_EVENT_TYPE_CHOICE_SET
   };
 
   struct VNParallelEvent {
@@ -43,6 +47,8 @@ namespace Dawn {
     struct VNParallelEvent parallel;
     struct VNMarker marker;
     struct VNGoToMarkerEvent gotoMarker;
+    struct VNChoiceEvent choices;
+    struct VNChoiceSetEvent choiceSet;
   };
 
   class VNSceneEventsParser : public XmlParser<struct VNSceneEventList> {
diff --git a/src/dawntools/vnscenetool/events/VNTextEventParser.cpp b/src/dawntools/vnscenetool/events/VNTextEventParser.cpp
index 65d79ad3..caadf738 100644
--- a/src/dawntools/vnscenetool/events/VNTextEventParser.cpp
+++ b/src/dawntools/vnscenetool/events/VNTextEventParser.cpp
@@ -7,6 +7,27 @@
 
 using namespace Dawn;
 
+std::vector<std::string> VNTextParser::getRequiredAttributes() {
+  return { "lang" };
+}
+
+std::map<std::string, std::string> VNTextParser::getOptionalAttributes() {
+  return { };
+}
+
+int32_t VNTextParser::onParse(
+  Xml *node,
+  std::map<std::string, std::string> values,
+  struct VNText *out,
+  std::string *error
+) {
+  out->language = values["lang"];
+  out->text = node->value;
+  return 0;
+}
+
+// // // // // // // // // // // // // // // // // // // // // // // // // // //
+
 std::vector<std::string> VNTextEventParser::getRequiredAttributes() {
   return { };
 }
@@ -21,6 +42,7 @@ int32_t VNTextEventParser::onParse(
   struct VNTextEvent *out,
   std::string *error
 ) {
+  int32_t ret;
   auto itChildren = node->children.begin();
   while(itChildren != node->children.end()) {
     Xml *child = *itChildren;
@@ -28,8 +50,8 @@ int32_t VNTextEventParser::onParse(
     // Parse strings
     if(child->node == "string") {
       VNText text;
-      text.language = child->attributes["lang"];
-      text.text = child->value;
+      ret = (VNTextParser()).parse(child, &text, error);
+      if(ret != 0) return ret;
       out->texts.push_back(text);
     } else {
       *error = "Unknown child node '" + child->node + "'";
diff --git a/src/dawntools/vnscenetool/events/VNTextEventParser.hpp b/src/dawntools/vnscenetool/events/VNTextEventParser.hpp
index d351896b..a28570b8 100644
--- a/src/dawntools/vnscenetool/events/VNTextEventParser.hpp
+++ b/src/dawntools/vnscenetool/events/VNTextEventParser.hpp
@@ -16,6 +16,18 @@ namespace Dawn {
     std::vector<VNText> texts;
   };
 
+  class VNTextParser : public XmlParser<struct VNText> {
+    protected:
+      std::vector<std::string> getRequiredAttributes() override;
+      std::map<std::string, std::string> getOptionalAttributes() override;
+      int32_t onParse(
+        Xml *node,
+        std::map<std::string, std::string> values,
+        struct VNText *out,
+        std::string *error
+      ) override;
+  };
+
   class VNTextEventParser : public XmlParser<struct VNTextEvent> {
     protected:
       std::vector<std::string> getRequiredAttributes() override;