From 3934a804f4f59aaca521070fbd19e1be6f7e9e8e Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 5 Mar 2019 10:35:14 +0800 Subject: [PATCH] Added new Drugs module and DrugsFile option (from Mr.Stalin) Fixed a bug in the party control module with the 'addict' notification box not getting hidden after the addiction ends. Removed DrugsSaveFix option, now the list of active drug pids will be saved to sfalldb.sav. --- artifacts/config_files/Drugs.ini | 30 +++ artifacts/ddraw.ini | 9 +- sfall/FalloutEngine/EngineUtils.cpp | 12 + sfall/FalloutEngine/EngineUtils.h | 2 + sfall/Modules/BugFixes.cpp | 112 ++++++++-- sfall/Modules/BugFixes.h | 3 + sfall/Modules/Drugs.cpp | 335 ++++++++++++++++++++++++++++ sfall/Modules/Drugs.h | 57 +++++ sfall/Modules/LoadGameHook.cpp | 19 +- sfall/Modules/LoadGameHook.h | 5 +- sfall/Modules/PartyControl.cpp | 61 ++++- sfall/Modules/PartyControl.h | 1 + sfall/ddraw.vcxproj | 2 + sfall/ddraw.vcxproj.filters | 18 +- sfall/main.cpp | 2 + 15 files changed, 624 insertions(+), 44 deletions(-) create mode 100644 artifacts/config_files/Drugs.ini create mode 100644 sfall/Modules/Drugs.cpp create mode 100644 sfall/Modules/Drugs.h diff --git a/artifacts/config_files/Drugs.ini b/artifacts/config_files/Drugs.ini new file mode 100644 index 00000000..f2780c0e --- /dev/null +++ b/artifacts/config_files/Drugs.ini @@ -0,0 +1,30 @@ +; Allows you to configure advanced settings for drugs. +; Maximum 50 drugs are allowed +; Note: for vanilla drugs, you can only override the values of NumEffect and AddictTime parameters + +[main] +; set to 1 to change Jet addiction to be removed after the time expires +JetWithdrawal=0 + +; total number of drugs in this file +Count=0 + +; count starts from 1 +[1] +; drug item PID +PID=0 +; set a limit on the count of first and second delayed effects when using the drug, i.e. if the queue has delayed effects from the drug, +; and their count is greater or equal to NumEffects, it is not possible to obtain new effects from using the drug +; this count is 4 for Buffout/Mentats/Psycho/Jet +; set -1 to leave this parameter unchanged +NumEffects=-1 +; the duration of the withdrawal effects of addiction in minutes (i.e. 1440 = 24 game hours) +; set to 0 to use the default time (7 days for all drugs except Jet) +AddictTime=0 +; the number of the global variable from vault13.gam responsible for displaying addiction in the character screen +; set to 0 if the addiction is not required +GvarID=0 +; the index number from editor.msg to display the title of the addiction (the description uses the index number of TextID + 100) +TextID=-1 +; the line number (0-indexed) of the corresponding FRM in skilldex.lst +FrmID=-1 diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 3b09f6e8..0369f43f 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -494,6 +494,10 @@ BoostScriptDialogLimit=0 ;See the Stats.ini in the modders pack for an example file ;DerivedStats=Stats.ini +;Allows you to change some parameters for drugs and their addictions +;See the Drugs.ini in the modders pack for an example file +;DrugsFile=Drugs.ini + ;These options modify the checks to see if a critter can carry an additional item, changing which items are counted towards the weight limit and adding an additional size check ;Set the mode to 0 to disable the size check, 1 to apply to the PC only, 2 to apply to the PC and party members, or 3 to apply to all critters ;Only the PC uses CritterInvSizeLimit. Other critters will use the unused stat (STAT_unused = 10) or have the size limit of 100 if the stat is not set @@ -660,11 +664,6 @@ PartyMemberExtraInfo=0 ;that overrides First Aid/Doctor functions has very limited usefulness PartyMemberSkillFix=0 -;Set to 1 to fix the active effects of drugs not being saved properly -;If you have problems loading old save games saved while the player is under drug effects, disable this option -;Set to 0 to disable -DrugsSaveFix=1 - ;Set to 1 to skip loading game settings from a saved game, except for the game/combat difficulty settings ;Set to 2 to also skip loading the game/combat difficulty settings SkipLoadingGameSettings=0 diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index 6dd2fdd0..afa5df1e 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -117,6 +117,18 @@ long CheckAddictByPid(fo::GameObject* critter, long pid) { /* keyword 'return' is not needed, the compiler will do everything correctly */ } +void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set) { + Proto* protoPtr; + if (fo::func::proto_ptr(npc->protoId, &protoPtr) != -1) { + long bit = (1 << flag); + if (set) { + protoPtr->critter.critterFlags |= bit; + } else { + protoPtr->critter.critterFlags &= ~bit; + } + } +} + //--------------------------------------------------------- //print text to surface void PrintText(char *DisplayText, BYTE ColourIndex, DWORD Xpos, DWORD Ypos, DWORD TxtWidth, DWORD ToWidth, BYTE *ToSurface) { diff --git a/sfall/FalloutEngine/EngineUtils.h b/sfall/FalloutEngine/EngineUtils.h index 75e38449..985b6aaa 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -62,6 +62,8 @@ bool HeroIsFemale(); long CheckAddictByPid(fo::GameObject* critter, long pid); +void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set); + // Print text to surface void PrintText(char *displayText, BYTE colorIndex, DWORD x, DWORD y, DWORD textWidth, DWORD destWidth, BYTE *surface); // gets the height of the currently selected font diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 2e43900a..b6821ac9 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -1,5 +1,6 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "Drugs.h" #include "LoadGameHook.h" #include "ScriptExtender.h" @@ -16,13 +17,51 @@ static DWORD weightOnBody = 0; static char textBuf[355]; +std::list drugsPid; + +static void __fastcall DrugPidPush(int pid) { + drugsPid.push_back(pid); +} + +static int __fastcall DrugPidPop() { + if (drugsPid.empty()) return 0; + int pid = drugsPid.front(); + drugsPid.pop_front(); + return pid; +} + +void BugFixes::DrugsSaveFix(HANDLE file) { + DWORD sizeWrite, count = drugsPid.size(); + WriteFile(file, &count, 4, &sizeWrite, 0); + if (!count) return; + for (auto it = drugsPid.begin(); it != drugsPid.end(); it++) { + int pid = *it; + WriteFile(file, &pid, 4, &sizeWrite, 0); + } + drugsPid.clear(); + return; +} + +bool BugFixes::DrugsLoadFix(HANDLE file) { + DWORD count, sizeRead; + ReadFile(file, &count, 4, &sizeRead, 0); + //if (sizeRead != 4) return true; + for (DWORD i = 0; i < count; i++) { + DWORD pid; + ReadFile(file, &pid, 4, &sizeRead, 0); + if (sizeRead != 4) return true; + drugsPid.push_back(pid); + } + return false; +} + void ResetBodyState() { __asm mov critterBody, 0; __asm mov sizeOnBody, 0; __asm mov weightOnBody, 0; } -void GameInitialization() { +static void MusicVolInitialization() { *(DWORD*)FO_VAR_gDialogMusicVol = *(DWORD*)FO_VAR_background_volume; // fix dialog music } @@ -224,7 +263,7 @@ static void __declspec(naked) RemoveJetAddictFunc() { jne end; cmp dword ptr [edx + 0x4], PID_JET; // queue_addict.drug_pid == PID_JET? end: - sete al; // 1 = Delete from queue, 0 = Don't touch + sete al; // 1 = Delete from queue, 0 = Don't touch and eax, 0xFF; retn; } @@ -247,12 +286,13 @@ skip: } } -static void __declspec(naked) item_d_load_hack() { +static void __declspec(naked) item_d_load_subfix() { __asm { sub esp, 4; // proto buf - mov [ebp], edi; // edi->queue_drug - mov ecx, 7; - mov esi, FO_VAR_drugInfoList + 12; +// mov [ebp], edi; // edi->queue_drug + mov ecx, 9; // vanilla count + mov esi, FO_VAR_drugInfoList; + mov ebx, 12; loopDrug: cmp dword ptr [esi + 8], 0; // drugInfoList.numeffects je nextDrug; @@ -270,20 +310,58 @@ loopDrug: cmp eax, [edi + 0xC]; // drug.stat2 == queue_drug.stat2? je foundPid; // Yes nextDrug: - add esi, 12; + lea esi, [esi + ebx]; dec ecx; jnz loopDrug; + xor ebp, ebp; // set drug_pid = 0 + cmp ebx, 12; + jnz end; // failed, this drug effect was not found + // try find in new drugs + call Drugs::GetDrugCount; + test eax, eax; jz end; + mov ebx, SIZE_S_DRUGS; // sizeof structure + mov ecx, eax; + lea edx, drugs; + mov esi, [edx]; + jmp loopDrug; foundPid: - mov eax, [esi]; // drugInfoList.pid - mov [edi], eax; // queue_drug.drug_pid + mov ebp, [esi]; // drugInfoList.pid end: + mov [edi], ebp; // queue_drug.drug_pid xor eax, eax; add esp, 4; retn; } } +// take the drug pid from the list after loading sfalldb.sav +static void __declspec(naked) item_d_load_hack() { + __asm { + mov [ebp], edi; // edi->queue_drug + call DrugPidPop; + test eax, eax; + jnz skip; + jmp item_d_load_subfix; // if the pid was not saved, then try to find it +skip: + mov [edi], eax; // queue_drug.drug_pid + xor eax, eax; + retn; + } +} + +// add drug pid to the list to save to sfalldb.sav +static void __declspec(naked) item_d_save_hack() { + __asm { + pushadc; + mov ecx, [edx]; // drug pid + call DrugPidPush; + popadc; + mov ebx, 3; + retn; + } +} + static void __declspec(naked) queue_clear_type_mem_free_hook() { __asm { mov ebx, [esi] @@ -1827,7 +1905,7 @@ void BugFixes::init() #endif // Missing game initialization - LoadGameHook::OnGameInit() += GameInitialization; + LoadGameHook::OnGameInit() += MusicVolInitialization; // fix vanilla negate operator on float values MakeCall(0x46AB68, NegateFixHack); @@ -1887,18 +1965,8 @@ void BugFixes::init() // Fix for gaining stats from more than two doses of a specific chem after save-load dlog("Applying fix for save-load unlimited drug use exploit.", DL_INIT); - if (GetConfigInt("Misc", "DrugsSaveFix", 0)) { - // games saved while the player is under drug effects will be incompatible - // item_d_save_ - SafeWrite8(0x47A25C, 4); - SafeWrite8(0x47A262, 0); - // item_d_load_ - SafeWrite8(0x47A1F6, 4); - SafeWrite8(0x47A1FB, 0xCA); // mov edx, esp > mov edx, ecx - SafeWrite8(0x47A21B, 0x27); // jnz 0x47A243 - } else { - MakeCall(0x47A243, item_d_load_hack); // partial fix without breaking save game compatibility - } + MakeCall(0x47A25B, item_d_save_hack); + MakeCall(0x47A243, item_d_load_hack); dlogr(" Done", DL_INIT); // Fix crash when leaving the map while waiting for someone to die of a super stimpak overdose diff --git a/sfall/Modules/BugFixes.h b/sfall/Modules/BugFixes.h index 0133f8bb..859a5a52 100644 --- a/sfall/Modules/BugFixes.h +++ b/sfall/Modules/BugFixes.h @@ -9,6 +9,9 @@ class BugFixes : public Module { public: const char* name() { return "BugFixes"; } void init(); + + static void DrugsSaveFix(HANDLE file); + static bool DrugsLoadFix(HANDLE file); }; extern int tagSkill4LevelBase; diff --git a/sfall/Modules/Drugs.cpp b/sfall/Modules/Drugs.cpp new file mode 100644 index 00000000..e07e5286 --- /dev/null +++ b/sfall/Modules/Drugs.cpp @@ -0,0 +1,335 @@ +/* + * sfall + * Copyright (C) 2008-2019 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "..\main.h" +#include "..\FalloutEngine\Fallout2.h" +#include "LoadGameHook.h" + +#include "Drugs.h" + +namespace sfall +{ + +static const int drugsMax = 50; +static int drugsCount = 0; +static bool drugsReset = false; // true - need reset + +long Drugs::addictionGvarCount = 0; + +sDrugs *drugs = nullptr; + +static long __fastcall FindDrugVar(DWORD pid) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + //if (drugs[i].gvarID == 0) break; + return drugs[i].gvarID; + } + } + return -1; +} + +static void __declspec(naked) pid_to_gvar_hack() { + __asm { + push ecx; + push edx; + mov ecx, eax; + call FindDrugVar; + pop edx; + pop ecx; + test eax, eax; + jg end; // if gvar > 0 + mov ebx, ds:[FO_VAR_drugInfoList]; + retn; +end: // force exit func + add esp, 4; + pop edx; + pop ebx; + retn; + } +} + +static long __fastcall AllowUseDrug(fo::GameObject* critter, DWORD pid) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + if (drugs[i].numEffects == -1) break; // for vanilla drugs + if (drugs[i].numEffects == 0) return 1; + auto queue = (fo::QueueDrug*)fo::func::queue_find_first(critter, 0); + if (!queue) return 1; + int num = 0; + while (queue->pid != pid || ++num < drugs[i].numEffects) { + queue = (fo::QueueDrug*)fo::func::queue_find_next(critter, 0); + if (!queue) return 1; + } + return 0; // not allow + } + } + return -1; // not found +} + +static void __declspec(naked) drug_effect_allowed_hack() { + __asm { + push ecx; + call AllowUseDrug; // ecx - critter, edx - drug pid + pop ecx; + test eax, eax; + jge end; // if eax > -1 + mov edi, ds:[FO_VAR_drugInfoList]; + retn; +end: // force exit func + add esp, 4; + pop edi; + pop esi; + pop ecx; + pop ebx; + retn; + } +} + +static long _stdcall FindDrugTime(DWORD pid) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + if (drugs[i].addictTimeOff <= 0) break; + return drugs[i].addictTimeOff; + } + } + return 10080; // default time (7 days) +} + +static void __declspec(naked) perform_withdrawal_start_hack() { + __asm { + push ecx; + push edi; // drug pid + call FindDrugTime; + pop ecx; + mov ebx, eax; // time + retn; + } +} + +static long __fastcall PrintAddictionList(long isSeparator) { + long isSelect = 0; + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].gvarID > 0 && fo::var::game_global_vars[drugs[i].gvarID]) { + if (!isSeparator) { // print separator line + isSeparator = 1; + const char* message = fo::GetMessageStr(&fo::var::editor_message_file, 4001); + if (fo::func::folder_print_seperator(message)) { + fo::var::folder_card_title = (DWORD)message; + fo::var::folder_card_title2 = 0; + fo::var::folder_card_desc = (DWORD)fo::GetMessageStr(&fo::var::editor_message_file, 4101); + fo::var::folder_card_fid = 53; + isSelect = 1; + } + } + int msgNum = drugs[i].msgID; + const char* message = fo::GetMessageStr(&fo::var::editor_message_file, msgNum); + if (fo::func::folder_print_line(message)) { + fo::var::folder_card_title = (DWORD)message; + fo::var::folder_card_title2 = 0; + if (msgNum > 0) msgNum += 100; + fo::var::folder_card_desc = (DWORD)fo::GetMessageStr(&fo::var::editor_message_file, msgNum); + fo::var::folder_card_fid = drugs[i].frmID; + isSelect = 1; + } + } + } + return isSelect; +} + +static const DWORD list_karma_Ret = 0x43C1A3; +static void __declspec(naked) list_karma_hack() { + __asm { + mov ecx, [esp + 0x168 - 0x1C + 4]; + call PrintAddictionList; + or eax, edi; + jnz end; + mov edi, 47; + retn; +end: + add esp, 4; + jmp list_karma_Ret; + } +} + +static void CheckEngineNumEffects(int &set, long pid) { + switch (pid) { + case fo::PID_RADAWAY: + set = 4; + break; + case fo::PID_MENTATS: + set = 2; + break; + case fo::PID_BUFFOUT: + set = 1; + break; + case fo::PID_NUKA_COLA: + set = 0; + break; + case fo::PID_PSYCHO: + set = 3; + break; + case fo::PID_BEER: + set = 5; + break; + case fo::PID_BOOZE: + set = 6; + break; + case fo::PID_JET: + set = 7; + break; + case fo::PID_DECK_OF_TRAGIC_CARDS: + set = 8; + } +} + +static void CheckValidGvarNumber() { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].gvarID > 0) { + if (drugs[i].gvarID >= (long)fo::var::num_game_global_vars) { + drugs[i].gvarID = -1; + } + } + } +} + +static void ResetDrugs() { + if (!drugsReset) return; + drugsReset = false; + + int set = -1; + for (int i = 0; i < drugsCount; i++) { + CheckEngineNumEffects(set, drugs[i].drugPid); + if (set != -1) { + fo::var::drugInfoList[set].numEffects = drugs[i].iniNumEffects; + set = -1; + } else { + drugs[i].numEffects = drugs[i].iniNumEffects; + } + drugs[i].addictTimeOff = drugs[i].iniAddictTimeOff; + } +} + +long Drugs::GetDrugCount() { + return drugsCount; +} + +long Drugs::GetDrugPid(long n) { + return drugs[n].drugPid; +} + +long Drugs::GetDrugGvar(long n) { + return drugs[n].gvarID; +} + +long Drugs::SetDrugNumEffect(long pid, long effect) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + int set = -1; + CheckEngineNumEffects(set, pid); + if (set != -1) { + fo::var::drugInfoList[set].numEffects = effect; + } else { + drugs[i].numEffects = effect; + } + drugsReset = true; + return 0; + } + } + return -1; +} + +long Drugs::SetDrugAddictTimeOff(long pid, long time) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + drugs[i].addictTimeOff = time; + drugsReset = true; + return 0; + } + } + return -1; +} + +void Drugs::init() { + auto drugsFile = GetConfigString("Misc", "DrugsFile", "", MAX_PATH); + if (!drugsFile.empty()) { + dlog("Applying drugs patch...", DL_INIT); + const char* iniDrugs = drugsFile.insert(0, ".\\").c_str(); + + if (GetPrivateProfileIntA("main", "JetWithdrawal", 0, iniDrugs) == 1) SafeWrite8(0x47A3A8, 0); + + int count = GetPrivateProfileIntA("main", "Count", 0, iniDrugs); + if (count > 0) { + if (count > drugsMax) count = drugsMax; + drugs = new sDrugs[count](); + + int set = -1; + char section[4]; + for (int i = 1; i <= count; i++) { + _itoa_s(i, section, 10); + int pid = GetPrivateProfileIntA(section, "PID", 0, iniDrugs); + if (pid > 0) { + CheckEngineNumEffects(set, pid); + drugs[drugsCount].drugPid = pid; + drugs[drugsCount].addictTimeOff = drugs[drugsCount].iniAddictTimeOff = GetPrivateProfileIntA(section, "AddictTime", 0, iniDrugs); + long ef = GetPrivateProfileIntA(section, "NumEffects", -1, iniDrugs); + if (set != -1) { + if (ef > -1) { + drugs[i].iniNumEffects = ef; + fo::var::drugInfoList[set].numEffects = ef; + } else { + drugs[i].iniNumEffects = fo::var::drugInfoList[set].numEffects; + } + set = -1; + drugs[i].numEffects = set; // set -1, to use the value from the engine + continue; + } + drugs[drugsCount].numEffects = drugs[drugsCount].iniNumEffects = max(0, ef); + int gvar = GetPrivateProfileIntA(section, "GvarID", 0, iniDrugs); + drugs[drugsCount].gvarID = max(0, gvar); // not allow negative values + if (gvar) { + int msg = GetPrivateProfileIntA(section, "TextID", -1, iniDrugs); + drugs[drugsCount].msgID = (msg > 0) ? msg : -1; + drugs[drugsCount].frmID = GetPrivateProfileIntA(section, "FrmID", -1, iniDrugs); + addictionGvarCount++; + } + drugsCount++; + } + } + if (drugsCount) { + MakeCall(0x479EEC, drug_effect_allowed_hack, 1); + MakeCall(0x43C15C, list_karma_hack, 2); + MakeCall(0x47A5B8, pid_to_gvar_hack, 1); + MakeCall(0x47A50C, perform_withdrawal_start_hack); + SafeWrite32(0x47A523, 0x9090EBD1); // shr ebx, 1 + SafeWrite8(0x47A527, 0x90); + + if (addictionGvarCount) { + LoadGameHook::OnAfterGameInit() += CheckValidGvarNumber; + } + LoadGameHook::OnGameReset() += ResetDrugs; + } + } + dlog_f(" (%d/%d drugs) Done\n", DL_INIT, drugsCount, count); + } +} + +void Drugs::exit() { + if (drugs) delete[] drugs; +} + +} diff --git a/sfall/Modules/Drugs.h b/sfall/Modules/Drugs.h new file mode 100644 index 00000000..b82666b1 --- /dev/null +++ b/sfall/Modules/Drugs.h @@ -0,0 +1,57 @@ +/* + * sfall + * Copyright (C) 2008-2019 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Module.h" + +namespace sfall +{ + +class Drugs : public Module { +public: + const char* name() { return "Drugs"; } + void init(); + void exit() override; + + static long addictionGvarCount; + + static long GetDrugCount(); + static long GetDrugPid(long n); + static long GetDrugGvar(long n); + + static long SetDrugNumEffect(long pid, long effect); + static long SetDrugAddictTimeOff(long pid, long time); +}; + +#define SIZE_S_DRUGS (32) + +struct sDrugs { + DWORD drugPid; // don't move + long gvarID; // don't move + long numEffects; // don't move + long addictTimeOff; + long msgID; + long frmID; + long iniNumEffects; + long iniAddictTimeOff; +}; + +extern sDrugs *drugs; + +} diff --git a/sfall/Modules/LoadGameHook.cpp b/sfall/Modules/LoadGameHook.cpp index df31aa54..d4240c53 100644 --- a/sfall/Modules/LoadGameHook.cpp +++ b/sfall/Modules/LoadGameHook.cpp @@ -51,6 +51,7 @@ namespace sfall popadc } static Delegate<> onGameInit; +static Delegate<> onAfterGameInit; static Delegate<> onGameExit; static Delegate<> onGameReset; static Delegate<> onBeforeGameStart; @@ -137,6 +138,7 @@ static void _stdcall SaveGame2() { h = CreateFileA(buf, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); if (h != INVALID_HANDLE_VALUE) { Worldmap::SaveData(h); + BugFixes::DrugsSaveFix(h); CloseHandle(h); } else { goto errorSave; @@ -234,6 +236,7 @@ static bool LoadGame_Before() { h = CreateFileA(buf, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if (h != INVALID_HANDLE_VALUE) { if (Worldmap::LoadData(h)) goto errorLoad; + if (BugFixes::DrugsLoadFix(h)) goto errorLoad; CloseHandle(h); } else { dlogr("Cannot open sfalldb.sav.", DL_MAIN); @@ -321,10 +324,14 @@ static void __declspec(naked) main_load_new_hook() { } } -static void __stdcall GameInitialized() { +static void __stdcall GameInitialization() { onGameInit.invoke(); } +static void __stdcall GameInitialized() { + onAfterGameInit.invoke(); +} + static void __stdcall GameExit() { onGameExit.invoke(); } @@ -335,10 +342,14 @@ static void __stdcall GameClose() { static void __declspec(naked) main_init_system_hook() { __asm { + pushadc; + call GameInitialization; + popadc; + call fo::funcoffs::main_init_system_; pushadc; call GameInitialized; popadc; - jmp fo::funcoffs::main_init_system_; + retn; } } @@ -662,6 +673,10 @@ Delegate<>& LoadGameHook::OnGameInit() { return onGameInit; } +Delegate<>& LoadGameHook::OnAfterGameInit() { + return onAfterGameInit; +} + Delegate<>& LoadGameHook::OnGameExit() { return onGameExit; } diff --git a/sfall/Modules/LoadGameHook.h b/sfall/Modules/LoadGameHook.h index 354a25fd..17dfacd6 100644 --- a/sfall/Modules/LoadGameHook.h +++ b/sfall/Modules/LoadGameHook.h @@ -29,9 +29,12 @@ public: const char* name() { return "LoadGameHook"; } void init(); - // Invoked when the game has initialized (game_init_ was called). + // Invoked before game initialization (game_init_ was called). static Delegate<>& OnGameInit(); + // Invoked when the game has been initialized + static Delegate<>& OnAfterGameInit(); + // Invoked when the game exits to main menu static Delegate<>& OnGameExit(); diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 68427f23..2f08ee70 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -21,6 +21,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "HookScripts\InventoryHs.h" +#include "Drugs.h" #include "HookScripts.h" #include "LoadGameHook.h" @@ -57,8 +58,44 @@ static struct DudeState { long addictGvar[8]; long tag_skill[4]; //DWORD bbox_sneak; + long* extendAddictGvar = nullptr; } realDude; +static void SaveAddictGvarState() { + int n = 0; + for (int i = 0; i < Drugs::GetDrugCount(); i++) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID > 0) realDude.extendAddictGvar[n++] = fo::var::game_global_vars[gvarID]; + } +} + +static void RestoreAddictGvarState() { + int n = 0; + for (int i = 0; i < Drugs::GetDrugCount(); i++) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID > 0) fo::var::game_global_vars[gvarID] = realDude.extendAddictGvar[n++]; + } +} + +static bool SetAddictGvar(fo::GameObject* npc) { + bool isAddict = false; + int count = Drugs::GetDrugCount(); + for (int i = 0; i < count; i++) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID > 0) fo::var::game_global_vars[gvarID] = 0; + } + for (int i = 0; i < count; i++) { + long pid = Drugs::GetDrugPid(i); + if (pid > 0) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID <= 0 || !fo::CheckAddictByPid(npc, pid)) continue; + fo::var::game_global_vars[gvarID] = 1; + isAddict = true; + } + } + return isAddict; +} + // saves the state of PC before moving control to NPC static void SaveRealDudeState() { realDude.obj_dude = fo::var::obj_dude; @@ -79,6 +116,7 @@ static void SaveRealDudeState() { for (int i = 0; i < 6; i++) realDude.addictGvar[i] = fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar]; realDude.addictGvar[6] = fo::var::game_global_vars[fo::var::drugInfoList[7].addictGvar]; realDude.addictGvar[7] = fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar]; + if (realDude.extendAddictGvar) SaveAddictGvarState(); if (skipCounterAnim) SafeWriteBatch(0, {0x422BDE, 0x4229EC}); // no animate } @@ -123,16 +161,15 @@ static void SetCurrentDude(fo::GameObject* npc) { fo::var::itemCurrentItem = 1; } - long alcoholAddict = 0; + bool isAddict = false; + for (int i = 0; i < 9; i++) fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar] = 0; for (int i = 0; i < 9; i++) { - long result = fo::CheckAddictByPid(npc, fo::var::drugInfoList[i].itemPid); - if (i == 6) { - alcoholAddict = result; - } else if (i == 7) { - result |= alcoholAddict; - } - fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar] = result; + if (!fo::CheckAddictByPid(npc, fo::var::drugInfoList[i].itemPid)) continue; + fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar] = 1; + isAddict = true; } + if (realDude.extendAddictGvar) isAddict |= SetAddictGvar(npc); // check new added addictions + fo::ToggleNpcFlag(npc, 4, isAddict); // for show/hide addiction box (fix bug) // switch main dude_obj pointers - this should be done last! fo::var::obj_dude = npc; @@ -177,6 +214,7 @@ static void RestoreRealDudeState() { for (int i = 0; i < 6; i++) fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar] = realDude.addictGvar[i]; fo::var::game_global_vars[fo::var::drugInfoList[7].addictGvar] = realDude.addictGvar[6]; fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar] = realDude.addictGvar[7]; + if (realDude.extendAddictGvar) RestoreAddictGvarState(); if (skipCounterAnim) SafeWriteBatch(1, {0x422BDE, 0x4229EC}); // restore fo::func::intface_redraw(); @@ -314,6 +352,9 @@ void PartyControl::init() { SafeWrite16(0x4AFC1C, 0x840F); } }; + + if (Drugs::addictionGvarCount) realDude.extendAddictGvar = new long[Drugs::addictionGvarCount]; + HookCall(0x454218, stat_pc_add_experience_hook); // call inside op_give_exp_points_hook HookCalls(pc_flag_toggle_hook, { 0x4124F1, 0x41279A }); @@ -332,4 +373,8 @@ void PartyControl::init() { } } +void PartyControl::exit() { + if (realDude.extendAddictGvar) delete[] realDude.extendAddictGvar; +} + } diff --git a/sfall/Modules/PartyControl.h b/sfall/Modules/PartyControl.h index 55a7b34a..ba66f964 100644 --- a/sfall/Modules/PartyControl.h +++ b/sfall/Modules/PartyControl.h @@ -26,6 +26,7 @@ class PartyControl : public Module { public: const char* name() { return "PartyControl"; } void init(); + void exit() override; static int __stdcall SwitchHandHook(fo::GameObject* item); diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 2055efa3..8041dd89 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -224,6 +224,7 @@ + @@ -312,6 +313,7 @@ + diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index 4d7e147b..dc0b0f6b 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -37,9 +37,6 @@ Modules - - Modules - Modules @@ -275,6 +272,12 @@ Modules + + Modules + + + Modules + @@ -296,9 +299,6 @@ Modules - - Modules - Modules @@ -510,6 +510,12 @@ Modules + + Modules + + + Modules + diff --git a/sfall/main.cpp b/sfall/main.cpp index 6c20d97c..4178e33b 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -36,6 +36,7 @@ #include "Modules\Credits.h" #include "Modules\Criticals.h" #include "Modules\DebugEditor.h" +#include "Modules\Drugs.h" #include "Modules\Elevators.h" #include "Modules\Explosions.h" #include "Modules\FileSystem.h" @@ -162,6 +163,7 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); // should be loaded before PartyControl manager.add(); manager.add(); manager.add();