diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 3b432e33..9dc1acfd 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -387,8 +387,8 @@ TownMapHotkeysFix=1 DisableHorrigan=0 ;Set to 1 to disable the random element in party member leveling -;This will prevent party members from randomly leveling up 'earlier' and instead level them up at defined regular intervals -PartyMemberNoEarlyLevelUp=0 +;This will prevent party members from randomly leveling up early and instead level them up at defined intervals +PartyMemberNonRandomLevelUp=0 ;Change the initial starting location and world map viewport ;Leave at -1 for default diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index d61977fd..796e6d29 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -31,6 +31,7 @@ FUNC(LoadSlot_, 0x47DC68) FUNC(MapDirErase_, 0x480040) FUNC(NixHotLines_, 0x4999C0) FUNC(OptionWindow_, 0x437C08) +FUNC(PauseWindow_, 0x4902B0) FUNC(PipStatus_, 0x497BD8) FUNC(PrintBasicStat_, 0x434B38) FUNC(PrintLevelWin_, 0x434920) diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index ec2fbb22..ece3c0a6 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -44,7 +44,7 @@ WRAP_WATCOM_FUNC3(long, item_w_mp_cost, fo::GameObject*, source, long, hitMode, // Calculates path and returns it's length WRAP_WATCOM_FUNC6(long, make_path_func, fo::GameObject*, objectFrom, long, tileFrom, long, tileTo, char*, pathDataBuffer, long, checkTileTo, void*, blockingFunc) WRAP_WATCOM_FUNC7(long, make_straight_path_func, fo::GameObject*, objFrom, DWORD, tileFrom, DWORD, tileTo, void*, arrayPtr, DWORD*, outObject, long, flags, void*, blockingFunc) -WRAP_WATCOM_FUNC3(long, message_find, DWORD*, msgFile, long, msgNumber, DWORD*, outBuf) +WRAP_WATCOM_FUNC3(long, message_find, fo::MessageList*, msgFile, long, msgNumber, DWORD*, outBuf) WRAP_WATCOM_FUNC4(long, mouse_click_in, long, x, long, y, long, x_offs, long, y_offs) WRAP_WATCOM_FUNC4(long, mouse_in, long, x, long, y, long, x_offs, long, y_offs) WRAP_WATCOM_FUNC3(fo::GameObject*, obj_blocking_at, fo::GameObject*, object, long, tile, long, elevation) diff --git a/sfall/Modules/Message.cpp b/sfall/Modules/Message.cpp index 16b538a7..d2e22cef 100644 --- a/sfall/Modules/Message.cpp +++ b/sfall/Modules/Message.cpp @@ -71,7 +71,7 @@ static long heroIsFemale = -1; // Searches the special character in the text and removes the text depending on the player's gender // example: -static long __fastcall ReplaceGenderWord(fo::MessageNode* msgData, DWORD* msgFile) { +static long __fastcall ReplaceGenderWord(fo::MessageNode* msgData, fo::MessageList* msgFile) { if (!InDialog() || msgData->flags & MSG_GENDER_CHECK_FLG) return 1; if (heroIsFemale < 0) heroIsFemale = fo::util::HeroIsFemale(); @@ -118,7 +118,7 @@ static long __fastcall ReplaceGenderWord(fo::MessageNode* msgData, DWORD* msgFil // set flag unsigned long outValue; fo::func::message_find(msgFile, msgData->number, &outValue); - ((fo::MessageNode*)(msgFile[1] + (outValue * 16)))->flags |= MSG_GENDER_CHECK_FLG; + msgFile->nodes[outValue].flags |= MSG_GENDER_CHECK_FLG; return 1; } @@ -138,26 +138,94 @@ end: } } +struct MessageListInfo { + std::string FileName; + fo::MessageList* List; +}; + +std::unordered_map messageListMap; + +static bool messageLoadForceEnglish; + +static fo::DbFile* __fastcall MessageLoadHook(const char* fullPath, const char* mode, const char* msgFile, fo::MessageList* messageList) { + fo::DbFile* file = !messageLoadForceEnglish ? fo::func::db_fopen(fullPath, mode) : nullptr; + if (file == nullptr) { + messageLoadForceEnglish = false; + char buf[MAX_PATH]; + sprintf(buf, "text\\english\\%s", msgFile); + return fo::func::db_fopen(buf, mode); + } + + // Remember loaded message list name. + MessageListInfo listInfo = {msgFile, nullptr}; + messageListMap.insert(std::make_pair(messageList, listInfo)); + return file; +} + +static long __fastcall MessageFindHook(fo::MessageList** list, long num, DWORD* outIndex) { + if (fo::func::message_find(*list, num, outIndex)) { + return true; + } + // If message not found in original list, try to find in fallback list. + std::unordered_map::iterator& listIt = messageListMap.find(*list); + if (listIt == messageListMap.end()) { + return false; + } + MessageListInfo& listInfo = listIt->second; + if (listInfo.List == nullptr) { + // Load fallback message list. + messageLoadForceEnglish = true; + listInfo.List = new fo::MessageList(); + fo::func::message_load(listInfo.List, listInfo.FileName.c_str()); + // TODO: some error message if fallback file wasn't loaded? + } + *list = listInfo.List; + return fo::func::message_find(*list, num, outIndex); +} + +static void __fastcall MessageExitHook(fo::MessageList* list) { + // Delete fallback message file. + std::unordered_map::iterator& listIt = messageListMap.find(list); + if (listIt == messageListMap.end()) { + return; + } + MessageListInfo& listInfo = listIt->second; + if (listInfo.List != nullptr) { + fo::func::message_exit(listInfo.List); + delete listInfo.List; + } + messageListMap.erase(listIt); +} + // Loads the msg file from the 'english' folder if it does not exist in the current language directory -static void __declspec(naked) message_load_hook() { +static void __declspec(naked) message_load__db_fopen_hook() { __asm { - mov ebx, edx; // keep mode - mov ecx, eax; // keep buf - call fo::funcoffs::db_fopen_; - test eax, eax; - jz noFile; + push esi; // message list + push ebp; // relative path + mov ecx, eax; // full path + call MessageLoadHook; // edx - mode retn; -noFile: - push ebp; // file - push 0x500208; // "english" - push 0x50B7D0; // "text" - push 0x50B7D8; // "%s\%s\%s" - push ecx; // buf - call fo::funcoffs::sprintf_; - add esp, 20; - mov edx, ebx; - mov eax, ecx; - jmp fo::funcoffs::db_fopen_; + } +} + +static void __declspec(naked) message_search__message_find_hook() { + __asm { + push ecx; // save msg list + push ebx; // out index + lea ecx, [esp + 4]; // msg list ** + call MessageFindHook; // edx - msg num + pop ecx; // restore, possibly modified list ptr + retn; + } +} + +static void __declspec(naked) message_exit__mem_free_hook() { + __asm { + push eax; // save msgList->nodes + mov ecx, ebx; // message list + call MessageExitHook; + pop eax; // restore + jmp fo::funcoffs::mem_free_; } } @@ -235,7 +303,11 @@ void FallbackEnglishLoadMsgFiles() { const char* lang; if (fo::func::get_game_config_string(&lang, "system", "language")) { strncpy_s(gameLanguage, lang, _TRUNCATE); - if (_stricmp(lang, "english") != 0) HookCall(0x484B18, message_load_hook); + if (_stricmp(lang, "english") != 0) { + HookCall(0x484B18, message_load__db_fopen_hook); + HookCall(0x484C4B, message_search__message_find_hook); + HookCall(0x4849B9, message_exit__mem_free_hook); + } } } diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 406f967a..33cdcdb7 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -691,9 +691,10 @@ void PartyControl::OrderAttackPatch() { orderAttackPatch = true; } -static void PartyMemberNoEarlyLevelUpPatch() { - if (IniReader::GetConfigInt("Misc", "PartyMemberNoEarlyLevelUp", 0)) { - dlogr("Applying no early level-up patch for party members.", DL_INIT); +static void PartyMemberNonRandomLevelUpPatch() { + if (IniReader::GetConfigInt("Misc", "PartyMemberNonRandomLevelUp", 0)) { + dlogr("Applying non-random level-up patch for party members.", DL_INIT); + // if (numLevels % level_up_every) != 0, skip random "early level up" roll and continue to the next party member instead __int64 data = 0x014FE9; // jmp 0x495E51 SafeWriteBytes(0x495CFD, (BYTE*)&data, 5); } @@ -724,7 +725,7 @@ void PartyControl::init() { const DWORD switchHandAddr[] = {0x4712E3, 0x47136D}; // left slot, right slot HookCalls(inven_pickup_hook, switchHandAddr); // will be overwritten if HOOK_INVENTORYMOVE is injected - PartyMemberNoEarlyLevelUpPatch(); + PartyMemberNonRandomLevelUpPatch(); // Display party member's current level & AC & addict flag if (IniReader::GetConfigInt("Misc", "PartyMemberExtraInfo", 0)) { diff --git a/sfall/Modules/Scripting/Handlers/Utils.cpp b/sfall/Modules/Scripting/Handlers/Utils.cpp index 22be0e5f..7a54e796 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.cpp +++ b/sfall/Modules/Scripting/Handlers/Utils.cpp @@ -319,7 +319,7 @@ void op_message_str_game(OpcodeContext& ctx) { } else if (fileId >= 0x2000) { // Extra game message files. ExtraGameMessageListsMap::iterator it = Message::gExtraGameMsgLists.find(fileId); if (it != Message::gExtraGameMsgLists.end()) { - msg = fo::util::GetMsg(it->second, msgId, 2); + msg = fo::util::GetMessageStr(it->second, msgId); } } }