From 7867076bbe7d1dbfcb537d78f172d39f1b94c596 Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Thu, 17 Oct 2024 07:17:36 -0700
Subject: [PATCH] Added language support to main menu and sign

---
 assets/en.json                               | 15 ++++--
 assets/testmap.json                          |  2 +-
 src/dawn/game/game.c                         | 18 ++-----
 src/dawn/locale/language.c                   | 26 +++++----
 src/dawn/locale/language.h                   | 16 +++---
 src/dawn/rpg/conversation/CMakeLists.txt     |  1 +
 src/dawn/rpg/conversation/conversation.c     | 57 ++++++++++----------
 src/dawn/rpg/conversation/conversation.h     | 26 +++++----
 src/dawn/rpg/conversation/conversationsign.c | 32 +++++++++++
 src/dawn/rpg/conversation/conversationsign.h | 12 +++++
 src/dawn/rpg/entity/interact.c               |  5 +-
 src/dawn/ui/mainmenu.c                       | 21 +++++---
 src/dawn/ui/textbox.c                        | 16 ++++--
 13 files changed, 162 insertions(+), 85 deletions(-)
 create mode 100644 src/dawn/rpg/conversation/conversationsign.c
 create mode 100644 src/dawn/rpg/conversation/conversationsign.h

diff --git a/assets/en.json b/assets/en.json
index 41ac2903..69c86f61 100644
--- a/assets/en.json
+++ b/assets/en.json
@@ -1,7 +1,16 @@
 {
-  "general": {
-    "test": {
-      "test": "test"
+  "main_menu": {
+    "new_game": "New Game",
+    "load_game": "Load Game",
+    "options": "Options",
+    "exit": "Exit"
+  },
+  "maps": {
+    "testmap": {
+      "sign": {
+        "text": "This is a sign.",
+        "name": "Sign"
+      }
     }
   }
 }
\ No newline at end of file
diff --git a/assets/testmap.json b/assets/testmap.json
index 5238323a..9259338e 100644
--- a/assets/testmap.json
+++ b/assets/testmap.json
@@ -40,7 +40,7 @@
       "type": 3,
       "x": 3,
       "y": 8,
-      "text": "This is a sign."
+      "text": "maps.testmap.sign"
     },
     {
       "type": 4,
diff --git a/src/dawn/game/game.c b/src/dawn/game/game.c
index c508b8cd..4a35c2a7 100644
--- a/src/dawn/game/game.c
+++ b/src/dawn/game/game.c
@@ -28,28 +28,17 @@ game_t GAME;
 
 void gameInit() {
   memset(&GAME, 0, sizeof(game_t));
-
+  
   timeInit();
   inputInit();
   displayInit();
   assetInit();
+  assetLanguageLoad("en.json");
 
   textboxInit();
   testMenuInit();
   mainMenuInit();
-
-  assetLanguageLoad("en.json");
-
-  char_t buffer[LANGUAGE_STRING_LENGTH_MAX];
-  languageGet(buffer, "general.test.test");
-
-  conversation_t conversation;
-  conversationInit(
-    &conversation,
-    conversationTestInit,
-    conversationTestUpdate
-  );
-  conversationUpdate(&conversation);
+  conversationInit();
 
   GAME.state = GAME_STATE_INITIAL;
 }
@@ -57,6 +46,7 @@ void gameInit() {
 gameupdateresult_t gameUpdate(const float_t delta) {
   timeUpdate(delta);
   inputUpdate();
+  conversationUpdate();
 
   switch(GAME.state) {
     case GAME_STATE_INITIAL:
diff --git a/src/dawn/locale/language.c b/src/dawn/locale/language.c
index 3c09850a..ad065cd3 100644
--- a/src/dawn/locale/language.c
+++ b/src/dawn/locale/language.c
@@ -14,23 +14,29 @@ void languageInit() {
   memset(&LANGUAGE, 0, sizeof(language_t));
 }
 
-int32_t languageGet(
-  char_t *buffer,
-  const char_t *key,
-  ...
-) {
+const char_t * languageGetPointer(const char_t *key) {
   assertNotNull(key, "Key cannot be NULL.");
 
-  int32_t i;
+  language_t *lang = &LANGUAGE;
+  lang->count;
+
+  int32_t i = 0;
   while(i < LANGUAGE.count) {
     if(strcmp(key, LANGUAGE.keys[i]) != 0) {
       i++;
       continue;
     }
-
-    if(buffer != NULL) strcpy(buffer, LANGUAGE.strings[i]);
-    return strlen(LANGUAGE.strings[i]);
+    return LANGUAGE.strings[i];
   }
 
-  return -1;
+  return NULL;
+}
+
+int32_t languageGet(char_t *buffer, const char_t *key) {
+  const char_t *str = languageGetPointer(key);
+  if(str == NULL) return -1;
+  if(buffer == NULL) return strlen(str);
+
+  strcpy(buffer, str);
+  return strlen(str);
 }
\ No newline at end of file
diff --git a/src/dawn/locale/language.h b/src/dawn/locale/language.h
index 3c4da7d3..cf711499 100644
--- a/src/dawn/locale/language.h
+++ b/src/dawn/locale/language.h
@@ -25,16 +25,20 @@ extern language_t LANGUAGE;
  */
 void languageInit();
 
+/**
+ * Gets the pointer to the language string for the given key. Pointer should not
+ * be modified or freed.
+ * 
+ * @param key The key to get the string for.
+ * @return The pointer to the string.
+ */
+const char_t * languageGetPointer(const char_t *key);
+
 /**
  * Returns the language string for the given key.
  * 
  * @param buffer The buffer to write the string to, or NULL to get length.
  * @param key The key to get the string for.
- * @param ... The arguments to replace in the string.
  * @return The length of the string.
  */
-int32_t languageGet(
-  char_t *buffer,
-  const char_t *key,
-  ...
-);
\ No newline at end of file
+int32_t languageGet(char_t *buffer, const char_t *key);
\ No newline at end of file
diff --git a/src/dawn/rpg/conversation/CMakeLists.txt b/src/dawn/rpg/conversation/CMakeLists.txt
index 7c22f709..870ab965 100644
--- a/src/dawn/rpg/conversation/CMakeLists.txt
+++ b/src/dawn/rpg/conversation/CMakeLists.txt
@@ -9,5 +9,6 @@
 target_sources(${DAWN_TARGET_NAME}
   PRIVATE
     conversation.c
+    conversationsign.c
     testconversation.c
 )
\ No newline at end of file
diff --git a/src/dawn/rpg/conversation/conversation.c b/src/dawn/rpg/conversation/conversation.c
index e0060366..1a23d528 100644
--- a/src/dawn/rpg/conversation/conversation.c
+++ b/src/dawn/rpg/conversation/conversation.c
@@ -8,44 +8,48 @@
 #include "conversation.h"
 #include "assert/assert.h"
 
-void conversationInit(
-  conversation_t *conversation,
-  const conversationcallback_t init,
-  const conversationcallback_t update
-) {
-  assertNotNull(conversation, "Conversation is NULL.");
+conversation_t CONVERSATION;
 
-  memset(conversation, 0, sizeof(conversation_t));
-  conversation->init = init;
-  conversation->update = update;
-  conversation->index = CONVERSATION_NOT_INITIALIZED;
+void conversationInit() {
+  memset(&CONVERSATION, 0, sizeof(conversation_t));
 }
 
-void conversationUpdate(conversation_t *conversation) {
+void conversationSet(
+  const conversationcallback_t init,
+  const conversationcallback_t update,
+  void *user
+) {
+  CONVERSATION.init = init;
+  CONVERSATION.update = update;
+  CONVERSATION.index = CONVERSATION_NOT_INITIALIZED;
+  CONVERSATION.user = user;
+}
+
+void conversationUpdate() {
+  if(CONVERSATION.init == NULL || CONVERSATION.update == NULL) return;
+
   uint16_t ret;
 
-  assertNotNull(conversation, "Conversation is NULL.");
-
   // Init or update.
-  switch(conversation->index) {
+  switch(CONVERSATION.index) {
     case CONVERSATION_NOT_INITIALIZED:
-      conversation->index = 0;
-      ret = conversation->init(
-        conversation, conversation->index
+      CONVERSATION.index = 0;
+      ret = CONVERSATION.init(
+        &CONVERSATION, CONVERSATION.index
       );
       break;
     
     default:
-      ret = conversation->update(conversation, conversation->index);
+      ret = CONVERSATION.update(&CONVERSATION, CONVERSATION.index);
       break;
   }
 
   // Check ret value and update conversation.
   switch(ret) {
     case CONVERSATION_NEXT:
-      conversation->index++;
-      conversation->index = conversation->init(
-        conversation, conversation->index
+      CONVERSATION.index++;
+      CONVERSATION.index = CONVERSATION.init(
+        &CONVERSATION, CONVERSATION.index
       );
       break;
     
@@ -53,8 +57,8 @@ void conversationUpdate(conversation_t *conversation) {
       return;
     
     case CONVERSATION_RESTART:
-      conversation->index = 0;
-      conversation->init(conversation, conversation->index);
+      CONVERSATION.index = 0;
+      CONVERSATION.init(&CONVERSATION, CONVERSATION.index);
       break;
     
     case CONVERSATION_INVALID:
@@ -62,13 +66,12 @@ void conversationUpdate(conversation_t *conversation) {
       break;
 
     case CONVERSATION_DONE:
+      CONVERSATION.init = NULL;
+      CONVERSATION.update = NULL;
       break;
     
     default:
-      conversation->index = ret;
-      conversation->index = conversation->init(
-        conversation, conversation->index
-      );
+      CONVERSATION.index = ret;
       break;
   }
 }
\ No newline at end of file
diff --git a/src/dawn/rpg/conversation/conversation.h b/src/dawn/rpg/conversation/conversation.h
index f1b59096..9a717cce 100644
--- a/src/dawn/rpg/conversation/conversation.h
+++ b/src/dawn/rpg/conversation/conversation.h
@@ -28,21 +28,27 @@ typedef struct _conversation_t {
   void *user;
 } conversation_t;
 
+extern conversation_t CONVERSATION;
+
 /**
- * Initializes a conversation object.
- * 
- * @param conversation Conversation to initialize.
- * @param update Function to handle conversation updates.
+ * Initializes the conversation object.
  */
-void conversationInit(
-  conversation_t *conversation,
+void conversationInit();
+
+/**
+ * Sets the conversation object.
+ * 
+ * @param init The initialization callback.
+ * @param update The update callback.
+ * @param user The user data.
+ */
+void conversationSet(
   const conversationcallback_t init,
-  const conversationcallback_t update
+  const conversationcallback_t update,
+  void *user
 );
 
 /**
  * Update a given conversation.
- * 
- * @param conversastion Conversation to update.
  */
-void conversationUpdate(conversation_t *conversation);
\ No newline at end of file
+void conversationUpdate();
\ No newline at end of file
diff --git a/src/dawn/rpg/conversation/conversationsign.c b/src/dawn/rpg/conversation/conversationsign.c
new file mode 100644
index 00000000..06a99378
--- /dev/null
+++ b/src/dawn/rpg/conversation/conversationsign.c
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2024 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include "conversationsign.h"
+#include "assert/assert.h"
+#include "rpg/entity/entity.h"
+#include "locale/language.h"
+
+uint16_t conversationSignInit(conversation_t *convo, const uint16_t i) {
+  assertNotNull(convo, "Conversation is NULL!");
+  assertNotNull(convo->user, "Conversation user is NULL!");
+
+  entity_t *e = (entity_t*)convo->user;
+  assertTrue(e->type == ENTITY_TYPE_SIGN, "Entity is not a sign!");
+  textboxSetText("maps.testmap.sign.name", "maps.testmap.sign.text");
+
+  return 0;
+}
+
+uint16_t conversationSignUpdate(conversation_t *convo, const uint16_t i) {
+  switch(i) {
+    case 0:
+      return textboxIsOpen() ? CONVERSATION_CONTINUE : CONVERSATION_DONE;
+    
+    default:
+      return CONVERSATION_INVALID;
+  }
+}
\ No newline at end of file
diff --git a/src/dawn/rpg/conversation/conversationsign.h b/src/dawn/rpg/conversation/conversationsign.h
new file mode 100644
index 00000000..d0bb0451
--- /dev/null
+++ b/src/dawn/rpg/conversation/conversationsign.h
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2024 Dominic Masters
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#pragma once
+#include "conversation.h"
+
+uint16_t conversationSignInit(conversation_t *convo, const uint16_t i);
+uint16_t conversationSignUpdate(conversation_t *convo, const uint16_t i);
\ No newline at end of file
diff --git a/src/dawn/rpg/entity/interact.c b/src/dawn/rpg/entity/interact.c
index 325149ed..babe6226 100644
--- a/src/dawn/rpg/entity/interact.c
+++ b/src/dawn/rpg/entity/interact.c
@@ -8,6 +8,9 @@
 #include "interact.h"
 #include "assert/assert.h"
 #include "game/game.h"
+#include "locale/language.h"
+
+#include "rpg/conversation/conversationsign.h"
 
 void entityInteractEntity(
   entity_t *source,
@@ -29,7 +32,7 @@ void entityInteractEntity(
       return;
 
     case ENTITY_TYPE_SIGN:
-      textboxSetText("Sign", target->sign.text);
+      conversationSet(conversationSignInit, conversationSignUpdate, target);
       return;
 
     case ENTITY_TYPE_DOOR:
diff --git a/src/dawn/ui/mainmenu.c b/src/dawn/ui/mainmenu.c
index 2bf4e775..d00fb600 100644
--- a/src/dawn/ui/mainmenu.c
+++ b/src/dawn/ui/mainmenu.c
@@ -7,15 +7,16 @@
 
 #include "mainmenu.h"
 #include "game/game.h"
+#include "locale/language.h"
 
 menu_t MAIN_MENU;
 
 void mainMenuInit() {
   const char_t* strings[] = {
-    "New Game",
-    "Load Game",
-    "Options",
-    "Exit"
+    languageGetPointer("main_menu.new_game"),
+    languageGetPointer("main_menu.load_game"),
+    languageGetPointer("main_menu.options"),
+    languageGetPointer("main_menu.exit")
   };
 
   uint16_t columns = 1;
@@ -35,21 +36,25 @@ void mainMenuSelectCallback(
   const uint16_t y,
   const char_t *str
 ) {
-  if(strcmp(str, "New Game") == 0) {
+  // New Game
+  if(y == 0) {
     GAME.mapNext = MAP_LIST_TEST;
     GAME.state = GAME_STATE_MAP_CHANGE;
     return;
   }
 
-  if(strcmp(str, "Load Game") == 0) {
+  // Load Game
+  if(y == 1) {
     return;
   }
 
-  if(strcmp(str, "Options") == 0) {
+  // Options
+  if(y == 2) {
     return;
   }
 
-  if(strcmp(str, "Exit") == 0) {
+  // Exit
+  if(y == 3) {
     GAME.shouldExit = true;
     return;
   }
diff --git a/src/dawn/ui/textbox.c b/src/dawn/ui/textbox.c
index 32cee000..559c9d8d 100644
--- a/src/dawn/ui/textbox.c
+++ b/src/dawn/ui/textbox.c
@@ -9,6 +9,7 @@
 #include "assert/assert.h"
 #include "input.h"
 #include "game/time.h"
+#include "locale/language.h"
 
 textbox_t TEXTBOX;
 
@@ -20,18 +21,23 @@ void textboxSetText(
   const char_t *title,
   const char_t *text
 ) {
+  size_t len;
+  const char_t *temp;
   assertNotNull(text, "Text cannot be NULL.");
 
-  // Setup text copies
-  size_t len = strlen(text);
+  // Copy translation
+  temp = languageGetPointer(text);
+  len = strlen(temp);
   assertTrue(len < TEXTBOX_TEXT_MAX, "Text is too long.");
-  strcpy(TEXTBOX.text, text);
+  strcpy(TEXTBOX.text, temp);
   TEXTBOX.textLength = len;
 
   // Setup title
   if(title) {
-    assertTrue(strlen(title) < TEXTBOX_TITLE_MAX, "Title is too long.");
-    strcpy(TEXTBOX.title, title);
+    temp = languageGetPointer(title);
+    len = strlen(temp);
+    assertTrue(len < TEXTBOX_TITLE_MAX, "Title is too long.");
+    strcpy(TEXTBOX.title, temp);
   } else {
     TEXTBOX.title[0] = '\0';
   }