From 78d8f46af673e6b8c9e1d2ba2a4357f1177fa638 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 24 Mar 2021 12:52:00 +0800 Subject: [PATCH] Added "set_quest_failure_value" script function --- artifacts/scripting/headers/sfall.h | 1 + artifacts/scripting/sfall function notes.txt | 5 + sfall/FalloutEngine/VariableOffsets.h | 1 + sfall/Modules/LoadOrder.cpp | 12 ++- sfall/Modules/QuestList.cpp | 98 ++++++++++++++----- sfall/Modules/QuestList.h | 2 + sfall/Modules/Scripting/Handlers/Metarule.cpp | 1 + sfall/Modules/Scripting/Handlers/Misc.cpp | 5 + sfall/Modules/Scripting/Handlers/Misc.h | 2 + 9 files changed, 96 insertions(+), 31 deletions(-) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 87d8fc0e..1392eb39 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -377,6 +377,7 @@ #define set_map_enter_position(tile, elev, rot) sfall_func3("set_map_enter_position", tile, elev, rot) #define set_object_data(obj, offset, value) sfall_func3("set_object_data", obj, offset, value) #define set_outline(obj, color) sfall_func2("set_outline", obj, color) +#define set_quest_failure_value(gvar, threshold) sfall_func2("set_quest_failure_value", gvar, threshold) #define set_rest_heal_time(time) sfall_func1("set_rest_heal_time", time) #define set_rest_mode(mode) sfall_func1("set_rest_mode", mode) #define set_terrain_name(x, y, name) sfall_func3("set_terrain_name", x, y, name) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 8824c34f..0e80c855 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -791,6 +791,11 @@ optional arguments: 2 - clears the overlay area or the specified rectangle defined by the x, y, width, height arguments 0 - destroys the created overlay surface (frees up the memory allocated to the surface) +> void sfall_func2("set_quest_failure_value", int gvarNumber, int thresholdValue) +- sets the threshold value (failure_threshold) for the quest at which the quest will be considered failed (its description in the pipboy will be crossed out and colored red) +- gvarNumber: the number of the global variable controlling the quest +- thresholdValue: the value of the global variable at which the quest is counted as a failure + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index 07a5fc26..720d338c 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -224,6 +224,7 @@ #define FO_VAR_ptable 0x59E934 #define FO_VAR_pud 0x59E960 #define FO_VAR_quest_count 0x51C12C +#define FO_VAR_quests 0x51C128 #define FO_VAR_queue 0x6648C0 #define FO_VAR_quick_done 0x5193BC #define FO_VAR_read_callback 0x51DEEC diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 650957db..c103de89 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -197,11 +197,13 @@ static void __fastcall game_init_databases_hook1() { static bool NormalizePath(std::string &path) { if (path.find(':') != std::string::npos) return false; + int pos = 0; do { // replace all '/' char to '\' pos = path.find('/', pos); if (pos != std::string::npos) path[pos] = '\\'; } while (pos != std::string::npos); + if (path.find(".\\") != std::string::npos || path.find("..\\") != std::string::npos) return false; while (path.front() == '\\') path.erase(0, 1); // remove firsts '\' return true; @@ -434,18 +436,20 @@ artNotExist: } static void SfallResourceFile() { + const char* sfallRes = "sfall.dat"; + WIN32_FIND_DATA findData; HANDLE hFind = FindFirstFile("sfall_???.dat", &findData); // example: sfall_ru.dat, sfall_cht.dat if (hFind != INVALID_HANDLE_VALUE) { - dlog_f("Loading a localized sfall resource file: %s\n", DL_MAIN, findData.cFileName); - patchFiles.push_back(findData.cFileName); FindClose(hFind); + dlog_f("Loading a localized sfall resource file: %s\n", DL_MAIN, findData.cFileName); + sfallRes = findData.cFileName; } - if (patchFiles.empty()) patchFiles.push_back("sfall.dat"); + patchFiles.push_back(sfallRes); } void LoadOrder::init() { - SfallResourceFile(); // Load external sfall resource file (load order is before patchXXX.dat) + SfallResourceFile(); // Add external sfall resource file (load order is before patchXXX.dat) GetExtraPatches(); MultiPatchesPatch(); diff --git a/sfall/Modules/QuestList.cpp b/sfall/Modules/QuestList.cpp index d90dbf03..be4ab468 100644 --- a/sfall/Modules/QuestList.cpp +++ b/sfall/Modules/QuestList.cpp @@ -18,6 +18,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "LoadGameHook.h" #include "QuestList.h" @@ -314,8 +315,8 @@ static DWORD __fastcall ActionButtons(DWORD key) { curent_quest_page++; first_quest_page = pageQuest[curent_quest_page]; last_quest_page = ((pageQuest.size() - 1) > curent_quest_page) - ? pageQuest[curent_quest_page + 1] - 1 - : INT_MAX; + ? pageQuest[curent_quest_page + 1] - 1 + : INT_MAX; buttonsPressed = true; return called_quest_number; } @@ -343,31 +344,17 @@ skip: } } -static void RegisterButtonSoundFunc0() { +static void RegisterButtonSound() { __asm { mov ebx, fo::funcoffs::gsound_red_butt_release_; mov edx, fo::funcoffs::gsound_red_butt_press_; - call fo::funcoffs::win_register_button_sound_func_; + call fo::funcoffs::win_register_button_sound_func_; // eax - register button } } -static void __stdcall ArtButtonFunc(DWORD buttonKey, DWORD buttonMem, DWORD indexArt) { - using namespace fo; - __asm { - xor ecx, ecx; - xor ebx, ebx; - mov edx, indexArt; // index from intrface.lst - mov eax, OBJ_TYPE_INTRFACE; - push ecx; - call fo::funcoffs::art_id_; - // - mov ecx, buttonKey; - xor ebx, ebx; - xor edx, edx; - call fo::funcoffs::art_ptr_lock_data_; - mov ecx, buttonMem; - mov dword ptr [ecx], eax; // first texture memory address - } +static void LoadArtButton(DWORD buttonKey, DWORD buttonMem, DWORD indexArt) { // indexArt - index from intrface.lst + long artId = fo::func::art_id(fo::ArtType::OBJ_TYPE_INTRFACE, indexArt, 0, 0, 0); + *(BYTE**)buttonMem = fo::func::art_ptr_lock_data(artId, 0, 0, (DWORD*)buttonKey); // first texture memory address } // Create buttons @@ -407,11 +394,11 @@ static void __declspec(naked) StartPipboy_hack() { // Load new texture for first (up) button. I used memory address for texture from buttons at chracter screen. // Everything fine, because this buttons can't use in one time, and they everytime recreating. // Down - ArtButtonFunc(FO_VAR_optionsButtonUpKey, FO_VAR_optionsButtonUp, indexUpArt0); - ArtButtonFunc(FO_VAR_optionsButtonDownKey, FO_VAR_optionsButtonDown, indexDownArt0); + LoadArtButton(FO_VAR_optionsButtonUpKey, FO_VAR_optionsButtonUp, indexUpArt0); + LoadArtButton(FO_VAR_optionsButtonDownKey, FO_VAR_optionsButtonDown, indexDownArt0); // Up - ArtButtonFunc(FO_VAR_optionsButtonUpKey, FO_VAR_optionsButtonUp1, indexUpArt1); - ArtButtonFunc(FO_VAR_optionsButtonDownKey, FO_VAR_optionsButtonDown1, indexDownArt1); + LoadArtButton(FO_VAR_optionsButtonUpKey, FO_VAR_optionsButtonUp1, indexUpArt1); + LoadArtButton(FO_VAR_optionsButtonDownKey, FO_VAR_optionsButtonDown1, indexDownArt1); xPos = questsScrollButtonsX; yPos = questsScrollButtonsY; @@ -421,13 +408,13 @@ static void __declspec(naked) StartPipboy_hack() { picDown = (BYTE*)fo::var::optionsButtonDown1; picUp = (BYTE*)fo::var::optionsButtonUp1; if (fo::func::win_register_button(winRef, xPos, yPos, width, height, -1, -1, -1, 0x300, picUp, picDown, 0, 32) != -1) { - RegisterButtonSoundFunc0(); + RegisterButtonSound(); } picDown = (BYTE*)fo::var::optionsButtonDown; picUp = (BYTE*)fo::var::optionsButtonUp; if (fo::func::win_register_button(winRef, xPos, yPos + height, width, height, -1, -1, -1, 0x301, picUp, picDown, 0, 32) != -1) { - RegisterButtonSoundFunc0(); + RegisterButtonSound(); } __asm { @@ -504,7 +491,64 @@ void QuestListPatch() { MakeCall(0x497A7D, pip_print_hack); } +//////////////////////////////////////////////////////////////////////////////// + +struct QuestFailure { + long gvarNum; + long failureVal; +}; + +std::vector questFailures; + +static long FindGVarQuestFailure(long globalVarNum) { + for (size_t i = 0; i < questFailures.size(); i++) { + if (questFailures[i].gvarNum == globalVarNum) return i; + } + return -1; +} + +void QuestList::AddQuestFailureValue(long globalVarNum, long failureThreshold) { + long index = FindGVarQuestFailure(globalVarNum); + if (index == -1) { + questFailures.push_back({ globalVarNum, failureThreshold }); + } else { + questFailures[index].failureVal = failureThreshold; + } +} + +static BYTE __fastcall CheckQuestFailureState(fo::QuestData* quest, BYTE completeColor) { + if (questFailures.empty()) return completeColor; + + const BYTE failureColor = 136; // dark red + + long index = FindGVarQuestFailure(quest->gvarIndex); + return (index != -1 && fo::var::game_global_vars[quest->gvarIndex] >= questFailures[index].failureVal) ? failureColor : completeColor; +} + +static void __declspec(naked) PipStatus_hack() { + __asm { + push eax; + push ecx; + mov dl, ds:[0x6A5B34]; // completeColor (dark green) + mov ecx, ds:[FO_VAR_quests]; + add ecx, [esp + 0x4BC - 0x28 + 12]; + call CheckQuestFailureState; + mov bl, al; + pop ecx; + pop eax; + retn; + } +} + +static void ResetQuests() { + questFailures.clear(); +} + void QuestList::init() { + LoadGameHook::OnGameReset() += ResetQuests; + + MakeCall(0x498222, PipStatus_hack, 1); + questsButtonsType = GetConfigInt("Misc", "UseScrollingQuestsList", 0); if (questsButtonsType > 0) { dlog("Applying quests list patch.", DL_INIT); diff --git a/sfall/Modules/QuestList.h b/sfall/Modules/QuestList.h index 4bb9d11b..e71a0f1b 100644 --- a/sfall/Modules/QuestList.h +++ b/sfall/Modules/QuestList.h @@ -25,6 +25,8 @@ class QuestList : public Module { public: const char* name() { return "QuestList"; } void init(); + + static void AddQuestFailureValue(long globalVarNum, long failureThreshold); }; } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 86d1264e..513d70e5 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -136,6 +136,7 @@ static const SfallMetarule metarules[] = { {"set_map_enter_position", mf_set_map_enter_position, 3, 3, -1, {ARG_INT, ARG_INT, ARG_INT}}, {"set_object_data", mf_set_object_data, 3, 3, -1, {ARG_OBJECT, ARG_INT, ARG_INT}}, {"set_outline", mf_set_outline, 2, 2, -1, {ARG_OBJECT, ARG_INT}}, + {"set_quest_failure_value", mf_set_quest_failure_value, 2, 2, -1, {ARG_INT, ARG_INT}}, {"set_rest_heal_time", mf_set_rest_heal_time, 1, 1, -1, {ARG_INT}}, {"set_rest_mode", mf_set_rest_mode, 1, 1, -1, {ARG_INT}}, {"set_selectable_perk_npc", mf_set_selectable_perk_npc, 5, 5, -1, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index d5aabbe2..e5ef3a01 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -27,6 +27,7 @@ //#include "..\..\MiscPatches.h" #include "..\..\Movies.h" #include "..\..\PlayerModel.h" +#include "..\..\QuestList.h" #include "..\..\ScriptExtender.h" #include "..\..\Sound.h" @@ -496,5 +497,9 @@ void mf_get_ini_section(OpcodeContext& ctx) { ctx.setReturn(arrayId); } +void mf_set_quest_failure_value(OpcodeContext& ctx) { + QuestList::AddQuestFailureValue(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Misc.h b/sfall/Modules/Scripting/Handlers/Misc.h index 989c885d..1af23a26 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.h +++ b/sfall/Modules/Scripting/Handlers/Misc.h @@ -102,5 +102,7 @@ void mf_get_ini_sections(OpcodeContext&); void mf_get_ini_section(OpcodeContext&); +void mf_set_quest_failure_value(OpcodeContext&); + } }