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();