Backported the per-line msg fallback from 4.x

Renamed PartyMemberNonRandomLevelUp for clarity.
This commit is contained in:
NovaRain
2024-06-03 09:23:21 +08:00
parent e2ad532607
commit 6a773780a7
6 changed files with 102 additions and 28 deletions
+2 -2
View File
@@ -387,8 +387,8 @@ TownMapHotkeysFix=1
DisableHorrigan=0 DisableHorrigan=0
;Set to 1 to disable the random element in party member leveling ;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 ;This will prevent party members from randomly leveling up early and instead level them up at defined intervals
PartyMemberNoEarlyLevelUp=0 PartyMemberNonRandomLevelUp=0
;Change the initial starting location and world map viewport ;Change the initial starting location and world map viewport
;Leave at -1 for default ;Leave at -1 for default
@@ -31,6 +31,7 @@ FUNC(LoadSlot_, 0x47DC68)
FUNC(MapDirErase_, 0x480040) FUNC(MapDirErase_, 0x480040)
FUNC(NixHotLines_, 0x4999C0) FUNC(NixHotLines_, 0x4999C0)
FUNC(OptionWindow_, 0x437C08) FUNC(OptionWindow_, 0x437C08)
FUNC(PauseWindow_, 0x4902B0)
FUNC(PipStatus_, 0x497BD8) FUNC(PipStatus_, 0x497BD8)
FUNC(PrintBasicStat_, 0x434B38) FUNC(PrintBasicStat_, 0x434B38)
FUNC(PrintLevelWin_, 0x434920) FUNC(PrintLevelWin_, 0x434920)
+1 -1
View File
@@ -44,7 +44,7 @@ WRAP_WATCOM_FUNC3(long, item_w_mp_cost, fo::GameObject*, source, long, hitMode,
// Calculates path and returns it's length // 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_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_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_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_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) WRAP_WATCOM_FUNC3(fo::GameObject*, obj_blocking_at, fo::GameObject*, object, long, tile, long, elevation)
+92 -20
View File
@@ -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 // Searches the special character in the text and removes the text depending on the player's gender
// example: <MaleText^FemaleText> // example: <MaleText^FemaleText>
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 (!InDialog() || msgData->flags & MSG_GENDER_CHECK_FLG) return 1;
if (heroIsFemale < 0) heroIsFemale = fo::util::HeroIsFemale(); if (heroIsFemale < 0) heroIsFemale = fo::util::HeroIsFemale();
@@ -118,7 +118,7 @@ static long __fastcall ReplaceGenderWord(fo::MessageNode* msgData, DWORD* msgFil
// set flag // set flag
unsigned long outValue; unsigned long outValue;
fo::func::message_find(msgFile, msgData->number, &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; return 1;
} }
@@ -138,26 +138,94 @@ end:
} }
} }
struct MessageListInfo {
std::string FileName;
fo::MessageList* List;
};
std::unordered_map<fo::MessageList*, MessageListInfo> 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<fo::MessageList*, MessageListInfo>::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<fo::MessageList*, MessageListInfo>::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 // 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 { __asm {
mov ebx, edx; // keep mode push esi; // message list
mov ecx, eax; // keep buf push ebp; // relative path
call fo::funcoffs::db_fopen_; mov ecx, eax; // full path
test eax, eax; call MessageLoadHook; // edx - mode
jz noFile;
retn; retn;
noFile: }
push ebp; // file }
push 0x500208; // "english"
push 0x50B7D0; // "text" static void __declspec(naked) message_search__message_find_hook() {
push 0x50B7D8; // "%s\%s\%s" __asm {
push ecx; // buf push ecx; // save msg list
call fo::funcoffs::sprintf_; push ebx; // out index
add esp, 20; lea ecx, [esp + 4]; // msg list **
mov edx, ebx; call MessageFindHook; // edx - msg num
mov eax, ecx; pop ecx; // restore, possibly modified list ptr
jmp fo::funcoffs::db_fopen_; 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; const char* lang;
if (fo::func::get_game_config_string(&lang, "system", "language")) { if (fo::func::get_game_config_string(&lang, "system", "language")) {
strncpy_s(gameLanguage, lang, _TRUNCATE); 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);
}
} }
} }
+5 -4
View File
@@ -691,9 +691,10 @@ void PartyControl::OrderAttackPatch() {
orderAttackPatch = true; orderAttackPatch = true;
} }
static void PartyMemberNoEarlyLevelUpPatch() { static void PartyMemberNonRandomLevelUpPatch() {
if (IniReader::GetConfigInt("Misc", "PartyMemberNoEarlyLevelUp", 0)) { if (IniReader::GetConfigInt("Misc", "PartyMemberNonRandomLevelUp", 0)) {
dlogr("Applying no early level-up patch for party members.", DL_INIT); 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 __int64 data = 0x014FE9; // jmp 0x495E51
SafeWriteBytes(0x495CFD, (BYTE*)&data, 5); SafeWriteBytes(0x495CFD, (BYTE*)&data, 5);
} }
@@ -724,7 +725,7 @@ void PartyControl::init() {
const DWORD switchHandAddr[] = {0x4712E3, 0x47136D}; // left slot, right slot const DWORD switchHandAddr[] = {0x4712E3, 0x47136D}; // left slot, right slot
HookCalls(inven_pickup_hook, switchHandAddr); // will be overwritten if HOOK_INVENTORYMOVE is injected HookCalls(inven_pickup_hook, switchHandAddr); // will be overwritten if HOOK_INVENTORYMOVE is injected
PartyMemberNoEarlyLevelUpPatch(); PartyMemberNonRandomLevelUpPatch();
// Display party member's current level & AC & addict flag // Display party member's current level & AC & addict flag
if (IniReader::GetConfigInt("Misc", "PartyMemberExtraInfo", 0)) { if (IniReader::GetConfigInt("Misc", "PartyMemberExtraInfo", 0)) {
+1 -1
View File
@@ -319,7 +319,7 @@ void op_message_str_game(OpcodeContext& ctx) {
} else if (fileId >= 0x2000) { // Extra game message files. } else if (fileId >= 0x2000) { // Extra game message files.
ExtraGameMessageListsMap::iterator it = Message::gExtraGameMsgLists.find(fileId); ExtraGameMessageListsMap::iterator it = Message::gExtraGameMsgLists.find(fileId);
if (it != Message::gExtraGameMsgLists.end()) { if (it != Message::gExtraGameMsgLists.end()) {
msg = fo::util::GetMsg(it->second, msgId, 2); msg = fo::util::GetMessageStr(it->second, msgId);
} }
} }
} }