From ce1ecd0d4ba15ff6fc3f57b8b4471a5d21c35396 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 2 Mar 2019 12:06:24 +0100 Subject: [PATCH] - implemented string table macros. --- src/d_main.cpp | 3 +- src/gamedata/d_dehacked.cpp | 14 +- src/gamedata/stringtable.cpp | 251 +++++++++++++++++++++------ src/gamedata/stringtable.h | 21 ++- src/p_interaction.cpp | 4 +- src/utility/xlsxread/xlsxio_read.cpp | 2 +- 6 files changed, 228 insertions(+), 67 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index d26ae1d0c..f73049397 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2581,7 +2581,8 @@ void D_DoomMain (void) }; for (p = 0; p < 5; ++p) { - const char *str = GStrings[startupString[p]]; + // At this point we cannot use the player's gender info yet so force 'male' here. + const char *str = GStrings.GetString(startupString[p], nullptr, 0); if (str != NULL && str[0] != '\0') { Printf("%s\n", str); diff --git a/src/gamedata/d_dehacked.cpp b/src/gamedata/d_dehacked.cpp index 4643cd492..c7105117d 100644 --- a/src/gamedata/d_dehacked.cpp +++ b/src/gamedata/d_dehacked.cpp @@ -2164,13 +2164,14 @@ static int PatchMusic (int dummy) while ((result = GetLine()) == 1) { - const char *newname = skipwhite (Line2); + FString newname = skipwhite (Line2); FString keystring; keystring << "MUSIC_" << Line1; - DehStrings.Insert(keystring, newname); - DPrintf (DMSG_SPAMMY, "Music %s set to:\n%s\n", keystring.GetChars(), newname); + TableElement te = { newname, newname, newname, newname }; + DehStrings.Insert(keystring, te); + DPrintf (DMSG_SPAMMY, "Music %s set to:\n%s\n", keystring.GetChars(), newname.GetChars()); } return result; @@ -2283,7 +2284,9 @@ static int PatchText (int oldSize) str = EnglishStrings.MatchString(oldStr); if (str != NULL) { - DehStrings.Insert(str, newStr); + FString newname = newStr; + TableElement te = { newname, newname, newname, newname }; + DehStrings.Insert(str, te); EnglishStrings.Remove(str); // remove entry so that it won't get found again by the next iteration or by another replacement later good = true; } @@ -2337,7 +2340,8 @@ static int PatchStrings (int dummy) // Account for a discrepancy between Boom's and ZDoom's name for the red skull key pickup message const char *ll = Line1; if (!stricmp(ll, "GOTREDSKULL")) ll = "GOTREDSKUL"; - DehStrings.Insert(ll, holdstring); + TableElement te = { holdstring, holdstring, holdstring, holdstring }; + DehStrings.Insert(ll, te); DPrintf (DMSG_SPAMMY, "%s set to:\n%s\n", Line1, holdstring.GetChars()); } diff --git a/src/gamedata/stringtable.cpp b/src/gamedata/stringtable.cpp index 2fbe12a61..a892992e1 100644 --- a/src/gamedata/stringtable.cpp +++ b/src/gamedata/stringtable.cpp @@ -42,9 +42,16 @@ #include "c_dispatch.h" #include "v_text.h" #include "gi.h" +#include "d_player.h" #include "xlsxread/xlsxio_read.h" +//========================================================================== +// +// +// +//========================================================================== + void FStringTable::LoadStrings () { int lastlump, lump; @@ -58,8 +65,14 @@ void FStringTable::LoadStrings () } SetLanguageIDs(); UpdateLanguage(); + allMacros.Clear(); } +//========================================================================== +// +// +// +//========================================================================== bool FStringTable::LoadLanguageFromSpreadsheet(int lumpnum) { @@ -69,14 +82,60 @@ bool FStringTable::LoadLanguageFromSpreadsheet(int lumpnum) { return false; } - - if (!readSheetIntoTable(xlsxio, "strings")) return false; - // readMacros(xlsxio, "macros"); + readMacros(xlsxio, "macros"); + readSheetIntoTable(xlsxio, "strings"); xlsxioread_close(xlsxio); return true; } +//========================================================================== +// +// +// +//========================================================================== + +bool FStringTable::readMacros(xlsxioreader reader, const char *sheetname) +{ + xlsxioreadersheet sheet = xlsxioread_sheet_open(reader, sheetname, XLSXIOREAD_SKIP_NONE); + if (sheet == nullptr) return false; + + while (xlsxioread_sheet_next_row(sheet)) + { + auto macroname = xlsxioread_sheet_next_cell(sheet); + auto language = xlsxioread_sheet_next_cell(sheet); + if (!macroname || !language) continue; + FStringf combined_name("%s/%s", language, macroname); + free(language); + free(macroname); + FName name = combined_name; + + StringMacro macro; + + char *value; + for (int i = 0; i < 4; i++) + { + value = xlsxioread_sheet_next_cell(sheet); + macro.Replacements[i] = value; + free(value); + } + // This is needed because the reader code would choke on incompletely read rows. + while ((value = xlsxioread_sheet_next_cell(sheet)) != nullptr) + { + free(value); + } + allMacros.Insert(name, macro); + } + xlsxioread_sheet_close(sheet); + return true; +} + +//========================================================================== +// +// +// +//========================================================================== + bool FStringTable::readSheetIntoTable(xlsxioreader reader, const char *sheetname) { @@ -94,7 +153,7 @@ bool FStringTable::readSheetIntoTable(xlsxioreader reader, const char *sheetname while ((value = xlsxioread_sheet_next_cell(sheet)) != nullptr) { auto vcopy = value; - if (table.Size() <= row) table.Reserve(1); + if (table.Size() <= (unsigned)row) table.Reserve(1); while (*vcopy && iswspace((unsigned char)*vcopy)) vcopy++; // skip over leaading whitespace; auto vend = vcopy + strlen(vcopy); while (vend > vcopy && iswspace((unsigned char)vend[-1])) *--vend = 0; // skip over trailing whitespace @@ -105,67 +164,74 @@ bool FStringTable::readSheetIntoTable(xlsxioreader reader, const char *sheetname } row++; } - xlsxioread_sheet_close(sheet); int labelcol = -1; int filtercol = -1; TArray> langrows; - for (unsigned column = 0; column < table[0].Size(); column++) + if (table.Size() > 0) { - auto &entry = table[0][column]; - if (entry.CompareNoCase("filter") == 0) + for (unsigned column = 0; column < table[0].Size(); column++) { - filtercol = column; - } - else if (entry.CompareNoCase("identifier") == 0) - { - labelcol = column;; - } - else - { - auto languages = entry.Split(" ", FString::TOK_SKIPEMPTY); - for (auto &lang : languages) + auto &entry = table[0][column]; + if (entry.CompareNoCase("filter") == 0) { - if (lang.CompareNoCase("default") == 0) + filtercol = column; + } + else if (entry.CompareNoCase("identifier") == 0) + { + labelcol = column;; + } + else + { + auto languages = entry.Split(" ", FString::TOK_SKIPEMPTY); + for (auto &lang : languages) { - langrows.Push(std::make_pair(column, default_table)); + if (lang.CompareNoCase("default") == 0) + { + langrows.Push(std::make_pair(column, default_table)); + } + else if (lang.Len() < 4) + { + lang.ToLower(); + langrows.Push(std::make_pair(column, MAKE_ID(lang[0], lang[1], lang[2], 0))); + } } - else if (lang.Len() < 4) + } + } + + for (unsigned i = 1; i < table.Size(); i++) + { + auto &row = table[i]; + if (filtercol > -1) + { + auto filterstr = row[filtercol]; + auto filter = filterstr.Split(" ", FString::TOK_SKIPEMPTY); + if (filter.Size() > 0 && filter.FindEx([](const auto &str) { return str.CompareNoCase(GameNames[gameinfo.gametype]) == 0; }) == filter.Size()) + continue; + } + + FName strName = row[labelcol]; + for (auto &langentry : langrows) + { + auto str = row[langentry.first]; + if (str.Len() > 0) { - lang.ToLower(); - langrows.Push(std::make_pair(column, MAKE_ID(lang[0], lang[1], lang[2], 0))); + InsertString(langentry.second, strName, str); } } } } - - for (unsigned i = 1; i < table.Size(); i++) - { - auto &row = table[i]; - if (filtercol > -1) - { - auto filterstr = row[filtercol]; - auto filter = filterstr.Split(" ", FString::TOK_SKIPEMPTY); - if (filter.Size() > 0 && filter.FindEx([](const auto &str) { return str.CompareNoCase(GameNames[gameinfo.gametype]) == 0; }) == filter.Size()) - continue; - } - - FName strName = row[labelcol]; - for (auto &langentry : langrows) - { - auto str = row[langentry.first]; - if (str.Len() > 0) - { - allStrings[langentry.second].Insert(strName, str); - str.Substitute("\n", "|"); - Printf(PRINT_LOG, "Setting %s for %s to %.40s\n\n", strName.GetChars(), &langentry.second, str.GetChars()); - } - } - } + xlsxioread_sheet_close(sheet); return true; } +//========================================================================== +// +// +// +//========================================================================== + void FStringTable::LoadLanguage (int lumpnum) { bool errordone = false; @@ -262,13 +328,52 @@ void FStringTable::LoadLanguage (int lumpnum) // Insert the string into all relevant tables. for (auto map : activeMaps) { - allStrings[map].Insert(strName, strText); + InsertString(map, strName, strText); } } } } } +//========================================================================== +// +// +// +//========================================================================== + +void FStringTable::InsertString(int langid, FName label, const FString &string) +{ + const char *strlangid = (const char *)&langid; + TableElement te = { string, string, string, string }; + long index; + while ((index = te.strings[0].IndexOf("@[")) >= 0) + { + auto endindex = te.strings[0].IndexOf(']', index); + if (endindex == -1) + { + Printf("Bad macro in %s : %s\n", strlangid, label.GetChars()); + break; + } + FString macroname(string.GetChars() + index + 2, endindex - index - 2); + FStringf lookupstr("%s/%s", strlangid, macroname.GetChars()); + FStringf replacee("@[%s]", macroname.GetChars()); + FName lookupname(lookupstr, true); + auto replace = allMacros.CheckKey(lookupname); + for (int i = 0; i < 4; i++) + { + const char *replacement = replace && replace->Replacements[i] ? replace->Replacements[i] : ""; + te.strings[i].Substitute(replacee, replacement); + } + } + allStrings[langid].Insert(label, te); +} + +//========================================================================== +// +// +// +//========================================================================== + void FStringTable::UpdateLanguage() { currentLanguageSet.Clear(); @@ -290,7 +395,12 @@ void FStringTable::UpdateLanguage() checkone(default_table); } +//========================================================================== +// // Replace \ escape sequences in a string with the escaped characters. +// +//========================================================================== + size_t FStringTable::ProcessEscapes (char *iptr) { char *sptr = iptr, *optr = iptr, c; @@ -317,10 +427,15 @@ size_t FStringTable::ProcessEscapes (char *iptr) return optr - sptr; } +//========================================================================== +// +// Checks if the given key exists in any one of the default string tables that are valid for all languages. +// To replace IWAD content this condition must be true. +// +//========================================================================== + bool FStringTable::exists(const char *name) { - // Checks if the given key exists in any one of the default string tables that are valid for all languages. - // To replace IWAD content this condition must be true. if (name == nullptr || *name == 0) { return false; @@ -343,13 +458,20 @@ bool FStringTable::exists(const char *name) return false; } +//========================================================================== +// // Finds a string by name and returns its value -const char *FStringTable::GetString(const char *name, uint32_t *langtable) const +// +//========================================================================== + +const char *FStringTable::GetString(const char *name, uint32_t *langtable, int gender) const { if (name == nullptr || *name == 0) { return nullptr; } + if (gender == -1) gender = players[consoleplayer].userinfo.GetGender(); + if (gender < 0 || gender > 3) gender = 0; FName nm(name, true); if (nm != NAME_None) { @@ -359,20 +481,27 @@ const char *FStringTable::GetString(const char *name, uint32_t *langtable) const if (item) { if (langtable) *langtable = map.first; - return item->GetChars(); + return item->strings[gender].GetChars(); } } } return nullptr; } +//========================================================================== +// // Finds a string by name in a given language -const char *FStringTable::GetLanguageString(const char *name, uint32_t langtable) const +// +//========================================================================== + +const char *FStringTable::GetLanguageString(const char *name, uint32_t langtable, int gender) const { if (name == nullptr || *name == 0) { return nullptr; } + if (gender == -1) gender = players[consoleplayer].userinfo.GetGender(); + if (gender < 0 || gender > 3) gender = 0; FName nm(name, true); if (nm != NAME_None) { @@ -381,14 +510,19 @@ const char *FStringTable::GetLanguageString(const char *name, uint32_t langtable auto item = map->CheckKey(nm); if (item) { - return item->GetChars(); + return item->strings[gender].GetChars(); } } return nullptr; } +//========================================================================== +// // Finds a string by name and returns its value. If the string does // not exist, returns the passed name instead. +// +//========================================================================== + const char *FStringTable::operator() (const char *name) const { const char *str = operator[] (name); @@ -396,7 +530,14 @@ const char *FStringTable::operator() (const char *name) const } +//========================================================================== +// // Find a string with the same exact text. Returns its name. +// This does not need to check genders, it is only used by +// Dehacked on the English table for finding stock strings. +// +//========================================================================== + const char *StringMap::MatchString (const char *string) const { StringMap::ConstIterator it(*this); @@ -404,7 +545,7 @@ const char *StringMap::MatchString (const char *string) const while (it.NextPair(pair)) { - if (pair->Value.CompareNoCase(string) == 0) + if (pair->Value.strings[0].CompareNoCase(string) == 0) { return pair->Key.GetChars(); } diff --git a/src/gamedata/stringtable.h b/src/gamedata/stringtable.h index d0d7b0347..c341d97b8 100644 --- a/src/gamedata/stringtable.h +++ b/src/gamedata/stringtable.h @@ -47,14 +47,25 @@ #include "doomdef.h" #include "doomtype.h" +struct TableElement +{ + FString strings[4]; +}; + // This public interface is for Dehacked -class StringMap : public TMap +class StringMap : public TMap { public: const char *MatchString(const char *string) const; }; +struct StringMacro +{ + FString Replacements[4]; +}; + + class FStringTable { public: @@ -66,6 +77,7 @@ public: }; using LangMap = TMap; + using StringMacroMap = TMap; void LoadStrings (); void UpdateLanguage(); @@ -76,8 +88,8 @@ public: UpdateLanguage(); } - const char *GetLanguageString(const char *name, uint32_t langtable) const; - const char *GetString(const char *name, uint32_t *langtable) const; + const char *GetLanguageString(const char *name, uint32_t langtable, int gender = -1) const; + const char *GetString(const char *name, uint32_t *langtable, int gender = -1) const; const char *operator() (const char *name) const; // Never returns NULL const char *operator[] (const char *name) const { @@ -87,12 +99,15 @@ public: private: + StringMacroMap allMacros; LangMap allStrings; TArray> currentLanguageSet; void LoadLanguage (int lumpnum); bool LoadLanguageFromSpreadsheet(int lumpnum); + bool readMacros(struct xlsxio_read_struct *reader, const char *sheet); bool readSheetIntoTable(struct xlsxio_read_struct *reader, const char *sheet); + void InsertString(int langid, FName label, const FString &string); static size_t ProcessEscapes (char *str); }; diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 6cd290c1b..121fb1cad 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -261,12 +261,12 @@ void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgf if (message != NULL && message[0] == '$') { - message = GStrings[message+1]; + message = GStrings.GetString(message+1, nullptr, self->player->userinfo.GetGender()); } if (message == NULL) { - message = GStrings("OB_DEFAULT"); + message = GStrings.GetString("OB_DEFAULT", nullptr, self->player->userinfo.GetGender()); } // [CK] Don't display empty strings diff --git a/src/utility/xlsxread/xlsxio_read.cpp b/src/utility/xlsxread/xlsxio_read.cpp index 738268707..48f6de5d8 100644 --- a/src/utility/xlsxread/xlsxio_read.cpp +++ b/src/utility/xlsxread/xlsxio_read.cpp @@ -63,7 +63,7 @@ void zip_close(zip_t *zipfile) zip_file_t *zip_fopen(zip_t *zipfile, const char *filename) { - if (!zipfile) return NULL; + if (!zipfile || !filename) return NULL; auto lump = zipfile->FindLump(filename); if (!lump) return NULL; return new FileReader(std::move(lump->NewReader()));