- rewrote the language table so that it doesn't have to reload everything on a language change.
It now reads everything into a two-dimensional TMap and creates a list of mappings that apply to the current setting. The constant need for reloading was the main blocker in redesigning how Dehacked strings get inserted. Currently they override everything, but IWAD-based Dehacked text shouldn't block PWAD overrides from PWADs' LANGUAGE lumps and instead be treated as coming from an [en default] block. This also renames the main block from [enu default] to [en default], because it should be treated as the English default for all English locales and not just make it fall through to the base default as it did before.
This commit is contained in:
parent
7fa3081581
commit
495298079b
6 changed files with 90 additions and 273 deletions
|
|
@ -43,119 +43,31 @@
|
|||
#include "v_text.h"
|
||||
#include "gi.h"
|
||||
|
||||
// PassNum identifies which language pass this string is from.
|
||||
// PassNum 0 is for DeHacked.
|
||||
// PassNum 1 is for * strings.
|
||||
// PassNum 2+ are for specific locales.
|
||||
|
||||
struct FStringTable::StringEntry
|
||||
{
|
||||
StringEntry *Next;
|
||||
char *Name;
|
||||
uint8_t PassNum;
|
||||
char String[];
|
||||
};
|
||||
|
||||
FStringTable::FStringTable ()
|
||||
{
|
||||
for (int i = 0; i < HASH_SIZE; ++i)
|
||||
{
|
||||
Buckets[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
FStringTable::~FStringTable ()
|
||||
{
|
||||
FreeData ();
|
||||
}
|
||||
|
||||
void FStringTable::FreeData ()
|
||||
{
|
||||
for (int i = 0; i < HASH_SIZE; ++i)
|
||||
{
|
||||
StringEntry *entry = Buckets[i], *next;
|
||||
Buckets[i] = NULL;
|
||||
while (entry != NULL)
|
||||
{
|
||||
next = entry->Next;
|
||||
M_Free (entry);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FStringTable::FreeNonDehackedStrings ()
|
||||
{
|
||||
for (int i = 0; i < HASH_SIZE; ++i)
|
||||
{
|
||||
StringEntry *entry, *next, **pentry;
|
||||
|
||||
for (pentry = &Buckets[i], entry = *pentry; entry != NULL; )
|
||||
{
|
||||
next = entry->Next;
|
||||
if (entry->PassNum != 0)
|
||||
{
|
||||
*pentry = next;
|
||||
M_Free (entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
pentry = &entry->Next;
|
||||
}
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FStringTable::LoadStrings (bool enuOnly)
|
||||
void FStringTable::LoadStrings ()
|
||||
{
|
||||
int lastlump, lump;
|
||||
int i, j;
|
||||
|
||||
FreeNonDehackedStrings ();
|
||||
|
||||
lastlump = 0;
|
||||
|
||||
while ((lump = Wads.FindLump ("LANGUAGE", &lastlump)) != -1)
|
||||
{
|
||||
j = 0;
|
||||
if (!enuOnly)
|
||||
{
|
||||
LoadLanguage (lump, MAKE_ID('*',0,0,0), true, ++j);
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
LoadLanguage (lump, LanguageIDs[i], true, ++j);
|
||||
LoadLanguage (lump, LanguageIDs[i] & MAKE_ID(0xff,0xff,0,0), true, ++j);
|
||||
LoadLanguage (lump, LanguageIDs[i], false, ++j);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in any missing strings with the default language
|
||||
LoadLanguage (lump, MAKE_ID('*','*',0,0), true, ++j);
|
||||
LoadLanguage (lump);
|
||||
}
|
||||
UpdateLanguage();
|
||||
}
|
||||
|
||||
void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, int passnum)
|
||||
void FStringTable::LoadLanguage (int lumpnum)
|
||||
{
|
||||
static bool errordone = false;
|
||||
const uint32_t orMask = exactMatch ? 0 : MAKE_ID(0,0,0xff,0);
|
||||
uint32_t inCode = 0;
|
||||
StringEntry *entry, **pentry;
|
||||
uint32_t bucket;
|
||||
int cmpval;
|
||||
bool skip = true;
|
||||
|
||||
code |= orMask;
|
||||
|
||||
bool errordone = false;
|
||||
TArray<uint32_t> activeMaps;
|
||||
FScanner sc(lumpnum);
|
||||
sc.SetCMode (true);
|
||||
while (sc.GetString ())
|
||||
{
|
||||
if (sc.Compare ("["))
|
||||
{ // Process language identifiers
|
||||
bool donot = false;
|
||||
bool forceskip = false;
|
||||
skip = true;
|
||||
activeMaps.Clear();
|
||||
sc.MustGetString ();
|
||||
do
|
||||
{
|
||||
|
|
@ -164,17 +76,18 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
|
|||
{
|
||||
if (len == 1 && sc.String[0] == '~')
|
||||
{
|
||||
donot = true;
|
||||
// deprecated and ignored
|
||||
sc.ScriptMessage("Deprecated option '~' found in language list");
|
||||
sc.MustGetString ();
|
||||
continue;
|
||||
}
|
||||
if (len == 1 && sc.String[0] == '*')
|
||||
{
|
||||
inCode = MAKE_ID('*',0,0,0);
|
||||
activeMaps.Push(MAKE_ID('*', 0, 0, 0));
|
||||
}
|
||||
else if (len == 7 && stricmp (sc.String, "default") == 0)
|
||||
{
|
||||
inCode = MAKE_ID('*','*',0,0);
|
||||
activeMaps.Push(MAKE_ID('*', '*', 0, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -184,31 +97,14 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
|
|||
}
|
||||
else
|
||||
{
|
||||
inCode = MAKE_ID(tolower(sc.String[0]), tolower(sc.String[1]), tolower(sc.String[2]), 0);
|
||||
}
|
||||
if ((inCode | orMask) == code)
|
||||
{
|
||||
if (donot)
|
||||
{
|
||||
forceskip = true;
|
||||
donot = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
skip = false;
|
||||
}
|
||||
activeMaps.Push(MAKE_ID(tolower(sc.String[0]), tolower(sc.String[1]), tolower(sc.String[2]), 0));
|
||||
}
|
||||
sc.MustGetString ();
|
||||
} while (!sc.Compare ("]"));
|
||||
if (donot)
|
||||
{
|
||||
sc.ScriptError ("You must specify a language after ~");
|
||||
}
|
||||
skip |= forceskip;
|
||||
}
|
||||
else
|
||||
{ // Process string definitions.
|
||||
if (inCode == 0)
|
||||
if (activeMaps.Size() == 0)
|
||||
{
|
||||
// LANGUAGE lump is bad. We need to check if this is an old binary
|
||||
// lump and if so just skip it to allow old WADs to run which contain
|
||||
|
|
@ -222,7 +118,7 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
|
|||
sc.ScriptError ("Found a string without a language specified.");
|
||||
}
|
||||
|
||||
bool savedskip = skip;
|
||||
bool skip = false;
|
||||
if (sc.Compare("$"))
|
||||
{
|
||||
sc.MustGetStringName("ifgame");
|
||||
|
|
@ -234,20 +130,7 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
|
|||
|
||||
}
|
||||
|
||||
if (skip)
|
||||
{ // We're not interested in this language, so skip the string.
|
||||
sc.MustGetStringName ("=");
|
||||
sc.MustGetString ();
|
||||
do
|
||||
{
|
||||
sc.MustGetString ();
|
||||
}
|
||||
while (!sc.Compare (";"));
|
||||
skip = savedskip;
|
||||
continue;
|
||||
}
|
||||
|
||||
FString strName (sc.String);
|
||||
FName strName (sc.String);
|
||||
sc.MustGetStringName ("=");
|
||||
sc.MustGetString ();
|
||||
FString strText (sc.String, ProcessEscapes (sc.String));
|
||||
|
|
@ -258,39 +141,36 @@ void FStringTable::LoadLanguage (int lumpnum, uint32_t code, bool exactMatch, in
|
|||
strText += sc.String;
|
||||
sc.MustGetString ();
|
||||
}
|
||||
|
||||
// Does this string exist? If so, should we overwrite it?
|
||||
bucket = MakeKey (strName.GetChars()) & (HASH_SIZE-1);
|
||||
pentry = &Buckets[bucket];
|
||||
entry = *pentry;
|
||||
cmpval = 1;
|
||||
while (entry != NULL)
|
||||
// Insert the string into all relevant tables.
|
||||
for (auto map : activeMaps)
|
||||
{
|
||||
cmpval = stricmp (entry->Name, strName.GetChars());
|
||||
if (cmpval >= 0)
|
||||
break;
|
||||
pentry = &entry->Next;
|
||||
entry = *pentry;
|
||||
}
|
||||
if (cmpval == 0 && entry->PassNum >= passnum)
|
||||
{
|
||||
*pentry = entry->Next;
|
||||
M_Free (entry);
|
||||
entry = NULL;
|
||||
}
|
||||
if (entry == NULL || cmpval > 0)
|
||||
{
|
||||
entry = (StringEntry *)M_Malloc (sizeof(*entry) + strText.Len() + strName.Len() + 2);
|
||||
entry->Next = *pentry;
|
||||
*pentry = entry;
|
||||
strcpy (entry->String, strText.GetChars());
|
||||
strcpy (entry->Name = entry->String + strText.Len() + 1, strName.GetChars());
|
||||
entry->PassNum = passnum;
|
||||
allStrings[map].Insert(strName, strText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FStringTable::UpdateLanguage()
|
||||
{
|
||||
currentLanguageSet.Clear();
|
||||
|
||||
auto checkone = [&](uint32_t lang_id)
|
||||
{
|
||||
auto list = allStrings.CheckKey(lang_id);
|
||||
if (list && currentLanguageSet.Find(list) == currentLanguageSet.Size())
|
||||
currentLanguageSet.Push(list);
|
||||
};
|
||||
|
||||
checkone(MAKE_ID('*', '*', '*', 0));
|
||||
checkone(MAKE_ID('*', 0, 0, 0));
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
checkone(LanguageIDs[i]);
|
||||
checkone(LanguageIDs[i] & MAKE_ID(0xff, 0xff, 0, 0));
|
||||
}
|
||||
checkone(MAKE_ID('*', '*', 0, 0));
|
||||
}
|
||||
|
||||
// Replace \ escape sequences in a string with the escaped characters.
|
||||
size_t FStringTable::ProcessEscapes (char *iptr)
|
||||
{
|
||||
|
|
@ -321,27 +201,20 @@ size_t FStringTable::ProcessEscapes (char *iptr)
|
|||
// Finds a string by name and returns its value
|
||||
const char *FStringTable::operator[] (const char *name) const
|
||||
{
|
||||
if (name == NULL)
|
||||
if (name == nullptr || *name == 0)
|
||||
{
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
uint32_t bucket = MakeKey (name) & (HASH_SIZE - 1);
|
||||
StringEntry *entry = Buckets[bucket];
|
||||
|
||||
while (entry != NULL)
|
||||
FName nm(name, true);
|
||||
if (nm != NAME_None)
|
||||
{
|
||||
int cmpval = stricmp (entry->Name, name);
|
||||
if (cmpval == 0)
|
||||
for (auto map : currentLanguageSet)
|
||||
{
|
||||
return entry->String;
|
||||
auto item = map->CheckKey(nm);
|
||||
if (item) return item->GetChars();
|
||||
}
|
||||
if (cmpval == 1)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
entry = entry->Next;
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Finds a string by name and returns its value. If the string does
|
||||
|
|
@ -352,75 +225,19 @@ const char *FStringTable::operator() (const char *name) const
|
|||
return str ? str : name;
|
||||
}
|
||||
|
||||
// Find a string by name. pentry1 is a pointer to a pointer to it, and entry1 is a
|
||||
// pointer to it. Return NULL for entry1 if it wasn't found.
|
||||
void FStringTable::FindString (const char *name, StringEntry **&pentry1, StringEntry *&entry1)
|
||||
{
|
||||
uint32_t bucket = MakeKey (name) & (HASH_SIZE - 1);
|
||||
StringEntry **pentry = &Buckets[bucket], *entry = *pentry;
|
||||
|
||||
while (entry != NULL)
|
||||
{
|
||||
int cmpval = stricmp (entry->Name, name);
|
||||
if (cmpval == 0)
|
||||
{
|
||||
pentry1 = pentry;
|
||||
entry1 = entry;
|
||||
return;
|
||||
}
|
||||
if (cmpval == 1)
|
||||
{
|
||||
pentry1 = pentry;
|
||||
entry1 = NULL;
|
||||
return;
|
||||
}
|
||||
pentry = &entry->Next;
|
||||
entry = *pentry;
|
||||
}
|
||||
pentry1 = pentry;
|
||||
entry1 = entry;
|
||||
}
|
||||
|
||||
// Find a string with the same exact text. Returns its name.
|
||||
const char *FStringTable::MatchString (const char *string) const
|
||||
const char *StringMap::MatchString (const char *string) const
|
||||
{
|
||||
for (int i = 0; i < HASH_SIZE; ++i)
|
||||
StringMap::ConstIterator it(*this);
|
||||
StringMap::ConstPair *pair;
|
||||
|
||||
while (it.NextPair(pair))
|
||||
{
|
||||
for (StringEntry *entry = Buckets[i]; entry != NULL; entry = entry->Next)
|
||||
if (pair->Value.Compare(string) == 0)
|
||||
{
|
||||
if (strcmp (entry->String, string) == 0)
|
||||
{
|
||||
return entry->Name;
|
||||
}
|
||||
return pair->Key.GetChars();
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void FStringTable::SetString (const char *name, const char *newString)
|
||||
{
|
||||
StringEntry **pentry, *oentry;
|
||||
FindString (name, pentry, oentry);
|
||||
|
||||
size_t newlen = strlen (newString);
|
||||
size_t namelen = strlen (name);
|
||||
|
||||
// Create a new string entry
|
||||
StringEntry *entry = (StringEntry *)M_Malloc (sizeof(*entry) + newlen + namelen + 2);
|
||||
strcpy (entry->String, newString);
|
||||
strcpy (entry->Name = entry->String + newlen + 1, name);
|
||||
entry->PassNum = 0;
|
||||
|
||||
// If this is a new string, insert it. Otherwise, replace the old one.
|
||||
if (oentry == NULL)
|
||||
{
|
||||
entry->Next = *pentry;
|
||||
*pentry = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
*pentry = entry;
|
||||
entry->Next = oentry->Next;
|
||||
M_Free (oentry);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue