From fb34d7c16ef0bec219f6077ce3cf2cfecaa39b21 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 4 Feb 2023 23:02:43 -0800 Subject: [PATCH] New Language Tool --- assets/games/pokergame/locale/en.csv | 86 ---- assets/games/pokergame/locale/jp.csv | 21 - assets/games/pokergame/locale/locale.xml | 91 ++++ assets/games/pokergame/ui/uitest.xml | 13 - assets/games/pokergame/vn/testvn.txt | 36 ++ lint.js | 94 ++++ src/dawnpokergame/CMakeLists.txt | 8 +- src/dawntools/CMakeLists.txt | 22 +- src/dawntools/display/uigen/CMakeLists.txt | 2 +- src/dawntools/locale/CMakeLists.txt | 30 +- .../locale/languagegen/CMakeLists.txt | 47 +- src/dawntools/locale/languagegen/main.c | 112 ----- src/dawntools/locale/languagegen/main.cpp | 218 +++++++++ src/dawntools/utils/xml.c | 419 +++++++++--------- src/dawntools/utils/xml.h | 2 +- src/dawntools/visualnovel/CMakeLists.txt | 6 + .../visualnovel/vnscenegen/CMakeLists.txt | 22 + src/dawntools/visualnovel/vnscenegen/main.cpp | 15 + 18 files changed, 747 insertions(+), 497 deletions(-) delete mode 100644 assets/games/pokergame/locale/en.csv delete mode 100644 assets/games/pokergame/locale/jp.csv create mode 100644 assets/games/pokergame/locale/locale.xml delete mode 100644 assets/games/pokergame/ui/uitest.xml create mode 100644 assets/games/pokergame/vn/testvn.txt create mode 100644 lint.js delete mode 100644 src/dawntools/locale/languagegen/main.c create mode 100644 src/dawntools/locale/languagegen/main.cpp create mode 100644 src/dawntools/visualnovel/CMakeLists.txt create mode 100644 src/dawntools/visualnovel/vnscenegen/CMakeLists.txt create mode 100644 src/dawntools/visualnovel/vnscenegen/main.cpp diff --git a/assets/games/pokergame/locale/en.csv b/assets/games/pokergame/locale/en.csv deleted file mode 100644 index f791a745..00000000 --- a/assets/games/pokergame/locale/en.csv +++ /dev/null @@ -1,86 +0,0 @@ -undefined,UNDEFINED -hello.world,Hello World! -character.death.name,"Santa Muerte" -character.penny.name,Penny - -scene.1.1,"...Huh? Where am I?" -scene.1.2,"Relax child, come forward, it is time." -scene.1.3,"Who said that? Where is that voice coming from?" -scene.1.4,"Hello, and welcome to the other side wanderer." -scene.1.5,"Other side? Wait that robe! You don't mean?" -scene.1.6,"Yeah let's speed things up here a bit. Normally I go on script but short on time right now unfortunately." -scene.1.7,"You died, dead, passed on, kaput. This is the space between spaces. You may be going up, you may be going down, that's not my choice, I'm just here to guide you." -scene.1.8,"...Are you?" -scene.1.9,"Yes, some call me the Grim Reaper, some Muerte, and a long time ago it was Torva messor." -scene.1.10,"Nowadays most people others just call me Death, but I prefer Nami, short for Izanami." -scene.1.11,"Truth be told I am not the reason you are here, I am just here to guide you, don't know why I keep getting lumped with this bad reputation of killing people." -scene.1.12,"Wait, so I am dead? For real? This has to be some kind of misunderstanding-wait I must be dreaming, that's it." -scene.1.121,"Oh no this can't be happening, this can't be real..." -scene.1.13,"If I had a penny for every time... -No, don't get it wrong, you are meant to be here." -scene.1.131,"I can't be.. you gotta send me back! How did I die?" -scene.1.132,"I don't know. Regardless, I can't send you back to your world. We are getting off topic anyway." -scene.1.133,"What?" -scene.1.134,"*ahem* I have a request of you child." -scene.1.14,"Me? Help you? Unless you're taking me back to my home I don't think you can convince me to help the likes of you." -scene.1.141,"Yes... See, normally I'd just take you to Charon. It is his role to take the newcomers where they need to go." -scene.1.142,"You know how it goes, I introduce you to Charon, you pay a coin, jump on a boat, and then you're not my problem any more." -scene.1.15,"You mean send me down the river styx? You're not helping your case." -scene.1.16,"Yes, well I would normally do that, but at the moment I have a bit of a problem with capacity." -scene.1.17,"Capacity? What kind of capacity issue?" -scene.1.18,"I manage a few worlds, not just the one that you come from." -scene.1.19,"In your world, people rely on technology and their own ability to progress civilization." -scene.1.20,"In another world, I manage people who raise and tame small pocket sized creatures with different abilities to progress their civilization." -scene.1.21,"But one world I manage has been having some real troubles lately." -scene.1.22,"This world contains humans alongside other intelligent creatures and species. They would be analagous to the medieval times in your world." -scene.1.23,"So far they have managed to keep a good balance by having some creatures weild weapons, and some use magical powers." -scene.1.24,"But lately there has been more cases of stronger creatures killing off the human population." -scene.1.25,"It's causing me to work overtime just to make sure I can get everyone where they need to go." -scene.1.26,"You have to work overtime?" -scene.1.27,"The benefits here are really good, my union gives us time and a half for overtime we do." -scene.1.28,"Death has unionized? That sounds horrible." -scene.1.29,"Yes I did, and please just call me Nami. My union is starting to threaten a strike and I need to try and prevent that." -scene.1.30,"I haven't been on strike since the 1940's when we had our last capacity issue." -scene.1.31,"The union negotiated a great dental plan after that. But we need to try and avoid striking this time." -scene.1.32,"Okay but I don't know how you think I can help." -scene.1.33,"It's easy. Now understand that I cannot send you back to your world, that is out of my control." -scene.1.34,"But there are no rules about you being sent to another world." -scene.1.35,"So, I send you to the medieval world, you get another crack at life and perhaps you can assist me in finding out how to stop all the deaths." -scene.1.36,"So, Nami, you want me to go to a world I don't know, that has monsters and a problem with lots of death, stuck in the medieval times, just to help out literal death?" -scene.1.37,"Not to mention I won't know anyone, I won't understand their language and don't know their culture." -scene.1.38,"I'll put it bluntly Nami, it sounds like I would be signing up for a short, miserable, lonely life, just for a chance to help you out." -scene.1.39,"That's great, thanks for offering to help out!" -scene.1.40,"I don't have a choice do I?" -scene.1.41,"No, besides we have already started. Now, stand still and take a deep breath I am going to send you over to the other world now." -scene.1.42,"I feel really tired all of a sudden... Wait does the Grim Reaper even have any kind of powers?" -scene.1.43,"Of course I do, now open your eyes..." - -scene.2.1,"This scene has you waking in the new world" -scene.3.1,"This is the scene to prep you for the first game" -scene.4.1,"First Poker Game with friendlies" -scene.5.1,"After first game, wind up maybe shop instructions or something" -scene.6.1,"Talk with death probably, check in on you, etc." -scene.7.1,"Pre-Orc Battle" -scene.8.1,"Orc Battle" -scene.9.1,"Immediately Post Orc Battle" -scene.10.1,"Morning after orc battle and some foreshadowing" -scene.11.1,"Pre battle with theif" -scene.12.1,"Battle with theif" -unknown1,"There needs to be the big bad here." -scene.13.1,"After battle with theif, things cool off a bit." -scene.14.1,"Harem times maybe" -scene.15.1,"Harem times interrupted by some chaos" -scene.16.1,"I'm putting chaos here but this scene is probably redundant." -scene.17.1,"Chaos battle" -scene.18.1,"Post Chaos Battle, things are still gloomy" -unknown2,"Another big bad here probably." -scene.19.1,"Pre King Arrival, rumours" -scene.20.1,"King Arrival, Everyone's annoyed" -scene.21.1,"King in the bar, running his mouth" -scene.22.1,"King Fight" -scene.23.1,"Post King Fight, angry he leaves, town rallies around you" -scene.24.1," Go to one of the kingsmen who's working with the big bad, he's worried that the townspeople know more than they let on." -unknown3,"Something here has to bring the big bad to town." -unknown4,"It's revealed that death knows the big bad", -test.1,"Test" -test.2,"Test*" \ No newline at end of file diff --git a/assets/games/pokergame/locale/jp.csv b/assets/games/pokergame/locale/jp.csv deleted file mode 100644 index 911a9f52..00000000 --- a/assets/games/pokergame/locale/jp.csv +++ /dev/null @@ -1,21 +0,0 @@ -undefined,UNDEFINED -hello.world,Hello World! -character.penny.name,Penny -scene.1.1,"日本語パスコん" -scene.2.1,"This scene has you waking in the new world" -scene.3.1,"This is the scene to prep you for the first game" -scene.4.1,"First Poker Game with friendlies" -scene.5.1,"After first game, wind up maybe shop instructions or something" -scene.6.1,"Talk with death probably, check in on you, etc." -scene.7.1,"Pre-Orc Battle" -scene.8.1,"Orc Battle" -scene.9.1,"Immediately Post Orc Battle" -scene.10.1,"Morning after orc battle and some foreshadowing" -scene.11.1,"Pre battle with theif" -scene.12.1,"Battle with theif" -scene.13.1,"After battle with theif, things cool off a bit." -scene.14.1,"Harem times maybe" -scene.15.1,"Harem times interrupted by some chaos" -scene.16.1,"I'm putting chaos here but this scene is probably redundant." -scene.17.1,"Chaos battle" -scene.18.1,"Post Chaos Battle, things are still gloomy" \ No newline at end of file diff --git a/assets/games/pokergame/locale/locale.xml b/assets/games/pokergame/locale/locale.xml new file mode 100644 index 00000000..8498383e --- /dev/null +++ b/assets/games/pokergame/locale/locale.xml @@ -0,0 +1,91 @@ + + + + + + ...Huh? Where am I? + + + + This scene has you waking in the new world + + + This is the scene to prep you for the first game + + + First Poker Game with friendlies + + + After first game, wind up maybe shop instructions or something + + + Talk with death probably, check in on you, etc. + + + Pre-Orc Battle + + + Orc Battle + + + Immediately Post Orc Battle + + + Morning after orc battle and some foreshadowing + + + Pre battle with theif + + + Battle with theif + + + There needs to be the big bad here. + + + After battle with theif, things cool off a bit. + + + Harem times maybe + + + Harem times interrupted by some chaos + + + I'm putting chaos here but this scene is probably redundant. + + + Chaos battle + + + Post Chaos Battle, things are still gloomy + + + Another big bad here probably. + + + Pre King Arrival, rumours + + + King Arrival, Everyone's annoyed + + + King in the bar, running his mouth + + + King Fight + + + Post King Fight, angry he leaves, town rallies around you + + + Go to one of the kingsmen who's working with the big bad, he's worried that the townspeople know more than they let on. + + + Something here has to bring the big bad to town. + + + It's revealed that death knows the big bad + + + \ No newline at end of file diff --git a/assets/games/pokergame/ui/uitest.xml b/assets/games/pokergame/ui/uitest.xml deleted file mode 100644 index 88d82609..00000000 --- a/assets/games/pokergame/ui/uitest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/assets/games/pokergame/vn/testvn.txt b/assets/games/pokergame/vn/testvn.txt new file mode 100644 index 00000000..b513ecb3 --- /dev/null +++ b/assets/games/pokergame/vn/testvn.txt @@ -0,0 +1,36 @@ +# This is a sample VN Scene that I am creating to create a scene generator. +# The scene generator will simply create a series of events, nothing more. +# Infact, the scene generator really is just a scene event with trailed events +# Generator. + +# Scene name is probably going to be inherited from either the target or the +# filename + +# By default the mode we are in is in TEXT mode, unless you explicitly change +# the mode. The text mode takes the person, emotion and text string and tries +# to convert it to this C++ equivallent code; +# new VisualNovelTextboxEvent(vnManager, this->[person]->vnCharacter, this->[person]->[emotion], [text]) + +# Here is our first example. We take death, give her an emotion, and write some +# text. +death:happy: scene.1.1 + +# Here, we inherit the existing emotion. I've also omitted the space because it +# is just there for show. +death:scene.1.1 + +# Now let's fade our character in. Fade char in events start like this +fade death in quad over 1.0 +# Could also be written like any of these +# fade death out +# fade death in +# fade death out over 1.0 seconds +# fade death in over 1.0 seconds +# fade death in linear +# fade death out linear over 10 seconds +# fade death out over 10 +# Basically the format just needs to be +# fade [character] [in/out] (easing function) (time) +# Each time a fade occurs the parameters will be remembered, and used for any +# other fade after that, so you just need to define the easing curve once per +# scene. \ No newline at end of file diff --git a/lint.js b/lint.js new file mode 100644 index 00000000..b00b5479 --- /dev/null +++ b/lint.js @@ -0,0 +1,94 @@ +const fs = require('fs'); +const path = require('path'); + +const DIR_SOURCES = path.resolve('src'); + +const fileGetContents = filePath => fs.readFileSync(filePath, 'utf-8'); + +const ensureCopyright = (file, contents) => { + if( + contents.includes('Copyright (c)') && + contents.includes('MIT') + ) return; + throw new Error(`${file} is missing its copyright!`); +} + +const ensurePragma = (file, contents) => { + if(contents.includes('#pragma once')) return; + throw new Error(`${file} is missing a #pragma once`); +} + + +const fileHandleCpp = filePath => { + // Load contents + const contents = fileGetContents(filePath); + ensureCopyright(filePath, contents); +} + +const fileHandleC = filePath => { + // Load contents + const contents = fileGetContents(filePath); + ensureCopyright(filePath, contents); +} + +const fileHandleHpp = filePath => { + // Load contents + const contents = fileGetContents(filePath); + + ensureCopyright(filePath, contents); + ensurePragma(filePath, contents); +} + +const fileHandleH = filePath => { + // Load contents + const contents = fileGetContents(filePath); + + ensureCopyright(filePath, contents); + ensurePragma(filePath, contents); +} + +const fileHandleCmake = filePath => { + // Load contents + const contents = fileGetContents(filePath); + + // Check for the copyright + ensureCopyright(filePath, contents); +} + + + +const fileScan = filePath => { + const ext = path.extname(filePath).replace(/\./g, ''); + const base = path.basename(filePath); + + if(ext === 'cpp') { + fileHandleCpp(filePath); + } else if(ext === 'hpp') { + fileHandleHpp(filePath); + } else if(ext === 'c') { + fileHandleC(filePath); + } else if(ext === 'h') { + fileHandleH(filePath); + }else if(base === 'CMakeLists.txt') { + fileHandleCmake(filePath); + } else { + throw new Error(`Unknown file type ${filePath}`); + } +}; + +const dirScan = directory => { + const contents = fs.readdirSync(directory); + contents.forEach(filePath => { + const abs = path.join(directory, filePath); + const stat = fs.statSync(abs); + if(stat.isDirectory()) { + dirScan(abs); + } else { + fileScan(abs); + } + }); +} + +(() => { + dirScan(DIR_SOURCES); +})(); \ No newline at end of file diff --git a/src/dawnpokergame/CMakeLists.txt b/src/dawnpokergame/CMakeLists.txt index dedd4ee0..2fd520ef 100644 --- a/src/dawnpokergame/CMakeLists.txt +++ b/src/dawnpokergame/CMakeLists.txt @@ -30,7 +30,7 @@ add_subdirectory(scenes) set(DIR_GAME_ASSETS games/pokergame) tool_texture(texture_test texture_test.png) -tool_language(language_en ${DIR_GAME_ASSETS}/locale/en.csv) +tool_language(locale_poker ${DIR_GAME_ASSETS}/locale/locale.xml) tool_tileset(tileset_death texture_death ${DIR_GAME_ASSETS}/characters/death/sheet.png 1 3) tool_tileset(tileset_penny texture_penny ${DIR_GAME_ASSETS}/characters/penny/sheet.png 1 3) @@ -39,10 +39,8 @@ tool_truetype(truetype_alice ${DIR_GAME_ASSETS}/font/Alice-Regular.ttf truetype_ tool_audio(audio_test borrowed/sample_short.wav) -tool_ui(ui_test ${DIR_GAME_ASSETS}/ui/uitest.xml) - add_dependencies(${DAWN_TARGET_NAME} - language_en + locale_poker tileset_death tileset_penny @@ -52,6 +50,4 @@ add_dependencies(${DAWN_TARGET_NAME} texture_test audio_test - - ui_test ) \ No newline at end of file diff --git a/src/dawntools/CMakeLists.txt b/src/dawntools/CMakeLists.txt index d73b2021..30206d91 100644 --- a/src/dawntools/CMakeLists.txt +++ b/src/dawntools/CMakeLists.txt @@ -1,9 +1,13 @@ -# Copyright (c) 2021 Dominic Msters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_subdirectory(audio) -add_subdirectory(display) -add_subdirectory(file) -add_subdirectory(locale) \ No newline at end of file +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(audio) +add_subdirectory(display) +add_subdirectory(file) +add_subdirectory(locale) + +if(DAWN_VISUAL_NOVEL) + add_subdirectory(visualnovel) +endif() \ No newline at end of file diff --git a/src/dawntools/display/uigen/CMakeLists.txt b/src/dawntools/display/uigen/CMakeLists.txt index 9fb7a2c9..70ed4992 100644 --- a/src/dawntools/display/uigen/CMakeLists.txt +++ b/src/dawntools/display/uigen/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Dominic Msters +# Copyright (c) 2023 Dominic Msters # # This software is released under the MIT License. # https://opensource.org/licenses/MIT diff --git a/src/dawntools/locale/CMakeLists.txt b/src/dawntools/locale/CMakeLists.txt index 65ad32d6..79506401 100644 --- a/src/dawntools/locale/CMakeLists.txt +++ b/src/dawntools/locale/CMakeLists.txt @@ -1,15 +1,15 @@ -# Copyright (c) 2021 Dominic Msters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_subdirectory(languagegen) - -# Language Tool -function(tool_language target in) - add_custom_target(${target} - COMMAND languagegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${target}" - COMMENT "Generating texture ${target} from ${in}" - DEPENDS languagegen - ) -endfunction() \ No newline at end of file +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(languagegen) + +# Language Tool +function(tool_language target in) + add_custom_target(${target} + COMMAND languagegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}" + COMMENT "Generating language set ${target} from ${in}" + DEPENDS languagegen + ) +endfunction() diff --git a/src/dawntools/locale/languagegen/CMakeLists.txt b/src/dawntools/locale/languagegen/CMakeLists.txt index e628bc38..423852ae 100644 --- a/src/dawntools/locale/languagegen/CMakeLists.txt +++ b/src/dawntools/locale/languagegen/CMakeLists.txt @@ -1,25 +1,24 @@ -# Copyright (c) 2021 Dominic Msters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Texture Build Tool -project(languagegen VERSION 1.0) -add_executable(languagegen) -target_sources(languagegen - PRIVATE - main.c - ../../utils/file.c - ../../utils/image.c - ../../utils/csv.c -) -target_include_directories(languagegen - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/../../ - ${CMAKE_CURRENT_LIST_DIR} -) -target_link_libraries(languagegen - PUBLIC - ${DAWN_BUILD_HOST_LIBS} - stb +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Texture Build Tool +project(languagegen VERSION 2.0) +add_executable(languagegen) +target_sources(languagegen + PRIVATE + main.cpp + ../../utils/file.c + ../../utils/csv.c + ../../utils/xml.c +) +target_include_directories(languagegen + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../ + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(languagegen + PUBLIC + ${DAWN_BUILD_HOST_LIBS} ) \ No newline at end of file diff --git a/src/dawntools/locale/languagegen/main.c b/src/dawntools/locale/languagegen/main.c deleted file mode 100644 index a37aedd9..00000000 --- a/src/dawntools/locale/languagegen/main.c +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (c) 2021 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "../../utils/common.h" -#include "../../utils/file.h" -#include "../../utils/csv.h" - -int main(int argc, char *argv[]) { - FILE *file; - char path[FILENAME_MAX + 1]; - csv_t csv; - char *in; - char *out; - char *buffer; - char sep = '|'; - - if(argc != 3) { - printf("Invalid number of arguments\n"); - return 1; - } - - // Set up strings - in = argv[1]; - out = argv[2]; - - // Normalize slashes - fileNormalizeSlashes(in); - fileNormalizeSlashes(out); - - // Check the output doesn't already exist - sprintf(path, "%s.language", out); - // file = fopen(path, "rb"); - // if(file != NULL) { - // fclose(file); - // return 0; - // } - - // Read in original CSV string - file = fopen(in, "rb"); - if(file == NULL) { - printf("Failed to open file!\n"); - return 1; - } - - // Seek to end, get length, seek back to start. - fseek(file, 0, SEEK_END); - size_t fileSize = ftell(file); - fseek(file, 0, SEEK_SET); - - // Read in all data - buffer = malloc(sizeof(char) * (fileSize + 1)); - size_t readSize = fread(buffer, 1, fileSize, file); - fclose(file); - if(readSize < fileSize) { - printf("Failed to read all data from CSV\n"); - free(buffer); - return 1; - } - buffer[fileSize] = '\0'; - - csvParse(buffer, &csv); - free(buffer); - - // Prepare output file for writing. - sprintf(path, "%s.language", out); - fileMkdirp(path); - file = fopen(path, "wb"); - if(file == NULL) { - printf("Failed to create output language file\n"); - csvDispose(&csv); - return 1; - } - - // Iterate over the CSV - for(int32_t y = 0; y < csv.rowCount; y++) { - // Ensure valid line - if(csv.cellCounts[y] != 2) { - printf("Failed to parse language. Line %i has %i cells instead of 2\n", y, csv.cellCounts); - fclose(file); - csvDispose(&csv); - return 1; - } - - char *key = csvGetCell(&csv, y, 0); - char *value = csvGetCell(&csv, y, 1); - - // 23/01/14 - Replace \r in CSV. - stringRemoveAll(key, '\r'); - stringRemoveAll(value, '\r'); - if(strlen(key) <= 0 || strlen(value) <= 0) { - printf("Failed to parse language. Line %i has an invalid string\n", y); - fclose(file); - csvDispose(&csv); - return 1; - } - - fwrite(key, sizeof(char), strlen(key), file); - fwrite(&sep, sizeof(char), 1, file); - fwrite(value, sizeof(char), strlen(value), file); - fwrite(&sep, sizeof(char), 1, file); - } - - // Finished writing - fclose(file); - csvDispose(&csv); - - return 0; -} \ No newline at end of file diff --git a/src/dawntools/locale/languagegen/main.cpp b/src/dawntools/locale/languagegen/main.cpp new file mode 100644 index 00000000..a2b57854 --- /dev/null +++ b/src/dawntools/locale/languagegen/main.cpp @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +extern "C" { + #include "../../utils/common.h" + #include "../../utils/file.h" + #include "../../utils/csv.h" + #include "../../utils/xml.h" + #include +} + +#include +#include +#include +#include +#include + +struct LanguageString { + std::string key; + std::string value; +}; + +int32_t parseString( + xml_t *stringNode, + std::string key, + std::map> *strings +) { + auto attrLang = xmlGetAttributeByName(stringNode, "lang"); + if(attrLang == -1) { + std::cout << "String is missing lang parameter." << std::endl; + return -1; + } + + std::string lang(stringNode->attributeDatas[attrLang]); + struct LanguageString str; + str.key = key; + str.value = std::string(stringNode->value); + + auto existing = (*strings).find(lang); + if(existing == (*strings).end()) { + (*strings).insert(std::make_pair(lang, std::vector())); + } + (*strings)[lang].push_back(str); + return 0; +} + +int32_t parseGroup( + xml_t *groupNode, + std::string key, + std::map> *strings +) { + int32_t ret; + + auto attrKey = xmlGetAttributeByName(groupNode, "key"); + if(attrKey == -1) { + std::cout << "Group node is missing key" << std::endl; + return 1; + } + + if(key.size() > 0) key += "."; + key += std::string(groupNode->attributeDatas[attrKey]); + + for(int32_t i = 0; i < groupNode->childrenCount; i++) { + auto c = groupNode->children + i; + if(std::string(c->node) == "string") { + ret = parseString(c, key, strings); + if(ret != 0) return ret; + } else if(std::string(c->node) == "group") { + ret = parseGroup(c, key, strings); + if(ret != 0) return ret; + } + } + + return 0; +} + +int main(int argc, char *argv[]) { + if(argc != 3) { + std::cout << "Invalid number of arguments provided to language gen!" << std::endl; + return 1; + } + + char *fileInName = argv[1]; + fileNormalizeSlashes(fileInName); + + FILE *fileIn = fopen(fileInName, "rb"); + if(fileIn == NULL) { + std::cout << "Failed to open input file " << fileInName << std::endl; + return 1; + } + + auto size = assetReadString(fileIn, NULL); + char *buffer = (char *)malloc(sizeof(char) * size); + if(buffer == NULL) { + std::cout << "Failed to allocate memory for locale string XML" << std::endl; + fclose(fileIn); + return 1; + } + + assetReadString(fileIn, buffer); + fclose(fileIn); + + xml_t xml; + xmlLoad(&xml, buffer); + free(buffer); + + // Begin parsing. Start by looking for the tags + std::vector languages; + for(int32_t i = 0; i < xml.childrenCount; i++) { + auto c = xml.children + i; + if(std::string(c->node) != "language") continue; + auto attrName = xmlGetAttributeByName(c, "name"); + + if(attrName == -1) { + std::cout << "Missing name param on language node" << std::endl; + xmlDispose(&xml); + return 1; + } + languages.push_back(std::string(c->attributeDatas[attrName])); + } + + // Now begin actually parsing + std::map> strings; + for(int32_t i = 0; i < xml.childrenCount; i++) { + auto c = xml.children + i; + if(std::string(c->node) == "group") { + auto ret = parseGroup(c, "", &strings); + if(ret != 0) { + xmlDispose(&xml); + return ret; + } + } else if(std::string(c->node) == "string") { + std::cout << "String cannot be a root node" << std::endl; + xmlDispose(&xml); + return 1; + } + } + + xmlDispose(&xml); + + // Now we validate each lang has each key. + std::vector keys; + + auto it = strings.begin(); + while(it != strings.end()) { + auto it2 = it->second.begin(); + while(it2 != it->second.end()) { + auto key = it2->key; + auto exist = std::find(keys.begin(), keys.end(), key); + if(exist == keys.end()) { + keys.push_back(key); + } + it2++; + } + ++it; + } + + // Now we actually parse each string, validating as we go. + it = strings.begin(); + while(it != strings.end()) { + std::vector itKeys; + + std::string bufferOut = ""; + + auto it2 = it->second.begin(); + while(it2 != it->second.end()) { + auto l = *it2; + itKeys.push_back(l.key); + bufferOut += l.key + "|" + l.value + "|"; + it2++; + } + + std::string filenameOut(argv[2]); + filenameOut += "/nlanguage_" + it->first + ".language"; + + char *filenameOutC = (char *)malloc(sizeof(char) * (filenameOut.size() + 1)); + if(filenameOutC == NULL) { + std::cout << "Failed to allocate filename memory" << std::endl; + return 1; + } + strcpy(filenameOutC, filenameOut.c_str()); + fileNormalizeSlashes(filenameOutC); + + fileMkdirp(filenameOutC); + FILE *fileOut = fopen(filenameOutC, "wb"); + free(filenameOutC); + if(fileOut == NULL) { + std::cout << "Failed to create output file " << filenameOut << std::endl; + return 1; + } + + const char *strOut = bufferOut.c_str(); + fwrite(strOut, sizeof(char), strlen(strOut), fileOut); + fclose(fileOut); + + auto it3 = keys.begin(); + while(it3 != keys.end()) { + auto key = *it3; + auto inIt = std::find(itKeys.begin(), itKeys.end(), key); + if(inIt == itKeys.end()) { + std::cout << "Locale " << it->first << " missing key " << key << std::endl; + } + it3++; + } + + if(itKeys.size() != keys.size()) { + std::cout << "Locale is missing some keys, see above" << std::endl; + return 1; + } + ++it; + } + + return 0; +} \ No newline at end of file diff --git a/src/dawntools/utils/xml.c b/src/dawntools/utils/xml.c index 8c6c43ed..caeea259 100644 --- a/src/dawntools/utils/xml.c +++ b/src/dawntools/utils/xml.c @@ -1,210 +1,211 @@ -/** - * Copyright (c) 2021 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "xml.h" - -int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i) { - char c; - int32_t level = 0; - uint8_t doing = XML_DOING_NOTHING; - bool insideTag = false; - char* buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); - int32_t bufferLength = 0; - - xml->value = NULL; - xml->attributeCount = 0; - - xml->children = malloc(sizeof(xml_t) * XML_CHILD_COUNT_MAX); - xml->childrenCount = 0; - - while(c = data[i++]) { - switch(doing) { - case XML_DOING_NOTHING: - // Look for either an opening tag (<) or a word for a value. - if(c == '>') continue; - if(c == '<') { - if(insideTag) { - i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1); - doing = XML_PARSING_CHILD; - } else { - doing = XML_PARSING_TAG_NAME; - level++; - insideTag = true; - } - continue; - } - - if(xmlIsWhitespace(c)) continue; - doing = XML_PARSING_VALUE; - buffer[bufferLength++] = c; - break; - - case XML_PARSING_TAG_NAME: - // Just keep reading until we either hit a space (end of the tag name) - // or a closing tag value, either / or > - if(xmlIsWhitespace(c) || c == '>' || c == '/') { - buffer[bufferLength] = '\0'; - xml->node = buffer; - buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); - bufferLength = 0; - if(c == '/') { - level--; - insideTag = false; - doing = XML_PARSING_CLOSE; - } else { - doing = c == '>' ? XML_DOING_NOTHING : XML_LOOKING_FOR_ATTRIBUTE; - } - continue; - } - buffer[bufferLength++] = c; - break; - - case XML_LOOKING_FOR_ATTRIBUTE: - // Look until we hit either the end of a tag, or the attribute itself - if(xmlIsWhitespace(c) || c == '>' || c == '/' || c == '=') { - if(c == '>' || c == '/') { - doing = XML_DOING_NOTHING; - if(c == '/') { - level--; - insideTag = false; - doing = XML_PARSING_CLOSE; - } - } else if(c == '=') { - doing = XML_LOOKING_FOR_ATTRIBUTE_VALUE; - } else { - doing = XML_LOOKING_FOR_ATTRIBUTE; - } - - if(bufferLength > 0) { - buffer[bufferLength] = '\0'; - xml->attributeNames[xml->attributeCount++] = buffer; - xml->attributeDatas[xml->attributeCount] = NULL; - buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); - bufferLength = 0; - } - continue; - } - - buffer[bufferLength++] = c; - break; - - case XML_LOOKING_FOR_ATTRIBUTE_VALUE: - // Keep looking until we find a quote mark - if(xmlIsWhitespace(c)) continue; - if(c == '>' || c == '/') { - doing = XML_DOING_NOTHING; - insideTag = false; - continue; - } - - if(c != '"') continue; - doing = XML_PARSING_ATTRIBUTE_VALUE; - break; - - case XML_PARSING_ATTRIBUTE_VALUE: - // Parse the attribute value until we find a quote mark. - if(c == '"') { - doing = XML_LOOKING_FOR_ATTRIBUTE; - buffer[bufferLength] = '\0'; - xml->attributeDatas[xml->attributeCount - 1] = buffer; - buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); - bufferLength = 0; - continue; - } - - buffer[bufferLength++] = c; - break; - - case XML_PARSING_VALUE: - // Keep parsing child until we find a < for an opening/closing tag. - if(c == '<') { - // In HTML Spec there could be a child here but not in XML spec. - doing = XML_PARSING_CLOSE; - buffer[bufferLength] = '\0'; - bufferLength = 0; - xml->value = buffer; - buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); - continue; - } - - buffer[bufferLength++] = c; - break; - - case XML_PARSING_CHILD: - if(c == '<') { - // Read ahead and confirm this is a close or not - if(data[i] == '/') { - doing = XML_PARSING_CLOSE; - continue; - } - - // Likely another child. - i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1); - } - - if(xmlIsWhitespace(c)) continue; - - // In HTML Spec there's a chance for there to be a value here, but not - // in the XML spec. - break; - - case XML_PARSING_CLOSE: - // Just keep parsing until the tag closer finishes. - if(c != '>') continue; - doing = XML_DOING_NOTHING; - - //TODO: Return index or something? - free(buffer); - return i; - - default: - break; - } - } - - free(buffer); - return i; -} - -void xmlLoad(xml_t *xml, char *data) { - xmlLoadChild(xml, data, 0); -} - -void xmlDispose(xml_t *xml) { - uint8_t i; - - // Dispose children recursively - for(i = 0; i < xml->childrenCount; i++) { - xmlDispose(xml->children + i); - } - - // Free children array. - free(xml->children); - - // Dispose attributes - for(i = 0; i < xml->attributeCount; i++) { - free(xml->attributeNames[i]); - if((xml->attributeDatas + i) != NULL) { - free(xml->attributeDatas[i]); - } - } - - free(xml->node); - if(xml-> value != NULL) free(xml->value); -} - -int16_t xmlGetAttributeByName(xml_t *xml, char *name) { - int16_t i; - for(i = 0; i < xml->attributeCount; i++) { - if(strcmp(xml->attributeNames[i], name) == 0) return i; - } - return -1; -} - -bool xmlIsWhitespace(char c) { - return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "xml.h" + +int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i) { + char c; + int32_t level = 0; + uint8_t doing = XML_DOING_NOTHING; + bool insideTag = false; + char* buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + int32_t bufferLength = 0; + + xml->value = NULL; + xml->attributeCount = 0; + + xml->children = malloc(sizeof(xml_t) * XML_CHILD_COUNT_MAX); + xml->childrenCount = 0; + + while(c = data[i++]) { + switch(doing) { + case XML_DOING_NOTHING: + // Look for either an opening tag (<) or a word for a value. + if(c == '>') continue; + if(c == '<') { + if(insideTag) { + i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1); + doing = XML_PARSING_CHILD; + } else { + doing = XML_PARSING_TAG_NAME; + level++; + insideTag = true; + } + continue; + } + + if(xmlIsWhitespace(c)) continue; + doing = XML_PARSING_VALUE; + buffer[bufferLength++] = c; + break; + + case XML_PARSING_TAG_NAME: + // Just keep reading until we either hit a space (end of the tag name) + // or a closing tag value, either / or > + if(xmlIsWhitespace(c) || c == '>' || c == '/') { + buffer[bufferLength] = '\0'; + xml->node = buffer; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + bufferLength = 0; + if(c == '/') { + level--; + insideTag = false; + doing = XML_PARSING_CLOSE; + } else { + doing = c == '>' ? XML_DOING_NOTHING : XML_LOOKING_FOR_ATTRIBUTE; + } + continue; + } + buffer[bufferLength++] = c; + break; + + case XML_LOOKING_FOR_ATTRIBUTE: + // Look until we hit either the end of a tag, or the attribute itself + if(xmlIsWhitespace(c) || c == '>' || c == '/' || c == '=') { + if(c == '>' || c == '/') { + doing = XML_DOING_NOTHING; + if(c == '/') { + level--; + insideTag = false; + doing = XML_PARSING_CLOSE; + } + } else if(c == '=') { + doing = XML_LOOKING_FOR_ATTRIBUTE_VALUE; + } else { + doing = XML_LOOKING_FOR_ATTRIBUTE; + } + + if(bufferLength > 0) { + buffer[bufferLength] = '\0'; + xml->attributeNames[xml->attributeCount++] = buffer; + xml->attributeDatas[xml->attributeCount] = NULL; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + bufferLength = 0; + } + continue; + } + + buffer[bufferLength++] = c; + break; + + case XML_LOOKING_FOR_ATTRIBUTE_VALUE: + // Keep looking until we find a quote mark + if(xmlIsWhitespace(c)) continue; + if(c == '>' || c == '/') { + doing = XML_DOING_NOTHING; + insideTag = false; + continue; + } + + if(c != '"') continue; + doing = XML_PARSING_ATTRIBUTE_VALUE; + break; + + case XML_PARSING_ATTRIBUTE_VALUE: + // Parse the attribute value until we find a quote mark. + if(c == '"') { + doing = XML_LOOKING_FOR_ATTRIBUTE; + buffer[bufferLength] = '\0'; + xml->attributeDatas[xml->attributeCount - 1] = buffer; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + bufferLength = 0; + continue; + } + + buffer[bufferLength++] = c; + break; + + case XML_PARSING_VALUE: + // Keep parsing child until we find a < for an opening/closing tag. + if(c == '<') { + // In HTML Spec there could be a child here but not in XML spec. + doing = XML_PARSING_CLOSE; + buffer[bufferLength] = '\0'; + bufferLength = 0; + xml->value = buffer; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + continue; + } + + buffer[bufferLength++] = c; + break; + + case XML_PARSING_CHILD: + if(c == '<') { + // Read ahead and confirm this is a close or not + if(data[i] == '/') { + doing = XML_PARSING_CLOSE; + continue; + } + + // Likely another child. + i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1); + } + + if(xmlIsWhitespace(c)) continue; + + // In HTML Spec there's a chance for there to be a value here, but not + // in the XML spec. + break; + + case XML_PARSING_CLOSE: + // Just keep parsing until the tag closer finishes. + if(c != '>') continue; + doing = XML_DOING_NOTHING; + + //TODO: Return index or something? + free(buffer); + return i; + + default: + break; + } + } + + free(buffer); + return i; +} + +void xmlLoad(xml_t *xml, char *data) { + xmlLoadChild(xml, data, 0); +} + +void xmlDispose(xml_t *xml) { + uint8_t i; + + // Dispose children recursively + for(i = 0; i < xml->childrenCount; i++) { + xmlDispose(xml->children + i); + } + + // Free children array. + free(xml->children); + + // Dispose attributes + for(i = 0; i < xml->attributeCount; i++) { + free(xml->attributeNames[i]); + if((xml->attributeDatas + i) != NULL) { + free(xml->attributeDatas[i]); + } + } + + + free(xml->node); + if(xml-> value != NULL) free(xml->value); +} + +int16_t xmlGetAttributeByName(xml_t *xml, char *name) { + int16_t i; + for(i = 0; i < xml->attributeCount; i++) { + if(strcmp(xml->attributeNames[i], name) == 0) return i; + } + return -1; +} + +bool xmlIsWhitespace(char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; } \ No newline at end of file diff --git a/src/dawntools/utils/xml.h b/src/dawntools/utils/xml.h index 1bfacd04..6e045b36 100644 --- a/src/dawntools/utils/xml.h +++ b/src/dawntools/utils/xml.h @@ -19,7 +19,7 @@ #define XML_PARSING_CHILD 0x07 #define XML_PARSING_CLOSE 0x08 -#define XML_TEXT_BUFFER_MAX 256 +#define XML_TEXT_BUFFER_MAX 2048 #define XML_CHILD_COUNT_MAX 128 #define XML_ATTRIBUTE_MAX 128 diff --git a/src/dawntools/visualnovel/CMakeLists.txt b/src/dawntools/visualnovel/CMakeLists.txt new file mode 100644 index 00000000..9466b558 --- /dev/null +++ b/src/dawntools/visualnovel/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2023 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(vnscenegen) \ No newline at end of file diff --git a/src/dawntools/visualnovel/vnscenegen/CMakeLists.txt b/src/dawntools/visualnovel/vnscenegen/CMakeLists.txt new file mode 100644 index 00000000..3daf5c33 --- /dev/null +++ b/src/dawntools/visualnovel/vnscenegen/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2023 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Texture Build Tool +project(vnscenegen VERSION 1.0) +add_executable(vnscenegen) +target_sources(vnscenegen + PRIVATE + main.cpp + ../../utils/file.c +) +target_include_directories(vnscenegen + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../ + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(vnscenegen + PUBLIC + ${DAWN_BUILD_HOST_LIBS} +) \ No newline at end of file diff --git a/src/dawntools/visualnovel/vnscenegen/main.cpp b/src/dawntools/visualnovel/vnscenegen/main.cpp new file mode 100644 index 00000000..5b4376ae --- /dev/null +++ b/src/dawntools/visualnovel/vnscenegen/main.cpp @@ -0,0 +1,15 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +extern "C" { + #include "../../utils/file.h" + #include +} +#include +#include + +int main(int32_t argc, char *args[]) { + +} \ No newline at end of file