diff --git a/src/maploader/maploader.cpp b/src/maploader/maploader.cpp index 91ddaca49..5e3dc9669 100644 --- a/src/maploader/maploader.cpp +++ b/src/maploader/maploader.cpp @@ -2863,7 +2863,7 @@ void MapLoader::LoadBehavior(MapData * map) { if (map->Size(ML_BEHAVIOR) > 0) { - Level->Behaviors.LoadModule(-1, &map->Reader(ML_BEHAVIOR), map->Size(ML_BEHAVIOR)); + Level->Behaviors.LoadModule(-1, &map->Reader(ML_BEHAVIOR), map->Size(ML_BEHAVIOR), map->lumpnum); } if (!Level->Behaviors.CheckAllGood()) { diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 94f14353a..40e38c690 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -1942,7 +1942,7 @@ void FBehaviorContainer::LoadDefaultModules () } } -FBehavior *FBehaviorContainer::LoadModule (int lumpnum, FileReader *fr, int len) +FBehavior *FBehaviorContainer::LoadModule (int lumpnum, FileReader *fr, int len, int reallumpnum) { if (lumpnum == -1 && fr == NULL) return NULL; @@ -1955,7 +1955,7 @@ FBehavior *FBehaviorContainer::LoadModule (int lumpnum, FileReader *fr, int len) } FBehavior * behavior = new FBehavior (); - if (behavior->Init(Level, lumpnum, fr, len)) + if (behavior->Init(Level, lumpnum, fr, len, reallumpnum)) { return behavior; } @@ -2209,7 +2209,7 @@ FBehavior::FBehavior() } -bool FBehavior::Init(FLevelLocals *Level, int lumpnum, FileReader * fr, int len) +bool FBehavior::Init(FLevelLocals *Level, int lumpnum, FileReader * fr, int len, int reallumpnum) { uint8_t *object; int i; @@ -2303,6 +2303,8 @@ bool FBehavior::Init(FLevelLocals *Level, int lumpnum, FileReader * fr, int len) // Forget about the compatibility cruft at the end of the lump DataSize = LittleLong(((uint32_t *)object)[1]) - 8; } + + ShouldLocalize = false; } else { @@ -2316,6 +2318,17 @@ bool FBehavior::Init(FLevelLocals *Level, int lumpnum, FileReader * fr, int len) StringTable = LittleLong(((uint32_t *)Data)[1]); StringTable += LittleLong(((uint32_t *)(Data + StringTable))[0]) * 12 + 4; UnescapeStringTable(Data + StringTable, Data, false); + // If this is an original Hexen BEHAVIOR, set up some localization info for it. Original Hexen BEHAVIORs are always in the old format. + if ((Level->flags2 & LEVEL2_HEXENHACK) && gameinfo.gametype == GAME_Hexen && lumpnum == -1 && reallumpnum > 0) + { + int fileno = Wads.GetLumpFile(reallumpnum); + const char * filename = Wads.GetWadName(fileno); + if (!stricmp(filename, "HEXEN.WAD") || !stricmp(filename, "HEXDD.WAD")) + { + ShouldLocalize = true; + } + } + } else { @@ -3193,7 +3206,7 @@ uint8_t *FBehavior::NextChunk (uint8_t *chunk) const return NULL; } -const char *FBehaviorContainer::LookupString (uint32_t index) +const char *FBehaviorContainer::LookupString (uint32_t index, bool forprint) { uint32_t lib = index >> LIBRARYID_SHIFT; @@ -3205,10 +3218,10 @@ const char *FBehaviorContainer::LookupString (uint32_t index) { return NULL; } - return StaticModules[lib]->LookupString (index & 0xffff); + return StaticModules[lib]->LookupString (index & 0xffff, forprint); } -const char *FBehavior::LookupString (uint32_t index) const +const char *FBehavior::LookupString (uint32_t index, bool forprint) const { if (StringTable == 0) { @@ -3220,7 +3233,26 @@ const char *FBehavior::LookupString (uint32_t index) const if (index >= list[0]) return NULL; // Out of range for this list; - return (const char *)(Data + list[1+index]); + + const char *s = (const char *)(Data + list[1 + index]); + // Allow translations for Hexen's original strings. + // This synthesizes a string label and looks it up. + // It will only do this for original Hexen maps and PCD_PRINTSTRING operations. + // For localizing user content better solutions exist so this hack won't be available as an editing feature. + if (ShouldLocalize && forprint) + { + FString token = s; + token.ToUpper(); + token.ReplaceChars(".,-+!?", ' '); + token.Substitute(" ", ""); + token.Truncate(5); + + FStringf label("TXT_ACS_%s_%d_%.5s", Level->MapName.GetChars(), index, token); + auto p = GStrings[label]; + if (p) return p; + } + + return s; } else { @@ -8362,7 +8394,7 @@ scriptwait: case PCD_PRINTSTRING: case PCD_PRINTLOCALIZED: - lookup = Level->Behaviors.LookupString (STACK(1)); + lookup = Level->Behaviors.LookupString (STACK(1), true); if (pcd == PCD_PRINTLOCALIZED) { lookup = GStrings(lookup); diff --git a/src/p_acs.h b/src/p_acs.h index bbd1001bf..7c31068a0 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -350,7 +350,7 @@ class FBehavior public: FBehavior (); ~FBehavior (); - bool Init(FLevelLocals *l, int lumpnum, FileReader * fr = NULL, int len = 0); + bool Init(FLevelLocals *l, int lumpnum, FileReader * fr = NULL, int len = 0, int reallumpnum = -1); bool IsGood (); uint8_t *FindChunk (uint32_t id) const; @@ -378,7 +378,7 @@ public: const char *GetModuleName() const { return ModuleName; } ACSProfileInfo *GetFunctionProfileData(int index) { return index >= 0 && index < NumFunctions ? &FunctionProfileData[index] : NULL; } ACSProfileInfo *GetFunctionProfileData(ScriptFunction *func) { return GetFunctionProfileData((int)(func - (ScriptFunction *)Functions)); } - const char *LookupString (uint32_t index) const; + const char *LookupString (uint32_t index, bool forprint = false) const; BoundsCheckingArray MapVars; @@ -386,26 +386,28 @@ public: private: struct ArrayInfo; - ACSFormat Format; - FLevelLocals *Level; - int LumpNum; uint8_t *Data; - int DataSize; uint8_t *Chunks; ScriptPtr *Scripts; - int NumScripts; ScriptFunction *Functions; ACSProfileInfo *FunctionProfileData; - int NumFunctions; ArrayInfo *ArrayStore; - int NumArrays; ArrayInfo **Arrays; + + ACSFormat Format; + int LumpNum; + int DataSize; + int NumScripts; + int NumFunctions; + int NumArrays; int NumTotalArrays; uint32_t StringTable; + uint32_t LibraryID; + bool ShouldLocalize; + int32_t MapVarStore[NUM_MAPVARS]; TArray Imports; - uint32_t LibraryID; char ModuleName[9]; TArray JumpPoints; @@ -432,7 +434,7 @@ struct FBehaviorContainer FBehaviorContainer(FLevelLocals *l) : Level(l) {} - FBehavior *LoadModule(int lumpnum, FileReader *fr = nullptr, int len = 0); + FBehavior *LoadModule(int lumpnum, FileReader *fr = nullptr, int len = 0, int reallumpnum = -1); void LoadDefaultModules(); void UnloadModules(); bool CheckAllGood(); @@ -443,7 +445,7 @@ struct FBehaviorContainer void UnlockLevelVarStrings(int levelnum); const ScriptPtr *FindScript(int script, FBehavior *&module); - const char *LookupString(uint32_t index); + const char *LookupString(uint32_t index, bool forprint = false); void StartTypedScripts(uint16_t type, AActor *activator, bool always, int arg1 = 0, bool runNow = false); void StopMyScripts(AActor *actor); void ArrangeScriptProfiles(TArray &profiles); diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 54cb51ef1..be78def26 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2943,3 +2943,131 @@ MSG_TALISMANRED = "You have a feeling that it wasn't to be touched..."; MSG_TALISMANGREEN = "Whatever it is, it doesn't belong in this world..."; MSG_TALISMANBLUE = "It must do something..."; MSG_TALISMANPOWER = "You have super strength!"; + +// Strings from Hexen's IWAD scripts. Technically they are not needed here for English, they are mainly meant to be documentation for translating. + +TXT_ACS_map01_5_THEDO = "THE DOOR IS LOCKED"; +TXT_ACS_map02_9_GREET = "GREETINGS, MORTAL"; +TXT_ACS_map02_11_AREYO = "ARE YOU READY TO DIE?"; +TXT_ACS_map02_20_ADOOR = "A DOOR OPENED ON THE GUARDIAN OF ICE"; +TXT_ACS_map03_12_THISP = "THIS PATH IS BARRED"; +TXT_ACS_map04_9_ONEHA = "ONE HALF OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map04_10_ONTHE = "ON THE SEVEN PORTALS"; +TXT_ACS_map04_11_ONETH = "ONE THIRD OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map04_12_STAIR = "STAIRS HAVE RISEN ON THE SEVEN PORTALS"; +TXT_ACS_map05_6_ONETH = "ONE THIRD OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map05_7_ONTHE = "ON THE SEVEN PORTALS"; +TXT_ACS_map05_8_STAIR = "STAIRS HAVE RISEN ON THE SEVEN PORTALS"; +TXT_ACS_map05_9_YOUHA = "YOU HAVE TO FIND ANOTHER SWITCH..."; +TXT_ACS_map05_10_STONE = "STONES GRIND ON THE SEVEN PORTALS"; +TXT_ACS_map08_6_ONESI = "ONE SIXTH OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map08_7_ONTHE = "ON THE SHADOW WOOD"; +TXT_ACS_map08_10_THEDO = "THE DOOR IS BARRED FROM THE INSIDE"; +TXT_ACS_map08_11_YOUHE = "YOU HEAR A DOOR OPEN IN THE DISTANCE"; +TXT_ACS_map09_6_ONESI = "ONE SIXTH OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map09_7_ONTHE = "ON THE SHADOW WOOD"; +TXT_ACS_map10_6_ONESI = "ONE SIXTH OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map10_7_ONTHE = "ON THE SHADOW WOOD"; +TXT_ACS_map11_0_ETTIN = " ETTINS LEFT"; +TXT_ACS_map11_1_YOUWA = "YOU WAITED TOO LONG, NOW YOU DIE!"; +TXT_ACS_map11_7_ADOOR = "A DOOR OPENED ON THE FORSAKEN OUTPOST"; +TXT_ACS_map12_9_THISD = "THIS DOOR WON'T OPEN YET"; +TXT_ACS_map13_11_MYSER = "MY SERVANTS CAN SMELL YOUR BLOOD, HUMAN"; +TXT_ACS_map21_0_ADOOR = "A DOOR OPENED IN THE GIBBET"; +TXT_ACS_map21_2_THEDO = "THE DOOR IS BARRED FROM THE INSIDE"; +TXT_ACS_map22_3_APLAT = "A PLATFORM HAS LOWERED IN THE TOWER"; +TXT_ACS_map22_27_YOUHA = "YOU HAVE PLAYED THIS GAME TOO LONG, MORTAL..."; +TXT_ACS_map22_29_ITHIN = "I THINK I SHALL REMOVE YOU FROM THE BOARD"; +TXT_ACS_map23_10_YOUHE = "YOU HEAR A DOOR OPEN UPSTAIRS"; +TXT_ACS_map27_8_WORSH = "WORSHIP ME, AND I MAY YET BE MERCIFUL"; +TXT_ACS_map27_10_THENA = "THEN AGAIN, MAYBE NOT"; +TXT_ACS_map28_6_ONENI = "ONE NINTH OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map28_7_ONTHE = "ON THE MONASTERY"; +TXT_ACS_map30_6_ONENI = "ONE NINTH OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map30_7_ONTHE = "ON THE MONASTERY"; +TXT_ACS_map34_1_ONENI = "ONE NINTH OF THE PUZZLE HAS BEEN SOLVED"; +TXT_ACS_map34_2_ONTHE = "ON THE MONASTERY"; +TXT_ACS_map35_0_THEPO = "THE PORTAL HAS BEEN SEALED"; +TXT_ACS_map35_1_CHOOS = "CHOOSE YOUR FATE"; +TXT_ACS_map35_3_THEDO = "THE DOOR IS BARRED FROM THE INSIDE"; +TXT_ACS_map35_12_AREYO = "ARE YOU STRONG ENOUGH"; +TXT_ACS_map35_14_TOFAC = "TO FACE YOUR OWN MASTERS?"; + +// Deathkings texts + +TXT_ACS_map33_6_YOUDA = "YOU DARE BATTLE IN THE READY ROOM?"; +TXT_ACS_map33_7_FORTH = "FOR THAT, YOU SHALL DIE!"; +TXT_ACS_map41_6_THEWA = "THE WATERFALL IS OPEN"; +TXT_ACS_map41_7_THEWA = "THE WATERFALL IS BLOCKED"; +TXT_ACS_map41_8_ADOOR = "A DOOR HAS OPENED IN THE CHAPEL"; +TXT_ACS_map42_4_NOWTH = "NOW THAT'S ODD..."; +TXT_ACS_map44_1_THREE = "THREE MORE PARTS OF THE PUZZLE REMAIN"; +TXT_ACS_map44_2_TWOMO = "TWO MORE PARTS OF THE PUZZLE REMAIN"; +TXT_ACS_map44_3_ONEMO = "ONE MORE PART OF THE PUZZLE REMAINS"; +TXT_ACS_map44_4_THEPU = "THE PUZZLE IS COMPLETE"; +TXT_ACS_map44_6_YOUHA = "YOU HAVE NOT COMPLETED THE PUZZLE"; +TXT_ACS_map44_8_THEFL = "THE FLOOR IS NOT SAFE!"; +TXT_ACS_map44_10_ONETH = "ONE THIRD OF THE PUZZLE IS SOLVED"; +TXT_ACS_map44_11_TWOTH = "TWO THIRDS OF THE PUZZLE IS SOLVED"; +TXT_ACS_map45_1_YOUHE = "YOU HEAR A PLATFORM MOVING IN THE DISTANCE"; +TXT_ACS_map46_0_ITISD = "IT IS DONE..."; +TXT_ACS_map46_1_YOUHA = "YOU HAVE NOT COMPLETED THE PUZZLE"; +TXT_ACS_map46_2_I'MWA = "I'M WARNING YOU..."; +TXT_ACS_map46_3_STUBB = "STUBBORN, AREN'T YOU?"; +TXT_ACS_map46_4_ANDST = "AND STUPID, TOO"; +TXT_ACS_map46_8_ONEFO = "ONE FOURTH OF THIS PUZZLE IS COMPLETE"; +TXT_ACS_map46_9_BADCH = "BAD CHOICE..."; +TXT_ACS_map47_2_THESY = "THE SYMBOLS ARE NOT ALIGNED"; +TXT_ACS_map48_2_THEDO = "THE DOOR WON'T OPEN FROM THIS SIDE"; +TXT_ACS_map50_1_THEDO = "THE DOOR IS BARRED FROM THE OUTSIDE"; +TXT_ACS_map51_5_SACRI = "SACRILEGE !"; +TXT_ACS_map51_6_YOUHA = "YOU HAVE DEFILED ERIC'S TOMB !!"; +TXT_ACS_map51_7_ANDNO = "AND NOW YOU DIE !!!"; +TXT_ACS_map51_8_ONETH = "ONE THIRD OF THE PUZZLE IS SOLVED"; +TXT_ACS_map51_9_TWOTH = "TWO THIRDS OF THE PUZZLE IS SOLVED"; +TXT_ACS_map51_10_THECR = "THE CRYPT IS OPEN"; +TXT_ACS_map51_11_BEWAR = "BEWARE THE SPIDER'S TOMB"; +TXT_ACS_map51_13_YOUHE = "YOU HEAR A PLATFORM RISE OUTSIDE"; +TXT_ACS_map51_14_DOYOU = "DO YOU FEEL LUCKY?"; +TXT_ACS_map51_15_YOUGU = "YOU GUESSED WRONG!"; +TXT_ACS_map51_16_GOODG = "GOOD GUESS"; +TXT_ACS_map51_17_CANYO = "CAN YOU DO ALL THE SCRIPTING FOR MY LEVEL?"; +TXT_ACS_map51_18_DON'T = "DON'T TOUCH MY GLOPPY"; +TXT_ACS_map51_19_VORPA = "VORPAL ?!?!?!"; +TXT_ACS_map51_20_GIMME = "GIMME SOME SUGAR, BABY"; +TXT_ACS_map51_21_DUHUH = "DUH-UHHH..."; +TXT_ACS_map51_22_FILMI = "FILM IN AN HOUR?"; +TXT_ACS_map51_23_IDON' = "I DON'T EVEN GET MY OWN TOMBSTONE - CF"; +TXT_ACS_map51_24_LETNO = "LET NO BLOOD BE SPILT"; +TXT_ACS_map51_25_LETNO = "LET NO HAND BE RAISED IN ANGER"; +TXT_ACS_map52_9_WHODA = "WHO DARES DISTURB OUR SLUMBER?"; +TXT_ACS_map52_10_THEWA = "THE WAY IS OPEN"; +TXT_ACS_map53_2_YOUHA = "YOU HAVE "; +TXT_ACS_map53_3_SWITC = " SWITCHES LEFT"; +TXT_ACS_map53_4_YOUHA = "YOU HAVE ONLY "; +TXT_ACS_map53_5_SWITC = " SWITCH LEFT"; +TXT_ACS_map53_6_THEEX = "THE EXIT IS OPEN"; +TXT_ACS_map54_1_THEDO = "THE DOORS WON'T OPEN FROM THIS SIDE"; +TXT_ACS_map54_4_THEDO = "THE DOORS ARE OPEN..."; +TXT_ACS_map54_5_IFYOU = "...IF YOU ARE READY"; +TXT_ACS_map54_9_ADOOR = "A DOOR HAS OPENED"; +TXT_ACS_map54_10_ONTHE = "ON THE CHANTRY"; +TXT_ACS_map54_11_ABRID = "A BRIDGE HAS BEEN BUILT"; +TXT_ACS_map54_12_ONTHE = "ON THE ABATTOIR"; +TXT_ACS_map54_13_ASTAI = "A STAIR HAS BEEN BUILT"; +TXT_ACS_map54_14_ONTHE = "ON THE DARK WATCH"; +TXT_ACS_map54_15_ONEGE = "ONE GEAR HAS BEEN PLACED"; +TXT_ACS_map54_16_GEARS = " GEARS HAVE BEEN PLACED"; +TXT_ACS_map54_17_ABARR = "A BARRICADE HAS OPENED"; +TXT_ACS_map54_18_ONTHE = "ON THE CLOACA"; +TXT_ACS_map54_20_THEWA = "THE WAY BACK IS OPEN"; +TXT_ACS_map55_9_THEDO = "THE DOOR IS BARRED FROM THE INSIDE"; +TXT_ACS_map56_0_YOUDA = "YOU DARE PLUNDER THE TOMB"; +TXT_ACS_map56_1_OFTHE = "OF THE EXECUTIONER?"; +TXT_ACS_map56_2_PREPA = "PREPARE TO DIE"; +TXT_ACS_map59_1_YOUHA = "YOU HAVE "; +TXT_ACS_map59_2_MORES = " MORE SWITCHES TO FIND"; +TXT_ACS_map59_3_YOUHA = "YOU HAVE ONLY "; +TXT_ACS_map59_4_SWITC = " SWITCH LEFT"; +TXT_ACS_map59_5_THEWA = "THE WAY TO THE TOWER IS OPEN"; +TXT_ACS_map60_3_THEWA = "THE WAY IS OPEN";