From f788246283b3a7a26e639c8aea3a525b4080d2ca Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 30 Apr 2019 06:45:22 +0800 Subject: [PATCH 01/32] Reordered sfall modules to fix the issue #235 Unified the directory separator in documents. Updated version number. --- artifacts/ddraw.ini | 16 ++++++++-------- artifacts/scripting/sfall function notes.txt | 2 +- sfall/FalloutEngine/Fallout2.h | 2 +- sfall/Modules/KillCounter.cpp | 6 +++--- sfall/main.cpp | 11 +++++++---- sfall/version.h | 4 ++-- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 2dd9efee7..42bc8f4cf 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -1,12 +1,12 @@ ;sfall configuration settings -;v4.1.7 +;v4.1.8 [Main] ;Change to 1 if you want to use command line args to tell sfall to use another ini file. UseCommandLine=0 ;Uncomment and point to a file to get alternate translations for some sfall messages -;TranslationsINI=./Translations.ini +;TranslationsINI=.\Translations.ini ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [ExtraPatches] @@ -79,7 +79,7 @@ GraphicsHeight=0 GPUBlt=0 ;Set to 1 to allow using 32-bit textures for talking heads -;The texture files should be placed in art/heads// (w/o extension) +;The texture files should be placed in art\heads\\ (w/o extension) ;The files in the folder should be numbered according to the number of frames in the talking head FRM file (0.png, 1.png, etc.) ;See the text file in the modders pack for a detailed description ;Requires DX9 graphics mode and v2.0 pixel shader support (see GPUBlt option) @@ -218,8 +218,8 @@ UseFileSystemOverride=0 ;Modified: master_patches > critter_patches > patchXXX.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat DataLoadOrderPatch=0 -;Set to 1 to enable loading alternative dialog msg files from text/language/dialog_female/ for female PC -;Set to 2 to also load subtitle files from text/language/cuts_female/ for female PC +;Set to 1 to enable loading alternative dialog msg files from text\\dialog_female\ for female PC +;Set to 2 to also load subtitle files from text\\cuts_female\ for female PC FemaleDialogMsgs=0 ;To change the default and starting player models, uncomment the next four lines. @@ -371,7 +371,7 @@ ProcessorIdle=-1 LoadProtoMaxLimit=-1 ;Set to 1 if using the hero appearance mod -;You can add AppChCrt.frm and AppChEdt.frm files to art/intrface/ to set a custom background for the character screen +;You can add AppChCrt.frm and AppChEdt.frm files to art\intrface\ to set a custom background for the character screen EnableHeroAppearanceMod=0 ;Set to 1 to skip the 3 opening movies @@ -391,7 +391,7 @@ AIDrugUsePerfFix=0 ;Allows the use of tiles over 80*36 in size. sfall will just split and resave them at startup ;Set to 1 to check all tiles on started (slow) -;Set to 2 if you provide a XLtiles.lst file in art/tiles/ containing a list of the tile ids that need checking +;Set to 2 if you provide a XLtiles.lst file in art\tiles\ containing a list of the tile ids that need checking AllowLargeTiles=0 ;Set to 1 to boost the maximum number of tile FRMs from 4096 to 16383 @@ -693,7 +693,7 @@ CreditsAtBottom=0 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Scripts] ;Comma-separated list of masked paths to load global scripts from -;Only use single slash \ as directory separator +;Only use single backslash \ as the directory separator ;Paths outside of scripts folder are supported GlobalScriptPaths=scripts\gl*.int,scripts\sfall\gl*.int diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 49b7dd204..b6f1579c1 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -317,7 +317,7 @@ Some utility/math functions are available: - see "hookscripts.txt" for more details > string message_str_game(int fileId, int messageId) -- works exactly the same as message_str, except you get messages from files in "text/english/game" folder +- works exactly the same as message_str, except you get messages from files in "text\\game\" directory - use GAME_MSG_* defines or mstr_* macros from sfall.h to use specific msg file - Additional game msg files added by ExtraGameMsgFileList setting will have consecutive fileIds assigned beginning from 0x2000. (e.g. if you set ExtraGameMsgFileList=foo,bar in ddraw.ini, foo.msg will be associated with 0x2000 and bar.msg with 0x2001.) - if a file has a specific number assigned in ExtraGameMsgFileList, its fileId will be (0x2000 + assigned number). (e.g. with ExtraGameMsgFileList=foo,bar:2,foobar in ddraw.ini, bar.msg will be associated with 0x2002 and foobar.msg with 0x2003.) diff --git a/sfall/FalloutEngine/Fallout2.h b/sfall/FalloutEngine/Fallout2.h index 0c1c5a4d3..e860bbb95 100644 --- a/sfall/FalloutEngine/Fallout2.h +++ b/sfall/FalloutEngine/Fallout2.h @@ -69,7 +69,7 @@ Returns the value to the script eax register must contain the script_ptr edx register must contain the returned value - rscript - name register for temporary save script_ptr + rscript - register name for temporarily saving script_ptr */ #define _RET_VAL_INT(rscript) __asm { \ __asm mov rscript, eax \ diff --git a/sfall/Modules/KillCounter.cpp b/sfall/Modules/KillCounter.cpp index c460a1ace..c6642832a 100644 --- a/sfall/Modules/KillCounter.cpp +++ b/sfall/Modules/KillCounter.cpp @@ -35,10 +35,10 @@ static const DWORD extraKillTypesCountAddr[] = { 0x4344E4, // Change char sheet to loop through the extra kill types }; -static int usingExtraKillTypes = 0; +static bool usingExtraKillTypes = false; bool UsingExtraKillTypes() { - return usingExtraKillTypes != 0; + return usingExtraKillTypes; } static DWORD __declspec(naked) ReadKillCounter() { @@ -57,7 +57,7 @@ static void __declspec(naked) IncKillCounter() { } static void KillCounterInit() { - usingExtraKillTypes = 1; + usingExtraKillTypes = true; // Overwrite the critter_kill_count_ function that reads the kill counter MakeCall(0x42D8B5, ReadKillCounter, 2); diff --git a/sfall/main.cpp b/sfall/main.cpp index 0f85697e1..50ad43d9d 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -138,16 +138,14 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); + manager.add(); manager.add(); manager.add(); manager.add(); manager.add(); manager.add(); manager.add(); - manager.add(); - manager.add(); - manager.add(); - manager.add(); manager.add(); manager.add(); manager.add(); @@ -180,6 +178,11 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + + // all built-in events(delegates) of modules should be executed before running the script handlers + manager.add(); + manager.add(); + manager.add(); manager.initAll(); diff --git a/sfall/version.h b/sfall/version.h index 58b131584..13870dd9c 100644 --- a/sfall/version.h +++ b/sfall/version.h @@ -24,10 +24,10 @@ #define VERSION_MAJOR 4 #define VERSION_MINOR 1 -#define VERSION_BUILD 7 +#define VERSION_BUILD 8 #define VERSION_REV 0 -#define VERSION_STRING "4.1.7" +#define VERSION_STRING "4.1.8" //#define CHECK_VAL (4) From 6a38fd0794c4c56b151c56661216e90068262108 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 30 Apr 2019 10:09:34 +0800 Subject: [PATCH 02/32] Replaced opcode/metarule names in error messages * before they are explicitly specified in text. Now the names are taken from the opcode/metarule tables. --- sfall/Modules/Scripting/Handlers/Anims.cpp | 2 +- sfall/Modules/Scripting/Handlers/Arrays.cpp | 6 +-- sfall/Modules/Scripting/Handlers/Core.cpp | 4 +- .../Modules/Scripting/Handlers/Interface.cpp | 10 ++-- sfall/Modules/Scripting/Handlers/Memory.cpp | 12 ++--- sfall/Modules/Scripting/Handlers/Metarule.cpp | 36 ++++--------- sfall/Modules/Scripting/Handlers/Misc.cpp | 46 ++++++++-------- sfall/Modules/Scripting/Handlers/Objects.cpp | 16 +++--- sfall/Modules/Scripting/Handlers/Perks.cpp | 20 +++---- sfall/Modules/Scripting/Handlers/Stats.cpp | 26 ++++----- sfall/Modules/Scripting/Handlers/Worldmap.cpp | 18 +++---- sfall/Modules/Scripting/OpcodeContext.cpp | 54 +++++++++++-------- sfall/Modules/Scripting/OpcodeContext.h | 29 +++++++++- sfall/Modules/Scripting/Opcodes.cpp | 4 +- 14 files changed, 150 insertions(+), 133 deletions(-) diff --git a/sfall/Modules/Scripting/Handlers/Anims.cpp b/sfall/Modules/Scripting/Handlers/Anims.cpp index 8e037f67f..ba80cdfe7 100644 --- a/sfall/Modules/Scripting/Handlers/Anims.cpp +++ b/sfall/Modules/Scripting/Handlers/Anims.cpp @@ -105,7 +105,7 @@ void sf_explosions_metarule(OpcodeContext& ctx) { result = ExplosionsMetaruleFunc(mode, ctx.arg(1).asInt(), ctx.arg(2).asInt()); if (result == -1) { - ctx.printOpcodeError("ExplosionsMetarule() - mode (%d) is not supported for the function.", mode); + ctx.printOpcodeError("%s() - mode (%d) is not supported for the function.", ctx.getOpcodeName(), mode); } ctx.setReturn(result); } diff --git a/sfall/Modules/Scripting/Handlers/Arrays.cpp b/sfall/Modules/Scripting/Handlers/Arrays.cpp index ddc2501dc..288cc114d 100644 --- a/sfall/Modules/Scripting/Handlers/Arrays.cpp +++ b/sfall/Modules/Scripting/Handlers/Arrays.cpp @@ -59,10 +59,10 @@ void sf_get_array(OpcodeContext& ctx) { auto str = Substring(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), 1); ctx.setReturn(str); } else { - ctx.printOpcodeError("get_array() - index must be numeric when used on a string."); + ctx.printOpcodeError("%s() - index must be numeric when used on a string.", ctx.getOpcodeName()); } } else { - ctx.printOpcodeError("get_array() - argument 0 must be an array ID or a string."); + ctx.printOpcodeError("%s() - argument 0 must be an array ID or a string.", ctx.getOpcodeName()); } } @@ -78,7 +78,7 @@ void sf_len_array(OpcodeContext& ctx) { void sf_resize_array(OpcodeContext& ctx) { if (ResizeArray(ctx.arg(0).asInt(), ctx.arg(1).asInt())) { - ctx.printOpcodeError("resize_array() - array sorting error."); + ctx.printOpcodeError("%s() - array sorting error.", ctx.getOpcodeName()); } } diff --git a/sfall/Modules/Scripting/Handlers/Core.cpp b/sfall/Modules/Scripting/Handlers/Core.cpp index a43f1f0bf..08dc20c97 100644 --- a/sfall/Modules/Scripting/Handlers/Core.cpp +++ b/sfall/Modules/Scripting/Handlers/Core.cpp @@ -77,7 +77,7 @@ void __declspec(naked) op_available_global_script_types() { void sf_set_sfall_global(OpcodeContext& ctx) { if (ctx.arg(0).isString()) { if (SetGlobalVar(ctx.arg(0).strValue(), ctx.arg(1).rawValue())) { - ctx.printOpcodeError("set_sfall_global() - The name of the global variable must consist of 8 characters."); + ctx.printOpcodeError("%s() - the name of the global variable must consist of 8 characters.", ctx.getOpcodeName()); } } else { SetGlobalVarInt(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); @@ -87,7 +87,7 @@ void sf_set_sfall_global(OpcodeContext& ctx) { static long GetGlobalVarNameString(OpcodeContext& ctx) { const char* var = ctx.arg(0).strValue(); if (strlen(var) != 8) { - ctx.printOpcodeError("get_sfall_global() - The name of the global variable must consist of 8 characters."); + ctx.printOpcodeError("%s() - the name of the global variable must consist of 8 characters.", ctx.getOpcodeName()); return 0; } return GetGlobalVarInternal(*(__int64*)var); diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 0237a15b0..8d7bf069b 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -224,8 +224,8 @@ void __declspec(naked) op_set_viewport_x() { _GET_ARG_INT(end); mov ds:[FO_VAR_wmWorldOffsetX], eax; end: - pop edx; - pop ecx; + pop edx; + pop ecx; retn; } } @@ -245,7 +245,7 @@ void __declspec(naked) op_set_viewport_y() { void sf_add_iface_tag(OpcodeContext &ctx) { int result = BarBoxes::AddExtraBox(); - if (result == -1) ctx.printOpcodeError("add_iface_tag() - cannot add new tag as the maximum limit of 126 tags has been reached."); + if (result == -1) ctx.printOpcodeError("%s() - cannot add new tag as the maximum limit of 126 tags has been reached.", ctx.getMetaruleName()); ctx.setReturn(result); } @@ -347,7 +347,7 @@ void sf_set_iface_tag_text(OpcodeContext& ctx) { if (boxTag > 4 && boxTag <= maxBox) { BarBoxes::SetText(boxTag, ctx.arg(1).strValue(), ctx.arg(2).asInt()); } else { - ctx.printOpcodeError("set_iface_tag_text() - tag value must be in the range of 5 to %d.", maxBox); + ctx.printOpcodeError("%s() - tag value must be in the range of 5 to %d.", ctx.getMetaruleName(), maxBox); } } @@ -396,7 +396,7 @@ void sf_create_win(OpcodeContext& ctx) { ctx.arg(3).asInt(), ctx.arg(4).asInt(), // w, h 256, flags) == -1) { - ctx.printOpcodeError("create_win() - couldn't create window."); + ctx.printOpcodeError("%s() - couldn't create window.", ctx.getMetaruleName()); } } diff --git a/sfall/Modules/Scripting/Handlers/Memory.cpp b/sfall/Modules/Scripting/Handlers/Memory.cpp index 685b3bc6a..54ac94581 100644 --- a/sfall/Modules/Scripting/Handlers/Memory.cpp +++ b/sfall/Modules/Scripting/Handlers/Memory.cpp @@ -39,7 +39,7 @@ void __declspec(naked) op_read_byte() { call fo::funcoffs::interpretPopLong_; cmp dx, VAR_TYPE_INT; jnz error; - movzx edx, byte ptr ds : [eax]; + movzx edx, byte ptr ds:[eax]; jmp result; error: mov edx, 0; @@ -68,7 +68,7 @@ void __declspec(naked) op_read_short() { call fo::funcoffs::interpretPopLong_; cmp dx, VAR_TYPE_INT; jnz error; - movzx edx, word ptr ds : [eax]; + movzx edx, word ptr ds:[eax]; jmp result; error: mov edx, 0; @@ -97,7 +97,7 @@ void __declspec(naked) op_read_int() { call fo::funcoffs::interpretPopLong_; cmp dx, VAR_TYPE_INT; jnz error; - mov edx, dword ptr ds : [eax]; + mov edx, dword ptr ds:[eax]; jmp result; error: mov edx, 0; @@ -225,7 +225,7 @@ void __declspec(naked) op_write_int() { call SafeWrite32; end: popad - retn; + retn; } } @@ -233,7 +233,7 @@ static void _stdcall WriteStringInternal(const char* str, char* addr) { bool hitnull = false; while (*str) { if (!*addr) hitnull = true; - if (hitnull&&addr[1]) break; + if (hitnull && addr[1]) break; *addr = *str; addr++; str++; @@ -295,7 +295,7 @@ static void _stdcall CallOffsetInternal(DWORD func, DWORD script) { call fo::funcoffs::interpretPopLong_; lea ecx, args; add ecx, i; - mov[ecx], eax; + mov [ecx], eax; } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index a30fa78c7..78d74b78b 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -41,30 +41,10 @@ namespace script // Use ctx.setReturn(x) to set return value. // If you want to call user-defined procedures in your handler, use RunScriptProc(). -struct SfallMetarule { - // function name - const char* name; - - // pointer to handler function - ScriptingFunctionHandler func; - - // minimum number of arguments - short minArgs; - - // maximum number of arguments - short maxArgs; - - // argument validation settings - OpcodeArgumentType argValidation[OP_MAX_ARGUMENTS]; -}; - typedef std::unordered_map MetaruleTableType; static MetaruleTableType metaruleTable; -// currently executed metarule -static const SfallMetarule* currentMetarule; - /* Metarule AKA sfall_funcX. @@ -156,11 +136,13 @@ void InitMetaruleTable() { // Validates arguments against metarule specification. // On error prints to debug.log and returns false. -static bool ValidateMetaruleArguments(OpcodeContext& ctx, const SfallMetarule* metaruleInfo) { +static bool ValidateMetaruleArguments(OpcodeContext& ctx) { + const SfallMetarule* metaruleInfo = ctx.metarule; int argCount = ctx.numArgs(); if (argCount < metaruleInfo->minArgs || argCount > metaruleInfo->maxArgs) { ctx.printOpcodeError( - "sfall_funcX(\"%s\", ...) - invalid number of arguments (%d), must be from %d to %d.", + "%s(\"%s\", ...) - invalid number of arguments (%d), must be from %d to %d.", + ctx.getOpcodeName(), metaruleInfo->name, argCount, metaruleInfo->minArgs, @@ -178,20 +160,20 @@ void HandleMetarule(OpcodeContext& ctx) { const char* name = nameArg.strValue(); MetaruleTableType::iterator lookup = metaruleTable.find(name); if (lookup != metaruleTable.end()) { - currentMetarule = lookup->second; + ctx.metarule = lookup->second; // shift function name away, so argument #0 will correspond to actual first argument of function // this allows to use the same handlers for opcodes and metarule functions ctx.setArgShift(1); - if (ValidateMetaruleArguments(ctx, currentMetarule)) { - currentMetarule->func(ctx); + if (ValidateMetaruleArguments(ctx)) { + ctx.metarule->func(ctx); } else if (ctx.hasReturn()) { ctx.setReturn(-1); } } else { - ctx.printOpcodeError("sfall_funcX(name, ...) - name '%s' is unknown.", name); + ctx.printOpcodeError("%s(\"%s\", ...) - metarule function is unknown.", ctx.getOpcodeName(), name); } } else { - ctx.printOpcodeError("sfall_funcX(name, ...) - name must be string."); + ctx.printOpcodeError("%s(name, ...) - name must be string.", ctx.getOpcodeName()); } } diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index 8643ec66e..154129d0f 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -296,16 +296,12 @@ void sf_set_object_knockback(OpcodeContext& ctx) { fo::GameObject* object = ctx.arg(0).asObject(); if (mode) { if (object->Type() != fo::OBJ_TYPE_CRITTER) { - if (mode == 1) { - ctx.printOpcodeError("set_target_knockback() - the object is not a critter."); - } else { - ctx.printOpcodeError("set_attacker_knockback() - the object is not a critter."); - } + ctx.printOpcodeError("%s() - the object is not a critter.", ctx.getOpcodeName()); return; } } else { if (object->Type() != fo::OBJ_TYPE_ITEM) { - ctx.printOpcodeError("set_weapon_knockback() - the object is not an item."); + ctx.printOpcodeError("%s() - the object is not an item.", ctx.getOpcodeName()); return; } } @@ -658,8 +654,8 @@ void __declspec(naked) op_get_uptime() { mov edx, eax; pop eax; _RET_VAL_INT(ecx); - pop edx; - pop ecx; + pop edx; + pop ecx; retn; } } @@ -763,7 +759,7 @@ void __declspec(naked) op_set_bodypart_hit_modifier() { } } -static char* valueOutRange = "argument values out of range"; +static char* valueOutRange = "%s() - argument values out of range"; void sf_set_critical_table(OpcodeContext& ctx) { DWORD critter = ctx.arg(0).asInt(), @@ -772,7 +768,7 @@ void sf_set_critical_table(OpcodeContext& ctx) { element = ctx.arg(3).asInt(); if (critter >= Criticals::critTableCount || bodypart >= 9 || slot >= 6 || element >= 7) { - ctx.printOpcodeError("set_critical_table() - %s.", valueOutRange); + ctx.printOpcodeError(valueOutRange, ctx.getOpcodeName()); } else { Criticals::SetCriticalTable(critter, bodypart, slot, element, ctx.arg(4).asInt()); } @@ -785,7 +781,7 @@ void sf_get_critical_table(OpcodeContext& ctx) { element = ctx.arg(3).asInt(); if (critter >= Criticals::critTableCount || bodypart >= 9 || slot >= 6 || element >= 7) { - ctx.printOpcodeError("get_critical_table() - %s.", valueOutRange); + ctx.printOpcodeError(valueOutRange, ctx.getOpcodeName()); } else { ctx.setReturn(Criticals::GetCriticalTable(critter, bodypart, slot, element)); } @@ -798,7 +794,7 @@ void sf_reset_critical_table(OpcodeContext& ctx) { element = ctx.arg(3).asInt(); if (critter >= Criticals::critTableCount || bodypart >= 9 || slot >= 6 || element >= 7) { - ctx.printOpcodeError("reset_critical_table() - %s.", valueOutRange); + ctx.printOpcodeError(valueOutRange, ctx.getOpcodeName()); } else { Criticals::ResetCriticalTable(critter, bodypart, slot, element); } @@ -903,7 +899,7 @@ void __declspec(naked) op_nb_create_char() { } } -static char* failedLoad = "failed to load a prototype id"; +static const char* failedLoad = "%s() - failed to load a prototype id: %d"; static bool protoMaxLimitPatch = false; void sf_get_proto_data(OpcodeContext& ctx) { @@ -913,7 +909,7 @@ void sf_get_proto_data(OpcodeContext& ctx) { if (result != -1) { result = *(long*)((BYTE*)protoPtr + ctx.arg(1).rawValue()); } else { - ctx.printOpcodeError("get_proto_data() - %s: %d", failedLoad, pid); + ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); } ctx.setReturn(result); } @@ -926,7 +922,7 @@ void sf_set_proto_data(OpcodeContext& ctx) { protoMaxLimitPatch = true; } } else { - ctx.printOpcodeError("set_proto_data() - %s: %d", failedLoad, pid); + ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); } } @@ -1348,29 +1344,33 @@ void sf_set_ini_setting(OpcodeContext& ctx) { switch (result) { case 0: - ctx.printOpcodeError("set_ini_setting() - value save error."); + ctx.printOpcodeError("%s() - value save error.", ctx.getMetaruleName()); break; case -1: - ctx.printOpcodeError("set_ini_setting() - invalid setting argument."); + ctx.printOpcodeError("%s() - invalid setting argument.", ctx.getMetaruleName()); break; } } -char getIniSectionBuf[512]; +char getIniSectionBuf[5120]; + void sf_get_ini_sections(OpcodeContext& ctx) { auto fileName = std::string(".\\") + ctx.arg(0).asString(); - GetPrivateProfileSectionNamesA(getIniSectionBuf, 512, fileName.data()); + GetPrivateProfileSectionNamesA(getIniSectionBuf, 5120, fileName.data()); std::vector sections; char* section = getIniSectionBuf; while (*section != 0) { sections.push_back(section); section += std::strlen(section) + 1; } - int arrayId = TempArray(sections.size(), 0); + size_t sz = sections.size(); + int arrayId = TempArray(sz, 0); auto& arr = arrays[arrayId]; - for (size_t i = 0; i < sections.size(); ++i) { - arr.val[i].set(sections[i]); + for (size_t i = 0; i < sz; ++i) { + size_t j = i + 1; + int len = (j < sz) ? sections[j] - sections[i] - 1 : -1; + arr.val[i].set(sections[i], len); } ctx.setReturn(arrayId); } @@ -1378,7 +1378,7 @@ void sf_get_ini_sections(OpcodeContext& ctx) { void sf_get_ini_section(OpcodeContext& ctx) { auto fileName = std::string(".\\") + ctx.arg(0).asString(); auto section = ctx.arg(1).asString(); - GetPrivateProfileSectionA(section, getIniSectionBuf, 512, fileName.data()); + GetPrivateProfileSectionA(section, getIniSectionBuf, 5120, fileName.data()); int arrayId = TempArray(-1, 0); // associative auto& arr = arrays[arrayId]; char *key = getIniSectionBuf, *val = nullptr; diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index 079016a22..82814d330 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.cpp +++ b/sfall/Modules/Scripting/Handlers/Objects.cpp @@ -328,7 +328,7 @@ void sf_critter_inven_obj2(OpcodeContext& ctx) { ctx.setReturn(critter->invenSize); break; default: - ctx.printOpcodeError("critter_inven_obj2() - invalid type."); + ctx.printOpcodeError("%s() - invalid type.", ctx.getMetaruleName()); } } @@ -367,7 +367,7 @@ void sf_set_dude_obj(OpcodeContext& ctx) { if (obj->Type() == fo::OBJ_TYPE_CRITTER) { PartyControl::SwitchToCritter(obj); } else { - ctx.printOpcodeError("Object is not a critter!"); + ctx.printOpcodeError("%s() - the object is not a critter.", ctx.getMetaruleName()); ctx.setReturn(-1); } } @@ -391,7 +391,7 @@ void sf_unjam_lock(OpcodeContext& ctx) { void sf_set_unjam_locks_time(OpcodeContext& ctx) { int time = ctx.arg(0).asInt(); if (time < 0 || time > 127) { - ctx.printOpcodeError("set_unjam_locks_time() - time argument must be in the range of 0 to 127."); + ctx.printOpcodeError("%s() - time argument must be in the range of 0 to 127.", ctx.getMetaruleName()); } else { Objects::SetAutoUnjamLockTime(time); } @@ -405,13 +405,13 @@ void sf_item_make_explosive(OpcodeContext& ctx) { if (min > max) { max = min; - ctx.printOpcodeError("item_make_explosive() - Warning: value of max argument is less than the min argument."); + ctx.printOpcodeError("%s() - Warning: value of max argument is less than the min argument.", ctx.getMetaruleName()); } if (pid > 0 && pidActive > 0) { Explosions::AddToExplosives(pid, pidActive, min, max); } else { - ctx.printOpcodeError("item_make_explosive() - PID arguments must be greater than 0."); + ctx.printOpcodeError("%s() - invalid PID number, must be greater than 0.", ctx.getMetaruleName()); } } @@ -495,7 +495,7 @@ void sf_get_object_ai_data(OpcodeContext& ctx) { value = arrayId; break; default: - ctx.printOpcodeError("get_object_ai_data() - invalid value for AI argument."); + ctx.printOpcodeError("%s() - invalid value for AI argument.", ctx.getMetaruleName()); } ctx.setReturn(value, DataType::INT); } @@ -513,10 +513,10 @@ void sf_set_drugs_data(OpcodeContext& ctx) { result = Drugs::SetDrugAddictTimeOff(pid, val); break; default: - ctx.printOpcodeError("set_drugs_data() - invalid value for type argument."); + ctx.printOpcodeError("%s() - invalid value for type argument.", ctx.getMetaruleName()); return; } - if (result) ctx.printOpcodeError("set_drugs_data() - drug PID not found in the configuration file."); + if (result) ctx.printOpcodeError("%s() - drug PID not found in the configuration file.", ctx.getMetaruleName()); } void sf_set_unique_id(OpcodeContext& ctx) { diff --git a/sfall/Modules/Scripting/Handlers/Perks.cpp b/sfall/Modules/Scripting/Handlers/Perks.cpp index 425cef5c5..df01bedaf 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.cpp +++ b/sfall/Modules/Scripting/Handlers/Perks.cpp @@ -217,13 +217,13 @@ void __declspec(naked) op_set_selectable_perk() { movzx eax, word ptr[esp + 20]; cmp eax, VAR_TYPE_INT; jne fail; - movzx eax, word ptr ds : [esp + 4]; + movzx eax, word ptr ds:[esp + 4]; cmp eax, VAR_TYPE_STR2; je next1; cmp eax, VAR_TYPE_STR; jne fail; next1: - movzx eax, word ptr ds : [esp + 28]; + movzx eax, word ptr ds:[esp + 28]; cmp eax, VAR_TYPE_STR2; je next2; cmp eax, VAR_TYPE_STR; @@ -286,13 +286,13 @@ void __declspec(naked) op_set_fake_perk() { movzx eax, word ptr[esp + 20]; cmp eax, VAR_TYPE_INT; jne fail; - movzx eax, word ptr ds : [esp + 4]; + movzx eax, word ptr ds:[esp + 4]; cmp eax, VAR_TYPE_STR2; je next1; cmp eax, VAR_TYPE_STR; jne fail; next1: - movzx eax, word ptr ds : [esp + 28]; + movzx eax, word ptr ds:[esp + 28]; cmp eax, VAR_TYPE_STR2; je next2; cmp eax, VAR_TYPE_STR; @@ -359,13 +359,13 @@ void __declspec(naked) op_set_fake_trait() { movzx eax, word ptr[esp + 20]; cmp eax, VAR_TYPE_INT; jne fail; - movzx eax, word ptr ds : [esp + 4]; + movzx eax, word ptr ds:[esp + 4]; cmp eax, VAR_TYPE_STR2; je next1; cmp eax, VAR_TYPE_STR; jne fail; next1: - movzx eax, word ptr ds : [esp + 28]; + movzx eax, word ptr ds:[esp + 28]; cmp eax, VAR_TYPE_STR2; je next2; cmp eax, VAR_TYPE_STR; @@ -524,13 +524,13 @@ void __declspec(naked) op_remove_trait() { mov ecx, ds:[FO_VAR_pc_trait + 4]; cmp eax, ds:[FO_VAR_pc_trait]; jne next; - mov ds : [FO_VAR_pc_trait], ecx; - mov ds : [FO_VAR_pc_trait + 4], ebx; + mov ds:[FO_VAR_pc_trait], ecx; + mov ds:[FO_VAR_pc_trait + 4], ebx; jmp end; next: - cmp eax, ds : [FO_VAR_pc_trait + 4]; + cmp eax, ds:[FO_VAR_pc_trait + 4]; jne end; - mov ds : [FO_VAR_pc_trait + 4], ebx; + mov ds:[FO_VAR_pc_trait + 4], ebx; end: popad; retn; diff --git a/sfall/Modules/Scripting/Handlers/Stats.cpp b/sfall/Modules/Scripting/Handlers/Stats.cpp index 7bf90202b..de4ead4cc 100644 --- a/sfall/Modules/Scripting/Handlers/Stats.cpp +++ b/sfall/Modules/Scripting/Handlers/Stats.cpp @@ -31,15 +31,15 @@ namespace sfall namespace script { -const char* invalidStat = "stat number out of range"; -const char* objNotCritter = "the object is not a critter"; +const char* invalidStat = "%s() - stat number out of range."; +const char* objNotCritter = "%s() - the object is not a critter."; void sf_set_pc_base_stat(OpcodeContext& ctx) { int stat = ctx.arg(0).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) { ((long*)FO_VAR_pc_proto)[9 + stat] = ctx.arg(1).rawValue(); } else { - ctx.printOpcodeError("set_pc_base_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } } @@ -48,7 +48,7 @@ void sf_set_pc_extra_stat(OpcodeContext& ctx) { if (stat >= 0 && stat < fo::STAT_max_stat) { ((long*)FO_VAR_pc_proto)[44 + stat] = ctx.arg(1).rawValue(); } else { - ctx.printOpcodeError("set_pc_extra_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } } @@ -58,7 +58,7 @@ void sf_get_pc_base_stat(OpcodeContext& ctx) { if (stat >= 0 && stat < fo::STAT_max_stat) { value = ((long*)FO_VAR_pc_proto)[9 + stat]; } else { - ctx.printOpcodeError("get_pc_base_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } ctx.setReturn(value); } @@ -69,7 +69,7 @@ void sf_get_pc_extra_stat(OpcodeContext& ctx) { if (stat >= 0 && stat < fo::STAT_max_stat) { value = ((long*)FO_VAR_pc_proto)[44 + stat]; } else { - ctx.printOpcodeError("get_pc_extra_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } ctx.setReturn(value); } @@ -80,7 +80,7 @@ void sf_set_critter_base_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) Stats::SetStat(obj, stat, ctx.arg(2).rawValue(), 9); } else { - ctx.printOpcodeError("set_critter_base_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } } @@ -90,7 +90,7 @@ void sf_set_critter_extra_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) Stats::SetStat(obj, stat, ctx.arg(2).rawValue(), 44); } else { - ctx.printOpcodeError("set_critter_extra_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } } @@ -101,7 +101,7 @@ void sf_get_critter_base_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) result = Stats::GetStat(obj, stat, 9); } else { - ctx.printOpcodeError("get_critter_base_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } ctx.setReturn(result); } @@ -113,7 +113,7 @@ void sf_get_critter_extra_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) result = Stats::GetStat(obj, stat, 44); } else { - ctx.printOpcodeError("get_critter_extra_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } ctx.setReturn(result); } @@ -159,7 +159,7 @@ void __declspec(naked) op_set_critter_skill_points() { mov edx, esp; call fo::funcoffs::proto_ptr_; mov eax, [esp]; - mov[eax + 0x13c + esi * 4], edi; + mov [eax + 0x13c + esi * 4], edi; end: //Restore registers and return add esp, 12; @@ -323,7 +323,7 @@ void __declspec(naked) op_set_critter_current_ap() { jnz end; cmp si, VAR_TYPE_INT; jnz end; - mov[eax + 0x40], ebx; + mov [eax + 0x40], ebx; mov ecx, ds:[FO_VAR_obj_dude] cmp ecx, eax; jne end; @@ -899,7 +899,7 @@ static void __declspec(naked) SetPerkLevelMod3() { } static void _stdcall SetPerkLevelMod2(int mod) { - if (mod < -25 || mod>25) return; + if (mod < -25 || mod > 25) return; PerkLevelMod = mod; HookCall(0x49687F, &SetPerkLevelMod3); } diff --git a/sfall/Modules/Scripting/Handlers/Worldmap.cpp b/sfall/Modules/Scripting/Handlers/Worldmap.cpp index 9d18fd2df..17024b872 100644 --- a/sfall/Modules/Scripting/Handlers/Worldmap.cpp +++ b/sfall/Modules/Scripting/Handlers/Worldmap.cpp @@ -71,8 +71,8 @@ void __declspec(naked) op_force_encounter() { mov ecx, eax; // mapID call ForceEncounter; end: - pop edx; - pop ecx; + pop edx; + pop ecx; retn; } } @@ -153,8 +153,8 @@ void __declspec(naked) op_get_world_map_y_pos() { push ecx; mov edx, ds:[FO_VAR_world_ypos]; _RET_VAL_INT(ecx); - pop ecx; - pop edx; + pop ecx; + pop edx; retn; } } @@ -181,8 +181,8 @@ void __declspec(naked) op_set_world_map_pos() { jnz end; cmp si, VAR_TYPE_INT; jnz end; - mov ds : [FO_VAR_world_xpos], eax; - mov ds : [FO_VAR_world_ypos], edi; + mov ds:[FO_VAR_world_xpos], eax; + mov ds:[FO_VAR_world_ypos], edi; end: pop esi; pop edi; @@ -262,12 +262,12 @@ void sf_set_rest_mode(OpcodeContext& ctx) { void sf_set_rest_on_map(OpcodeContext& ctx) { long mapId = ctx.arg(0).asInt(); if (mapId < 0) { - ctx.printOpcodeError("set_can_rest_on_map() - invalid map number argument."); + ctx.printOpcodeError("%s() - invalid map number argument.", ctx.getMetaruleName()); return; } long elev = ctx.arg(1).asInt(); if (elev < -1 && elev > 2) { - ctx.printOpcodeError("set_can_rest_on_map() - invalid map elevation argument."); + ctx.printOpcodeError("%s() - invalid map elevation argument.", ctx.getMetaruleName()); } else { Worldmap::SetRestMapLevel(mapId, elev, ctx.arg(2).asBool()); } @@ -276,7 +276,7 @@ void sf_set_rest_on_map(OpcodeContext& ctx) { void sf_get_rest_on_map(OpcodeContext& ctx) { long elev = ctx.arg(1).asInt(); if (elev < 0 && elev > 2) { - ctx.printOpcodeError("get_can_rest_on_map() - invalid map elevation argument."); + ctx.printOpcodeError("%s() - invalid map elevation argument.", ctx.getMetaruleName()); } else { ctx.setReturn(Worldmap::GetRestMapLevel(elev, ctx.arg(0).asInt())); } diff --git a/sfall/Modules/Scripting/OpcodeContext.cpp b/sfall/Modules/Scripting/OpcodeContext.cpp index e5ea83a38..d0844dc76 100644 --- a/sfall/Modules/Scripting/OpcodeContext.cpp +++ b/sfall/Modules/Scripting/OpcodeContext.cpp @@ -27,15 +27,24 @@ namespace sfall namespace script { -OpcodeContext::OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn) { +OpcodeContext::OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn) + : _program(program), _opcode(opcode), _numArgs(argNum), _hasReturn(hasReturn), _argShift(0) +{ assert(argNum < OP_MAX_ARGUMENTS); +} + +OpcodeContext::OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn, const char* opcodeName) + : OpcodeContext::OpcodeContext(program, opcode, argNum, hasReturn) +{ + _opcodeName = opcodeName; +} - _program = program; - _opcode = opcode; +const char* OpcodeContext::getOpcodeName() const { + return _opcodeName; +} - _numArgs = argNum; - _hasReturn = hasReturn; - _argShift = 0; +const char* OpcodeContext::getMetaruleName() const { + return metarule->name; } int OpcodeContext::numArgs() const { @@ -95,11 +104,11 @@ void OpcodeContext::printOpcodeError(const char* fmt, ...) const { bool OpcodeContext::validateArguments(const OpcodeArgumentType argTypes[], const char* opcodeName) const { for (int i = 0; i < _numArgs; i++) { - auto argType = argTypes[i]; auto actualType = arg(i).type(); // display invalid type error if type is set and differs from actual type // exception is when type set to if (actualType == DataType::NONE) break; + auto argType = argTypes[i]; if ((argType == ARG_INT || argType == ARG_OBJECT) && !(actualType == DataType::INT)) { printOpcodeError("%s() - argument #%d is not an integer.", opcodeName, ++i); return false; @@ -121,22 +130,23 @@ bool OpcodeContext::validateArguments(const OpcodeArgumentType argTypes[], const } void OpcodeContext::handleOpcode(ScriptingFunctionHandler func) { - _popArguments(); + if (_numArgs) _popArguments(); func(*this); - _pushReturnValue(); + if (_hasReturn) _pushReturnValue(); } -void OpcodeContext::handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[], const char* opcodeName) { - _popArguments(); +void OpcodeContext::handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[]) { + if (_numArgs) _popArguments(); - if (validateArguments(argTypes, opcodeName)) { + if (!_numArgs || validateArguments(argTypes, _opcodeName)) { func(*this); } else if (_hasReturn) { setReturn(-1); // is a common practice to return -1 in case of errors in fallout engine } - _pushReturnValue(); + + if (_hasReturn) _pushReturnValue(); } void __stdcall OpcodeContext::handleOpcodeStatic(fo::Program* program, DWORD opcodeOffset, ScriptingFunctionHandler func, char argNum, bool hasReturn) { @@ -206,17 +216,15 @@ void OpcodeContext::_popArguments() { } void OpcodeContext::_pushReturnValue() { - if (_hasReturn) { - if (_ret.type() == DataType::NONE) { - _ret = ScriptValue(0); // if no value was set in handler, force return 0 to avoid stack error - } - DWORD rawResult = _ret.rawValue(); - if (_ret.type() == DataType::STR) { - rawResult = fo::func::interpretAddString(_program, _ret.strValue()); - } - fo::func::interpretPushLong(_program, rawResult); - fo::func::interpretPushShort(_program, getScriptTypeBySfallType(_ret.type())); + if (_ret.type() == DataType::NONE) { + _ret = ScriptValue(0); // if no value was set in handler, force return 0 to avoid stack error + } + DWORD rawResult = _ret.rawValue(); + if (_ret.type() == DataType::STR) { + rawResult = fo::func::interpretAddString(_program, _ret.strValue()); } + fo::func::interpretPushLong(_program, rawResult); + fo::func::interpretPushShort(_program, getScriptTypeBySfallType(_ret.type())); } } diff --git a/sfall/Modules/Scripting/OpcodeContext.h b/sfall/Modules/Scripting/OpcodeContext.h index 48067b546..69e95eb38 100644 --- a/sfall/Modules/Scripting/OpcodeContext.h +++ b/sfall/Modules/Scripting/OpcodeContext.h @@ -66,6 +66,23 @@ typedef struct SfallOpcodeInfo { } SfallOpcodeInfo; +typedef struct SfallMetarule { + // function name + const char* name; + + // pointer to handler function + ScriptingFunctionHandler func; + + // minimum number of arguments + short minArgs; + + // maximum number of arguments + short maxArgs; + + // argument validation settings + OpcodeArgumentType argValidation[OP_MAX_ARGUMENTS]; +} SfallMetarule; + // A context for handling opcodes. Opcode handlers can retrieve arguments and set opcode return value via context. class OpcodeContext { public: @@ -73,7 +90,15 @@ class OpcodeContext { // opcode - opcode number // argNum - number of arguments for this opcode // hasReturn - true if opcode has return value (is expression) + // opcodeName - name of a function (for logging) OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn); + OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn, const char* opcodeName); + + const char* getOpcodeName() const; + const char* getMetaruleName() const; + + // currently executed metarule func + const SfallMetarule* metarule; // number of arguments, possibly reduced by argShift int numArgs() const; @@ -121,7 +146,7 @@ class OpcodeContext { // func - opcode handler // argTypes - argument types for validation // opcodeName - name of a function (for logging) - void handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[], const char* opcodeName); + void handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[]); // handles opcode using default instance static void __stdcall handleOpcodeStatic(fo::Program* program, DWORD opcodeOffset, ScriptingFunctionHandler func, char argNum, bool hasReturn); @@ -141,6 +166,8 @@ class OpcodeContext { fo::Program* _program; DWORD _opcode; + const char* _opcodeName; + int _numArgs; bool _hasReturn; int _argShift; diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index ce1c2953a..bcd8c23e9 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -221,8 +221,8 @@ void __fastcall defaultOpcodeHandlerCall(fo::Program* program, DWORD opcodeOffse auto iter = opcodeInfoMap.find(opcode); if (iter != opcodeInfoMap.end()) { auto info = iter->second; - OpcodeContext ctx(program, opcode, info->argNum, info->hasReturn); - ctx.handleOpcode(info->handler, info->argValidation, info->name); + OpcodeContext ctx(program, opcode, info->argNum, info->hasReturn, info->name); + ctx.handleOpcode(info->handler, info->argValidation); } else { fo::func::interpretError("Unknown opcode: %d", opcode); } From c65e1ec6d3aeab1333d4d3a197afda88d7d4466a Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 30 Apr 2019 11:17:37 +0800 Subject: [PATCH 03/32] Fixed the argument value of dialogue_reaction script function --- sfall/Modules/BugFixes.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 02ec7250c..4ad28cf12 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -2066,6 +2066,22 @@ static void __declspec(naked) exec_script_proc_hack1() { } } +static void __declspec(naked) op_dialogue_reaction_hook() { + __asm { + cmp eax, 4; // neutral fidget + mov eax, 1; + jb good; + je neutral; + jmp bad; +good: + dec eax; +neutral: + dec eax; +bad: + jmp fo::funcoffs::talk_to_critter_reacts_; // -1 - good, 0 - neutral, 1 - bad + } +} + void BugFixes::init() { #ifndef NDEBUG @@ -2615,6 +2631,9 @@ void BugFixes::init() // Fix for the start procedure not being called correctly if the required standard script procedure is missing MakeCall(0x4A4926, exec_script_proc_hack); MakeCall(0x4A4979, exec_script_proc_hack1); + + // Fix the argument value of dialogue_reaction function + HookCall(0x456FFA, op_dialogue_reaction_hook); } } From 045926c8607f4202a9eb423fe76adbf7b67f2b67 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 1 May 2019 09:45:51 +0800 Subject: [PATCH 04/32] Reordered the execution of functions before exiting the map * partyMemberSaveProtos and queue_leaving_map are now called after executing map_exit_p_proc procedure. Replaced some obsolete implementation in ScriptExtender/Worldmap. --- sfall/Modules/KillCounter.cpp | 2 +- sfall/Modules/ScriptExtender.cpp | 31 +++++++++++++++++++++++++------ sfall/Modules/ScriptExtender.h | 3 ++- sfall/Modules/Worldmap.cpp | 9 ++++----- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/sfall/Modules/KillCounter.cpp b/sfall/Modules/KillCounter.cpp index c6642832a..75539914d 100644 --- a/sfall/Modules/KillCounter.cpp +++ b/sfall/Modules/KillCounter.cpp @@ -69,7 +69,7 @@ static void KillCounterInit() { // Edit the functions to accept kill types over 19 SafeWriteBatch(38, extraKillTypesCountAddr); - SafeWrite32(0x42D9DD, 1488); + SafeWrite32(0x42D9DD, 1488); // critter_kill_info_ } void KillCounter::init() { diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index 833abd510..4f8051ecf 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -76,6 +76,8 @@ struct SelfOverrideObj { fo::GameObject* object; char counter; + SelfOverrideObj(fo::GameObject* obj) : object(obj), counter(0) {} + bool UnSetSelf() { if (counter) counter--; return counter == 0; @@ -102,7 +104,7 @@ DWORD isGlobalScriptLoading = 0; std::unordered_map<__int64, int> globalVars; typedef std::unordered_map<__int64, int> :: iterator glob_itr; typedef std::unordered_map<__int64, int> :: const_iterator glob_citr; -typedef std::pair<__int64, int> glob_pair; +//typedef std::pair<__int64, int> glob_pair; DWORD availableGlobalScriptTypes = 0; bool isGameLoading; @@ -341,7 +343,7 @@ void __fastcall SetGlobalScriptType(fo::Program* script, int type) { static void SetGlobalVarInternal(__int64 var, int val) { glob_itr itr = globalVars.find(var); if (itr == globalVars.end()) { - globalVars.insert(glob_pair(var, val)); + globalVars.emplace(var, val); } else { if (val == 0) { globalVars.erase(itr); // applies for both float 0.0 and integer 0 @@ -395,10 +397,10 @@ void __fastcall SetSelfObject(fo::Program* script, fo::GameObject* obj) { it->second.counter = 0; } } else { - selfOverrideMap[script] = {obj, 0}; + selfOverrideMap.emplace(script, obj); } - } else { - if (isFind) selfOverrideMap.erase(it); + } else if (isFind) { + selfOverrideMap.erase(it); } } @@ -646,7 +648,7 @@ bool LoadGlobals(HANDLE h) { GlobalVar var; for (DWORD i = 0; i < count; i++) { ReadFile(h, &var, sizeof(GlobalVar), &unused, 0); - globalVars.insert(glob_pair(var.id, var.val)); + globalVars.emplace(var.id, var.val); } return false; } @@ -697,6 +699,17 @@ void SetGlobals(GlobalVar* globals) { } } +static void __declspec(naked) map_save_in_game_hook() { + __asm { + call fo::funcoffs::partyMemberSaveProtos_; + test cl, 1; + jz skip; + call fo::funcoffs::queue_leaving_map_; +skip: + jmp fo::funcoffs::game_time_; + } +} + void ScriptExtender::init() { LoadGameHook::OnAfterGameStarted() += LoadGlobalScripts; LoadGameHook::OnGameReset() += [] () { @@ -751,6 +764,12 @@ void ScriptExtender::init() { HookCall(0x421B72, CombatBeginHook); HookCall(0x421FC1, CombatOverHook); + // Reorder the execution of functions before exiting the map + // Call saving party member prototypes and removing the drug effects for NPC after executing map_exit_p_proc procedure + HookCall(0x483CF9, map_save_in_game_hook); + BlockCall(0x483CB4); // partyMemberSaveProtos_ + BlockCall(0x483CBE); // queue_leaving_map_ + InitNewOpcodes(); } diff --git a/sfall/Modules/ScriptExtender.h b/sfall/Modules/ScriptExtender.h index 295fb9ab8..dc300d903 100644 --- a/sfall/Modules/ScriptExtender.h +++ b/sfall/Modules/ScriptExtender.h @@ -36,12 +36,13 @@ class ScriptExtender : public Module { static Delegate<>& OnMapExit(); }; -#pragma pack(8) +#pragma pack(push, 8) struct GlobalVar { __int64 id; __int32 val; __int32 unused; }; +#pragma pack(pop) typedef struct { fo::Program* ptr = nullptr; diff --git a/sfall/Modules/Worldmap.cpp b/sfall/Modules/Worldmap.cpp index ba824bc9f..5258b7ac5 100644 --- a/sfall/Modules/Worldmap.cpp +++ b/sfall/Modules/Worldmap.cpp @@ -635,14 +635,13 @@ void Worldmap::SetRestMapLevel(int mapId, long elev, bool canRest) { } levelRest elevData = {-1, -1, -1, -1}; - std::pair info (mapId, elevData); if (elev == -1) { - info.second.level[++elev] = canRest; - info.second.level[++elev] = canRest; + elevData.level[++elev] = canRest; + elevData.level[++elev] = canRest; elev++; } - info.second.level[elev] = canRest; - mapRestInfo.insert(info); + elevData.level[elev] = canRest; + mapRestInfo.insert(std::make_pair(mapId, elevData)); } } From edac0ba3939be2da53231a6636910a2c9d404c84 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 1 May 2019 20:56:35 +0800 Subject: [PATCH 05/32] Fixed the issue with read-only party member base protos. * the protos being placed in the proto\critters\ directory will cause party member's stats reset after loading a saved game. --- sfall/FalloutEngine/FunctionOffsets_def.h | 1 + sfall/FalloutEngine/Functions_def.h | 1 + sfall/Modules/LoadOrder.cpp | 127 ++++++++++++++++++++++ 3 files changed, 129 insertions(+) diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index 02d7d2e04..dc42ab6d7 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -156,6 +156,7 @@ FUNC(LoadLibraryA_0, 0x4FE1A8) FUNC(LoadObjDudeCid_, 0x480710) FUNC(LoadSlot_, 0x47DC68) FUNC(LoadTumbSlot_, 0x47EC48) +FUNC(MapDirErase_, 0x480040) FUNC(MVE_MemAlloc_, 0x4F4D40) FUNC(MVE_MemFree_, 0x4F48C0) FUNC(MVE_MemInit_, 0x4F4890) diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 4b2d479dd..56c0c8e21 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -76,6 +76,7 @@ WRAP_WATCOM_FUNC1(long, item_w_max_ammo, GameObject*, item) WRAP_WATCOM_FUNC1(long, item_weight, GameObject*, item) // returns light level at given tile WRAP_WATCOM_FUNC2(long, light_get_tile, long, elevation, long, tileNum) +WRAP_WATCOM_FUNC2(void, MapDirErase, const char*, folder, const char*, ext) WRAP_WATCOM_FUNC2(void, mouse_get_position, long*, outX, long*, outY) WRAP_WATCOM_FUNC0(void, mouse_show) WRAP_WATCOM_FUNC0(void, mouse_hide) diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 4f65a04c2..60a7cf5ac 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -38,6 +38,7 @@ static DWORD format; static bool cutsPatch = false; static std::vector patchFiles; +static std::vector savPrototypes; static void CheckPlayerGender() { isFemale = fo::HeroIsFemale(); @@ -185,6 +186,119 @@ static void GetExtraPatches() { } } +////////////////////////////// SAVE PARTY MEMBER PROTOTYPES ////////////////////////////// + +static void __fastcall AddSavPrototype(long pid) { + for (const auto& _pid : savPrototypes) { + if (_pid == pid) return; + } + savPrototypes.push_back(pid); +} + +static long ChangePrototypeExt(char* path) { + long len = strlen(path); + if (len) { + len -= 4; + if (path[len] == '.') { + path[++len] = 's'; + path[++len] = 'a'; + path[++len] = 'v'; + } else { + len = 0; + } + } + return len; +} + +static void __fastcall ExistSavPrototype(long pid, char* path) { + if (savPrototypes.empty()) return; + for (const auto& _pid : savPrototypes) { + if (_pid == pid) { + ChangePrototypeExt(path); + break; + } + } +} + +static long __fastcall CheckProtoType(long pid, char* path) { + if (pid >> 24 != fo::OBJ_TYPE_CRITTER) return 0; + return ChangePrototypeExt(path); +} + +// saves prototypes (all party members) before saving game or exiting the map +static void __declspec(naked) proto_save_pid_hook() { + __asm { + push ecx; + call fo::funcoffs::proto_list_str_; + test eax, eax; + jnz end; + mov ecx, ebx; // party pid + mov edx, edi; // path buffer + call CheckProtoType; + test eax, eax; + jz end; + mov ecx, ebx; // party pid + call AddSavPrototype; +end: + pop ecx; + retn; + } +} + +static void __declspec(naked) GameMap2Slot_hack() { // save party pids + __asm { + push ecx; + mov edx, 0x6143F4; // path buffer + call CheckProtoType; // ecx - party pid + pop ecx; + lea eax, [esp + 0x14 + 4]; + retn 0x14; + } +} + +static void __declspec(naked) SlotMap2Game_hack() { // load party pids + __asm { + push edx; + mov ecx, edi; // party pid + mov edx, 0x6143F4; // path buffer + call CheckProtoType; + test eax, eax; + jz end; + mov ecx, edi; + call AddSavPrototype; +end: + pop edx; + lea eax, [esp + 0x14 + 4]; + retn 0x14; + } +} + +static void __declspec(naked) proto_load_pid_hook() { +using namespace fo; + __asm { // eax - party pid + push ecx; + mov ecx, eax; + call fo::funcoffs::proto_list_str_; + test eax, eax; + jnz end; + // check pid type + mov edx, ecx; + shr edx, 24; + cmp edx, OBJ_TYPE_CRITTER; + jnz end; + mov edx, edi; // path buffer + call ExistSavPrototype; // ecx - party pid + xor eax, eax; +end: + pop ecx; + retn; + } +} + +static void RemoveSavFiles() { + fo::func::MapDirErase((const char*)0x50A490, (const char*)0x50A480); +} + void LoadOrder::init() { GetExtraPatches(); @@ -218,6 +332,19 @@ void LoadOrder::init() { } dlogr(" Done", DL_INIT); } + + dlog("Applying party member protos save/load patch.", DL_INIT); + savPrototypes.reserve(25); + HookCall(0x4A1CF2, proto_load_pid_hook); + HookCall(0x4A1BEE, proto_save_pid_hook); + MakeCall(0x47F5A5, GameMap2Slot_hack); // save game + MakeCall(0x47FB80, SlotMap2Game_hack); // load game + LoadGameHook::OnAfterGameInit() += RemoveSavFiles; + LoadGameHook::OnGameReset() += []() { + savPrototypes.clear(); + RemoveSavFiles(); + }; + dlogr(" Done", DL_INIT); } } From 42cf5ded97d8b1a620493ad9a37c892a1fdfb209 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 2 May 2019 14:23:02 +0800 Subject: [PATCH 06/32] Unset the read-only attribute for party member's .sav proto files --- sfall/Modules/LoadOrder.cpp | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 60a7cf5ac..1e78792df 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -245,11 +245,12 @@ static void __declspec(naked) proto_save_pid_hook() { } } +#define _F_PATHFILE 0x6143F4 static void __declspec(naked) GameMap2Slot_hack() { // save party pids __asm { push ecx; - mov edx, 0x6143F4; // path buffer - call CheckProtoType; // ecx - party pid + mov edx, _F_PATHFILE; // path buffer + call CheckProtoType; // ecx - party pid pop ecx; lea eax, [esp + 0x14 + 4]; retn 0x14; @@ -259,8 +260,8 @@ static void __declspec(naked) GameMap2Slot_hack() { // save party pids static void __declspec(naked) SlotMap2Game_hack() { // load party pids __asm { push edx; - mov ecx, edi; // party pid - mov edx, 0x6143F4; // path buffer + mov ecx, edi; // party pid + mov edx, _F_PATHFILE; // path buffer call CheckProtoType; test eax, eax; jz end; @@ -295,8 +296,32 @@ using namespace fo; } } +static void ResetReadOnlyAttr() { + DWORD attr = GetFileAttributesA((const char*)_F_PATHFILE); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY)) { + SetFileAttributesA((const char*)_F_PATHFILE, (attr & ~FILE_ATTRIBUTE_READONLY)); + } +} + +static void __declspec(naked) SlotMap2Game_hack_attr() { +using namespace fo; + __asm { + cmp eax, -1; + je end; + cmp ebx, OBJ_TYPE_CRITTER; + jne end; + call ResetReadOnlyAttr; + or eax, 1; // reset ZF +end: + retn 0x8; + } +} + +#define _F_SAV (const char*)0x50A480 +#define _F_PROTO_CRITTERS (const char*)0x50A490 + static void RemoveSavFiles() { - fo::func::MapDirErase((const char*)0x50A490, (const char*)0x50A480); + fo::func::MapDirErase(_F_PROTO_CRITTERS, _F_SAV); } void LoadOrder::init() { @@ -339,6 +364,8 @@ void LoadOrder::init() { HookCall(0x4A1BEE, proto_save_pid_hook); MakeCall(0x47F5A5, GameMap2Slot_hack); // save game MakeCall(0x47FB80, SlotMap2Game_hack); // load game + MakeCall(0x47FBBF, SlotMap2Game_hack_attr, 1); + LoadGameHook::OnAfterGameInit() += RemoveSavFiles; LoadGameHook::OnGameReset() += []() { savPrototypes.clear(); From 0f98591b77237178463563ce3e150e265b71743f Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 5 May 2019 08:47:08 +0800 Subject: [PATCH 07/32] Code refactoring in Arrays.cpp. Added a new example mod gl_ammomod to modderspack. --- artifacts/ddraw.ini | 2 +- artifacts/example_mods/AmmoMod/AmmoGlovz.ini | 201 ++++++++++++++++++ artifacts/example_mods/AmmoMod/AmmoYAAM.ini | 201 ++++++++++++++++++ artifacts/example_mods/AmmoMod/gl_ammomod.int | Bin 0 -> 1288 bytes artifacts/example_mods/AmmoMod/gl_ammomod.ssl | 63 ++++++ artifacts/example_mods/AmmoMod/readme.txt | 12 ++ sfall/FalloutEngine/Functions_def.h | 4 +- sfall/FalloutEngine/Variables_def.h | 10 +- sfall/Modules/LoadOrder.cpp | 2 +- sfall/Modules/ScriptExtender.cpp | 8 +- sfall/Modules/Scripting/Arrays.cpp | 13 +- sfall/Modules/Scripting/Arrays.h | 7 +- sfall/Modules/Scripting/Handlers/Arrays.cpp | 92 ++++---- .../Modules/Scripting/Handlers/Interface.cpp | 10 +- sfall/Modules/Worldmap.cpp | 2 +- 15 files changed, 558 insertions(+), 69 deletions(-) create mode 100644 artifacts/example_mods/AmmoMod/AmmoGlovz.ini create mode 100644 artifacts/example_mods/AmmoMod/AmmoYAAM.ini create mode 100644 artifacts/example_mods/AmmoMod/gl_ammomod.int create mode 100644 artifacts/example_mods/AmmoMod/gl_ammomod.ssl create mode 100644 artifacts/example_mods/AmmoMod/readme.txt diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 42bc8f4cf..533826735 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -733,7 +733,7 @@ SkipSizeCheck=0 ;Does not require sfall debugging mode ;ExtraCRC=0x00000000,0x00000000 -;Set to 1 to stop Fallout from deleting non readonly protos at startup +;Set to 1 to stop Fallout from deleting non read-only protos at startup ;Has pretty nasty side effects when saving/reloading, so don't use for regular gameplay DontDeleteProtos=0 diff --git a/artifacts/example_mods/AmmoMod/AmmoGlovz.ini b/artifacts/example_mods/AmmoMod/AmmoGlovz.ini new file mode 100644 index 000000000..5441e2f03 --- /dev/null +++ b/artifacts/example_mods/AmmoMod/AmmoGlovz.ini @@ -0,0 +1,201 @@ +;Glovz's Damage Fix + +; PID_EXPLOSIVE_ROCKET +[1] +pid=14 +ac_adjust=-10 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_10MM_JHP +[2] +pid=29 +ac_adjust=0 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_10MM_AP +[3] +pid=30 +ac_adjust=-5 +dr_adjust=0 +dam_mult=1 +dam_div=2 + +; PID_44_MAGNUM_JHP +[4] +pid=31 +ac_adjust=0 +dr_adjust=-10 +dam_mult=2 +dam_div=1 + +; PID_FLAMETHROWER_FUEL +[5] +pid=32 +ac_adjust=-20 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_14MM_AP +[6] +pid=33 +ac_adjust=-5 +dr_adjust=-30 +dam_mult=1 +dam_div=2 + +; PID_223_FMJ +[7] +pid=34 +ac_adjust=-15 +dr_adjust=-15 +dam_mult=2 +dam_div=2 + +; PID_5MM_JHP +[8] +pid=35 +ac_adjust=0 +dr_adjust=-15 +dam_mult=2 +dam_div=1 + +; PID_5MM_AP +[9] +pid=36 +ac_adjust=-5 +dr_adjust=-15 +dam_mult=1 +dam_div=2 + +; PID_ROCKET_AP +[10] +pid=37 +ac_adjust=-10 +dr_adjust=0 +dam_mult=1 +dam_div=2 + +; PID_SMALL_ENERGY_CELL +[11] +pid=38 +ac_adjust=-20 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_MICRO_FUSION_CELL +[12] +pid=39 +ac_adjust=-20 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_SHOTGUN_SHELLS +[13] +pid=95 +ac_adjust=-15 +dr_adjust=0 +dam_mult=3 +dam_div=1 + +; PID_44_FMJ_MAGNUM +[14] +pid=111 +ac_adjust=-5 +dr_adjust=-10 +dam_mult=2 +dam_div=2 + +; PID_9MM_BALL +[15] +pid=121 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_BBS +[16] +pid=163 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_ROBO_ROCKET_AMMO +[17] +pid=274 +ac_adjust=-10 +dr_adjust=-10 +dam_mult=1 +dam_div=1 + +; PID_45_CALIBER_AMMO +[18] +pid=357 +ac_adjust=0 +dr_adjust=-5 +dam_mult=2 +dam_div=1 + +; PID_2MM_EC_AMMO +[19] +pid=358 +ac_adjust=-25 +dr_adjust=-20 +dam_mult=1 +dam_div=3 + +; PID_4_7MM_CASELESS +[20] +pid=359 +ac_adjust=-5 +dr_adjust=-25 +dam_mult=1 +dam_div=2 + +; PID_9MM_AMMO +[21] +pid=360 +ac_adjust=0 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_HN_NEEDLER_CARTRIDGE +[22] +pid=361 +ac_adjust=-20 +dr_adjust=-5 +dam_mult=1 +dam_div=1 + +; PID_HN_AP_NEEDLER_CARTRIDGE +[23] +pid=362 +ac_adjust=-20 +dr_adjust=-5 +dam_mult=1 +dam_div=2 + +; PID_7_62MM_AMMO +[24] +pid=363 +ac_adjust=-10 +dr_adjust=-20 +dam_mult=2 +dam_div=2 + +; PID_FLAMETHROWER_FUEL_MK_II +[25] +pid=382 +ac_adjust=-20 +dr_adjust=-15 +dam_mult=1 +dam_div=1 diff --git a/artifacts/example_mods/AmmoMod/AmmoYAAM.ini b/artifacts/example_mods/AmmoMod/AmmoYAAM.ini new file mode 100644 index 000000000..07cdbb781 --- /dev/null +++ b/artifacts/example_mods/AmmoMod/AmmoYAAM.ini @@ -0,0 +1,201 @@ +;YAAM + +; PID_EXPLOSIVE_ROCKET +[1] +pid=14 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_10MM_JHP +[2] +pid=29 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_10MM_AP +[3] +pid=30 +ac_adjust=0 +dr_adjust=4 +dam_mult=1 +dam_div=1 + +; PID_44_MAGNUM_JHP +[4] +pid=31 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_FLAMETHROWER_FUEL +[5] +pid=32 +ac_adjust=-20 +dr_adjust=0 +dam_mult=3 +dam_div=4 + +; PID_14MM_AP +[6] +pid=33 +ac_adjust=0 +dr_adjust=8 +dam_mult=1 +dam_div=1 + +; PID_223_FMJ +[7] +pid=34 +ac_adjust=-20 +dr_adjust=5 +dam_mult=1 +dam_div=1 + +; PID_5MM_JHP +[8] +pid=35 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_5MM_AP +[9] +pid=36 +ac_adjust=0 +dr_adjust=4 +dam_mult=1 +dam_div=1 + +; PID_ROCKET_AP +[10] +pid=37 +ac_adjust=0 +dr_adjust=20 +dam_mult=1 +dam_div=1 + +; PID_SMALL_ENERGY_CELL +[11] +pid=38 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_MICRO_FUSION_CELL +[12] +pid=39 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_SHOTGUN_SHELLS +[13] +pid=95 +ac_adjust=-10 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_44_FMJ_MAGNUM +[14] +pid=111 +ac_adjust=0 +dr_adjust=5 +dam_mult=1 +dam_div=1 + +; PID_9MM_BALL +[15] +pid=121 +ac_adjust=0 +dr_adjust=2 +dam_mult=3 +dam_div=2 + +; PID_BBS +[16] +pid=163 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_ROBO_ROCKET_AMMO +[17] +pid=274 +ac_adjust=0 +dr_adjust=10 +dam_mult=1 +dam_div=1 + +; PID_45_CALIBER_AMMO +[18] +pid=357 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_2MM_EC_AMMO +[19] +pid=358 +ac_adjust=-30 +dr_adjust=9 +dam_mult=3 +dam_div=2 + +; PID_4_7MM_CASELESS +[20] +pid=359 +ac_adjust=-5 +dr_adjust=7 +dam_mult=3 +dam_div=2 + +; PID_9MM_AMMO +[21] +pid=360 +ac_adjust=0 +dr_adjust=2 +dam_mult=1 +dam_div=1 + +; PID_HN_NEEDLER_CARTRIDGE +[22] +pid=361 +ac_adjust=-10 +dr_adjust=2 +dam_mult=1 +dam_div=1 + +; PID_HN_AP_NEEDLER_CARTRIDGE +[23] +pid=362 +ac_adjust=-10 +dr_adjust=5 +dam_mult=2 +dam_div=1 + +; PID_7_62MM_AMMO +[24] +pid=363 +ac_adjust=-5 +dr_adjust=2 +dam_mult=1 +dam_div=1 + +; PID_FLAMETHROWER_FUEL_MK_II +[25] +pid=382 +ac_adjust=-20 +dr_adjust=0 +dam_mult=1 +dam_div=1 diff --git a/artifacts/example_mods/AmmoMod/gl_ammomod.int b/artifacts/example_mods/AmmoMod/gl_ammomod.int new file mode 100644 index 0000000000000000000000000000000000000000..3572190caa64ee7cf794c5b86ce1652bce4246b9 GIT binary patch literal 1288 zcma)6&59F25bmDogk{MtOT6f^Eb$lr$o_(fx5%>MAs)O5!qBEinI)46lWc@AhuS@e zM_D}h0Fsl>Ap00Tg)F{+R(IF5GYg{*bh^H(ud2SON&?N^gh-`-&~m0^9K^>%3|U)6Gh}TlmCASRXcT4rXfntSWGvr?he?W5iSgAQ_I^4{ zW^>IU{Ib{UTgVzZ-cOW;OWBUqqV>T;vjKchiN_dppg{Oob7PF^cc!Y^ScO)8osX{3m%X-87wwPMw@ho$}3aw+Ex2pS#(v zz%{rJ_n-kA=H4joS}aZ!Y?-|sx~*L?Js33UHWmG!gz_!!QQjN!n=J9l_7$xX+k?Ne zpO@G*$DUoTZIr$*dT|GK)}IQKwOSU2$K88$?DtN5vHwNP{uVgYV4W&f%t~BTRQmD% Gm3{-`%MN(} literal 0 HcmV?d00001 diff --git a/artifacts/example_mods/AmmoMod/gl_ammomod.ssl b/artifacts/example_mods/AmmoMod/gl_ammomod.ssl new file mode 100644 index 000000000..144cf495c --- /dev/null +++ b/artifacts/example_mods/AmmoMod/gl_ammomod.ssl @@ -0,0 +1,63 @@ +/* + +Ammo INI Loader mod for Fallout 2 by NovaRain +--------------------------------------------- + +- modifies ammo protos with data from an INI file: + * AmmoGlovz.ini if DamageFormula=1 or 2 in ddraw.ini + * AmmoYAAM.ini if DamageFormula=5 in ddraw.ini + * AmmoMod.ini if not using any bulit-in damage formula + +Requires sfall 3.5 or higher + +*/ + +#include "..\headers\define_extra.h" + +procedure set_ammo_mod; +procedure start; +procedure map_enter_p_proc; + +variable ammoIni; +variable enabled; + +procedure set_ammo_mod begin + variable pid, dmg_mod, i := 1; + + while (i > 0) do begin + pid := get_ini_setting(ammoIni + "|" + i + "|pid"); + if (pid < 1) then break; + set_proto_data(pid, PROTO_AM_AC_MOD, get_ini_setting(ammoIni + "|" + i + "|ac_adjust")); + set_proto_data(pid, PROTO_AM_DR_MOD, get_ini_setting(ammoIni + "|" + i + "|dr_adjust")); + + // dam_mult and dam_div must be positive integers + dmg_mod := get_ini_setting(ammoIni + "|" + i + "|dam_mult"); + if (dmg_mod < 1) then dmg_mod := 1; + set_proto_data(pid, PROTO_AM_DMG_MULT, dmg_mod); + dmg_mod := get_ini_setting(ammoIni + "|" + i + "|dam_div"); + if (dmg_mod < 1) then dmg_mod := 1; + set_proto_data(pid, PROTO_AM_DMG_DIV, dmg_mod); + + i++; + end + //debug_msg("AmmoMod: " + ammoIni + " - set " + (i - 1) + " ammo protos."); +end + +procedure start begin + if game_loaded then begin + ammoIni := get_ini_setting("ddraw.ini|Misc|DamageFormula"); + switch (ammoIni) begin + case 1: ammoIni := "AmmoGlovz.ini"; + case 2: ammoIni := "AmmoGlovz.ini"; + case 5: ammoIni := "AmmoYAAM.ini"; + default: ammoIni := "AmmoMod.ini"; + end + + enabled := get_ini_setting(ammoIni + "|1|pid"); + if (enabled > 0) then call set_ammo_mod; + end +end + +procedure map_enter_p_proc begin + if (enabled > 0) then call set_ammo_mod; +end diff --git a/artifacts/example_mods/AmmoMod/readme.txt b/artifacts/example_mods/AmmoMod/readme.txt new file mode 100644 index 000000000..cdae1926a --- /dev/null +++ b/artifacts/example_mods/AmmoMod/readme.txt @@ -0,0 +1,12 @@ +Ammo INI Loader mod for Fallout 2 by NovaRain +--------------------------------------------- + +- modifies ammo protos with data from an INI file: + * AmmoGlovz.ini if DamageFormula=1 or 2 in ddraw.ini + * AmmoYAAM.ini if DamageFormula=5 in ddraw.ini + * AmmoMod.ini if not using any bulit-in damage formula + + +Requires sfall 3.5 or higher. + +To use, copy gl_ammomod.int to your scripts folder, and copy the INI files to the same directory as sfall. diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 56c0c8e21..03f2c809e 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -18,6 +18,8 @@ // because the compiler builds the better/optimized code when calling the engine functions WRAP_WATCOM_FFUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, buf, BYTE*, count) WRAP_WATCOM_FFUNC7(long, createWindow, const char*, winName, long, x, long, y, long, width, long, height, long, bgColorIndex, long, flags) +WRAP_WATCOM_FFUNC3(void, display_inventory, long, inventoryOffset, long, visibleOffset, long, mode) +WRAP_WATCOM_FFUNC4(void, display_target_inventory, long, inventoryOffset, long, visibleOffset, DWORD*, targetInventory, long, mode) WRAP_WATCOM_FFUNC3(FrmSubframeData*, frame_ptr, FrmFrameData*, frm, long, frame, long, direction) WRAP_WATCOM_FFUNC7(void, make_straight_path_func, fo::GameObject*, objFrom, DWORD, tileFrom, DWORD, tileTo, void*, rotationPtr, DWORD*, result, long, flags, void*, func) WRAP_WATCOM_FFUNC3(long, object_under_mouse, long, crSwitch, long, inclDude, long, elevation) @@ -42,8 +44,6 @@ WRAP_WATCOM_FUNC3(long, db_freadIntCount, DbFile*, file, DWORD*, dest, long, cou WRAP_WATCOM_FUNC2(long, db_fwriteByte, DbFile*, file, long, value) WRAP_WATCOM_FUNC2(long, db_fwriteInt, DbFile*, file, long, value) WRAP_WATCOM_FUNC0(void, display_stats) -WRAP_WATCOM_FUNC3(void, display_inventory, long, inventoryOffset, long, visibleOffset, long, mode) -WRAP_WATCOM_FUNC4(void, display_target_inventory, long, inventoryOffset, long, visibleOffset, DWORD*, targetInventory, long, mode) // perform combat turn for a given critter WRAP_WATCOM_FUNC2(long, combat_turn, GameObject*, critter, long, isDudeTurn) WRAP_WATCOM_FUNC1(long, critter_is_dead, GameObject*, critter) diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 7fbf1fbbf..d2bb010fd 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -172,16 +172,16 @@ VAR_(sound_music_path1, char*) VAR_(sound_music_path2, char*) VAR_(square, DWORD) VAR_(squares, DWORD*) -VAR_(stack, DWORD) -VAR_(stack_offset, DWORD) +VARA(stack, DWORD, 10) +VARA(stack_offset, DWORD, 10) VARA(stat_data, StatInfo, STAT_real_max_stat) // dynamic array VAR_(stat_flag, DWORD) VAR_(Tag_, DWORD) VAR_(tag_skill, DWORD) VAR_(target_curr_stack, DWORD) -VAR_(target_pud, DWORD) -VAR_(target_stack, DWORD) -VAR_(target_stack_offset, DWORD) +VAR_(target_pud, DWORD*) +VARA(target_stack, DWORD, 10) +VARA(target_stack_offset, DWORD, 10) VAR_(target_str, DWORD) VAR_(target_xpos, DWORD) VAR_(target_ypos, DWORD) diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 1e78792df..374794d6c 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -365,13 +365,13 @@ void LoadOrder::init() { MakeCall(0x47F5A5, GameMap2Slot_hack); // save game MakeCall(0x47FB80, SlotMap2Game_hack); // load game MakeCall(0x47FBBF, SlotMap2Game_hack_attr, 1); + dlogr(" Done", DL_INIT); LoadGameHook::OnAfterGameInit() += RemoveSavFiles; LoadGameHook::OnGameReset() += []() { savPrototypes.clear(); RemoveSavFiles(); }; - dlogr(" Done", DL_INIT); } } diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index 4f8051ecf..def7180ce 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -17,7 +17,7 @@ */ #include -#include +//#include #include #include #include @@ -582,11 +582,7 @@ static void RunScript(GlobalScript* script) { - reset reg_anim_* combatstate checks */ static void ResetStateAfterFrame() { - if (!tempArrays.empty()) { - for (std::set::iterator it = tempArrays.begin(); it != tempArrays.end(); ++it) - FreeArray(*it); - tempArrays.clear(); - } + DeleteAllTempArrays(); RegAnimCombatCheck(1); } diff --git a/sfall/Modules/Scripting/Arrays.cpp b/sfall/Modules/Scripting/Arrays.cpp index def6b441d..045667afd 100644 --- a/sfall/Modules/Scripting/Arrays.cpp +++ b/sfall/Modules/Scripting/Arrays.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include "..\ScriptExtender.h" @@ -22,7 +22,7 @@ ArraysMap arrays; // auto-incremented ID DWORD nextArrayID = 1; // temp arrays: set of arrayId -std::set tempArrays; +std::unordered_set tempArrays; // saved arrays: arrayKey => arrayId ArrayKeysMap savedArrays; // special array ID for array expressions @@ -407,6 +407,15 @@ void _stdcall FreeArray(DWORD id) { } } +void DeleteAllTempArrays() { + if (!tempArrays.empty()) { + for (std::unordered_set::iterator it = tempArrays.begin(); it != tempArrays.end(); ++it) { + FreeArray(*it); + } + tempArrays.clear(); + } +} + ScriptValue _stdcall GetArrayKey(DWORD id, int index) { if (arrays.find(id) == arrays.end() || index < -1 || index > arrays[id].size()) { return ScriptValue(0); diff --git a/sfall/Modules/Scripting/Arrays.h b/sfall/Modules/Scripting/Arrays.h index bdd2918a9..f2a457c90 100644 --- a/sfall/Modules/Scripting/Arrays.h +++ b/sfall/Modules/Scripting/Arrays.h @@ -2,7 +2,6 @@ #include #include -#include #include #include "ScriptValue.h" @@ -149,9 +148,6 @@ extern DWORD nextArrayID; extern DWORD arraysBehavior; -// temp arrays: set of arrayId -extern std::set tempArrays; - // saved arrays: arrayKey => arrayId extern ArrayKeysMap savedArrays; @@ -172,6 +168,9 @@ DWORD _stdcall TempArray(DWORD len, DWORD flags); // destroys array void _stdcall FreeArray(DWORD id); +// destroy all temp arrays +void DeleteAllTempArrays(); + /* op_get_array_key can be used to iterate over all keys in associative array */ diff --git a/sfall/Modules/Scripting/Handlers/Arrays.cpp b/sfall/Modules/Scripting/Handlers/Arrays.cpp index 288cc114d..32133d58b 100644 --- a/sfall/Modules/Scripting/Handlers/Arrays.cpp +++ b/sfall/Modules/Scripting/Handlers/Arrays.cpp @@ -31,9 +31,7 @@ namespace script void sf_create_array(OpcodeContext& ctx) { auto arrayId = CreateArray(ctx.arg(0).asInt(), ctx.arg(1).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, arrayId) - ); + ctx.setReturn(arrayId, DataType::INT); } void sf_set_array(OpcodeContext& ctx) { @@ -84,9 +82,7 @@ void sf_resize_array(OpcodeContext& ctx) { void sf_temp_array(OpcodeContext& ctx) { auto arrayId = TempArray(ctx.arg(0).asInt(), ctx.arg(1).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, arrayId) - ); + ctx.setReturn(arrayId, DataType::INT); } void sf_fix_array(OpcodeContext& ctx) { @@ -122,23 +118,34 @@ void sf_stack_array(OpcodeContext& ctx) { } // object LISTS - struct sList { fo::GameObject** obj; DWORD len; DWORD pos; - sList(const std::vector* vec) { + sList(const std::vector* vec) : pos(0) { len = vec->size(); obj = new fo::GameObject*[len]; for (size_t i = 0; i < len; i++) { obj[i] = (*vec)[i]; } - pos = 0; } }; +static DWORD listID = 0xCCCCCC; + +struct ListId { + sList* list; + DWORD id; + + ListId(sList* lst) : list(lst) { + id = ++listID; + } +}; +static std::vector mList; + static void FillListVector(DWORD type, std::vector& vec) { + vec.reserve(100); if (type == 6) { fo::ScriptInstance* scriptPtr; fo::GameObject* self_obj; @@ -155,22 +162,20 @@ static void FillListVector(DWORD type, std::vector& vec) { scriptPtr = fo::func::scr_find_next_at(); } } - } else if (type == 4) { + /*} else if (type == 4) { // TODO: verify code correctness - - /*for(int elv=0;elv<2;elv++) { + for(int elv=0;elv<2;elv++) { DWORD* esquares = &fo::var::squares[elv]; for(int tile=0;tile<10000;tile++) { esquares[tile]=0x8f000002; } }*/ - - } else { + } else if (type != 4) { for (int elv = 0; elv < 3; elv++) { for (int tile = 0; tile < 40000; tile++) { fo::GameObject* obj = fo::func::obj_find_first_at_tile(elv, tile); while (obj) { - DWORD otype = (obj->protoId & 0xff000000) >> 24; + DWORD otype = obj->Type(); if (type == 9 || (type == 0 && otype == 1) || (type == 1 && otype == 0) || (type >= 2 && type <= 5 && type == otype)) { vec.push_back(obj); } @@ -181,59 +186,64 @@ static void FillListVector(DWORD type, std::vector& vec) { } } -static void* _stdcall ListBegin(DWORD type) { +static DWORD ListBegin(DWORD type) { std::vector vec = std::vector(); FillListVector(type, vec); sList* list = new sList(&vec); - return list; + mList.emplace_back(list); + return listID; } -static DWORD _stdcall ListAsArray(DWORD type) { +static DWORD ListAsArray(DWORD type) { std::vector vec = std::vector(); FillListVector(type, vec); - DWORD id = TempArray(vec.size(), 4); - for (DWORD i = 0; i < vec.size(); i++) { + size_t sz = vec.size(); + DWORD id = TempArray(sz, 0); + for (size_t i = 0; i < sz; i++) { arrays[id].val[i].set((long)vec[i]); } return id; } -static fo::GameObject* _stdcall ListNext(sList* list) { - if (list->pos == list->len) return 0; +static fo::GameObject* ListNext(sList* list) { + if (!list || list->pos == list->len) return 0; else return list->obj[list->pos++]; } -static void _stdcall ListEnd(sList* list) { - delete[] list->obj; - delete list; +static void ListEnd(DWORD id) { + for (std::vector::const_iterator it = mList.cbegin(), it_end = mList.cend(); it != it_end; ++it) { + if (it->id == id) { + delete[] it->list->obj; + delete it->list; + mList.erase(it); + break; + } + } } void sf_list_begin(OpcodeContext& ctx) { - auto list = ListBegin(ctx.arg(0).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, reinterpret_cast(list)) - ); + ctx.setReturn(ListBegin(ctx.arg(0).rawValue()), DataType::INT); } void sf_list_as_array(OpcodeContext& ctx) { - auto arrayId = ListAsArray(ctx.arg(0).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, arrayId) - ); + auto arrayId = ListAsArray(ctx.arg(0).rawValue()); + ctx.setReturn(arrayId, DataType::INT); } void sf_list_next(OpcodeContext& ctx) { - // TODO: make it safer - auto list = reinterpret_cast(ctx.arg(0).rawValue()); - ctx.setReturn( - ListNext(list) - ); + auto id = ctx.arg(0).rawValue(); + sList* list = nullptr; + for (std::vector::const_iterator it = mList.cbegin(), it_end = mList.cend(); it != it_end; ++it) { + if (it->id == id) { + list = it->list; + break; + } + } + ctx.setReturn(ListNext(list)); } void sf_list_end(OpcodeContext& ctx) { - // TODO: make it safer - auto list = reinterpret_cast(ctx.arg(0).rawValue()); - ListEnd(list); + ListEnd(ctx.arg(0).rawValue()); } } diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 8d7bf069b..1db980f0f 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -352,7 +352,7 @@ void sf_set_iface_tag_text(OpcodeContext& ctx) { } void sf_inventory_redraw(OpcodeContext& ctx) { - int mode = -1; + int mode; DWORD loopFlag = GetLoopFlags(); if (loopFlag & INVENTORY) { mode = 0; @@ -367,13 +367,11 @@ void sf_inventory_redraw(OpcodeContext& ctx) { } if (!ctx.arg(0).asBool()) { - int* stack_offset = (int*)FO_VAR_stack_offset; - stack_offset[fo::var::curr_stack * 4] = 0; + fo::var::stack_offset[fo::var::curr_stack] = 0; fo::func::display_inventory(0, -1, mode); } else if (mode >= 2) { - int* target_stack_offset = (int*)FO_VAR_target_stack_offset; - target_stack_offset[fo::var::target_curr_stack * 4] = 0; - fo::func::display_target_inventory(0, -1, (DWORD*)fo::var::target_pud, mode); + fo::var::target_stack_offset[fo::var::target_curr_stack] = 0; + fo::func::display_target_inventory(0, -1, fo::var::target_pud, mode); fo::func::win_draw(fo::var::i_wid); } } diff --git a/sfall/Modules/Worldmap.cpp b/sfall/Modules/Worldmap.cpp index 5258b7ac5..3cc33fc7e 100644 --- a/sfall/Modules/Worldmap.cpp +++ b/sfall/Modules/Worldmap.cpp @@ -478,7 +478,7 @@ void PathfinderFixInit() { void StartingStatePatches() { int date = GetConfigInt("Misc", "StartYear", -1); - if (date > 0) { + if (date >= 0) { dlog("Applying starting year patch.", DL_INIT); SafeWrite32(0x4A336C, date); dlogr(" Done", DL_INIT); From bc47c87583eb19a829113d7febf400628883f2cc Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 5 May 2019 10:50:14 +0800 Subject: [PATCH 08/32] Moved script shader functions code from Graphics.cpp to a separate module. --- sfall/Modules/Graphics.cpp | 182 +++------------- sfall/Modules/Graphics.h | 18 +- sfall/Modules/Movies.cpp | 3 - sfall/Modules/ScriptShaders.cpp | 197 ++++++++++++++++++ sfall/Modules/ScriptShaders.h | 35 ++++ sfall/Modules/Scripting/Handlers/Graphics.cpp | 1 + sfall/Modules/TalkingHeads.cpp | 2 - sfall/ddraw.vcxproj | 2 + sfall/ddraw.vcxproj.filters | 6 + sfall/main.cpp | 2 + 10 files changed, 273 insertions(+), 175 deletions(-) create mode 100644 sfall/Modules/ScriptShaders.cpp create mode 100644 sfall/Modules/ScriptShaders.h diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index 179a61ffd..1663a90cd 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -39,6 +39,7 @@ #include "Graphics.h" #include "LoadGameHook.h" +#include "ScriptShaders.h" namespace sfall { @@ -47,7 +48,6 @@ typedef HRESULT (_stdcall *DDrawCreateProc)(void*, IDirectDraw**, void*); typedef IDirect3D9* (_stdcall *D3DCreateProc)(UINT version); #define UNUSEDFUNCTION { DEBUGMESS("\n[SFALL] Unused function called: %s", __FUNCTION__); return DDERR_GENERIC; } -#define SAFERELEASE(a) { if (a) { a->Release(); a = 0; } } static DWORD ResWidth; static DWORD ResHeight; @@ -109,36 +109,37 @@ static const char* gpuEffect= // shader for displaying head textures "float4 P1( in float2 Tex : TEXCOORD0 ) : COLOR0 {" - " float backdrop = tex2D(s0, Tex).a;" - " float3 result;" - " if(abs(backdrop - 1.0) < 0.001) {" // (48.0 / 255.0) // 48 - key index color - " result = tex2D(s2, saturate((Tex - corner) / size));" - " } else {" - " result = tex1D(s1, backdrop);" - " result = float3(result.b, result.g, result.r);" - " }" + "float backdrop = tex2D(s0, Tex).a;" + "float3 result;" + "if (abs(backdrop - 1.0) < 0.001) {" // (48.0 / 255.0) // 48 - key index color + "result = tex2D(s2, saturate((Tex - corner) / size));" + "} else {" + "result = tex1D(s1, backdrop);" + "result = float3(result.b, result.g, result.r);" + "}" // blend highlights "if (showhl) {" "float4 h = tex2D(s3, saturate((Tex - cornerhl) / sizehl));" "result = saturate(result + h.rgb);" // saturate(result * (1 - h.a) * h.rgb * h.a)" "}" - " return float4(result.r, result.g, result.b, 1);" + "return float4(result.r, result.g, result.b, 1);" "}" "technique T1" "{" - " pass p1 { PixelShader = compile ps_2_0 P1(); }" + "pass p1 { PixelShader = compile ps_2_0 P1(); }" "}" "float4 P0( in float2 Tex : TEXCOORD0 ) : COLOR0 {" - " float3 result = tex1D(s1, tex2D(s0, Tex).a);" - " return float4(result.b, result.g, result.r, 1);" + "float3 result = tex1D(s1, tex2D(s0, Tex).a);" + "return float4(result.b, result.g, result.r, 1);" "}" "technique T0" "{" - " pass p0 { PixelShader = compile ps_2_0 P0(); }" - "}"; + "pass p0 { PixelShader = compile ps_2_0 P0(); }" + "}" +; static D3DXHANDLE gpuBltBuf; static D3DXHANDLE gpuBltPalette; @@ -152,25 +153,6 @@ static D3DXHANDLE gpuBltShowHighlight; static float rcpres[2]; -struct sShader { - ID3DXEffect* Effect; - bool Active; - D3DXHANDLE ehTicks; - DWORD mode; - DWORD mode2; - - sShader() { - Effect=0; - Active=false; - ehTicks=0; - mode=0; - mode2=0; - } -}; - -static std::vector shaders; -static std::vector shaderTextures; - #define MYVERTEXFORMAT D3DFVF_XYZRHW|D3DFVF_TEX1 struct MyVertex { float x,y,z,w,u,v; @@ -206,94 +188,8 @@ static void rcpresInit() { rcpres[1] = 1.0f / (float)Graphics::GetGameHeightRes(); } -void _stdcall SetShaderMode(DWORD d, DWORD mode) { - if (d >= shaders.size() || !shaders[d].Effect) return; - if (mode & 0x80000000) { - shaders[d].mode2 = mode ^ 0x80000000; - } else { - shaders[d].mode = mode; - } -} - -int _stdcall LoadShader(const char* path) { - if (!Graphics::mode || strstr(path, "..") || strstr(path, ":")) return -1; - char buf[MAX_PATH]; - sprintf(buf, "%s\\shaders\\%s", fo::var::patches, path); - for (DWORD d = 0; d < shaders.size(); d++) { - if (!shaders[d].Effect) { - if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shaders[d].Effect, 0))) return -1; - else return d; - } - } - sShader shader = sShader(); - if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shader.Effect, 0))) return -1; - - shader.Effect->SetFloatArray("rcpres", rcpres, 2); - - for (int i = 1; i < 128; i++) { - const char* name; - IDirect3DTexture9* tex; - - sprintf_s(buf, "texname%d", i); - if (FAILED(shader.Effect->GetString(buf, &name))) break; - sprintf_s(buf, "%s\\art\\stex\\%s", fo::var::patches, name); - if (FAILED(D3DXCreateTextureFromFileA(d3d9Device, buf, &tex))) continue; - sprintf_s(buf, "tex%d", i); - shader.Effect->SetTexture(buf, tex); - shaderTextures.push_back(tex); - } - - shader.ehTicks = shader.Effect->GetParameterByName(0, "tickcount"); - shaders.push_back(shader); - return shaders.size() - 1; -} - -void _stdcall ActivateShader(DWORD d) { - if (d < shaders.size() && shaders[d].Effect) shaders[d].Active = true; -} - -void _stdcall DeactivateShader(DWORD d) { - if (d < shaders.size()) shaders[d].Active = false; -} - -int _stdcall GetShaderTexture(DWORD d, DWORD id) { - if (id < 1 || id > 128 || d >= shaders.size() || !shaders[d].Effect) return -1; - IDirect3DBaseTexture9* tex = 0; - char buf[8] = "tex"; - _itoa_s(id, &buf[3], 4, 10); - if (FAILED(shaders[d].Effect->GetTexture(buf, &tex)) || !tex) return -1; - tex->Release(); - for (DWORD i = 0; i < shaderTextures.size(); i++) { - if (shaderTextures[i] == tex) return i; - } - return -1; -} - -void _stdcall FreeShader(DWORD d) { - if (d < shaders.size()) { - SAFERELEASE(shaders[d].Effect); - shaders[d].Active = false; - } -} - -void _stdcall SetShaderInt(DWORD d, const char* param, int value) { - if (d >= shaders.size() || !shaders[d].Effect) return; - shaders[d].Effect->SetInt(param, value); -} - -void _stdcall SetShaderFloat(DWORD d, const char* param, float value) { - if (d >= shaders.size() || !shaders[d].Effect) return; - shaders[d].Effect->SetFloat(param, value); -} - -void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4) { - if (d >= shaders.size() || !shaders[d].Effect) return; - shaders[d].Effect->SetFloatArray(param, &f1, 4); -} - -void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value) { - if (d >= shaders.size() || !shaders[d].Effect || value >= shaderTextures.size()) return; - shaders[d].Effect->SetTexture(param, shaderTextures[value]); +const float* Graphics::rcpresGet() { + return rcpres; } static void ResetDevice(bool CreateNew) { @@ -344,9 +240,7 @@ static void ResetDevice(bool CreateNew) { } else { d3d9Device->Reset(¶ms); if (gpuBltEffect) gpuBltEffect->OnResetDevice(); - for (DWORD d = 0; d < shaders.size(); d++) { - if (shaders[d].Effect) shaders[d].Effect->OnResetDevice(); - } + ScriptShaders::OnResetDevice(); } ShaderVertices[1].y = ResHeight - 0.5f; @@ -469,9 +363,7 @@ static void Present() { SAFERELEASE(movieBuffer); SAFERELEASE(gpuPalette); if (gpuBltEffect) gpuBltEffect->OnLostDevice(); - for (DWORD d = 0; d < shaders.size(); d++) { - if (shaders[d].Effect) shaders[d].Effect->OnLostDevice(); - } + ScriptShaders::OnLostDevice(); DeviceLost = true; } } @@ -484,7 +376,7 @@ void RefreshGraphics() { d3d9Device->SetStreamSource(0, vBuffer, 0, sizeof(MyVertex)); d3d9Device->SetRenderTarget(0, sSurf1); - if (Graphics::GPUBlt && shaders.size()) { + if (Graphics::GPUBlt && ScriptShaders::Count()) { UINT unused; gpuBltEffect->Begin(&unused, 0); gpuBltEffect->BeginPass(0); @@ -496,28 +388,11 @@ void RefreshGraphics() { } else { d3d9Device->SetTexture(0, Tex); } - for (int d = shaders.size() - 1; d >= 0; d--) { - if (!shaders[d].Effect || !shaders[d].Active) continue; - if (shaders[d].mode2 && !(shaders[d].mode2 & GetLoopFlags())) continue; - if (shaders[d].mode&GetLoopFlags()) continue; - - if (shaders[d].ehTicks) shaders[d].Effect->SetInt(shaders[d].ehTicks, GetTickCount()); - UINT passes; - shaders[d].Effect->Begin(&passes, 0); - for (DWORD pass = 0; pass < passes; pass++) { - shaders[d].Effect->BeginPass(pass); - d3d9Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); - shaders[d].Effect->EndPass(); - d3d9Device->StretchRect(sSurf1, 0, sSurf2, 0, D3DTEXF_NONE); - d3d9Device->SetTexture(0, sTex2); - } - shaders[d].Effect->End(); - d3d9Device->SetTexture(0, sTex2); - } + ScriptShaders::Refresh(sSurf1, sSurf2, sTex2); d3d9Device->SetStreamSource(0, vBuffer2, 0, sizeof(MyVertex)); d3d9Device->SetRenderTarget(0, backbuffer); - if (Graphics::GPUBlt && !shaders.size()) { + if (Graphics::GPUBlt && !ScriptShaders::Count()) { UINT unused; gpuBltEffect->Begin(&unused, 0); gpuBltEffect->BeginPass(0); @@ -629,11 +504,6 @@ void Graphics::SetDefaultTechnique() { gpuBltEffect->SetTechnique("T0"); } -void GraphicsResetOnGameLoad() { - for (DWORD d = 0; d < shaders.size(); d++) SAFERELEASE(shaders[d].Effect); - shaders.clear(); -} - class FakePalette2 : IDirectDrawPalette { private: ULONG Refs; @@ -964,10 +834,7 @@ class FakeDirectDraw2 : IDirectDraw ULONG _stdcall Release() { if (!--Refs) { - for (DWORD d = 0; d < shaders.size(); d++) SAFERELEASE(shaders[d].Effect); - for (DWORD d = 0; d < shaderTextures.size(); d++) shaderTextures[d]->Release(); - shaders.clear(); - shaderTextures.clear(); + ScriptShaders::Release(); SAFERELEASE(backbuffer); SAFERELEASE(sSurf1); SAFERELEASE(sSurf2); @@ -1140,7 +1007,6 @@ void Graphics::init() { } if (Graphics::mode) { - LoadGameHook::OnGameReset() += GraphicsResetOnGameLoad; LoadGameHook::OnAfterGameInit() += rcpresInit; } } diff --git a/sfall/Modules/Graphics.h b/sfall/Modules/Graphics.h index cf0b8226e..19cbe5ae3 100644 --- a/sfall/Modules/Graphics.h +++ b/sfall/Modules/Graphics.h @@ -27,6 +27,8 @@ namespace sfall { +#define SAFERELEASE(a) { if (a) { a->Release(); a = 0; } } + class Graphics : public Module { public: const char* name() { return "Graphics"; } @@ -39,25 +41,17 @@ class Graphics : public Module { static long GetGameWidthRes(); static long GetGameHeightRes(); + static const float* rcpresGet(); + static void SetHighlightTexture(IDirect3DTexture9* htex); static void SetHeadTex(IDirect3DTexture9* tex, int width, int height, int xoff, int yoff, int showHighlight); static void SetHeadTechnique(); static void SetDefaultTechnique(); }; -int _stdcall GetShaderVersion(); -int _stdcall LoadShader(const char*); -void _stdcall ActivateShader(DWORD); -void _stdcall DeactivateShader(DWORD); -void _stdcall FreeShader(DWORD); -void _stdcall SetShaderMode(DWORD d, DWORD mode); +extern IDirect3DDevice9* d3d9Device; -void _stdcall SetShaderInt(DWORD d, const char* param, int value); -void _stdcall SetShaderFloat(DWORD d, const char* param, float value); -void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4); - -int _stdcall GetShaderTexture(DWORD d, DWORD id); -void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value); +int _stdcall GetShaderVersion(); void RefreshGraphics(); void GetFalloutWindowInfo(DWORD* width, DWORD* height, HWND* window); diff --git a/sfall/Modules/Movies.cpp b/sfall/Modules/Movies.cpp index 13bb3a40c..149da74d7 100644 --- a/sfall/Modules/Movies.cpp +++ b/sfall/Modules/Movies.cpp @@ -38,9 +38,6 @@ static DWORD MoviePtrs[MaxMovies]; char MoviePaths[MaxMovies * 65]; extern IDirect3D9* d3d9; -extern IDirect3DDevice9* d3d9Device; - -#define SAFERELEASE(a) { if (a) { a->Release(); a = 0; } } class CAllocator : public IVMRSurfaceAllocator9, IVMRImagePresenter9 { private: diff --git a/sfall/Modules/ScriptShaders.cpp b/sfall/Modules/ScriptShaders.cpp new file mode 100644 index 000000000..1aa185a05 --- /dev/null +++ b/sfall/Modules/ScriptShaders.cpp @@ -0,0 +1,197 @@ +/* + * 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 "Graphics.h" +#include "LoadGameHook.h" + +#include "ScriptShaders.h" + +namespace sfall +{ + +static size_t shaderSize; + +struct sShader { + ID3DXEffect* Effect; + bool Active; + D3DXHANDLE ehTicks; + DWORD mode; + DWORD mode2; + + sShader() { + Effect = 0; + Active = false; + ehTicks = 0; + mode = 0; + mode2 = 0; + } +}; + +static std::vector shaders; +static std::vector shaderTextures; + +size_t ScriptShaders::Count() { + return shaderSize; +} + +void _stdcall SetShaderMode(DWORD d, DWORD mode) { + if (d >= shaders.size() || !shaders[d].Effect) return; + if (mode & 0x80000000) { + shaders[d].mode2 = mode ^ 0x80000000; + } else { + shaders[d].mode = mode; + } +} + +int _stdcall LoadShader(const char* path) { + if (!Graphics::mode || strstr(path, "..") || strstr(path, ":")) return -1; + char buf[MAX_PATH]; + sprintf(buf, "%s\\shaders\\%s", fo::var::patches, path); + for (DWORD d = 0; d < shaders.size(); d++) { + if (!shaders[d].Effect) { + if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shaders[d].Effect, 0))) return -1; + else return d; + } + } + sShader shader = sShader(); + if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shader.Effect, 0))) return -1; + + shader.Effect->SetFloatArray("rcpres", Graphics::rcpresGet(), 2); + + for (int i = 1; i < 128; i++) { + const char* name; + IDirect3DTexture9* tex; + + sprintf_s(buf, "texname%d", i); + if (FAILED(shader.Effect->GetString(buf, &name))) break; + sprintf_s(buf, "%s\\art\\stex\\%s", fo::var::patches, name); + if (FAILED(D3DXCreateTextureFromFileA(d3d9Device, buf, &tex))) continue; + sprintf_s(buf, "tex%d", i); + shader.Effect->SetTexture(buf, tex); + shaderTextures.push_back(tex); + } + + shader.ehTicks = shader.Effect->GetParameterByName(0, "tickcount"); + shaders.push_back(shader); + shaderSize = shaders.size(); + return shaderSize - 1; +} + +void _stdcall ActivateShader(DWORD d) { + if (d < shaders.size() && shaders[d].Effect) shaders[d].Active = true; +} + +void _stdcall DeactivateShader(DWORD d) { + if (d < shaders.size()) shaders[d].Active = false; +} + +int _stdcall GetShaderTexture(DWORD d, DWORD id) { + if (id < 1 || id > 128 || d >= shaders.size() || !shaders[d].Effect) return -1; + IDirect3DBaseTexture9* tex = 0; + char buf[8] = "tex"; + _itoa_s(id, &buf[3], 4, 10); + if (FAILED(shaders[d].Effect->GetTexture(buf, &tex)) || !tex) return -1; + tex->Release(); + for (DWORD i = 0; i < shaderTextures.size(); i++) { + if (shaderTextures[i] == tex) return i; + } + return -1; +} + +void _stdcall FreeShader(DWORD d) { + if (d < shaders.size()) { + SAFERELEASE(shaders[d].Effect); + shaders[d].Active = false; + } +} + +void _stdcall SetShaderInt(DWORD d, const char* param, int value) { + if (d >= shaders.size() || !shaders[d].Effect) return; + shaders[d].Effect->SetInt(param, value); +} + +void _stdcall SetShaderFloat(DWORD d, const char* param, float value) { + if (d >= shaders.size() || !shaders[d].Effect) return; + shaders[d].Effect->SetFloat(param, value); +} + +void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4) { + if (d >= shaders.size() || !shaders[d].Effect) return; + shaders[d].Effect->SetFloatArray(param, &f1, 4); +} + +void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value) { + if (d >= shaders.size() || !shaders[d].Effect || value >= shaderTextures.size()) return; + shaders[d].Effect->SetTexture(param, shaderTextures[value]); +} + +void ResetShaders() { + for (DWORD d = 0; d < shaders.size(); d++) SAFERELEASE(shaders[d].Effect); + shaders.clear(); + shaderSize = 0; +} + +void ScriptShaders::Refresh(IDirect3DSurface9* sSurf1, IDirect3DSurface9* sSurf2, IDirect3DTexture9* sTex2) { + for (int d = shaders.size() - 1; d >= 0; d--) { + if (!shaders[d].Effect || !shaders[d].Active) continue; + if (shaders[d].mode2 && !(shaders[d].mode2 & GetLoopFlags())) continue; + if (shaders[d].mode&GetLoopFlags()) continue; + + if (shaders[d].ehTicks) shaders[d].Effect->SetInt(shaders[d].ehTicks, GetTickCount()); + UINT passes; + shaders[d].Effect->Begin(&passes, 0); + for (DWORD pass = 0; pass < passes; pass++) { + shaders[d].Effect->BeginPass(pass); + d3d9Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + shaders[d].Effect->EndPass(); + d3d9Device->StretchRect(sSurf1, 0, sSurf2, 0, D3DTEXF_NONE); + d3d9Device->SetTexture(0, sTex2); + } + shaders[d].Effect->End(); + d3d9Device->SetTexture(0, sTex2); + } +} + +void ScriptShaders::OnResetDevice() { + for (DWORD d = 0; d < shaders.size(); d++) { + if (shaders[d].Effect) shaders[d].Effect->OnResetDevice(); + } +} + +void ScriptShaders::OnLostDevice() { + for (DWORD d = 0; d < shaders.size(); d++) { + if (shaders[d].Effect) shaders[d].Effect->OnLostDevice(); + } +} + +void ScriptShaders::Release() { + ResetShaders(); + for (DWORD d = 0; d < shaderTextures.size(); d++) shaderTextures[d]->Release(); + shaderTextures.clear(); +} + +void ScriptShaders::init() { + if (Graphics::mode) { + LoadGameHook::OnGameReset() += ResetShaders; + } +} + +} diff --git a/sfall/Modules/ScriptShaders.h b/sfall/Modules/ScriptShaders.h new file mode 100644 index 000000000..3cf30a0a5 --- /dev/null +++ b/sfall/Modules/ScriptShaders.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Module.h" + +namespace sfall +{ + +class ScriptShaders : public Module { +public: + const char* name() { return "ScriptShaders"; } + void init(); + //void exit() override; + + static size_t Count(); + + static void Refresh(IDirect3DSurface9* sSurf1, IDirect3DSurface9* sSurf2, IDirect3DTexture9* sTex2); + static void OnResetDevice(); + static void OnLostDevice(); + static void Release(); +}; + +int _stdcall LoadShader(const char*); +void _stdcall ActivateShader(DWORD); +void _stdcall DeactivateShader(DWORD); +void _stdcall FreeShader(DWORD); +void _stdcall SetShaderMode(DWORD d, DWORD mode); + +void _stdcall SetShaderInt(DWORD d, const char* param, int value); +void _stdcall SetShaderFloat(DWORD d, const char* param, float value); +void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4); + +int _stdcall GetShaderTexture(DWORD d, DWORD id); +void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value); + +} diff --git a/sfall/Modules/Scripting/Handlers/Graphics.cpp b/sfall/Modules/Scripting/Handlers/Graphics.cpp index 53a8c64bb..39d7d2e50 100644 --- a/sfall/Modules/Scripting/Handlers/Graphics.cpp +++ b/sfall/Modules/Scripting/Handlers/Graphics.cpp @@ -21,6 +21,7 @@ #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\Graphics.h" #include "..\..\ScriptExtender.h" +#include "..\..\ScriptShaders.h" #include "Graphics.h" diff --git a/sfall/Modules/TalkingHeads.cpp b/sfall/Modules/TalkingHeads.cpp index df83cd247..be7b31f04 100644 --- a/sfall/Modules/TalkingHeads.cpp +++ b/sfall/Modules/TalkingHeads.cpp @@ -29,8 +29,6 @@ namespace sfall { -extern IDirect3DDevice9* d3d9Device; - #pragma pack(push, 1) struct Frm { DWORD version; diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 34b97c585..8be8f4786 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -341,6 +341,7 @@ + @@ -462,6 +463,7 @@ + Create Create diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index cf5b80dd5..03d437a68 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -278,6 +278,9 @@ Modules + + Modules + @@ -516,6 +519,9 @@ Modules + + Modules + diff --git a/sfall/main.cpp b/sfall/main.cpp index 50ad43d9d..22b384ce7 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -63,6 +63,7 @@ #include "Modules\QuestList.h" #include "Modules\Reputations.h" #include "Modules\ScriptExtender.h" +#include "Modules\ScriptShaders.h" #include "Modules\Skills.h" #include "Modules\Sound.h" #include "Modules\SpeedPatch.h" @@ -178,6 +179,7 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); // all built-in events(delegates) of modules should be executed before running the script handlers manager.add(); From 943cd5d2dc641fcc09dd9493fe074bf3d98743f2 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 7 May 2019 13:49:26 +0800 Subject: [PATCH 09/32] Code edits on ScriptShaders & OpcodeContext.cpp. Minor edits on some other code. Added the missing Test_ForceFloats option to ddraw.ini. --- artifacts/ddraw.ini | 4 ++ sfall/FalloutEngine/Structs.h | 33 ++++++------- sfall/Modules/BarBoxes.cpp | 3 +- sfall/Modules/Graphics.cpp | 5 +- sfall/Modules/Graphics.h | 1 + sfall/Modules/Movies.cpp | 4 +- sfall/Modules/ScriptShaders.cpp | 56 ++++++++++------------- sfall/Modules/ScriptShaders.h | 1 + sfall/Modules/Scripting/OpcodeContext.cpp | 8 ++-- 9 files changed, 58 insertions(+), 57 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 533826735..fa8eb786e 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -25,6 +25,10 @@ NumSoundBuffers=0 ;Set to 1 to allow attaching sound files to combat float messages AllowSoundForFloats=1 +;Set to 1 to force critters to display combat float messages +;Requires sfall debugging mode and AllowSoundForFloats to be enabled +Test_ForceFloats=0 + ;Set to 1 to automatically search for alternative formats (mp3/wma/wav) when Fallout tries to play an acm ;Set to 2 to play alternative music files even if original acm files are not present in the music folder ;This does not effect the play_sfall_sound and stop_sfall_sound script functions diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index 5b67d7e06..a6af6648f 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -136,7 +136,7 @@ struct GameObject { return (protoId >> 24); } inline char TypeFid() { - return (artFid >> 24); + return ((artFid >> 24) & 0xF0); } }; @@ -271,18 +271,19 @@ struct ElevatorFrms { #pragma pack(1) struct FrmFile { - long id; //0x00 - long unused; //0x04 - short frames; //0x08 + long id; //0x00 + short fps; //0x04 + short actionFrame; //0x06 + short frames; //0x08 short xshift[6]; //0x0a short yshift[6]; //0x16 - long framestart[6];//0x22 - long size; //0x3a + long framestart[6]; //0x22 + long size; //0x3a short width; //0x3e - short height; //0x40 - long frmSize; //0x42 - short xoffset; //0x46 - short yoffset; //0x48 + short height; //0x40 + long frmSize; //0x42 + short xoffset; //0x46 + short yoffset; //0x48 BYTE pixels[80 * 36]; //0x4a }; @@ -301,13 +302,13 @@ typedef class FrmSubframeData { #pragma pack(2) typedef class FrmFrameData { public: - DWORD version; //version num - WORD fps; //frames per sec + DWORD version; // version num + WORD fps; // frames per sec WORD actionFrame; - WORD numFrames; //number of frames per direction - WORD xCentreShift[6]; //offset from frm centre +=right -=left - WORD yCentreShift[6]; //offset from frm centre +=down -=up - DWORD oriOffset[6]; //frame area offset for diff orientations + WORD numFrames; // number of frames per direction + WORD xCentreShift[6]; // offset from frm centre +=right -=left + WORD yCentreShift[6]; // offset from frm centre +=down -=up + DWORD oriOffset[6]; // frame area offset for diff orientations DWORD frameAreaSize; } FrmFrameData; diff --git a/sfall/Modules/BarBoxes.cpp b/sfall/Modules/BarBoxes.cpp index 64a340212..4a3385f65 100644 --- a/sfall/Modules/BarBoxes.cpp +++ b/sfall/Modules/BarBoxes.cpp @@ -18,6 +18,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "Graphics.h" #include "LoadGameHook.h" #include "BarBoxes.h" @@ -220,7 +221,7 @@ static void SetEngine(int count) { } static long SetMaxSlots() { - long scrWidth = fo::var::scr_size.offx - (fo::var::scr_size.x + 1); + long scrWidth = Graphics::GetGameWidthRes(); int slots = scrWidth / 127; if (++slots > 16) { diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index 1663a90cd..4792c753a 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -32,15 +32,14 @@ #include #include "..\main.h" - #include "..\FalloutEngine\Fallout2.h" #include "..\InputFuncs.h" #include "..\Version.h" - -#include "Graphics.h" #include "LoadGameHook.h" #include "ScriptShaders.h" +#include "Graphics.h" + namespace sfall { diff --git a/sfall/Modules/Graphics.h b/sfall/Modules/Graphics.h index 19cbe5ae3..421391519 100644 --- a/sfall/Modules/Graphics.h +++ b/sfall/Modules/Graphics.h @@ -49,6 +49,7 @@ class Graphics : public Module { static void SetDefaultTechnique(); }; +extern IDirect3D9* d3d9; extern IDirect3DDevice9* d3d9Device; int _stdcall GetShaderVersion(); diff --git a/sfall/Modules/Movies.cpp b/sfall/Modules/Movies.cpp index 149da74d7..d1e9ad7b1 100644 --- a/sfall/Modules/Movies.cpp +++ b/sfall/Modules/Movies.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include // should be above DX SDK includes to avoid warning 4995 +//#include // should be above DX SDK includes to avoid warning 4995 #include #include @@ -37,8 +37,6 @@ namespace sfall static DWORD MoviePtrs[MaxMovies]; char MoviePaths[MaxMovies * 65]; -extern IDirect3D9* d3d9; - class CAllocator : public IVMRSurfaceAllocator9, IVMRImagePresenter9 { private: IDirect3DSurface9* surface; diff --git a/sfall/Modules/ScriptShaders.cpp b/sfall/Modules/ScriptShaders.cpp index 1aa185a05..37f58554c 100644 --- a/sfall/Modules/ScriptShaders.cpp +++ b/sfall/Modules/ScriptShaders.cpp @@ -27,33 +27,27 @@ namespace sfall { -static size_t shaderSize; +static size_t shadersSize; struct sShader { ID3DXEffect* Effect; - bool Active; D3DXHANDLE ehTicks; DWORD mode; DWORD mode2; + bool Active; - sShader() { - Effect = 0; - Active = false; - ehTicks = 0; - mode = 0; - mode2 = 0; - } + sShader() : Effect(0), ehTicks(0), mode(0), mode2(0), Active(false) {} }; static std::vector shaders; static std::vector shaderTextures; size_t ScriptShaders::Count() { - return shaderSize; + return shadersSize; } void _stdcall SetShaderMode(DWORD d, DWORD mode) { - if (d >= shaders.size() || !shaders[d].Effect) return; + if (d >= shadersSize || !shaders[d].Effect) return; if (mode & 0x80000000) { shaders[d].mode2 = mode ^ 0x80000000; } else { @@ -64,8 +58,8 @@ void _stdcall SetShaderMode(DWORD d, DWORD mode) { int _stdcall LoadShader(const char* path) { if (!Graphics::mode || strstr(path, "..") || strstr(path, ":")) return -1; char buf[MAX_PATH]; - sprintf(buf, "%s\\shaders\\%s", fo::var::patches, path); - for (DWORD d = 0; d < shaders.size(); d++) { + sprintf_s(buf, "%s\\shaders\\%s", fo::var::patches, path); + for (DWORD d = 0; d < shadersSize; d++) { if (!shaders[d].Effect) { if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shaders[d].Effect, 0))) return -1; else return d; @@ -80,31 +74,31 @@ int _stdcall LoadShader(const char* path) { const char* name; IDirect3DTexture9* tex; - sprintf_s(buf, "texname%d", i); + sprintf(buf, "texname%d", i); if (FAILED(shader.Effect->GetString(buf, &name))) break; sprintf_s(buf, "%s\\art\\stex\\%s", fo::var::patches, name); if (FAILED(D3DXCreateTextureFromFileA(d3d9Device, buf, &tex))) continue; - sprintf_s(buf, "tex%d", i); + sprintf(buf, "tex%d", i); shader.Effect->SetTexture(buf, tex); shaderTextures.push_back(tex); } shader.ehTicks = shader.Effect->GetParameterByName(0, "tickcount"); shaders.push_back(shader); - shaderSize = shaders.size(); - return shaderSize - 1; + shadersSize = shaders.size(); + return shadersSize - 1; } void _stdcall ActivateShader(DWORD d) { - if (d < shaders.size() && shaders[d].Effect) shaders[d].Active = true; + if (d < shadersSize && shaders[d].Effect) shaders[d].Active = true; } void _stdcall DeactivateShader(DWORD d) { - if (d < shaders.size()) shaders[d].Active = false; + if (d < shadersSize) shaders[d].Active = false; } int _stdcall GetShaderTexture(DWORD d, DWORD id) { - if (id < 1 || id > 128 || d >= shaders.size() || !shaders[d].Effect) return -1; + if (id < 1 || id > 128 || d >= shadersSize || !shaders[d].Effect) return -1; IDirect3DBaseTexture9* tex = 0; char buf[8] = "tex"; _itoa_s(id, &buf[3], 4, 10); @@ -117,43 +111,43 @@ int _stdcall GetShaderTexture(DWORD d, DWORD id) { } void _stdcall FreeShader(DWORD d) { - if (d < shaders.size()) { + if (d < shadersSize) { SAFERELEASE(shaders[d].Effect); shaders[d].Active = false; } } void _stdcall SetShaderInt(DWORD d, const char* param, int value) { - if (d >= shaders.size() || !shaders[d].Effect) return; + if (d >= shadersSize || !shaders[d].Effect) return; shaders[d].Effect->SetInt(param, value); } void _stdcall SetShaderFloat(DWORD d, const char* param, float value) { - if (d >= shaders.size() || !shaders[d].Effect) return; + if (d >= shadersSize || !shaders[d].Effect) return; shaders[d].Effect->SetFloat(param, value); } void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4) { - if (d >= shaders.size() || !shaders[d].Effect) return; + if (d >= shadersSize || !shaders[d].Effect) return; shaders[d].Effect->SetFloatArray(param, &f1, 4); } void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value) { - if (d >= shaders.size() || !shaders[d].Effect || value >= shaderTextures.size()) return; + if (d >= shadersSize || !shaders[d].Effect || value >= shaderTextures.size()) return; shaders[d].Effect->SetTexture(param, shaderTextures[value]); } void ResetShaders() { - for (DWORD d = 0; d < shaders.size(); d++) SAFERELEASE(shaders[d].Effect); + for (DWORD d = 0; d < shadersSize; d++) SAFERELEASE(shaders[d].Effect); shaders.clear(); - shaderSize = 0; + shadersSize = 0; } void ScriptShaders::Refresh(IDirect3DSurface9* sSurf1, IDirect3DSurface9* sSurf2, IDirect3DTexture9* sTex2) { - for (int d = shaders.size() - 1; d >= 0; d--) { + for (int d = shadersSize - 1; d >= 0; d--) { if (!shaders[d].Effect || !shaders[d].Active) continue; if (shaders[d].mode2 && !(shaders[d].mode2 & GetLoopFlags())) continue; - if (shaders[d].mode&GetLoopFlags()) continue; + if (shaders[d].mode & GetLoopFlags()) continue; if (shaders[d].ehTicks) shaders[d].Effect->SetInt(shaders[d].ehTicks, GetTickCount()); UINT passes; @@ -171,13 +165,13 @@ void ScriptShaders::Refresh(IDirect3DSurface9* sSurf1, IDirect3DSurface9* sSurf2 } void ScriptShaders::OnResetDevice() { - for (DWORD d = 0; d < shaders.size(); d++) { + for (DWORD d = 0; d < shadersSize; d++) { if (shaders[d].Effect) shaders[d].Effect->OnResetDevice(); } } void ScriptShaders::OnLostDevice() { - for (DWORD d = 0; d < shaders.size(); d++) { + for (DWORD d = 0; d < shadersSize; d++) { if (shaders[d].Effect) shaders[d].Effect->OnLostDevice(); } } diff --git a/sfall/Modules/ScriptShaders.h b/sfall/Modules/ScriptShaders.h index 3cf30a0a5..a76d0fea6 100644 --- a/sfall/Modules/ScriptShaders.h +++ b/sfall/Modules/ScriptShaders.h @@ -1,6 +1,7 @@ #pragma once #include "Module.h" +#include "Graphics.h" namespace sfall { diff --git a/sfall/Modules/Scripting/OpcodeContext.cpp b/sfall/Modules/Scripting/OpcodeContext.cpp index d0844dc76..4ee830b06 100644 --- a/sfall/Modules/Scripting/OpcodeContext.cpp +++ b/sfall/Modules/Scripting/OpcodeContext.cpp @@ -66,7 +66,8 @@ void OpcodeContext::setArgShift (int shift) { } const ScriptValue& OpcodeContext::arg(int index) const { - return _args.at(index + _argShift); + assert((index + _argShift) < OP_MAX_ARGUMENTS); + return _args[index + _argShift]; } const ScriptValue& OpcodeContext::returnValue() const { @@ -109,6 +110,7 @@ bool OpcodeContext::validateArguments(const OpcodeArgumentType argTypes[], const // exception is when type set to if (actualType == DataType::NONE) break; auto argType = argTypes[i]; + if (argType == ARG_ANY) continue; if ((argType == ARG_INT || argType == ARG_OBJECT) && !(actualType == DataType::INT)) { printOpcodeError("%s() - argument #%d is not an integer.", opcodeName, ++i); return false; @@ -208,9 +210,9 @@ void OpcodeContext::_popArguments() { // retrieve string argument if (type == DataType::STR) { - _args.at(i) = fo::func::interpretGetString(_program, rawValueType, rawValue); + _args[i] = fo::func::interpretGetString(_program, rawValueType, rawValue); } else { - _args.at(i) = ScriptValue(type, rawValue); + _args[i] = ScriptValue(type, rawValue); } } } From 88cf6dcd32ead29d232ecfa845b06709b01d5b46 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 7 May 2019 23:04:00 +0800 Subject: [PATCH 10/32] Moved Test_ForceFloats to [Debugging] section. * Since it's never mentioned anywhere, should be OK moving it before the formal release. --- artifacts/ddraw.ini | 8 ++++---- sfall/Modules/Sound.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index fa8eb786e..9c76261bb 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -25,10 +25,6 @@ NumSoundBuffers=0 ;Set to 1 to allow attaching sound files to combat float messages AllowSoundForFloats=1 -;Set to 1 to force critters to display combat float messages -;Requires sfall debugging mode and AllowSoundForFloats to be enabled -Test_ForceFloats=0 - ;Set to 1 to automatically search for alternative formats (mp3/wma/wav) when Fallout tries to play an acm ;Set to 2 to play alternative music files even if original acm files are not present in the music folder ;This does not effect the play_sfall_sound and stop_sfall_sound script functions @@ -753,6 +749,10 @@ InjectAllGameHooks=0 ;Set to 1 to hide error messages in debug output when a null value is passed to the function as an object HideObjIsNullMsg=0 +;Set to 1 to force critters to display combat float messages +;Requires AllowSoundForFloats to be enabled +Test_ForceFloats=0 + ;These options control what output is saved in the debug log (sfall-log.txt) ;Prints messages duing sfall initialization diff --git a/sfall/Modules/Sound.cpp b/sfall/Modules/Sound.cpp index 5ec64e896..fe7f57a78 100644 --- a/sfall/Modules/Sound.cpp +++ b/sfall/Modules/Sound.cpp @@ -64,7 +64,7 @@ void Sound::init() { HookCall(0x42B849, ai_print_msg_hook); //Yes, I did leave this in on purpose. Will be of use to anyone trying to add in the sound effects - if (isDebug && GetConfigInt("Sound", "Test_ForceFloats", 0)) { + if (isDebug && GetPrivateProfileIntA("Debugging", "Test_ForceFloats", 0, ::sfall::ddrawIni)) { SafeWrite8(0x42B6F5, 0xEB); // bypass chance } } From 4bc1a422c31c8d223b505a0e01c1f0df96a9999d Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 9 May 2019 10:01:03 +0800 Subject: [PATCH 11/32] Changed the names of FRM structures. Added some functions for later use. Fixed get FID type. --- sfall/FalloutEngine/Functions.cpp | 45 ++++++++++++++++++++---- sfall/FalloutEngine/Functions.h | 21 +++++++---- sfall/FalloutEngine/Functions_def.h | 15 +++++--- sfall/FalloutEngine/Structs.h | 26 +++++++------- sfall/Modules/HeroAppearance.cpp | 2 +- sfall/Modules/Scripting/Handlers/Anims.h | 1 + sfall/Modules/Scripting/Opcodes.cpp | 4 +-- sfall/Modules/TalkingHeads.cpp | 4 +-- sfall/Modules/Tiles.cpp | 4 +-- 9 files changed, 83 insertions(+), 39 deletions(-) diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index d891082bd..8e3e473cd 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -165,7 +165,7 @@ long __stdcall db_init(const char* path_dat, const char* path_patches) { } // Check fallout paths for file -long __stdcall CheckFile(char *fileName, DWORD *sizeOut) { +long __stdcall db_file_exist(const char *fileName, DWORD *sizeOut) { WRAP_WATCOM_CALL2(db_dir_entry_, fileName, sizeOut) } @@ -200,11 +200,6 @@ void __stdcall gsound_play_sfx_file(const char* name) { WRAP_WATCOM_CALL1(gsound_play_sfx_file_, name) } -// redraws the main game interface windows (useful after changing some data like active hand, etc.) -void intface_redraw() { - WRAP_WATCOM_CALL0(intface_redraw_) -} - long __stdcall interpret(Program* program, long arg2) { WRAP_WATCOM_CALL2(interpret_, program, arg2) } @@ -354,7 +349,7 @@ GameObject* __stdcall scr_find_obj_from_program(Program* program) { WRAP_WATCOM_CALL1(scr_find_obj_from_program_, program) } -// Saves pointer to script object into scriptPtr using scriptID. +// Saves pointer to script object into scriptPtr using scriptID. // Returns 0 on success, -1 on failure. long __stdcall scr_ptr(long scriptId, ScriptInstance** scriptPtr) { WRAP_WATCOM_CALL2(scr_ptr_, scriptId, scriptPtr) @@ -422,6 +417,42 @@ void __fastcall DrawWinLine(int winRef, DWORD startXPos, DWORD endXPos, DWORD st } } +void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long isTrans) { + __asm { + push height; + push edx; // from_width + push y; + mov eax, data; // from + mov ebx, fo::funcoffs::windowDisplayBuf_; + cmp isTrans, 0; + cmovnz ebx, fo::funcoffs::windowDisplayTransBuf_; + call ebx; // *data, from_width, unused, X, Y, width, height + } +} + +void __fastcall displayInWindow(long w_here, long width, long height, void* data) { + __asm { + mov ebx, height; + mov eax, data; + call fo::funcoffs::displayInWindow_; // *data, width, height, where + } +} + +void __fastcall trans_cscale(long i_width, long i_height, long s_width, long s_height, long xy_shift, long w_width, void* data) { + __asm { + push w_width; + push s_height; + push s_width; + mov ebx, edx; // i_height + mov edx, ecx; // i_width + call fo::funcoffs::windowGetBuffer_; + add eax, xy_shift; + push eax; // w_buff + mov eax, data; + call fo::funcoffs::trans_cscale_; // *i_data@, i_width@, i_height@, i_width2@, w_buff, width, height, from_width + } +} + #define WRAP_WATCOM_FUNC0(retType, name) \ retType __stdcall name() { \ diff --git a/sfall/FalloutEngine/Functions.h b/sfall/FalloutEngine/Functions.h index af8997365..a278f0de9 100644 --- a/sfall/FalloutEngine/Functions.h +++ b/sfall/FalloutEngine/Functions.h @@ -20,8 +20,8 @@ #include "Structs.h" -// -// WRAPPERS for FO engine functions. +// +// WRAPPERS for FO engine functions. // Use those as you would if there were source code for the engine... // namespace fo @@ -78,6 +78,8 @@ long __stdcall db_get_file_list(const char* searchMask, char* * *fileList); long __stdcall db_init(const char* path_dat, const char* path_patches); +long __stdcall db_file_exist(const char *fileName, DWORD *sizeOut); + // prints message to debug.log file void __declspec() debug_printf(const char* fmt, ...); @@ -96,12 +98,9 @@ long __stdcall item_get_type(GameObject* item); // searches for message ID in given message file and places result in @result const char* _stdcall getmsg(const MessageList* fileAddr, MessageNode* result, long messageId); -// plays SFX sound with given name +// plays SFX sound with given name void __stdcall gsound_play_sfx_file(const char* name); -// redraws the main game interface windows (useful after changing some data like active hand, etc.) -void intface_redraw(); - long __stdcall interpret(Program* program, long arg2); // finds procedure ID for given script program pointer and procedure name @@ -183,7 +182,7 @@ ScriptInstance* __stdcall scr_find_next_at(); GameObject* __stdcall scr_find_obj_from_program(Program* program); -// Saves pointer to script object into scriptPtr using scriptID. +// Saves pointer to script object into scriptPtr using scriptID. // Returns 0 on success, -1 on failure. long __stdcall scr_ptr(long scriptId, ScriptInstance** scriptPtr); @@ -199,6 +198,14 @@ long __stdcall win_register_button(DWORD winRef, long xPos, long yPos, long widt void __stdcall DialogOut(const char* text); +// draws an image to the buffer without scaling and with possible transparency +void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long isTrans); + +// draws an image in the window and scales it to fit the window +void __fastcall displayInWindow(long w_here, long width, long height, void* data); + +void __fastcall trans_cscale(long i_width, long i_height, long s_width, long s_height, long xy_shift, long w_width, void* data); + // X-Macro for wrapper functions. diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 03f2c809e..dee31595e 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -20,7 +20,7 @@ WRAP_WATCOM_FFUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, b WRAP_WATCOM_FFUNC7(long, createWindow, const char*, winName, long, x, long, y, long, width, long, height, long, bgColorIndex, long, flags) WRAP_WATCOM_FFUNC3(void, display_inventory, long, inventoryOffset, long, visibleOffset, long, mode) WRAP_WATCOM_FFUNC4(void, display_target_inventory, long, inventoryOffset, long, visibleOffset, DWORD*, targetInventory, long, mode) -WRAP_WATCOM_FFUNC3(FrmSubframeData*, frame_ptr, FrmFrameData*, frm, long, frame, long, direction) +WRAP_WATCOM_FFUNC3(FrmFrameData*, frame_ptr, FrmHeaderData*, frm, long, frame, long, direction) WRAP_WATCOM_FFUNC7(void, make_straight_path_func, fo::GameObject*, objFrom, DWORD, tileFrom, DWORD, tileTo, void*, rotationPtr, DWORD*, result, long, flags, void*, func) WRAP_WATCOM_FFUNC3(long, object_under_mouse, long, crSwitch, long, inclDude, long, elevation) @@ -28,11 +28,12 @@ WRAP_WATCOM_FFUNC3(long, object_under_mouse, long, crSwitch, long, inclDude, lon WRAP_WATCOM_FUNC1(AIcap*, ai_cap, GameObject*, critter) WRAP_WATCOM_FUNC1(Program*, allocateProgram, const char*, filePath) WRAP_WATCOM_FUNC0(void, art_flush) +WRAP_WATCOM_FUNC1(const char*, art_get_name, long, artFID) WRAP_WATCOM_FUNC5(long, art_id, long, artType, long, lstIndex, long, animCode, long, weaponCode, long, directionCode) -WRAP_WATCOM_FUNC3(BYTE*, art_frame_data, FrmFrameData*, frm, long, frameNum, long, rotation) -WRAP_WATCOM_FUNC3(long, art_frame_width, FrmFrameData*, frm, long, frameNum, long, rotation) -WRAP_WATCOM_FUNC3(long, art_frame_length, FrmFrameData*, frm, long, frameNum, long, rotation) -WRAP_WATCOM_FUNC2(FrmFrameData*, art_ptr_lock, long, frmId, DWORD*, lockPtr) +WRAP_WATCOM_FUNC3(BYTE*, art_frame_data, FrmHeaderData*, frm, long, frameNum, long, rotation) +WRAP_WATCOM_FUNC3(long, art_frame_width, FrmHeaderData*, frm, long, frameNum, long, rotation) +WRAP_WATCOM_FUNC3(long, art_frame_length, FrmHeaderData*, frm, long, frameNum, long, rotation) +WRAP_WATCOM_FUNC2(FrmHeaderData*, art_ptr_lock, long, frmId, DWORD*, lockPtr) WRAP_WATCOM_FUNC4(BYTE*, art_ptr_lock_data, long, frmId, long, frameNum, long, rotation, DWORD*, lockPtr) WRAP_WATCOM_FUNC4(BYTE*, art_lock, long, frmId, DWORD*, lockPtr, long*, widthOut, long*, heightOut) WRAP_WATCOM_FUNC1(long, art_ptr_unlock, DWORD, lockId) @@ -56,6 +57,8 @@ WRAP_WATCOM_FUNC1(long, gmouse_3d_set_mode, long, mode) WRAP_WATCOM_FUNC1(long, gmouse_set_cursor, long, picNum) WRAP_WATCOM_FUNC1(Window*, GNW_find, long, winRef) WRAP_WATCOM_FUNC0(long, intface_is_item_right_hand) +// redraws the main game interface windows (useful after changing some data like active hand, etc.) +WRAP_WATCOM_FUNC0(void, intface_redraw) WRAP_WATCOM_FUNC0(void, intface_toggle_item_state) WRAP_WATCOM_FUNC0(void, intface_use_item) WRAP_WATCOM_FUNC0(long, is_pc_sneak_working) @@ -76,6 +79,7 @@ WRAP_WATCOM_FUNC1(long, item_w_max_ammo, GameObject*, item) WRAP_WATCOM_FUNC1(long, item_weight, GameObject*, item) // returns light level at given tile WRAP_WATCOM_FUNC2(long, light_get_tile, long, elevation, long, tileNum) +WRAP_WATCOM_FUNC2(long, load_frame, const char*, filename, FrmFile**, frmPtr) WRAP_WATCOM_FUNC2(void, MapDirErase, const char*, folder, const char*, ext) WRAP_WATCOM_FUNC2(void, mouse_get_position, long*, outX, long*, outY) WRAP_WATCOM_FUNC0(void, mouse_show) @@ -143,6 +147,7 @@ WRAP_WATCOM_FUNC1(void, win_hide, DWORD, winRef) WRAP_WATCOM_FUNC1(BYTE*, win_get_buf, DWORD, winRef) WRAP_WATCOM_FUNC1(void, win_draw, DWORD, winRef) WRAP_WATCOM_FUNC1(void, win_delete, DWORD, winRef) +WRAP_WATCOM_FUNC0(long, windowWidth) WRAP_WATCOM_FUNC1(void, wmCarUseGas, long, gasAmount) WRAP_WATCOM_FUNC0(void, wmPartyWalkingStep) WRAP_WATCOM_FUNC2(DbFile*, xfopen, const char*, fileName, const char*, flags) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index a6af6648f..58027fd99 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -136,7 +136,7 @@ struct GameObject { return (protoId >> 24); } inline char TypeFid() { - return ((artFid >> 24) & 0xF0); + return ((artFid >> 24) & 0x0F); } }; @@ -277,11 +277,11 @@ struct FrmFile { short frames; //0x08 short xshift[6]; //0x0a short yshift[6]; //0x16 - long framestart[6]; //0x22 - long size; //0x3a + long oriFrameOffset[6]; //0x22 + long frameAreaSize; //0x3a short width; //0x3e short height; //0x40 - long frmSize; //0x42 + long frameSize; //0x42 short xoffset; //0x46 short yoffset; //0x48 BYTE pixels[80 * 36]; //0x4a @@ -289,28 +289,28 @@ struct FrmFile { //structures for holding frms loaded with fallout2 functions #pragma pack(1) -typedef class FrmSubframeData { +typedef class FrmFrameData { // sizeof 12 + 1 byte public: WORD width; WORD height; - DWORD size; + DWORD size; // width * height WORD x; WORD y; BYTE data[1]; // begin frame data -} FrmSubframeData; +} FrmFrameData; #pragma pack(2) -typedef class FrmFrameData { +typedef class FrmHeaderData { // sizeof 62 public: DWORD version; // version num WORD fps; // frames per sec WORD actionFrame; WORD numFrames; // number of frames per direction - WORD xCentreShift[6]; // offset from frm centre +=right -=left - WORD yCentreShift[6]; // offset from frm centre +=down -=up - DWORD oriOffset[6]; // frame area offset for diff orientations - DWORD frameAreaSize; -} FrmFrameData; + WORD xCentreShift[6]; // shift in the X direction, of frames with orientations [0-5] + WORD yCentreShift[6]; // shift in the Y direction, of frames with orientations [0-5] + DWORD oriOffset[6]; // offset of first frame for direction [0-5] from begining of frame area + DWORD frameAreaSize; // size of all frames area +} FrmHeaderData; #pragma pack(1) struct MessageNode { diff --git a/sfall/Modules/HeroAppearance.cpp b/sfall/Modules/HeroAppearance.cpp index fd638c064..8fbd8ca78 100644 --- a/sfall/Modules/HeroAppearance.cpp +++ b/sfall/Modules/HeroAppearance.cpp @@ -649,7 +649,7 @@ static void sub_draw(long subWidth, long subHeight, long fromWidth, long fromHei static void DrawBody(DWORD critNum, BYTE* surface) { DWORD critFrmLock; - fo::FrmFrameData *critFrm = fo::func::art_ptr_lock(BuildFrmId(1, critNum), &critFrmLock); + fo::FrmHeaderData *critFrm = fo::func::art_ptr_lock(BuildFrmId(1, critNum), &critFrmLock); DWORD critWidth = fo::func::art_frame_width(critFrm, 0, charRotOri); DWORD critHeight = fo::func::art_frame_length(critFrm, 0, charRotOri); BYTE *critSurface = fo::func::art_frame_data(critFrm, 0, charRotOri); diff --git a/sfall/Modules/Scripting/Handlers/Anims.h b/sfall/Modules/Scripting/Handlers/Anims.h index 372aa5821..74fd802b1 100644 --- a/sfall/Modules/Scripting/Handlers/Anims.h +++ b/sfall/Modules/Scripting/Handlers/Anims.h @@ -34,6 +34,7 @@ void sf_reg_anim_light(OpcodeContext&); void sf_reg_anim_change_fid(OpcodeContext&); void sf_reg_anim_take_out(OpcodeContext&); void sf_reg_anim_turn_towards(OpcodeContext&); + void sf_explosions_metarule(OpcodeContext&); void sf_art_cache_flush(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index bcd8c23e9..9b386f65d 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -57,7 +57,7 @@ typedef std::unordered_map OpcodeInfoMapType; // opcode number, // function name, // function handler, -// number of arguments, +// number of arguments (max 7), // has return value, // { argument 1 type, argument 2 type, ...} // } @@ -198,7 +198,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x279, "sfall_func3", HandleMetarule, 4, true}, {0x27a, "sfall_func4", HandleMetarule, 5, true}, {0x27b, "sfall_func5", HandleMetarule, 6, true}, - {0x27c, "sfall_func6", HandleMetarule, 7, true}, // if you need more arguments - use arrays + {0x27c, "sfall_func6", HandleMetarule, 7, true}, // if you need more arguments - use arrays }; // A hash-table for opcode info, indexed by opcode. diff --git a/sfall/Modules/TalkingHeads.cpp b/sfall/Modules/TalkingHeads.cpp index be7b31f04..10f480f83 100644 --- a/sfall/Modules/TalkingHeads.cpp +++ b/sfall/Modules/TalkingHeads.cpp @@ -153,7 +153,7 @@ static bool LoadFrm(Frm* frm) { } // make mask image for (int i = 0; i < frm->frames; i++) { - fo::FrmSubframeData* frame = fo::func::frame_ptr((fo::FrmFrameData*)frm, i, 0); + fo::FrmFrameData* frame = fo::func::frame_ptr((fo::FrmHeaderData*)frm, i, 0); if (frm->bakedBackground) { memset(frame->data, 255, frame->size); } else { @@ -169,7 +169,7 @@ static bool LoadFrm(Frm* frm) { static void __fastcall DrawHeadFrame(Frm* frm, int frameno) { if (frm && !frm->broken) { if (!frm->loaded && !LoadFrm(frm)) goto loadFail; - fo::FrmSubframeData* frame = fo::func::frame_ptr((fo::FrmFrameData*)frm, frameno, 0); + fo::FrmFrameData* frame = fo::func::frame_ptr((fo::FrmHeaderData*)frm, frameno, 0); Graphics::SetHeadTex(frm->textures[frameno], frame->width, frame->height, frame->x + frm->xshift, frame->y + frm->yshift, (frm->showHighlights == 2)); showHighlights = frm->showHighlights; return; diff --git a/sfall/Modules/Tiles.cpp b/sfall/Modules/Tiles.cpp index 484d3da01..3db8bce8e 100644 --- a/sfall/Modules/Tiles.cpp +++ b/sfall/Modules/Tiles.cpp @@ -118,8 +118,8 @@ static int ProcessTile(fo::Art* tiles, int tile, int listpos) { fo::func::db_freadByteCount(art, (BYTE*)&frame, 0x4a); frame.height = ByteSwapW(36); frame.width = ByteSwapW(80); - frame.frmSize = ByteSwapD(80 * 36); - frame.size = ByteSwapD(80 * 36 + 12); + frame.frameSize = ByteSwapD(80 * 36); + frame.frameAreaSize = frame.frameSize + 12; int xoffset = x * 48 + (ysize - (y + 1)) * 32; int yoffset = height - (36 + x * 12 + y * 24); for (int y2 = 0; y2 < 36; y2++) { From 02c2df50ac580f79ca549b74cdc48a08a5a29d9b Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 9 May 2019 22:22:02 +0800 Subject: [PATCH 12/32] Added "draw_image" and "draw_image_scaled" script functions. --- artifacts/ddraw.ini | 2 +- artifacts/scripting/headers/sfall.h | 2 + artifacts/scripting/sfall function notes.txt | 11 +++ sfall/FalloutEngine/Functions.cpp | 2 + sfall/Modules/BugFixes.cpp | 6 +- .../Modules/Scripting/Handlers/Interface.cpp | 67 +++++++++++++++++++ sfall/Modules/Scripting/Handlers/Interface.h | 4 ++ sfall/Modules/Scripting/Handlers/Metarule.cpp | 6 +- 8 files changed, 94 insertions(+), 6 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 9c76261bb..392de3b3f 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -630,7 +630,7 @@ DontTurnOffSneakIfYouRun=0 ;Set to 0 to disable switching. (Default is 3) UseWalkDistance=3 -;Set to 1 to fix the bug that unable to sell used geiger counters or stealth boys +;Set to 1 to fix the bug of being unable to sell used geiger counters or stealth boys CanSellUsedGeiger=1 ;Set to 1 to fix the issue with being able to charge the car by using cells on other scenary/critters diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index a3789051a..9fe36a3b2 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -249,6 +249,8 @@ #define dialog_message(text) sfall_func1("dialog_message", text) #define dialog_obj sfall_func0("dialog_obj") #define display_stats sfall_func0("display_stats") +#define draw_image(frmFile, frame, x, y, transparent) sfall_func5("draw_image", frmFile, frame, x, y, transparent) +#define draw_image_scaled(frmFile, frame, x, y, w, h) sfall_func6("draw_image_scaled", frmFile, frame, x, y, w, h) #define exec_map_update_scripts sfall_func0("exec_map_update_scripts") #define floor2(value) sfall_func1("floor2", value) #define get_can_rest_on_map(map, elev) sfall_func2("get_can_rest_on_map", map, elev) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index b6f1579c1..4fa24564f 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -587,6 +587,17 @@ Some utility/math functions are available: - there is also a unique ID number range for the player and party members from 18000 to 83535 - to assign a new ID number generated by the engine to the object (i.e. unassign a unique ID), call the function with two arguments and pass -1 for the flag argument +> void sfall_func5("draw_image", string/int pathFile/artId, int frame, int x, int y, bool transparent) +> void sfall_func6("draw_image_scaled", string/int pathFile/artId, int frame, int x, int y, int width, int height) +- displays the specified FRM image in the active window created by vanilla CreateWin or sfall's create_win script function +- pathFile/artId: path to the FRM file (e.g. "art\\inven\\5mmap.frm"), or its FRM ID number (e.g. 117440550, see specification of the FID format) +Optional arguments: +- frame: frame number, the first frame starts from zero +- x/y: offset relative to the top-left corner of the window +- width/height: image size, used to scale the image when displaying it +- transparent: pass true to display an image with transparent background +- NOTE: calling the functions without creating a window first will crash the game + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index 8e3e473cd..14d87ebd9 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -417,6 +417,7 @@ void __fastcall DrawWinLine(int winRef, DWORD startXPos, DWORD endXPos, DWORD st } } +// draws an image to the buffer without scaling and with possible transparency void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long isTrans) { __asm { push height; @@ -430,6 +431,7 @@ void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* } } +// draws an image in the window and scales it to fit the window void __fastcall displayInWindow(long w_here, long width, long height, void* data) { __asm { mov ebx, height; diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 4ad28cf12..63b9b1277 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -2320,9 +2320,9 @@ void BugFixes::init() MakeCall(0x4130E5, action_explode_hack1); dlogr(" Done", DL_INIT); - // Fix for unable to sell used geiger counters or stealth boys + // Fix for being unable to sell used geiger counters or stealth boys if (GetConfigInt("Misc", "CanSellUsedGeiger", 1)) { - dlog("Applying fix for unable to sell used geiger counters or stealth boys.", DL_INIT); + dlog("Applying fix for being unable to sell used geiger counters or stealth boys.", DL_INIT); SafeWrite8(0x478115, 0xBA); SafeWrite8(0x478138, 0xBA); MakeJump(0x474D22, barter_attempt_transaction_hack); @@ -2487,7 +2487,7 @@ void BugFixes::init() SafeWrite8(0x4C1015, 0x90); HookCall(0x4C1042, wmSetupRandomEncounter_hook); - // Fix for unable to sell/give items in the barter screen when the player/party member is overloaded + // Fix for being unable to sell/give items in the barter screen when the player/party member is overloaded HookCalls(barter_attempt_transaction_hook_weight, {0x474C73, 0x474CCA}); // Fix for the underline position in the inventory display window when the item name is longer than one line diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 1db980f0f..6c23343b0 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -398,5 +398,72 @@ void sf_create_win(OpcodeContext& ctx) { } } +static void DrawImage(OpcodeContext& ctx, bool isScaled) { + long rotation = 0; + const char* file = nullptr; + if (ctx.arg(0).isInt()) { // art id + long fid = ctx.arg(0).rawValue(); + if (fid == -1) return; + long _fid = fid & 0xFFFFFFF; + file = fo::func::art_get_name(_fid); // .frm + if (_fid >> 24 == fo::OBJ_TYPE_CRITTER) { + rotation = (fid >> 28); + DWORD sz; + if (rotation && fo::func::db_file_exist(file, &sz)) { + file = fo::func::art_get_name(fid); // .fr# + } + } + } else { + file = ctx.arg(0).strValue(); // path to frm file + } + fo::FrmFile* frmPtr = nullptr; + if (fo::func::load_frame(file, &frmPtr)) { + ctx.printOpcodeError("%s() - can't open the file '%s'.", ctx.getMetaruleName(), file); + return; + } + fo::FrmFrameData* framePtr = (fo::FrmFrameData*)&frmPtr->width; + if (rotation > 0 && rotation < 6) { + BYTE* offsOriFrame = (BYTE*)framePtr; + offsOriFrame += frmPtr->oriFrameOffset[rotation]; + framePtr = (fo::FrmFrameData*)offsOriFrame; + } + int frameno = ctx.arg(1).rawValue(); + if (frameno > 0) { + int maxFrames = frmPtr->frames - 1; + if (frameno > maxFrames) frameno = maxFrames; + while (frameno-- > 0) { + BYTE* offsFrame = (BYTE*)framePtr; + offsFrame += framePtr->size + (sizeof(fo::FrmFrameData) - 1); + framePtr = (fo::FrmFrameData*)offsFrame; + } + } + if (isScaled && ctx.numArgs() < 3) { + fo::func::displayInWindow(framePtr->width, framePtr->width, framePtr->height, framePtr->data); // scaled to window size + } else { + int x = ctx.arg(2).rawValue(), y = ctx.arg(3).rawValue(); + if (isScaled) { // draw to scale + long s_width = (ctx.numArgs() > 4) ? ctx.arg(4).rawValue() : framePtr->width; + long s_height = (ctx.numArgs() > 5) ? ctx.arg(5).rawValue() : framePtr->height; + long w_width = fo::func::windowWidth(); + long xy_pos = (x * w_width) + y; + fo::func::trans_cscale(framePtr->width, framePtr->height, s_width, s_height, xy_pos, w_width, framePtr->data); // custom scaling + } else { + fo::func::windowDisplayBuf(x + frmPtr->xshift[rotation], framePtr->width, y + frmPtr->yshift[rotation], framePtr->height, framePtr->data, ctx.arg(4).rawValue()); + } + } + __asm { + mov eax, frmPtr; + call fo::funcoffs::mem_free_; + } +} + +void sf_draw_image(OpcodeContext& ctx) { + DrawImage(ctx, false); +} + +void sf_draw_image_scaled(OpcodeContext& ctx) { + DrawImage(ctx, true); +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Interface.h b/sfall/Modules/Scripting/Handlers/Interface.h index 260463c6f..90f00eb3e 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.h +++ b/sfall/Modules/Scripting/Handlers/Interface.h @@ -99,5 +99,9 @@ void sf_dialog_message(OpcodeContext&); void sf_create_win(OpcodeContext&); +void sf_draw_image(OpcodeContext&); + +void sf_draw_image_scaled(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 78d74b78b..4f65b5599 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -54,7 +54,7 @@ static MetaruleTableType metaruleTable; { name, handler, minArgs, maxArgs, {arg1, arg2, ...} } - name - name of function that will be used in scripts, - handler - pointer to handler function (see examples below), - - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function + - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function (max 6) - arg1, arg2, ... - argument types for automatic validation */ static const SfallMetarule metarules[] = { @@ -66,7 +66,9 @@ static const SfallMetarule metarules[] = { {"critter_inven_obj2", sf_critter_inven_obj2, 2, 2, {ARG_OBJECT, ARG_INT}}, {"dialog_message", sf_dialog_message, 1, 1, {ARG_STRING}}, {"dialog_obj", sf_get_dialog_object, 0, 0}, - {"display_stats", sf_display_stats, 0, 0}, + {"display_stats", sf_display_stats, 0, 0}, // refresh + {"draw_image", sf_draw_image, 1, 5, {ARG_INTSTR, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, + {"draw_image_scaled", sf_draw_image_scaled, 1, 6, {ARG_INTSTR, ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {"exec_map_update_scripts", sf_exec_map_update_scripts, 0, 0}, {"floor2", sf_floor2, 1, 1, {ARG_NUMBER}}, {"get_can_rest_on_map", sf_get_rest_on_map, 2, 2, {ARG_INT, ARG_INT}}, From 4b221b53673c1b6b284c9e734bcbb2c1a70d5fb4 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Fri, 10 May 2019 10:38:51 +0800 Subject: [PATCH 13/32] Improved the error handling for previous commit. Reverted the change in Tiles.cpp from commit 4bc1a422c31c8d223b505a0e01c1f0df96a9999d. --- artifacts/scripting/headers/sfall.h | 2 +- artifacts/scripting/sfall function notes.txt | 7 +++--- sfall/FalloutEngine/Functions.cpp | 14 ++++++------ sfall/FalloutEngine/Functions.h | 4 ++-- sfall/FalloutEngine/VariableOffsets.h | 1 + .../Modules/Scripting/Handlers/Interface.cpp | 22 +++++++++++-------- sfall/Modules/Tiles.cpp | 2 +- 7 files changed, 29 insertions(+), 23 deletions(-) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 9fe36a3b2..e367074af 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -249,7 +249,7 @@ #define dialog_message(text) sfall_func1("dialog_message", text) #define dialog_obj sfall_func0("dialog_obj") #define display_stats sfall_func0("display_stats") -#define draw_image(frmFile, frame, x, y, transparent) sfall_func5("draw_image", frmFile, frame, x, y, transparent) +#define draw_image(frmFile, frame, x, y, noTransparent) sfall_func5("draw_image", frmFile, frame, x, y, noTransparent) #define draw_image_scaled(frmFile, frame, x, y, w, h) sfall_func6("draw_image_scaled", frmFile, frame, x, y, w, h) #define exec_map_update_scripts sfall_func0("exec_map_update_scripts") #define floor2(value) sfall_func1("floor2", value) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 4fa24564f..a13c15ba9 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -587,7 +587,7 @@ Some utility/math functions are available: - there is also a unique ID number range for the player and party members from 18000 to 83535 - to assign a new ID number generated by the engine to the object (i.e. unassign a unique ID), call the function with two arguments and pass -1 for the flag argument -> void sfall_func5("draw_image", string/int pathFile/artId, int frame, int x, int y, bool transparent) +> void sfall_func5("draw_image", string/int pathFile/artId, int frame, int x, int y, bool noTransparent) > void sfall_func6("draw_image_scaled", string/int pathFile/artId, int frame, int x, int y, int width, int height) - displays the specified FRM image in the active window created by vanilla CreateWin or sfall's create_win script function - pathFile/artId: path to the FRM file (e.g. "art\\inven\\5mmap.frm"), or its FRM ID number (e.g. 117440550, see specification of the FID format) @@ -595,8 +595,9 @@ Optional arguments: - frame: frame number, the first frame starts from zero - x/y: offset relative to the top-left corner of the window - width/height: image size, used to scale the image when displaying it -- transparent: pass true to display an image with transparent background -- NOTE: calling the functions without creating a window first will crash the game +- noTransparent: pass true to display an image without transparent background +- NOTE: to omit optional arguments, call the functions with different sfall_funcX (e.g. sfall_func4("draw_image", pathFile, frame, x, y)) +- if draw_image_scaled is called without x/y/width/height arguments, the image will be scaled to fit the window without transparent background ------------------------ ------ MORE INFO ------- diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index 14d87ebd9..5583ad82b 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -417,16 +417,16 @@ void __fastcall DrawWinLine(int winRef, DWORD startXPos, DWORD endXPos, DWORD st } } -// draws an image to the buffer without scaling and with possible transparency -void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long isTrans) { +// draws an image to the buffer without scaling and with transparency display toggle +void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long noTrans) { __asm { push height; push edx; // from_width push y; mov eax, data; // from - mov ebx, fo::funcoffs::windowDisplayBuf_; - cmp isTrans, 0; - cmovnz ebx, fo::funcoffs::windowDisplayTransBuf_; + mov ebx, fo::funcoffs::windowDisplayTransBuf_; + cmp noTrans, 0; + cmovnz ebx, fo::funcoffs::windowDisplayBuf_; call ebx; // *data, from_width, unused, X, Y, width, height } } @@ -449,9 +449,9 @@ void __fastcall trans_cscale(long i_width, long i_height, long s_width, long s_h mov edx, ecx; // i_width call fo::funcoffs::windowGetBuffer_; add eax, xy_shift; - push eax; // w_buff + push eax; // to_buff mov eax, data; - call fo::funcoffs::trans_cscale_; // *i_data@, i_width@, i_height@, i_width2@, w_buff, width, height, from_width + call fo::funcoffs::trans_cscale_; // *from_buff, i_width, i_height, i_width2, to_buff, width, height, to_width } } diff --git a/sfall/FalloutEngine/Functions.h b/sfall/FalloutEngine/Functions.h index a278f0de9..869eaa784 100644 --- a/sfall/FalloutEngine/Functions.h +++ b/sfall/FalloutEngine/Functions.h @@ -198,8 +198,8 @@ long __stdcall win_register_button(DWORD winRef, long xPos, long yPos, long widt void __stdcall DialogOut(const char* text); -// draws an image to the buffer without scaling and with possible transparency -void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long isTrans); +// draws an image to the buffer without scaling and with transparency display toggle +void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long noTrans); // draws an image in the window and scales it to fit the window void __fastcall displayInWindow(long w_here, long width, long height, void* data); diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index 508640773..3fd84024f 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -43,6 +43,7 @@ #define FO_VAR_curr_font_num 0x51E3B0 #define FO_VAR_curr_pc_stat 0x6681AC #define FO_VAR_curr_stack 0x59E96C +#define FO_VAR_currentWindow 0x51DCB8 #define FO_VAR_cursor_line 0x664514 #define FO_VAR_dialogue_head 0x518850 #define FO_VAR_dialogue_state 0x518714 diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 6c23343b0..59ed65831 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -399,7 +399,11 @@ void sf_create_win(OpcodeContext& ctx) { } static void DrawImage(OpcodeContext& ctx, bool isScaled) { - long rotation = 0; + if (*(DWORD*)FO_VAR_currentWindow == -1) { + ctx.printOpcodeError("%s() - no created/selected window for the image.", ctx.getMetaruleName()); + return; + } + long direction = 0; const char* file = nullptr; if (ctx.arg(0).isInt()) { // art id long fid = ctx.arg(0).rawValue(); @@ -407,9 +411,9 @@ static void DrawImage(OpcodeContext& ctx, bool isScaled) { long _fid = fid & 0xFFFFFFF; file = fo::func::art_get_name(_fid); // .frm if (_fid >> 24 == fo::OBJ_TYPE_CRITTER) { - rotation = (fid >> 28); + direction = (fid >> 28); DWORD sz; - if (rotation && fo::func::db_file_exist(file, &sz)) { + if (direction && fo::func::db_file_exist(file, &sz)) { file = fo::func::art_get_name(fid); // .fr# } } @@ -418,13 +422,13 @@ static void DrawImage(OpcodeContext& ctx, bool isScaled) { } fo::FrmFile* frmPtr = nullptr; if (fo::func::load_frame(file, &frmPtr)) { - ctx.printOpcodeError("%s() - can't open the file '%s'.", ctx.getMetaruleName(), file); + ctx.printOpcodeError("%s() - can't open the file: %s", ctx.getMetaruleName(), file); return; } fo::FrmFrameData* framePtr = (fo::FrmFrameData*)&frmPtr->width; - if (rotation > 0 && rotation < 6) { + if (direction > 0 && direction < 6) { BYTE* offsOriFrame = (BYTE*)framePtr; - offsOriFrame += frmPtr->oriFrameOffset[rotation]; + offsOriFrame += frmPtr->oriFrameOffset[direction]; framePtr = (fo::FrmFrameData*)offsOriFrame; } int frameno = ctx.arg(1).rawValue(); @@ -438,7 +442,7 @@ static void DrawImage(OpcodeContext& ctx, bool isScaled) { } } if (isScaled && ctx.numArgs() < 3) { - fo::func::displayInWindow(framePtr->width, framePtr->width, framePtr->height, framePtr->data); // scaled to window size + fo::func::displayInWindow(framePtr->width, framePtr->width, framePtr->height, framePtr->data); // scaled to window size (w/o transparent) } else { int x = ctx.arg(2).rawValue(), y = ctx.arg(3).rawValue(); if (isScaled) { // draw to scale @@ -447,8 +451,8 @@ static void DrawImage(OpcodeContext& ctx, bool isScaled) { long w_width = fo::func::windowWidth(); long xy_pos = (x * w_width) + y; fo::func::trans_cscale(framePtr->width, framePtr->height, s_width, s_height, xy_pos, w_width, framePtr->data); // custom scaling - } else { - fo::func::windowDisplayBuf(x + frmPtr->xshift[rotation], framePtr->width, y + frmPtr->yshift[rotation], framePtr->height, framePtr->data, ctx.arg(4).rawValue()); + } else { // with x/y frame offsets + fo::func::windowDisplayBuf(x + frmPtr->xshift[direction], framePtr->width, y + frmPtr->yshift[direction], framePtr->height, framePtr->data, ctx.arg(4).rawValue()); } } __asm { diff --git a/sfall/Modules/Tiles.cpp b/sfall/Modules/Tiles.cpp index 3db8bce8e..3ba5d6148 100644 --- a/sfall/Modules/Tiles.cpp +++ b/sfall/Modules/Tiles.cpp @@ -119,7 +119,7 @@ static int ProcessTile(fo::Art* tiles, int tile, int listpos) { frame.height = ByteSwapW(36); frame.width = ByteSwapW(80); frame.frameSize = ByteSwapD(80 * 36); - frame.frameAreaSize = frame.frameSize + 12; + frame.frameAreaSize = ByteSwapD(80 * 36 + 12); int xoffset = x * 48 + (ysize - (y + 1)) * 32; int yoffset = height - (36 + x * 12 + y * 24); for (int y2 = 0; y2 < 36; y2++) { From a63ea24572395b35eb2f99b9794f39ed36518aea Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sat, 11 May 2019 08:49:30 +0800 Subject: [PATCH 14/32] Added aspect ratio scaling to draw_image_scaled. Rewrote create_message_window script function in C++. --- artifacts/scripting/headers/define_extra.h | 6 ++ artifacts/scripting/sfall function notes.txt | 4 +- sfall/FalloutEngine/Functions.cpp | 4 +- .../Modules/Scripting/Handlers/Interface.cpp | 67 +++++++------------ sfall/Modules/Scripting/Handlers/Interface.h | 2 +- sfall/Modules/Scripting/Opcodes.cpp | 2 +- 6 files changed, 35 insertions(+), 50 deletions(-) diff --git a/artifacts/scripting/headers/define_extra.h b/artifacts/scripting/headers/define_extra.h index 346b701b0..92a095f20 100644 --- a/artifacts/scripting/headers/define_extra.h +++ b/artifacts/scripting/headers/define_extra.h @@ -10,6 +10,12 @@ #define OBJ_TYPE_MISC (5) #define OBJ_TYPE_SPATIAL (6) +#define ART_TYPE_INTERFACE (6) +#define ART_TYPE_INVENT (7) +#define ART_TYPE_HEADS (8) +#define ART_TYPE_BACKGRND (9) +#define ART_TYPE_SKILLDEX (10) + #define WEAPON_TYPE_NONE (0) #define WEAPON_TYPE_UNARMED (1) #define WEAPON_TYPE_MELEE (2) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index a13c15ba9..d80016d3c 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -594,9 +594,9 @@ Some utility/math functions are available: Optional arguments: - frame: frame number, the first frame starts from zero - x/y: offset relative to the top-left corner of the window -- width/height: image size, used to scale the image when displaying it +- width/height: image size, used to scale the image when displaying it. Pass -1 to either width or height to keep aspect ratio when scaling - noTransparent: pass true to display an image without transparent background -- NOTE: to omit optional arguments, call the functions with different sfall_funcX (e.g. sfall_func4("draw_image", pathFile, frame, x, y)) +- NOTE: to omit optional arguments starting from the right, call the functions with different sfall_funcX (e.g. sfall_func4("draw_image", pathFile, frame, x, y)) - if draw_image_scaled is called without x/y/width/height arguments, the image will be scaled to fit the window without transparent background ------------------------ diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index 5583ad82b..7c35070e8 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -394,8 +394,8 @@ void __stdcall DialogOut(const char* text) { push eax; // DisplayMsg mov al, byte ptr ds:[0x6AB718]; push eax; // ColorIndex - push 0x74; // y - mov ecx, 0xC0; // x + push 116; // y + mov ecx, 192; // x mov eax, text; // DisplayText xor ebx, ebx; // ? xor edx, edx; // ? diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 59ed65831..d50ea9b2b 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -151,46 +151,11 @@ void __declspec(naked) op_resume_game() { } } -void __declspec(naked) op_create_message_window() { - __asm { - pushad - mov ebx, dword ptr ds:[FO_VAR_curr_font_num]; - cmp ebx, 0x65; - je end; - - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; -next: - mov ebx, eax; - mov eax, ecx; - call fo::funcoffs::interpretGetString_; - mov esi, eax; - - mov ecx, eax; - mov eax, 3; - push 1; // arg10 - mov al, byte ptr ds:[0x6AB718]; - push eax; // a9 - push 0; // *DisplayText - push eax; // ColorIndex - push 0x74; // y - mov ecx, 0xC0; // x - mov eax, esi; // text - xor ebx, ebx; // ? - xor edx, edx; // ? - call fo::funcoffs::dialog_out_; - //xor eax, eax; -end: - popad; - ret; - } +void sf_create_message_window(OpcodeContext &ctx) { + const char* str = ctx.arg(0).strValue(); + if (!str || str[0] == 0) return; + //if (fo::var::curr_font_num == 101) return; + fo::func::DialogOut(str); } void __declspec(naked) op_get_viewport_x() { @@ -422,7 +387,7 @@ static void DrawImage(OpcodeContext& ctx, bool isScaled) { } fo::FrmFile* frmPtr = nullptr; if (fo::func::load_frame(file, &frmPtr)) { - ctx.printOpcodeError("%s() - can't open the file: %s", ctx.getMetaruleName(), file); + ctx.printOpcodeError("%s() - cannot open the file: %s", ctx.getMetaruleName(), file); return; } fo::FrmFrameData* framePtr = (fo::FrmFrameData*)&frmPtr->width; @@ -446,10 +411,24 @@ static void DrawImage(OpcodeContext& ctx, bool isScaled) { } else { int x = ctx.arg(2).rawValue(), y = ctx.arg(3).rawValue(); if (isScaled) { // draw to scale - long s_width = (ctx.numArgs() > 4) ? ctx.arg(4).rawValue() : framePtr->width; - long s_height = (ctx.numArgs() > 5) ? ctx.arg(5).rawValue() : framePtr->height; + long s_width, s_height; + if (ctx.numArgs() < 5) { + s_width = framePtr->width; + s_height = framePtr->height; + } else { + s_width = ctx.arg(4).rawValue(); + s_height = (ctx.numArgs() > 5) ? ctx.arg(5).rawValue() : -1; + } + // scale with aspect ratio if w or h is set to -1 + if (s_width <= -1 && s_height > 0) { + s_width = s_height * framePtr->width / framePtr->height; + } else if (s_height <= -1 && s_width > 0) { + s_height = s_width * framePtr->height / framePtr->width; + } + if (s_width <= 0 || s_height <= 0) return; + long w_width = fo::func::windowWidth(); - long xy_pos = (x * w_width) + y; + long xy_pos = (y * w_width) + x; fo::func::trans_cscale(framePtr->width, framePtr->height, s_width, s_height, xy_pos, w_width, framePtr->data); // custom scaling } else { // with x/y frame offsets fo::func::windowDisplayBuf(x + frmPtr->xshift[direction], framePtr->width, y + frmPtr->yshift[direction], framePtr->height, framePtr->data, ctx.arg(4).rawValue()); diff --git a/sfall/Modules/Scripting/Handlers/Interface.h b/sfall/Modules/Scripting/Handlers/Interface.h index 90f00eb3e..f6dcaa026 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.h +++ b/sfall/Modules/Scripting/Handlers/Interface.h @@ -57,7 +57,7 @@ void __declspec() op_stop_game(); void __declspec() op_resume_game(); //Create a message window with given string -void __declspec() op_create_message_window(); +void sf_create_message_window(OpcodeContext&); void __declspec() op_get_viewport_x(); diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index 9b386f65d..10cae0bd4 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -135,6 +135,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x21a, "set_weapon_ammo_count", sf_set_weapon_ammo_count, 2, false, {ARG_OBJECT, ARG_INT}}, {0x21e, "get_mouse_buttons", sf_get_mouse_buttons, 0, true}, + {0x224, "create_message_window", sf_create_message_window, 1, false, {ARG_STRING}}, {0x22d, "create_array", sf_create_array, 2, true, {ARG_INT, ARG_INT}}, {0x22e, "set_array", sf_set_array, 3, false, {ARG_OBJECT, ARG_ANY, ARG_ANY}}, {0x22f, "get_array", sf_get_array, 2, true, {ARG_ANY, ARG_ANY}}, // can also be used on strings @@ -384,7 +385,6 @@ void InitNewOpcodes() { opcodes[0x221] = op_get_screen_height; opcodes[0x222] = op_stop_game; opcodes[0x223] = op_resume_game; - opcodes[0x224] = op_create_message_window; opcodes[0x225] = op_remove_trait; opcodes[0x226] = op_get_light_level; opcodes[0x227] = op_refresh_pc_art; From d2cf7281319e2481492e53d76cf209a8a9af9570 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sat, 11 May 2019 09:56:58 +0800 Subject: [PATCH 15/32] Refactored code in Tiles.cpp and EngineUtils.cpp. Moved RegAnimCombatCheck code from ScriptExtender.cpp to Anims.cpp. --- sfall/FalloutEngine/EngineUtils.cpp | 18 ++++- sfall/FalloutEngine/EngineUtils.h | 4 ++ sfall/Modules/ScriptExtender.cpp | 17 +---- sfall/Modules/ScriptExtender.h | 3 - sfall/Modules/Scripting/Handlers/Anims.cpp | 18 ++++- sfall/Modules/Scripting/Handlers/Anims.h | 2 + sfall/Modules/Stats.cpp | 14 +--- sfall/Modules/Tiles.cpp | 78 +++++++++------------- 8 files changed, 75 insertions(+), 79 deletions(-) diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index e23d972f8..ffc808868 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -62,6 +62,14 @@ Proto* GetProto(long pid) { return nullptr; } +bool CritterCopyProto(long pid, long* &proto_dst) { + fo::Proto* protoPtr; + if (fo::func::proto_ptr(pid, &protoPtr) == -1) return false; + /*if (!proto_dst)*/ proto_dst = new long[104]; + memcpy(proto_dst, protoPtr, 416); + return true; +} + void SkillGetTags(long* result, long num) { if (num > 4) { num = 4; @@ -129,18 +137,22 @@ void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set) { } } -bool IsPartyMember(fo::GameObject* critter) { - if (critter->id < 18000) return false; +bool IsPartyMemberByPid(long pid) { size_t patryCount = fo::var::partyMemberMaxCount; if (patryCount) { DWORD* memberPids = fo::var::partyMemberPidList; // pids from party.txt for (size_t i = 0; i < patryCount; i++) { - if (memberPids[i] == critter->protoId) return true;; + if (memberPids[i] == pid) return true;; } } return false; } +bool IsPartyMember(fo::GameObject* critter) { + if (critter->id < 18000) return false; + return IsPartyMemberByPid(critter->protoId); +} + //--------------------------------------------------------- //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 1386b344f..bad1502a1 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -44,6 +44,8 @@ const char* _stdcall MessageSearch(const MessageList* fileAddr, long messageId); // returns pointer to prototype by PID, or nullptr on failure Proto* GetProto(long pid); +bool CritterCopyProto(long pid, long* &proto_dst); + // wrapper for skill_get_tags with bounds checking void SkillGetTags(long* result, long num); @@ -64,6 +66,8 @@ long CheckAddictByPid(fo::GameObject* critter, long pid); void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set); +bool IsPartyMemberByPid(long pid); + bool IsPartyMember(fo::GameObject* critter); // Print text to surface diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index def7180ce..357faaec2 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -34,9 +34,11 @@ #include "LoadGameHook.h" #include "MainLoopHook.h" #include "Worldmap.h" + #include "Scripting\Arrays.h" #include "Scripting\Opcodes.h" #include "Scripting\OpcodeContext.h" +#include "Scripting\Handlers\Anims.h" #include "ScriptExtender.h" @@ -527,21 +529,6 @@ bool _stdcall ScriptHasLoaded(fo::Program* script) { return true; } -void _stdcall RegAnimCombatCheck(DWORD newValue) { - char oldValue = regAnimCombatCheck; - regAnimCombatCheck = (newValue > 0); - if (oldValue != regAnimCombatCheck) { - SafeWrite8(0x459C97, regAnimCombatCheck); // reg_anim_func - SafeWrite8(0x459D4B, regAnimCombatCheck); // reg_anim_animate - SafeWrite8(0x459E3B, regAnimCombatCheck); // reg_anim_animate_reverse - SafeWrite8(0x459EEB, regAnimCombatCheck); // reg_anim_obj_move_to_obj - SafeWrite8(0x459F9F, regAnimCombatCheck); // reg_anim_obj_run_to_obj - SafeWrite8(0x45A053, regAnimCombatCheck); // reg_anim_obj_move_to_tile - SafeWrite8(0x45A10B, regAnimCombatCheck); // reg_anim_obj_run_to_tile - SafeWrite8(0x45AE53, regAnimCombatCheck); // reg_anim_animate_forever - } -} - // this runs before actually loading/starting the game static void ClearGlobalScripts() { isGameLoading = true; diff --git a/sfall/Modules/ScriptExtender.h b/sfall/Modules/ScriptExtender.h index dc300d903..bde1fb274 100644 --- a/sfall/Modules/ScriptExtender.h +++ b/sfall/Modules/ScriptExtender.h @@ -71,8 +71,6 @@ long GetGlobalVarInternal(__int64 val); void __fastcall SetSelfObject(fo::Program* script, fo::GameObject* obj); -void _stdcall RegAnimCombatCheck(DWORD newValue); - bool _stdcall ScriptHasLoaded(fo::Program* script); // loads script from .int file into a sScriptProgram struct, filling script pointer and proc lookup table @@ -94,7 +92,6 @@ void AddProgramToMap(ScriptProgram &prog); ScriptProgram* GetGlobalScriptProgram(fo::Program* scriptPtr); // variables -static char regAnimCombatCheck = 1; extern DWORD isGlobalScriptLoading; extern DWORD availableGlobalScriptTypes; extern bool alwaysFindScripts; diff --git a/sfall/Modules/Scripting/Handlers/Anims.cpp b/sfall/Modules/Scripting/Handlers/Anims.cpp index ba80cdfe7..2e5ee370f 100644 --- a/sfall/Modules/Scripting/Handlers/Anims.cpp +++ b/sfall/Modules/Scripting/Handlers/Anims.cpp @@ -19,7 +19,6 @@ #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\..\SafeWrite.h" #include "..\..\Explosions.h" -#include "..\..\ScriptExtender.h" #include "..\OpcodeContext.h" #include "Anims.h" @@ -29,6 +28,23 @@ namespace sfall namespace script { +static char regAnimCombatCheck = 1; + +void RegAnimCombatCheck(DWORD newValue) { + char oldValue = regAnimCombatCheck; + regAnimCombatCheck = (newValue > 0); + if (oldValue != regAnimCombatCheck) { + SafeWrite8(0x459C97, regAnimCombatCheck); // reg_anim_func + SafeWrite8(0x459D4B, regAnimCombatCheck); // reg_anim_animate + SafeWrite8(0x459E3B, regAnimCombatCheck); // reg_anim_animate_reverse + SafeWrite8(0x459EEB, regAnimCombatCheck); // reg_anim_obj_move_to_obj + SafeWrite8(0x459F9F, regAnimCombatCheck); // reg_anim_obj_run_to_obj + SafeWrite8(0x45A053, regAnimCombatCheck); // reg_anim_obj_move_to_tile + SafeWrite8(0x45A10B, regAnimCombatCheck); // reg_anim_obj_run_to_tile + SafeWrite8(0x45AE53, regAnimCombatCheck); // reg_anim_animate_forever + } +} + // true if combat mode is active and combat check was not disabled bool checkCombatMode() { return (regAnimCombatCheck & fo::var::combat_state) != 0; diff --git a/sfall/Modules/Scripting/Handlers/Anims.h b/sfall/Modules/Scripting/Handlers/Anims.h index 74fd802b1..082bd1fc6 100644 --- a/sfall/Modules/Scripting/Handlers/Anims.h +++ b/sfall/Modules/Scripting/Handlers/Anims.h @@ -25,6 +25,8 @@ namespace script // new reg_anim functions (all using existing engine code) +void RegAnimCombatCheck(DWORD newValue); + class OpcodeContext; void sf_reg_anim_combat_check(OpcodeContext&); diff --git a/sfall/Modules/Stats.cpp b/sfall/Modules/Stats.cpp index 99115c58c..84cf7d7ab 100644 --- a/sfall/Modules/Stats.cpp +++ b/sfall/Modules/Stats.cpp @@ -255,19 +255,11 @@ static void SetStatValue(long* proto, long offset, long amount) { proto[offset] = amount; } -static long CopyProto(long pid, long* &proto_out) { - fo::Proto* _proto; - if (fo::func::proto_ptr(pid, &_proto) == -1) return 0; - proto_out = new long[104]; - memcpy(proto_out, _proto, 416); - return 1; -} - static long ApplyAllStatsToProto(const protoMem_iterator &iter, long* &proto_out) { long count = 0; for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { if (itBonus->objID == iter->first && itBonus->objPID == iter->second.pid) { - if (!proto_out && !CopyProto(iter->second.pid, proto_out)) return 0; + if (!proto_out && !fo::CritterCopyProto(iter->second.pid, proto_out)) return 0; itBonus->s_proto = proto_out; SetStatValue(proto_out, 44 + itBonus->stat, itBonus->amount); count++; @@ -275,7 +267,7 @@ static long ApplyAllStatsToProto(const protoMem_iterator &iter, long* &proto_out } for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { if (itBase->objID == iter->first && itBase->objPID == iter->second.pid) { - if (!proto_out && !CopyProto(iter->second.pid, proto_out)) return 0; + if (!proto_out && !fo::CritterCopyProto(iter->second.pid, proto_out)) return 0; itBase->s_proto = proto_out; SetStatValue(proto_out, 9 + itBase->stat, itBase->amount); count++; @@ -696,4 +688,4 @@ void _stdcall SetNPCStatMin(int stat, int i) { } } -} \ No newline at end of file +} diff --git a/sfall/Modules/Tiles.cpp b/sfall/Modules/Tiles.cpp index 3ba5d6148..b246099b2 100644 --- a/sfall/Modules/Tiles.cpp +++ b/sfall/Modules/Tiles.cpp @@ -29,6 +29,9 @@ namespace sfall { using namespace fo; +typedef int (_stdcall *functype)(); +static const functype art_init = (functype)fo::funcoffs::art_init_; + static const DWORD Tiles_0E[] = { 0x484255, 0x48429D, 0x484377, 0x484385, 0x48A897, 0x48A89A, 0x4B2231, 0x4B2374, 0x4B2381, 0x4B2480, 0x4B248D, 0x4B2A7C, 0x4B2BDA, @@ -50,40 +53,39 @@ static const DWORD Tiles_C0[] = { 0x4B247B, 0x4B2A77, 0x4B2BD5, }; +struct tilestruct { + short tile[2]; +}; + struct OverrideEntry { - //DWORD id; DWORD xtiles; DWORD ytiles; DWORD replacementid; - OverrideEntry(DWORD _xtiles, DWORD _ytiles, DWORD _repid) { - xtiles = _xtiles; - ytiles = _ytiles; - replacementid = _repid; + OverrideEntry(DWORD _xtiles, DWORD _ytiles, DWORD _repid) + : xtiles(_xtiles), ytiles(_ytiles), replacementid(_repid) { } }; static OverrideEntry** overrides; static DWORD origTileCount = 0; - -typedef int (_stdcall *functype)(); -static const functype _art_init = (functype)fo::funcoffs::art_init_; +static DWORD tileMode; static BYTE* mask; static void CreateMask() { - mask = new BYTE[80*36]; + mask = new BYTE[80 * 36]; fo::DbFile* file = fo::func::db_fopen("art\\tiles\\grid000.frm", "r"); - fo::func::db_fseek(file, 0x4a, 0); - fo::func::db_freadByteCount(file, mask, 80*36); + fo::func::db_fseek(file, 0x4A, 0); + fo::func::db_freadByteCount(file, mask, 80 * 36); fo::func::db_fclose(file); } static WORD ByteSwapW(WORD w) { - return ((w & 0xff) << 8) | ((w & 0xff00) >> 8); + return ((w & 0xFF) << 8) | ((w & 0xFF00) >> 8); } static DWORD ByteSwapD(DWORD w) { - return ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000) >> 24); + return ((w & 0xFF) << 24) | ((w & 0xFF00) << 8) | ((w & 0xFF0000) >> 8) | ((w & 0xFF000000) >> 24); } static int ProcessTile(fo::Art* tiles, int tile, int listpos) { @@ -94,7 +96,7 @@ static int ProcessTile(fo::Art* tiles, int tile, int listpos) { fo::DbFile* art = fo::func::db_fopen(buf, "r"); if (!art) return 0; - fo::func::db_fseek(art, 0x3e, 0); + fo::func::db_fseek(art, 0x3E, 0); WORD width; fo::func::db_freadShort(art, &width); //80; if (width == 80) { @@ -145,11 +147,8 @@ static int ProcessTile(fo::Art* tiles, int tile, int listpos) { return xsize * ysize; } -static DWORD tileMode; -static int _stdcall ArtInitHook2() { - if (_art_init()) { - return 1; - } +static int _stdcall ArtInitHook() { + if (art_init()) return -1; CreateMask(); @@ -186,27 +185,18 @@ static int _stdcall ArtInitHook2() { return 0; } -static void __declspec(naked) ArtInitHook() { +static void __declspec(naked) iso_init_hook() { __asm { - pushad; - mov eax, dword ptr ds:[FO_VAR_read_callback]; - push eax; - xor eax, eax; - mov dword ptr ds:[FO_VAR_read_callback], eax; - call ArtInitHook2; - pop eax; - mov dword ptr ds:[FO_VAR_read_callback], eax; - popad; - xor eax, eax; + mov ebx, dword ptr ds:[FO_VAR_read_callback]; + xor eax, eax; + mov dword ptr ds:[FO_VAR_read_callback], eax; + call ArtInitHook; + mov dword ptr ds:[FO_VAR_read_callback], ebx; retn; } } -struct tilestruct { - short tile[2]; -}; - -static void _stdcall SquareLoadCheck(tilestruct* data) { +static void __fastcall SquareLoadCheck(tilestruct* data) { for (DWORD y = 0; y < 100; y++) { for (DWORD x = 0; x < 100; x++) { for (DWORD z = 0; z < 2; z++) { @@ -226,16 +216,13 @@ static void _stdcall SquareLoadCheck(tilestruct* data) { } } -static void __declspec(naked) SquareLoadHook() { +static void __declspec(naked) square_load_hook() { __asm { - mov edi, edx; + mov ecx, edx; call fo::funcoffs::db_freadIntCount_; test eax, eax; - jnz end; - pushad; - push edi; - call SquareLoadCheck; - popad; + jnz end; + jmp SquareLoadCheck; end: retn; } @@ -270,11 +257,10 @@ static void __declspec(naked) art_get_name_hack() { } void Tiles::init() { - tileMode = GetConfigInt("Misc", "AllowLargeTiles", 0); - if (tileMode) { + if (tileMode = GetConfigInt("Misc", "AllowLargeTiles", 0)) { dlog("Applying allow large tiles patch.", DL_INIT); - HookCall(0x481D72, &ArtInitHook); - HookCall(0x48434C, SquareLoadHook); + HookCall(0x481D72, iso_init_hook); + HookCall(0x48434C, square_load_hook); dlogr(" Done", DL_INIT); } From 6ac9949feb132e9b2cfa4fe82ea7ae0fd4e03b28 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Mon, 13 May 2019 11:29:24 +0800 Subject: [PATCH 16/32] Added "dropping item on the character portrait" event to HOOK_INVENTORYMOVE. Rewrote get_ini_setting/string script functions in C++. Rewrote Ray's combat_p_proc fix. Minor code edits. --- artifacts/scripting/hookscripts.txt | 2 +- sfall/Modules/Combat.cpp | 4 +- sfall/Modules/DamageMod.cpp | 6 +- sfall/Modules/DamageMod.h | 4 +- sfall/Modules/HookScripts.h | 2 +- sfall/Modules/HookScripts/CombatHs.cpp | 18 ++-- sfall/Modules/HookScripts/InventoryHs.cpp | 91 ++++++++++++------- sfall/Modules/HookScripts/MiscHs.cpp | 6 +- sfall/Modules/HookScripts/ObjectHs.cpp | 10 +- sfall/Modules/Inventory.cpp | 2 +- sfall/Modules/MiscPatches.cpp | 61 +++---------- sfall/Modules/Scripting/Handlers/Misc.cpp | 106 +++------------------- sfall/Modules/Scripting/Handlers/Misc.h | 4 +- sfall/Modules/Scripting/Opcodes.cpp | 4 +- 14 files changed, 115 insertions(+), 205 deletions(-) diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 923f03268..6cf7a1760 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -421,7 +421,7 @@ What you can do: - add AP costs for all inventory movement including reloading - apply or remove some special scripted effects depending on PC's armor -int arg1 - Target slot (0 - main backpack, 1 - left hand, 2 - right hand, 3 - armor slot, 4 - weapon, when reloading it by dropping ammo, 5 - container, like bag/backpack, 6 - dropping on the ground, 7 - picking up item) +int arg1 - Target slot (0 - main backpack, 1 - left hand, 2 - right hand, 3 - armor slot, 4 - weapon, when reloading it by dropping ammo, 5 - container, like bag/backpack, 6 - dropping on the ground, 7 - picking up item, 8 - dropping item on the character portrait) Item arg2 - Item being moved Item arg3 - Item being replaced, weapon being reloaded, or container being filled (can be 0) diff --git a/sfall/Modules/Combat.cpp b/sfall/Modules/Combat.cpp index 24e22fa6b..64f1141d9 100644 --- a/sfall/Modules/Combat.cpp +++ b/sfall/Modules/Combat.cpp @@ -57,7 +57,7 @@ static DWORD __fastcall add_check_for_item_ammo_cost(register fo::GameObject* we rounds = fo::func::item_w_rounds(weapon); // ammo in burst } if (HookScripts::IsInjectHook(HOOK_AMMOCOST)) { - AmmoCostHook_Script(1, weapon, &rounds); // get rounds cost from hook + AmmoCostHook_Script(1, weapon, rounds); // get rounds cost from hook } else if (rounds == 1) { fo::func::item_w_compute_ammo_cost(weapon, &rounds); } @@ -125,7 +125,7 @@ static DWORD __fastcall divide_burst_rounds_by_ammo_cost(fo::GameObject* weapon, if (HookScripts::IsInjectHook(HOOK_AMMOCOST)) { rounds = burstRounds; // rounds in burst - AmmoCostHook_Script(2, weapon, &rounds); + AmmoCostHook_Script(2, weapon, rounds); } DWORD cost = burstRounds * rounds; // so much ammo is required for this burst diff --git a/sfall/Modules/DamageMod.cpp b/sfall/Modules/DamageMod.cpp index 953399dd8..2eb383add 100644 --- a/sfall/Modules/DamageMod.cpp +++ b/sfall/Modules/DamageMod.cpp @@ -32,7 +32,7 @@ int DamageMod::formula; // Damage Fix v5 (with v5.1 Damage Multiplier tweak) by Glovz 2014.04.16.xx.xx // TODO: rewrite in C++ -void DamageMod::DamageGlovz(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty) { +void DamageMod::DamageGlovz(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty) { if (rounds <= 0) return; int ammoY = fo::func::item_w_dam_div(ctd.weapon); // ammoY value (divisor) @@ -231,7 +231,7 @@ void DamageMod::DamageGlovz(fo::ComputeAttackResult &ctd, DWORD* accumulatedDama } // YAAM -void DamageMod::DamageYAAM(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty) { +void DamageMod::DamageYAAM(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty) { int ammoDiv = fo::func::item_w_dam_div(ctd.weapon); // Retrieve Ammo Divisor int ammoMult = fo::func::item_w_dam_mult(ctd.weapon); // Retrieve Ammo Dividend @@ -277,7 +277,7 @@ void DamageMod::DamageYAAM(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamag resistedDamage /= 100; // Resisted Damage = Resisted Damage / 100 rawDamage -= resistedDamage; // Raw Damage = Raw Damage - Resisted Damage - if (rawDamage > 0) *accumulatedDamage += rawDamage; // Accumulated Damage = Accumulated Damage + Raw Damage + if (rawDamage > 0) accumulatedDamage += rawDamage; // Accumulated Damage = Accumulated Damage + Raw Damage } } //////////////////////////////////////////////////////////////////////////////// diff --git a/sfall/Modules/DamageMod.h b/sfall/Modules/DamageMod.h index 1589a3973..91d8ec267 100644 --- a/sfall/Modules/DamageMod.h +++ b/sfall/Modules/DamageMod.h @@ -29,8 +29,8 @@ class DamageMod : public Module { void init(); static int formula; - static void DamageGlovz(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty); - static void DamageYAAM(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty); + static void DamageGlovz(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty); + static void DamageYAAM(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty); }; } diff --git a/sfall/Modules/HookScripts.h b/sfall/Modules/HookScripts.h index 61485585a..f36c3b859 100644 --- a/sfall/Modules/HookScripts.h +++ b/sfall/Modules/HookScripts.h @@ -98,7 +98,7 @@ void HookScriptClear(); void LoadHookScripts(); extern DWORD initingHookScripts; -extern int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD* rounds); +extern int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds); void _stdcall RunHookScriptsAtProc(DWORD procId); } diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 7ca4d8d3d..7013ee60b 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -178,7 +178,7 @@ static void __declspec(naked) ComputeDamageHook() { } } -static void __fastcall SubComputeDamageHook_Script(fo::ComputeAttackResult &ctd, DWORD armorDR, DWORD* accumulatedDamage, DWORD armorDT, DWORD bonusRangedDamage, DWORD rounds, DWORD multiplyDamage, DWORD difficulty) { +static void __fastcall SubComputeDamageHook_Script(fo::ComputeAttackResult &ctd, DWORD armorDR, DWORD &accumulatedDamage, DWORD armorDT, DWORD bonusRangedDamage, DWORD rounds, DWORD multiplyDamage, DWORD difficulty) { // calculated internal ammo mod damage switch (DamageMod::formula) { case 1: @@ -203,12 +203,12 @@ static void __fastcall SubComputeDamageHook_Script(fo::ComputeAttackResult &ctd, args[7] = bonusRangedDamage; args[8] = multiplyDamage; args[9] = difficulty; - args[10] = *accumulatedDamage; + args[10] = accumulatedDamage; args[11] = (DWORD)&ctd; RunHookScript(HOOK_SUBCOMBATDAMAGE); - if (cRet == 1) *accumulatedDamage = rets[0]; + if (cRet == 1) accumulatedDamage = rets[0]; EndHook(); } @@ -295,27 +295,27 @@ static void __declspec(naked) ItemDamageHook() { _asm jmp fo::funcoffs::roll_random_; } -int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD* rounds) { +int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds) { int result = 0; BeginHook(); argCount = 4; args[0] = (DWORD)weapon; - args[1] = *rounds; // rounds in attack + args[1] = rounds; // rounds in attack args[3] = hookType; if (hookType == 2) { // burst hook - *rounds = 1; // set default multiply for check burst attack + rounds = 1; // set default multiply for check burst attack } else { - result = fo::func::item_w_compute_ammo_cost(weapon, rounds); + result = fo::func::item_w_compute_ammo_cost(weapon, &rounds); if (result == -1) goto failed; // failed computed } - args[2] = *rounds; // rounds as computed by game (cost) + args[2] = rounds; // rounds as computed by game (cost) RunHookScript(HOOK_AMMOCOST); - if (cRet > 0) *rounds = rets[0]; // override rounds + if (cRet > 0) rounds = rets[0]; // override rounds failed: EndHook(); diff --git a/sfall/Modules/HookScripts/InventoryHs.cpp b/sfall/Modules/HookScripts/InventoryHs.cpp index 2142f8618..18be7a3b2 100644 --- a/sfall/Modules/HookScripts/InventoryHs.cpp +++ b/sfall/Modules/HookScripts/InventoryHs.cpp @@ -64,23 +64,6 @@ static void __declspec(naked) MoveCostHook() { } } -/* Common inventory move hook */ -static int __fastcall InventoryMoveHook_Script(DWORD itemReplace, DWORD item, int type) { - BeginHook(); - argCount = 3; - - args[0] = type; // event type - args[1] = item; // item being dropped - args[2] = itemReplace; // item being replaced here - - RunHookScript(HOOK_INVENTORYMOVE); - - int result = (cRet > 0) ? rets[0] : -1; - EndHook(); - - return result; -} - static int __fastcall SwitchHandHook_Script(fo::GameObject* item, fo::GameObject* itemReplaced, DWORD addr) { if (itemReplaced && fo::GetItemType(itemReplaced) == fo::item_type_weapon && fo::GetItemType(item) == fo::item_type_ammo) { return -1; // to prevent inappropriate hook call after dropping ammo on weapon @@ -111,21 +94,38 @@ static int __fastcall SwitchHandHook_Script(fo::GameObject* item, fo::GameObject */ static void _declspec(naked) SwitchHandHook() { __asm { - pushad; + pushadc; mov ecx, eax; // item being moved mov edx, [edx]; // other item - mov eax, [esp + 32]; // back address + mov eax, [esp + 12]; // back address push eax; call SwitchHandHook_Script; cmp eax, -1; // ret value - popad; + popadc; jne skip; - call fo::funcoffs::switch_hand_; + jmp fo::funcoffs::switch_hand_; skip: retn; } } +/* Common inventory move hook */ +static int __fastcall InventoryMoveHook_Script(DWORD itemReplace, DWORD item, int type) { + BeginHook(); + argCount = 3; + + args[0] = type; // event type + args[1] = item; // item being dropped + args[2] = itemReplace; // item being replaced here + + RunHookScript(HOOK_INVENTORYMOVE); + + int result = (cRet > 0) ? rets[0] : -1; + EndHook(); + + return result; +} + static const DWORD UseArmorHack_back = 0x4713AF; // normal operation (old 0x4713A9) static const DWORD UseArmorHack_skip = 0x471481; // skip code, prevent wearing armor // This hack is called when an armor is dropped into the armor slot at inventory screen @@ -133,34 +133,34 @@ static void _declspec(naked) UseArmorHack() { __asm { mov ecx, ds:[FO_VAR_i_worn]; // replacement item (override code) mov edx, [esp + 0x58 - 0x40]; // item - pushad; + push ecx; push 3; // event: armor slot - call InventoryMoveHook_Script; + call InventoryMoveHook_Script; // ecx - replacement item cmp eax, -1; // ret value - popad; - jne skip; - jmp UseArmorHack_back; + pop ecx; + jne skip; + jmp UseArmorHack_back; skip: - jmp UseArmorHack_skip; + jmp UseArmorHack_skip; } } static void _declspec(naked) MoveInventoryHook() { __asm { - pushad; - xor ecx, ecx; // no item replace - mov ebx, ecx; + pushadc; + xor eax, eax; + mov ecx, eax; // no item replace cmp dword ptr ds:[FO_VAR_curr_stack], 0; jle noCont; mov ecx, eax; // contaner ptr - mov ebx, 5; + mov eax, 5; noCont: - push ebx; // event: 0 - main backpack, 5 - contaner + push eax; // event: 0 - main backpack, 5 - contaner call InventoryMoveHook_Script; // edx - item cmp eax, -1; // ret value - popad; + popadc; jne skip; - call fo::funcoffs::item_add_force_; + jmp fo::funcoffs::item_add_force_; skip: retn; } @@ -307,6 +307,27 @@ static void __declspec(naked) PickupObjectHack() { } } +static void __declspec(naked) InvenPickupHook() { + __asm { + call fo::funcoffs::mouse_click_in_; + test eax, eax; + jnz runHook; + retn; +runHook: + cmp dword ptr ds:[FO_VAR_curr_stack], 0; + jnz skip; + mov edx, [esp + 0x58 - 0x40 + 4]; // item + xor ecx, ecx; // no itemReplace + push 8; // event: drop item on character portrait + call InventoryMoveHook_Script; + cmp eax, -1; // ret value + je skip; + xor eax, eax; // 0 - cancel, otherwise engine handler +skip: + retn; + } +} + /* Common InvenWield hook */ static bool InvenWieldHook_Script(int flag) { argCount = 4; @@ -453,6 +474,8 @@ void Inject_InventoryMoveHook() { SafeWrite32(0x49B665, 0x850FD285); // test edx, edx SafeWrite32(0x49B669, 0xC2); // jnz 0x49B72F SafeWrite8(0x49B66E, 0xFE); // cmp edi > cmp esi + + HookCall(0x471457, InvenPickupHook); } void Inject_InvenWieldHook() { diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 8b7c964c1..c1398c551 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -474,13 +474,13 @@ static void __declspec(naked) RestTimerEscapeHook() { } } -static int __fastcall ExplosiveTimerHook_Script(DWORD* type, DWORD item, DWORD time) { +static int __fastcall ExplosiveTimerHook_Script(DWORD &type, DWORD item, DWORD time) { BeginHook(); argCount = 3; args[0] = time; args[1] = item; - args[2] = (*type == 11) ? fo::ROLL_FAILURE : fo::ROLL_SUCCESS; + args[2] = (type == 11) ? fo::ROLL_FAILURE : fo::ROLL_SUCCESS; RunHookScript(HOOK_EXPLOSIVETIMER); @@ -490,7 +490,7 @@ static int __fastcall ExplosiveTimerHook_Script(DWORD* type, DWORD item, DWORD t if (cRet > 1) { int typeRet = rets[1]; if (typeRet >= fo::ROLL_CRITICAL_FAILURE && typeRet <= fo::ROLL_CRITICAL_SUCCESS) { - *type = (typeRet > fo::ROLL_FAILURE) ? 8 : 11; // returned new type (8 = SUCCESS, 11 = FAILURE) + type = (typeRet > fo::ROLL_FAILURE) ? 8 : 11; // returned new type (8 = SUCCESS, 11 = FAILURE) } } } diff --git a/sfall/Modules/HookScripts/ObjectHs.cpp b/sfall/Modules/HookScripts/ObjectHs.cpp index 62268cecd..5e3727c0f 100644 --- a/sfall/Modules/HookScripts/ObjectHs.cpp +++ b/sfall/Modules/HookScripts/ObjectHs.cpp @@ -169,24 +169,24 @@ static void __declspec(naked) DescriptionObjHook() { } } -static bool __fastcall SetLightingHook_Script(DWORD* intensity, DWORD* radius, DWORD object) { +static bool __fastcall SetLightingHook_Script(DWORD &intensity, DWORD &radius, DWORD object) { BeginHook(); argCount = 3; args[0] = object; - args[1] = *intensity; - args[2] = *radius; + args[1] = intensity; + args[2] = radius; RunHookScript(HOOK_SETLIGHTING); bool result = false; if (cRet > 0) { int light = rets[0]; if (light < 0) light = 0; - *intensity = light; + intensity = light; if (cRet > 1 && object != -1) { int dist = rets[1]; if (dist < 0) dist = 0; - *radius = dist; + radius = dist; } result = true; } diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index 5666dbae0..ea819e1b6 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -748,7 +748,7 @@ void Inventory::init() { BlockCall(0x4768A3); // mov ebx, 1 } - // Move items out of bag/backpack and back into the main inventory list by dragging them to character's image (similar to Fallout 1 behavior) + // Move items from bag/backpack to the main inventory list by dragging them on the character portrait (similar to Fallout 1 behavior) MakeCall(0x471452, inven_pickup_hack); // Move items to player's main inventory instead of the opened bag/backpack when confirming a trade diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index e1ebc99d9..83209493b 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -59,50 +59,14 @@ static const DWORD walkDistanceAddr[] = { 0x411FF0, 0x4121C4, 0x412475, 0x412906, }; -static void __declspec(naked) Combat_p_procFix() { +static void __declspec(naked) apply_damage_hack() { __asm { - push eax; - - mov eax, dword ptr ds:[FO_VAR_combat_state]; - cmp eax, 3; - jnz end_cppf; - - push esi; - push ebx; - push edx; - - mov esi, FO_VAR_main_ctd; - mov eax, [esi]; - mov ebx, [esi + 0x20]; - xor edx, edx; - mov eax, [eax + 0x78]; - call fo::funcoffs::scr_set_objs_; - mov eax, [esi]; - - cmp dword ptr ds:[esi + 0x2c], +0x0; - jng jmp1; - - test byte ptr ds:[esi + 0x15], 0x1; - jz jmp1; - mov edx, 0x2; - jmp jmp2; -jmp1: - mov edx, 0x1; -jmp2: - mov eax, [eax + 0x78]; - call fo::funcoffs::scr_set_ext_param_; - mov eax, [esi]; - mov edx, 0xd; - mov eax, [eax + 0x78]; - call fo::funcoffs::exec_script_proc_; - pop edx; - pop ebx; - pop esi; - -end_cppf: - pop eax; - call fo::funcoffs::stat_level_; - + xor edx, edx; + inc edx; // COMBAT_SUBTYPE_WEAPON_USED + test [esi + 0x15], dl; // ctd.flags2Source & DAM_HIT_ + jz end; // no hit + inc edx; // COMBAT_SUBTYPE_HIT_SUCCEEDED +end: retn; } } @@ -110,9 +74,9 @@ static void __declspec(naked) Combat_p_procFix() { static void __declspec(naked) WeaponAnimHook() { __asm { cmp edx, 11; - je c11; + je c11; cmp edx, 15; - je c15; + je c15; jmp fo::funcoffs::art_get_code_; c11: mov edx, 16; @@ -609,10 +573,9 @@ void DontTurnOffSneakIfYouRunPatch() { void CombatProcFix() { //Ray's combat_p_proc fix - dlog("Applying combat_p_proc fix.", DL_INIT); - HookCall(0x425252, Combat_p_procFix); - SafeWrite8(0x424DBC, 0xE9); - SafeWrite32(0x424DBD, 0x00000034); + dlog("Applying Ray's combat_p_proc patch.", DL_INIT); + MakeCall(0x424DD9, apply_damage_hack); + SafeWrite8(0x424DC7, 0x0); dlogr(" Done", DL_INIT); } diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index 154129d0f..9663bedc6 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -543,106 +543,30 @@ static int ParseIniSetting(const char* iniString, const char* &key, char section return 1; } -static char IniStrBuffer[128]; -static DWORD _stdcall GetIniSetting2(const char* c, DWORD string) { +static char IniStrBuffer[256]; +static DWORD GetIniSetting(const char* str, bool isString) { const char* key; char section[33], file[67]; - if (ParseIniSetting(c, key, section, file) < 0) { + if (ParseIniSetting(str, key, section, file) < 0) { return -1; } - if (string) { + if (isString) { IniStrBuffer[0] = 0; - GetPrivateProfileStringA(section, key, "", IniStrBuffer, 128, file); + GetPrivateProfileStringA(section, key, "", IniStrBuffer, 256, file); return (DWORD)&IniStrBuffer[0]; } else { return GetPrivateProfileIntA(section, key, -1, file); } } -void __declspec(naked) op_get_ini_setting() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov edi, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz error; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push 0; - push eax; - call GetIniSetting2; - mov edx, eax; - jmp result; -error: - mov edx, -1; -result: - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov edx, VAR_TYPE_INT; - mov eax, edi; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; - } +void sf_get_ini_setting(OpcodeContext& ctx) { + ctx.setReturn(GetIniSetting(ctx.arg(0).strValue(), false)); } -void __declspec(naked) op_get_ini_string() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov edi, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz error; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push 1; - push eax; - call GetIniSetting2; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretAddString_; - mov edx, eax; - mov ebx, VAR_TYPE_STR; - jmp result; -error: - xor edx, edx; - mov ebx, VAR_TYPE_INT -result: - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov edx, ebx; - mov eax, edi; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; - } +void sf_get_ini_string(OpcodeContext& ctx) { + DWORD result = GetIniSetting(ctx.arg(0).strValue(), true); + ctx.setReturn(result, (result != -1) ? DataType::STR : DataType::INT); } void __declspec(naked) op_get_uptime() { @@ -1326,20 +1250,20 @@ void sf_exec_map_update_scripts(OpcodeContext& ctx) { } void sf_set_ini_setting(OpcodeContext& ctx) { - const char* iniString = ctx.arg(0).asString(); const ScriptValue &argVal = ctx.arg(1); + const char* saveValue; if (argVal.isInt()) { _itoa_s(argVal.rawValue(), IniStrBuffer, 10); + saveValue = IniStrBuffer; } else { - strcpy_s(IniStrBuffer, argVal.asString()); + saveValue = argVal.strValue(); } - const char* key; char section[33], file[67]; - int result = ParseIniSetting(iniString, key, section, file); + int result = ParseIniSetting(ctx.arg(0).strValue(), key, section, file); if (result > 0) { - result = WritePrivateProfileString(section, key, IniStrBuffer, file); + result = WritePrivateProfileString(section, key, saveValue, file); } switch (result) { diff --git a/sfall/Modules/Scripting/Handlers/Misc.h b/sfall/Modules/Scripting/Handlers/Misc.h index 799ca360a..5a29befff 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.h +++ b/sfall/Modules/Scripting/Handlers/Misc.h @@ -66,9 +66,9 @@ void sf_inc_npc_level(OpcodeContext&); void sf_get_npc_level(OpcodeContext&); -void __declspec() op_get_ini_setting(); +void sf_get_ini_setting(OpcodeContext&); -void __declspec() op_get_ini_string(); +void sf_get_ini_string(OpcodeContext&); void __declspec() op_get_uptime(); diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index 10cae0bd4..22694036e 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -84,6 +84,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x19e, "get_sfall_global_int", sf_get_sfall_global_int, 1, true, {ARG_INTSTR}}, {0x19f, "get_sfall_global_float", sf_get_sfall_global_float, 1, true, {ARG_INTSTR}}, {0x1a5, "inc_npc_level", sf_inc_npc_level, 1, false, {ARG_INTSTR}}, + {0x1ac, "get_ini_setting", sf_get_ini_setting, 1, true, {ARG_STRING}}, {0x1c1, "has_fake_perk", sf_has_fake_perk, 1, true, {ARG_INTSTR}}, @@ -93,6 +94,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x1e1, "set_critical_table", sf_set_critical_table, 5, false, {ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {0x1e2, "get_critical_table", sf_get_critical_table, 4, true, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {0x1e3, "reset_critical_table", sf_reset_critical_table, 4, false, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, + {0x1eb, "get_ini_string", sf_get_ini_string, 1, true, {ARG_STRING}}, {0x1ec, "sqrt", sf_sqrt, 1, true, {ARG_NUMBER}}, {0x1ed, "abs", sf_abs, 1, true, {ARG_NUMBER}}, {0x1ee, "sin", sf_sin, 1, true, {ARG_NUMBER}}, @@ -314,7 +316,6 @@ void InitNewOpcodes() { opcodes[0x1a9] = op_set_viewport_y; opcodes[0x1aa] = op_set_xp_mod; opcodes[0x1ab] = op_set_perk_level_mod; - opcodes[0x1ac] = op_get_ini_setting; opcodes[0x1ad] = op_get_shader_version; opcodes[0x1ae] = op_set_shader_mode; opcodes[0x1af] = op_get_game_mode; @@ -365,7 +366,6 @@ void InitNewOpcodes() { opcodes[0x1e8] = op_set_unspent_ap_perk_bonus; opcodes[0x1e9] = op_get_unspent_ap_perk_bonus; opcodes[0x1ea] = op_init_hook; - opcodes[0x1eb] = op_get_ini_string; opcodes[0x1f2] = op_set_palette; opcodes[0x1f3] = op_remove_script; opcodes[0x1f4] = op_set_script; From 6a226aef3489f7284b3cc8c0fb69b2ec85299431 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 14 May 2019 06:38:57 +0800 Subject: [PATCH 17/32] Added a tweak to op_use_obj_on_obj_hack in BugFixes (from Mr.Stalin) Minor edits to code/documents. --- artifacts/scripting/sfall function notes.txt | 2 +- sfall/FalloutEngine/EngineUtils.cpp | 2 +- sfall/FalloutEngine/EngineUtils.h | 2 +- sfall/Modules/BugFixes.cpp | 6 ++++++ sfall/Modules/HookScripts/CombatHs.cpp | 6 +++--- sfall/Modules/HookScripts/InventoryHs.cpp | 14 +++++++------- sfall/Modules/HookScripts/MiscHs.cpp | 2 +- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index d80016d3c..14e4de8e5 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -594,7 +594,7 @@ Some utility/math functions are available: Optional arguments: - frame: frame number, the first frame starts from zero - x/y: offset relative to the top-left corner of the window -- width/height: image size, used to scale the image when displaying it. Pass -1 to either width or height to keep aspect ratio when scaling +- width/height: image size, used to scale the image when displaying it. Pass -1 to either width or height to keep the aspect ratio when scaling - noTransparent: pass true to display an image without transparent background - NOTE: to omit optional arguments starting from the right, call the functions with different sfall_funcX (e.g. sfall_func4("draw_image", pathFile, frame, x, y)) - if draw_image_scaled is called without x/y/width/height arguments, the image will be scaled to fit the window without transparent background diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index ffc808868..91691eff8 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -88,7 +88,7 @@ int __fastcall GetItemType(GameObject* item) { return fo::func::item_get_type(item); } -_declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot) { +__declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot) { GameObject* itemPtr = nullptr; switch (slot) { case fo::INVEN_TYPE_LEFT_HAND: diff --git a/sfall/FalloutEngine/EngineUtils.h b/sfall/FalloutEngine/EngineUtils.h index bad1502a1..55204c6ba 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -54,7 +54,7 @@ void SkillSetTags(long* tags, long num); int __fastcall GetItemType(GameObject* item); -_declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot); +__declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot); long& GetActiveItemMode(); diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 63b9b1277..ba18797e5 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -1879,6 +1879,10 @@ static void __declspec(naked) op_use_obj_on_obj_hack() { jz fail; mov edx, [eax + protoId]; shr edx, 24; + cmp dword ptr [esp + 4], eax; // target != source + jne skip; + xor edx, edx; // for calling obj_use_item_on_ instead of action_use_an_item_on_object_ +skip: retn; fail: add esp, 4; @@ -2567,6 +2571,8 @@ void BugFixes::init() } // Fix crash when calling use_obj/use_obj_on_obj without using set_self in global scripts + // also change the behavior of use_obj_on_obj function + // if the object uses the item on itself, then another function is called (not a bug fix) MakeCall(0x45C376, op_use_obj_on_obj_hack, 1); MakeCall(0x456A92, op_use_obj_hack, 1); diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 7013ee60b..6b62731cd 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -340,7 +340,7 @@ static void __declspec(naked) AmmoCostHook() { } // hooks combat_turn function -static void _declspec(naked) CombatTurnHook() { +static void __declspec(naked) CombatTurnHook() { __asm { HookBegin; mov args[0], 1; // turn begin @@ -380,7 +380,7 @@ static void _declspec(naked) CombatTurnHook() { // hack to exit from combat_add_noncoms function without crashing when you load game during NPC turn static const DWORD CombatHack_add_noncoms_back = 0x422359; -static void _declspec(naked) CombatAddNoncoms_CombatTurnHack() { +static void __declspec(naked) CombatAddNoncoms_CombatTurnHack() { __asm { call CombatTurnHook; cmp eax, -1; @@ -415,7 +415,7 @@ fo::GameObject* __fastcall ComputeExplosionOnExtrasHook_Script(fo::GameObject* o return result; } -static void _declspec(naked) ComputeExplosionOnExtrasHook() { +static void __declspec(naked) ComputeExplosionOnExtrasHook() { __asm { cmp dword ptr [esp + 0x34 + 4], 0x429533; // skip hook when AI assesses the situation in choosing the best weapon jz end; diff --git a/sfall/Modules/HookScripts/InventoryHs.cpp b/sfall/Modules/HookScripts/InventoryHs.cpp index 18be7a3b2..bf8ff0547 100644 --- a/sfall/Modules/HookScripts/InventoryHs.cpp +++ b/sfall/Modules/HookScripts/InventoryHs.cpp @@ -92,7 +92,7 @@ static int __fastcall SwitchHandHook_Script(fo::GameObject* item, fo::GameObject This hook is called every time an item is placed into either hand slot via inventory screen drag&drop If switch_hand_ function is not called, item is not placed anywhere (it remains in main inventory) */ -static void _declspec(naked) SwitchHandHook() { +static void __declspec(naked) SwitchHandHook() { __asm { pushadc; mov ecx, eax; // item being moved @@ -129,7 +129,7 @@ static int __fastcall InventoryMoveHook_Script(DWORD itemReplace, DWORD item, in static const DWORD UseArmorHack_back = 0x4713AF; // normal operation (old 0x4713A9) static const DWORD UseArmorHack_skip = 0x471481; // skip code, prevent wearing armor // This hack is called when an armor is dropped into the armor slot at inventory screen -static void _declspec(naked) UseArmorHack() { +static void __declspec(naked) UseArmorHack() { __asm { mov ecx, ds:[FO_VAR_i_worn]; // replacement item (override code) mov edx, [esp + 0x58 - 0x40]; // item @@ -145,7 +145,7 @@ static void _declspec(naked) UseArmorHack() { } } -static void _declspec(naked) MoveInventoryHook() { +static void __declspec(naked) MoveInventoryHook() { __asm { pushadc; xor eax, eax; @@ -266,7 +266,7 @@ static void __declspec(naked) DropIntoContainerHandSlotHack() { //static const DWORD DropAmmoIntoWeaponHack_back = 0x47658D; // proceed with reloading static const DWORD DropAmmoIntoWeaponHack_return = 0x476643; -static void _declspec(naked) DropAmmoIntoWeaponHook() { +static void __declspec(naked) DropAmmoIntoWeaponHook() { __asm { pushadc; mov ecx, ebp; // weapon ptr @@ -341,7 +341,7 @@ static bool InvenWieldHook_Script(int flag) { return result; // True - use engine handler } -static void _declspec(naked) InvenWieldFuncHook() { +static void __declspec(naked) InvenWieldFuncHook() { using namespace fo; __asm { HookBegin; @@ -370,7 +370,7 @@ static void _declspec(naked) InvenWieldFuncHook() { } // called when unwielding weapons -static void _declspec(naked) InvenUnwieldFuncHook() { +static void __declspec(naked) InvenUnwieldFuncHook() { __asm { HookBegin; mov args[0], eax; // critter @@ -397,7 +397,7 @@ static void _declspec(naked) InvenUnwieldFuncHook() { } } -static void _declspec(naked) CorrectFidForRemovedItemHook() { +static void __declspec(naked) CorrectFidForRemovedItemHook() { __asm { HookBegin; mov args[0], eax; // critter diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index c1398c551..7a0578a6a 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -498,7 +498,7 @@ static int __fastcall ExplosiveTimerHook_Script(DWORD &type, DWORD item, DWORD t return time; } -static void _declspec(naked) ExplosiveTimerHook() { +static void __declspec(naked) ExplosiveTimerHook() { __asm { push eax; // time in ticks for queue_add_ push edx; From 58d5271d4918a2d3f6aed2359a5135dd1ba04d1f Mon Sep 17 00:00:00 2001 From: NovaRain Date: Fri, 17 May 2019 09:47:38 +0800 Subject: [PATCH 18/32] Fixed two item pickup issues for NPC. * message for the player is displayed when the NPC cannot pick up item. * if in combat, NPC will also be stuck in a loop of picking up item. Fixed HideObjIsNullMsg option. --- sfall/Modules/BugFixes.cpp | 16 +++++++++++++++- sfall/Modules/DebugEditor.cpp | 11 +++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index ba18797e5..49b50a439 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -2086,10 +2086,20 @@ static void __declspec(naked) op_dialogue_reaction_hook() { } } +static void __declspec(naked) obj_pickup_hook() { + __asm { + cmp dword ptr ds:[FO_VAR_obj_dude], edi; + je dude; + jmp fo::funcoffs::item_add_force_; +dude: + jmp fo::funcoffs::item_add_mult_; + } +} + void BugFixes::init() { #ifndef NDEBUG - if (isDebug && (GetConfigInt("Debugging", "BugFixes", 1) == 0)) return; + if (isDebug && (GetPrivateProfileIntA("Debugging", "BugFixes", 1, ::sfall::ddrawIni) == 0)) return; #endif // Missing game initialization @@ -2640,6 +2650,10 @@ void BugFixes::init() // Fix the argument value of dialogue_reaction function HookCall(0x456FFA, op_dialogue_reaction_hook); + + // Fix for the incorrect message being displayed and NPC stuck in a loop in combat when the NPC cannot pick up an item + // due to not enough space in the inventory + HookCall(0x49B6E7, obj_pickup_hook); } } diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index d67a25860..0082e0a1c 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -267,14 +267,14 @@ void RunDebugEditor() { WSACleanup(); } +static const DWORD dbg_error_ret = 0x453FD8; static void __declspec(naked) dbg_error_hack() { __asm { cmp ebx, 1; je hide; sub esp, 0x104; - retn; + jmp dbg_error_ret; hide: - add esp, 4; // destroy this return addr pop esi; pop ecx; retn; @@ -313,6 +313,9 @@ void DebugModePatch() { } else { SafeWrite32(0x4C6D9C, (DWORD)debugGnw); } + if (GetPrivateProfileIntA("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { + MakeJump(0x453FD2, dbg_error_hack); + } dlogr(" Done", DL_INIT); } } @@ -338,10 +341,6 @@ void DebugEditor::init() { } }; } - - if (GetPrivateProfileIntA("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { - MakeCall(0x453FD2, dbg_error_hack, 1); - } } } From 16a0ef3bdf9e9fb778526e270c560775ac4fdb08 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sat, 18 May 2019 10:55:38 +0800 Subject: [PATCH 19/32] Improved the fix in previous commit. * now ignoring the inv capacity only happens in combat for preventing AI inf loop. The vanilla behavior still applies outside of combat. * added a correct message for NPC not being able to pick up an item. --- artifacts/config_files/Translations.ini | 1 + sfall/Modules/BugFixes.cpp | 36 ++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/artifacts/config_files/Translations.ini b/artifacts/config_files/Translations.ini index 756ec9258..6471fc09f 100644 --- a/artifacts/config_files/Translations.ini +++ b/artifacts/config_files/Translations.ini @@ -10,6 +10,7 @@ SaveSfallDataFail=ERROR saving extended savegame information! Check if other pro PartyLvlMsg=Lvl: PartyACMsg=AC: PartyAddictMsg=Addict +NPCPickupFail=%s cannot pick up the item. [AppearanceMod] RaceText=Race diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 49b50a439..43e366c8b 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -2088,14 +2088,41 @@ static void __declspec(naked) op_dialogue_reaction_hook() { static void __declspec(naked) obj_pickup_hook() { __asm { - cmp dword ptr ds:[FO_VAR_obj_dude], edi; + cmp edi, dword ptr ds:[FO_VAR_obj_dude]; je dude; + test byte ptr ds:[FO_VAR_combat_state], 1; // in combat? + jz dude; jmp fo::funcoffs::item_add_force_; dude: jmp fo::funcoffs::item_add_mult_; } } +static char pickupMessageBuf[65] = {0}; +static const char* __fastcall GetPickupMessage(const char* name) { + if (pickupMessageBuf[0] == 0) { + Translate("sfall", "NPCPickupFail", "%s cannot pick up the item.", pickupMessageBuf, 64); + } + sprintf(textBuf, pickupMessageBuf, name); + return textBuf; +} + +static void __declspec(naked) obj_pickup_hook_message() { + __asm { + cmp edi, dword ptr ds:[FO_VAR_obj_dude]; + je dude; + mov eax, edi; + call fo::funcoffs::critter_name_; + mov ecx, eax; + call GetPickupMessage; + mov [esp + 0x34 - 0x28 + 4], eax; + mov eax, 1; + retn; +dude: + jmp fo::funcoffs::message_search_; + } +} + void BugFixes::init() { #ifndef NDEBUG @@ -2115,7 +2142,7 @@ void BugFixes::init() SafeWrite16(0x46A566, 0x04DB); SafeWrite16(0x46A4E7, 0x04DB); - //if(GetConfigInt("Misc", "SpecialUnarmedAttacksFix", 1)) { + //if (GetConfigInt("Misc", "SpecialUnarmedAttacksFix", 1)) { dlog("Applying Special Unarmed Attacks fix.", DL_INIT); MakeJump(0x42394D, UnarmedAttacksFix); dlogr(" Done", DL_INIT); @@ -2651,9 +2678,10 @@ void BugFixes::init() // Fix the argument value of dialogue_reaction function HookCall(0x456FFA, op_dialogue_reaction_hook); - // Fix for the incorrect message being displayed and NPC stuck in a loop in combat when the NPC cannot pick up an item - // due to not enough space in the inventory + // Fix for NPC stuck in a loop of picking up items in combat and the incorrect message being displayed when the NPC cannot pick + // up an item due to not enough space in the inventory HookCall(0x49B6E7, obj_pickup_hook); + HookCall(0x49B71C, obj_pickup_hook_message); } } From 30ac2a969088b834b144d7bdf0595ba2a652e71e Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 19 May 2019 00:26:09 +0800 Subject: [PATCH 20/32] Slightly simplified the code of NPCStage6Fix. Moved NPCsTryToSpendExtraAP to AI.cpp. --- artifacts/ddraw.ini | 4 +- sfall/FalloutEngine/EngineUtils.cpp | 2 +- sfall/FalloutEngine/EngineUtils.h | 2 +- sfall/Modules/AI.cpp | 42 +++++++++++++++-- sfall/Modules/MiscPatches.cpp | 70 ++++++----------------------- 5 files changed, 57 insertions(+), 63 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 392de3b3f..64a4b0489 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -30,7 +30,7 @@ AllowSoundForFloats=1 ;This does not effect the play_sfall_sound and stop_sfall_sound script functions AllowDShowSound=0 -;Set to 1 to override the music path used by default if not present in the cfg +;Set to 1 to override the music path used by default (i.e. data\sound\music\) if not present in the cfg ;Set to 2 to overwrite all occurances of the music path OverrideMusicDir=2 @@ -256,7 +256,7 @@ Movie17=credits.mve ;StartDay=-1 ;To change the limit of the distance away from the player to which you're allowed to scroll the local maps, uncomment the next two lines -;Defaults are 0x1E0 in the x direction and 0x190 in the y direction. +;Defaults are 480 in the x direction and 400 in the y direction. ;Not compatible with the res patch! ;LocalMapXLimit=480 ;LocalMapYLimit=400 diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index 91691eff8..72d727d6e 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -84,7 +84,7 @@ void SkillSetTags(long* tags, long num) { fo::func::skill_set_tags(tags, num); } -int __fastcall GetItemType(GameObject* item) { +long __fastcall GetItemType(GameObject* item) { return fo::func::item_get_type(item); } diff --git a/sfall/FalloutEngine/EngineUtils.h b/sfall/FalloutEngine/EngineUtils.h index 55204c6ba..0e5e9b37c 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -52,7 +52,7 @@ void SkillGetTags(long* result, long num); // wrapper for skill_set_tags with bounds checking void SkillSetTags(long* tags, long num); -int __fastcall GetItemType(GameObject* item); +long __fastcall GetItemType(GameObject* item); __declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot); diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index 55c56e9e8..875c37004 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -26,11 +26,40 @@ namespace sfall { +using namespace fo; +using namespace Fields; -typedef std::unordered_map :: const_iterator iter; +typedef std::unordered_map::const_iterator iter; -static std::unordered_map targets; -static std::unordered_map sources; +static std::unordered_map targets; +static std::unordered_map sources; + +static DWORD RetryCombatLastAP; +static DWORD RetryCombatMinAP; +static void __declspec(naked) RetryCombatHook() { + __asm { + mov RetryCombatLastAP, 0; +retry: + call fo::funcoffs::combat_ai_; +process: + cmp dword ptr ds:[FO_VAR_combat_turn_running], 0; + jle next; + call fo::funcoffs::process_bk_; + jmp process; +next: + mov eax, [esi + movePoints]; + cmp eax, RetryCombatMinAP; + jl end; + cmp eax, RetryCombatLastAP; + je end; + mov RetryCombatLastAP, eax; + mov eax, esi; + xor edx, edx; + jmp retry; +end: + retn; + } +} static void __fastcall CombatAttackHook(DWORD source, DWORD target) { sources[target] = source; @@ -107,6 +136,13 @@ void AI::init() { MakeJump(0x45F6AF, BlockCombatHook1); // intface_use_item_ HookCall(0x4432A6, BlockCombatHook2); // game_handle_input_ combatBlockedMessage = Translate("sfall", "BlockedCombat", "You cannot enter combat at this time."); + + RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0); + if (RetryCombatMinAP > 0) { + dlog("Applying retry combat patch.", DL_INIT); + HookCall(0x422B94, RetryCombatHook); // combat_turn_ + dlogr(" Done", DL_INIT); + } } DWORD _stdcall AIGetLastAttacker(DWORD target) { diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index 83209493b..4e6d00420 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -243,60 +243,29 @@ static void __declspec(naked) CorpseHitFix2b() { } } -static DWORD RetryCombatLastAP; -static DWORD RetryCombatMinAP; -static void __declspec(naked) RetryCombatHook() { - using namespace fo; - using namespace Fields; - __asm { - mov RetryCombatLastAP, 0; -retry: - call fo::funcoffs::combat_ai_; -process: - cmp dword ptr ds:[FO_VAR_combat_turn_running], 0; - jle next; - call fo::funcoffs::process_bk_; - jmp process; -next: - mov eax, [esi + movePoints]; - cmp eax, RetryCombatMinAP; - jl end; - cmp eax, RetryCombatLastAP; - je end; - mov RetryCombatLastAP, eax; - mov eax, esi; - xor edx, edx; - jmp retry; -end: - retn; - } -} - static const DWORD NPCStage6Fix1End = 0x493D16; -static const DWORD NPCStage6Fix2End = 0x49423A; static void __declspec(naked) NPCStage6Fix1() { __asm { - mov eax,0xcc; // set record size to 204 bytes - imul eax,edx; // multiply by number of NPC records in party.txt - call fo::funcoffs::mem_malloc_; // malloc the necessary memory - mov edx,dword ptr ds:[FO_VAR_partyMemberMaxCount]; // retrieve number of NPC records in party.txt - mov ebx,0xcc; // set record size to 204 bytes - imul ebx,edx; // multiply by number of NPC records in party.txt - jmp NPCStage6Fix1End; // call memset to set all malloc'ed memory to 0 + mov eax, 204; // set record size to 204 bytes + imul eax, edx; // multiply by number of NPC records in party.txt + mov ebx, eax; // copy total record size for later memset + call fo::funcoffs::mem_malloc_; // malloc the necessary memory + jmp NPCStage6Fix1End; // call memset to set all malloc'ed memory to 0 } } +static const DWORD NPCStage6Fix2End = 0x49423A; static void __declspec(naked) NPCStage6Fix2() { __asm { - mov eax,0xcc; // record size is 204 bytes - imul edx,eax; // multiply by NPC number as listed in party.txt - mov eax,dword ptr ds:[FO_VAR_partyMemberAIOptions]; // get starting offset of internal NPC table - jmp NPCStage6Fix2End; // eax+edx = offset of specific NPC record + mov eax, 204; // record size is 204 bytes + imul edx, eax; // multiply by NPC number as listed in party.txt + mov eax, dword ptr ds:[FO_VAR_partyMemberAIOptions]; // get starting offset of internal NPC table + jmp NPCStage6Fix2End; // eax+edx = offset of specific NPC record } } -static const DWORD ScannerHookRet=0x41BC1D; -static const DWORD ScannerHookFail=0x41BC65; +static const DWORD ScannerHookRet = 0x41BC1D; +static const DWORD ScannerHookFail = 0x41BC65; static void __declspec(naked) ScannerAutomapHook() { using fo::PID_MOTION_SENSOR; __asm { @@ -605,21 +574,12 @@ void CorpseLineOfFireFix() { } } -void ApplyNpcExtraApPatch() { - RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0); - if (RetryCombatMinAP > 0) { - dlog("Applying retry combat patch.", DL_INIT); - HookCall(0x422B94, RetryCombatHook); // combat_turn_ - dlogr(" Done", DL_INIT); - } -} - void NpcStage6Fix() { if (GetConfigInt("Misc", "NPCStage6Fix", 0)) { dlog("Applying NPC Stage 6 Fix.", DL_INIT); MakeJump(0x493CE9, NPCStage6Fix1); - SafeWrite8(0x494063, 0x06); // loop should look for a potential 6th stage - SafeWrite8(0x4940BB, 0xCC); // move pointer by 204 bytes instead of 200 + SafeWrite8(0x494063, 6); // loop should look for a potential 6th stage + SafeWrite8(0x4940BB, 204); // move pointer by 204 bytes instead of 200 MakeJump(0x494224, NPCStage6Fix2); dlogr(" Done", DL_INIT); } @@ -906,8 +866,6 @@ void MiscPatches::init() { PlayIdleAnimOnReloadPatch(); CorpseLineOfFireFix(); - ApplyNpcExtraApPatch(); - SkilldexImagesPatch(); SpeedInterfaceCounterAnimsPatch(); From 3a86f1d13431659d913b7c85ebd9a72d254f8cae Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 19 May 2019 15:38:54 +0800 Subject: [PATCH 21/32] Added two fixes for NPC fleeing in combat. * added fix to allow fleeing NPC to use drugs. * fixed NPC stuck in fleeing mode when the hit chance of a target was too low. --- sfall/Modules/AI.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index 875c37004..bf4f27bf4 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -34,6 +34,30 @@ typedef std::unordered_map::const_iterator iter; static std::unordered_map targets; static std::unordered_map sources; +static void __declspec(naked) ai_try_attack_hook_FleeFix() { + __asm { + call fo::funcoffs::ai_run_away_; + mov dword ptr [esi + whoHitMe], 0; + and byte ptr [esi + combatState], ~4; // unset flee flag + retn; + } +} + +static const DWORD combat_ai_hook_flee_Ret = 0x42B22F; +static void __declspec(naked) combat_ai_hook_FleeFix() { + __asm { + call fo::funcoffs::ai_check_drugs_; // try to heal + test byte ptr [ebp], 4; // flee flag? (critter.combat_state) + jnz flee; + add esp, 4; + jmp combat_ai_hook_flee_Ret; +flee: + mov eax, esi + call fo::funcoffs::critter_name_; + retn; + } +} + static DWORD RetryCombatLastAP; static DWORD RetryCombatMinAP; static void __declspec(naked) RetryCombatHook() { @@ -143,6 +167,15 @@ void AI::init() { HookCall(0x422B94, RetryCombatHook); // combat_turn_ dlogr(" Done", DL_INIT); } + + /////////////////////// Combat AI behavior fixes /////////////////////// + + // Fix to allow fleeing NPC to use drugs + HookCall(0x42B1E3, combat_ai_hook_FleeFix); + // Fix for NPC stuck in fleeing mode when the hit chance of a target was too low + HookCalls(ai_try_attack_hook_FleeFix, {0x42ABA8, 0x42ACE5}); + // Disable fleeing when NPC cannot move closer to target + BlockCall(0x42ADF6); // ai_try_attack_ } DWORD _stdcall AIGetLastAttacker(DWORD target) { From 10a6b76d5b01013820de9bbb119edf1b68eacc9f Mon Sep 17 00:00:00 2001 From: NovaRain Date: Mon, 20 May 2019 11:25:42 +0800 Subject: [PATCH 22/32] Increased the max text width of the info card in the char screen. * the increased the width is according to the max width of one char in font (uppercase A seems to be the widest with 8 px). --- sfall/Modules/Console.cpp | 2 +- sfall/Modules/MiscPatches.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sfall/Modules/Console.cpp b/sfall/Modules/Console.cpp index 1648ad8fc..980a8eac8 100644 --- a/sfall/Modules/Console.cpp +++ b/sfall/Modules/Console.cpp @@ -51,7 +51,7 @@ static void __declspec(naked) ConsoleHook() { void Console::init() { auto path = GetConfigString("Misc", "ConsoleOutputPath", "", MAX_PATH); - if (path.size() > 0) { + if (!path.empty()) { consolefile.open(path); if (consolefile.is_open()) { MakeJump(0x43186C, ConsoleHook); diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index 4e6d00420..93427e18a 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -856,6 +856,9 @@ void MiscPatches::init() { dlogr(" Done", DL_INIT); } + // Increase the max text width of the information card in the character screen + SafeWriteBatch(144, {0x43ACD5, 0x43DD37}); // 136, 133 + LoadGameHook::OnBeforeGameStart() += BodypartHitChances; // set on start & load CombatProcFix(); From 119b0afacd0746bdbd5a762a61c7d7617a8fec56 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 22 May 2019 10:32:43 +0800 Subject: [PATCH 23/32] Fixed getting perks and traits of real_dude_obj while controlling other NPCs. * When switching control to party members, their own table of perks is copied into player's current perk table. Fixed the position of the items in active item slots after ending the NPC control (NPC's wielded weapon should always be in the right slot). --- sfall/FalloutEngine/EngineUtils.cpp | 9 ++-- sfall/FalloutEngine/EngineUtils.h | 3 +- sfall/FalloutEngine/Enums.h | 10 +++- sfall/Modules/PartyControl.cpp | 79 +++++++++++++++++++++-------- 4 files changed, 72 insertions(+), 29 deletions(-) diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index 72d727d6e..59adc0ceb 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -137,20 +137,21 @@ void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set) { } } -bool IsPartyMemberByPid(long pid) { +// Returns the number of party members in the existing table (begins from 1) +long IsPartyMemberByPid(long pid) { size_t patryCount = fo::var::partyMemberMaxCount; if (patryCount) { DWORD* memberPids = fo::var::partyMemberPidList; // pids from party.txt for (size_t i = 0; i < patryCount; i++) { - if (memberPids[i] == pid) return true;; + if (memberPids[i] == pid) return i + 1; } } - return false; + return 0; } bool IsPartyMember(fo::GameObject* critter) { if (critter->id < 18000) return false; - return IsPartyMemberByPid(critter->protoId); + return (IsPartyMemberByPid(critter->protoId) > 0); } //--------------------------------------------------------- diff --git a/sfall/FalloutEngine/EngineUtils.h b/sfall/FalloutEngine/EngineUtils.h index 0e5e9b37c..873515a5a 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -66,7 +66,8 @@ long CheckAddictByPid(fo::GameObject* critter, long pid); void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set); -bool IsPartyMemberByPid(long pid); +// Returns the number of party members in the existing table (begins from 1) +long IsPartyMemberByPid(long pid); bool IsPartyMember(fo::GameObject* critter); diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index 905c1a161..e39aad6f0 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -660,6 +660,12 @@ enum TicksTime : unsigned long ONE_GAME_YEAR = 315360000 }; +enum ActiveSlot : unsigned long +{ + Left = 0, + Right = 1 +}; + enum RollResult { ROLL_CRITICAL_FAILURE = 0x0, @@ -693,8 +699,8 @@ namespace Fields { scriptIndex = 0x80, }; - enum CritterObj : long - { + enum CritterObj : long + { reaction = 0x38, combatState = 0x3C, movePoints = 0x40, diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 2f08ee70e..7dceec589 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -131,18 +131,23 @@ static void SetCurrentDude(fo::GameObject* npc) { // reset traits fo::var::pc_trait[0] = fo::var::pc_trait[1] = -1; - // reset perks - for (int i = 0; i < fo::PERK_count; i++) { - fo::var::perkLevelDataList[i] = 0; + // copy existing party member perks or reset list for non-party member NPC + long isPartyMember = fo::IsPartyMemberByPid(npc->protoId); + if (isPartyMember) { + memcpy(fo::var::perkLevelDataList, fo::var::perkLevelDataList + (fo::PERK_count * (isPartyMember - 1)), sizeof(DWORD) * fo::PERK_count); + } else { + for (int i = 0; i < fo::PERK_count; i++) { + fo::var::perkLevelDataList[i] = 0; + } } // change character name fo::func::critter_pc_set_name(fo::func::critter_name(npc)); // change level - int level = fo::func::isPartyMember(npc) - ? fo::func::partyMemberGetCurLevel(npc) - : 0; + int level = (isPartyMember) // fo::func::isPartyMember(npc) + ? fo::func::partyMemberGetCurLevel(npc) + : 0; fo::var::Level_ = level; fo::var::last_level = level; @@ -155,10 +160,10 @@ static void SetCurrentDude(fo::GameObject* npc) { // deduce active hand by weapon anim code char critterAnim = (npc->artFid & 0xF000) >> 12; // current weapon as seen in hands - if (fo::AnimCodeByWeapon(fo::func::inven_left_hand(npc)) == critterAnim) { // definitely left hand.. - fo::var::itemCurrentItem = 0; + if (fo::AnimCodeByWeapon(fo::func::inven_left_hand(npc)) == critterAnim) { // definitely left hand + fo::var::itemCurrentItem = fo::ActiveSlot::Left; } else { - fo::var::itemCurrentItem = 1; + fo::var::itemCurrentItem = fo::ActiveSlot::Right; } bool isAddict = false; @@ -232,7 +237,7 @@ int __stdcall PartyControl::SwitchHandHook(fo::GameObject* item) { if (isControllingNPC && fo::func::item_get_type(item) == fo::ItemType::item_type_weapon) { int fId = fo::var::obj_dude->artFid; long weaponCode = fo::AnimCodeByWeapon(item); - fId = (fId & 0xffff0fff) | (weaponCode << 12); + fId = (fId & 0xFFFF0FFF) | (weaponCode << 12); if (!fo::func::art_exists(fId)) { DisplayCantDoThat(); return 1; @@ -241,28 +246,41 @@ int __stdcall PartyControl::SwitchHandHook(fo::GameObject* item) { return -1; } +long __fastcall GetRealDudePerk(fo::GameObject* source, long perk) { + if (isControllingNPC && source == realDude.obj_dude) { + return realDude.perkLevelDataList[perk]; + } + return fo::func::perk_level(source, perk); +} + +long __fastcall GetRealDudeTrait(fo::GameObject* source, long trait) { + if (isControllingNPC && source == realDude.obj_dude) { + return (trait == realDude.traits[0] || trait == realDude.traits[1]) ? 1 : 0; + } + return fo::func::trait_level(trait); +} + static void __declspec(naked) stat_pc_add_experience_hook() { __asm { - cmp isControllingNPC, 0 - je skip - add delayedExperience, esi - retn + cmp isControllingNPC, 0; + je skip; + add delayedExperience, esi; + retn; skip: - xchg esi, eax - jmp fo::funcoffs::stat_pc_add_experience_ + xchg esi, eax; + jmp fo::funcoffs::stat_pc_add_experience_; } } // prevents using sneak when controlling NPCs static void __declspec(naked) pc_flag_toggle_hook() { __asm { - cmp isControllingNPC, 0 - je end - call DisplayCantDoThat - retn + cmp isControllingNPC, 0; + je end; + call DisplayCantDoThat; + retn; end: - call fo::funcoffs::pc_flag_toggle_ - retn + jmp fo::funcoffs::pc_flag_toggle_; } } @@ -278,6 +296,19 @@ bool PartyControl::IsNpcControlled() { void PartyControl::SwitchToCritter(fo::GameObject* critter) { if (isControllingNPC) { + if (fo::var::itemCurrentItem == fo::ActiveSlot::Left) { + // set active left item to right slot + fo::GameObject* lItem = fo::func::inven_left_hand(fo::var::obj_dude); + if (lItem) { + fo::GameObject* rItem = fo::func::inven_right_hand(fo::var::obj_dude); + lItem->flags &= ~fo::ObjectFlag::Left_Hand; + lItem->flags |= fo::ObjectFlag::Right_Hand; + if (rItem) { + rItem->flags &= ~fo::ObjectFlag::Right_Hand; + rItem->flags |= fo::ObjectFlag::Left_Hand; + } + } + } if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(); } else { SaveRealDudeState(); @@ -287,6 +318,10 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) { if (switchHandHookInjected) return; switchHandHookInjected = true; if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); + // Gets dude perks and traits from script while controlling another NPC + // WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code + HookCall(0x458242, GetRealDudePerk); //op_has_trait_hook_ + HookCall(0x458326, GetRealDudeTrait); //op_has_trait_hook_ } } From f5317ad5ee735f94487b5b110040f2eadcbf1935 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 22 May 2019 10:41:37 +0800 Subject: [PATCH 24/32] Rewrote NPC fleeing fix. Removed DialogOptions9Lines option from ddraw.ini. --- artifacts/ddraw.ini | 3 --- sfall/Modules/AI.cpp | 31 +++++++++++++++++++++---------- sfall/Modules/BugFixes.cpp | 19 ++++++++----------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 64a4b0489..b98ca32c3 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -617,9 +617,6 @@ ReloadReserve=-1 ;Set to 1 to change the counter in the 'Move Items' window to start at the maximum number of items ItemCounterDefaultMax=0 -;Allows 9 options (lines of text) to be displayed correctly in a dialog window -DialogOptions9Lines=1 - ;Set to 1 to leave the music playing in dialogue with talking heads EnableMusicInDialogue=0 diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index bf4f27bf4..988f4f78d 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -36,25 +36,36 @@ static std::unordered_map sources; static void __declspec(naked) ai_try_attack_hook_FleeFix() { __asm { - call fo::funcoffs::ai_run_away_; - mov dword ptr [esi + whoHitMe], 0; - and byte ptr [esi + combatState], ~4; // unset flee flag - retn; + or byte ptr [esi + combatState], 8; // set new 'ReTarget' flag + jmp fo::funcoffs::ai_run_away_; } } static const DWORD combat_ai_hook_flee_Ret = 0x42B22F; static void __declspec(naked) combat_ai_hook_FleeFix() { __asm { - call fo::funcoffs::ai_check_drugs_; // try to heal + test byte ptr [ebp], 8; // 'ReTarget' flag + jnz reTarget; test byte ptr [ebp], 4; // flee flag? (critter.combat_state) - jnz flee; + jz tryHeal; +flee: + jmp fo::funcoffs::critter_name_; +tryHeal: + call fo::funcoffs::ai_check_drugs_; // try to heal + mov eax, esi; + mov edx, STAT_current_hp; + call fo::funcoffs::stat_level_; + cmp eax, [ebx + 0x10]; // minimum hp, below which NPC will run away + mov eax, esi; + jl flee; + add esp, 4; + jmp combat_ai_hook_flee_Ret; +reTarget: + and byte ptr [ebp], ~(4 | 8); // unset Flee/ReTarget flags + xor edi, edi; + mov dword ptr [esi + whoHitMe], edi; add esp, 4; jmp combat_ai_hook_flee_Ret; -flee: - mov eax, esi - call fo::funcoffs::critter_name_; - retn; } } diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 43e366c8b..d2ee6b0fd 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -449,15 +449,12 @@ static void __declspec(naked) partyMemberIncLevels_hook() { static void __declspec(naked) gdProcessUpdate_hack() { __asm { - add eax, esi - cmp eax, dword ptr ds:[FO_VAR_optionRect + 0xC] // _optionRect.offy - jge skip - add eax, 2 - push 0x44702D - retn + lea edx, [eax - 2]; + cmp edx, dword ptr ds:[FO_VAR_optionRect + 0xC]; // _optionRect.offy + jl skip; + mov eax, edx; skip: - push 0x4470DB - retn + retn; } } @@ -2215,11 +2212,11 @@ void BugFixes::init() dlogr(" Done", DL_INIT); // Allow 9 options (lines of text) to be displayed correctly in a dialog window - if (GetConfigInt("Misc", "DialogOptions9Lines", 1)) { + //if (GetConfigInt("Misc", "DialogOptions9Lines", 1)) { dlog("Applying 9 dialog options patch.", DL_INIT); - MakeJump(0x44701C, gdProcessUpdate_hack); + MakeCall(0x447021, gdProcessUpdate_hack, 1); dlogr(" Done", DL_INIT); - } + //} // Fix for "Unlimited Ammo" exploit dlog("Applying fix for Unlimited Ammo exploit.", DL_INIT); From 04d7f4aefa759b69830fd96a1c365171322c6921 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 23 May 2019 17:04:51 +0800 Subject: [PATCH 25/32] Removed LoadProtoMaxLimit option from ddraw.ini. * now the proto limit is always handled by set_proto_data function when needed, since the related issue was originally from using it to process lots of protos at once. --- artifacts/ddraw.ini | 4 ---- sfall/Modules/Objects.cpp | 13 +------------ 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index b98ca32c3..0fb211dd4 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -366,10 +366,6 @@ CorpseDeleteTime=6 ;Set to 1 (or some higher number if needed, the maximum is 127) to prevent 100% CPU use ProcessorIdle=-1 -;Set a number of how many protos per type can be loaded into memory at once (valid range: 512..4096) -;Set to -1 to let set_proto_data script function automatically increase the limit when needed -LoadProtoMaxLimit=-1 - ;Set to 1 if using the hero appearance mod ;You can add AppChCrt.frm and AppChEdt.frm files to art\intrface\ to set a custom background for the character screen EnableHeroAppearanceMod=0 diff --git a/sfall/Modules/Objects.cpp b/sfall/Modules/Objects.cpp index 7d54e5b2d..5531e8190 100644 --- a/sfall/Modules/Objects.cpp +++ b/sfall/Modules/Objects.cpp @@ -127,9 +127,7 @@ static void __declspec(naked) proto_ptr_hack() { } void Objects::LoadProtoAutoMaxLimit() { - if (maxCountProto != -1) { - MakeCall(0x4A21B2, proto_ptr_hack); - } + MakeCall(0x4A21B2, proto_ptr_hack); } void Objects::init() { @@ -137,15 +135,6 @@ void Objects::init() { RestoreObjUnjamAllLocks(); }; - int maxlimit = GetConfigInt("Misc", "LoadProtoMaxLimit", -1); - if (maxlimit != -1) { - maxCountProto = -1; - if (maxlimit > 512) { - if (maxlimit > 4096) maxlimit = 4096; - SafeWrite32(0x4A21B3, maxlimit); - } - } - HookCall(0x4A38A5, new_obj_id_hook); SafeWrite8(0x4A38B3, 0x90); // fix ID increment From fca3307bd98ff41671ae9878600b9841763146e9 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Fri, 24 May 2019 10:35:50 +0800 Subject: [PATCH 26/32] Rewrote the fix to allow fleeing NPC to use drugs. Code edits to Perks.cpp. --- sfall/Modules/AI.cpp | 45 +++++++++++++------- sfall/Modules/Objects.cpp | 6 +-- sfall/Modules/PartyControl.cpp | 10 ++--- sfall/Modules/Perks.cpp | 49 ++++++++++++---------- sfall/Modules/Scripting/Handlers/Perks.cpp | 38 ++++++++--------- 5 files changed, 82 insertions(+), 66 deletions(-) diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index 988f4f78d..070bb8bf1 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -44,22 +44,9 @@ static void __declspec(naked) ai_try_attack_hook_FleeFix() { static const DWORD combat_ai_hook_flee_Ret = 0x42B22F; static void __declspec(naked) combat_ai_hook_FleeFix() { __asm { - test byte ptr [ebp], 8; // 'ReTarget' flag + test byte ptr [ebp], 8; // 'ReTarget' flag (critter.combat_state) jnz reTarget; - test byte ptr [ebp], 4; // flee flag? (critter.combat_state) - jz tryHeal; -flee: jmp fo::funcoffs::critter_name_; -tryHeal: - call fo::funcoffs::ai_check_drugs_; // try to heal - mov eax, esi; - mov edx, STAT_current_hp; - call fo::funcoffs::stat_level_; - cmp eax, [ebx + 0x10]; // minimum hp, below which NPC will run away - mov eax, esi; - jl flee; - add esp, 4; - jmp combat_ai_hook_flee_Ret; reTarget: and byte ptr [ebp], ~(4 | 8); // unset Flee/ReTarget flags xor edi, edi; @@ -69,6 +56,31 @@ static void __declspec(naked) combat_ai_hook_FleeFix() { } } +static const DWORD combat_ai_hack_Ret = 0x42B204; +static void __declspec(naked) combat_ai_hack() { + __asm { + mov edx, [ebx + 0x10]; // cap.min_hp + cmp eax, edx; + jl tryHeal; // curr_hp < min_hp +end: + add esp, 4; + jmp combat_ai_hack_Ret; +tryHeal: + mov eax, esi; + call fo::funcoffs::ai_check_drugs_; + push edx; + mov eax, esi; + mov edx, STAT_current_hp; + call fo::funcoffs::stat_level_; + pop edx; + cmp eax, edx; // edx - minimum hp, below which NPC will run away + jge end; + retn; // flee + } +} + +//////////////////////////////////////////////////////////////////////////////// + static DWORD RetryCombatLastAP; static DWORD RetryCombatMinAP; static void __declspec(naked) RetryCombatHook() { @@ -96,6 +108,8 @@ static void __declspec(naked) RetryCombatHook() { } } +//////////////////////////////////////////////////////////////////////////////// + static void __fastcall CombatAttackHook(DWORD source, DWORD target) { sources[target] = source; targets[source] = target; @@ -182,8 +196,9 @@ void AI::init() { /////////////////////// Combat AI behavior fixes /////////////////////// // Fix to allow fleeing NPC to use drugs - HookCall(0x42B1E3, combat_ai_hook_FleeFix); + MakeCall(0x42B1DC, combat_ai_hack); // Fix for NPC stuck in fleeing mode when the hit chance of a target was too low + HookCall(0x42B1E3, combat_ai_hook_FleeFix); HookCalls(ai_try_attack_hook_FleeFix, {0x42ABA8, 0x42ACE5}); // Disable fleeing when NPC cannot move closer to target BlockCall(0x42ADF6); // ai_try_attack_ diff --git a/sfall/Modules/Objects.cpp b/sfall/Modules/Objects.cpp index 5531e8190..d296f6dee 100644 --- a/sfall/Modules/Objects.cpp +++ b/sfall/Modules/Objects.cpp @@ -26,7 +26,7 @@ namespace sfall { static int unjamTimeState; -static int maxCountProto = 512; +static int maxCountLoadProto = 512; long Objects::uniqueID = UniqueID::Start; // current counter id, saving to sfallgv.sav @@ -112,13 +112,13 @@ void RestoreObjUnjamAllLocks() { static void __declspec(naked) proto_ptr_hack() { __asm { - mov ecx, maxCountProto; + mov ecx, maxCountLoadProto; cmp ecx, 4096; jae skip; cmp eax, ecx; jb end; add ecx, 256; - mov maxCountProto, ecx; + mov maxCountLoadProto, ecx; skip: cmp eax, ecx; end: diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 7dceec589..6740d5ba8 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -141,9 +141,6 @@ static void SetCurrentDude(fo::GameObject* npc) { } } - // change character name - fo::func::critter_pc_set_name(fo::func::critter_name(npc)); - // change level int level = (isPartyMember) // fo::func::isPartyMember(npc) ? fo::func::partyMemberGetCurLevel(npc) @@ -152,6 +149,9 @@ static void SetCurrentDude(fo::GameObject* npc) { fo::var::Level_ = level; fo::var::last_level = level; + // change character name + fo::func::critter_pc_set_name(fo::func::critter_name(npc)); + // reset other stats fo::var::Experience_ = 0; fo::var::free_perk = 0; @@ -320,8 +320,8 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) { if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); // Gets dude perks and traits from script while controlling another NPC // WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code - HookCall(0x458242, GetRealDudePerk); //op_has_trait_hook_ - HookCall(0x458326, GetRealDudeTrait); //op_has_trait_hook_ + HookCall(0x458242, GetRealDudePerk); // op_has_trait_ + HookCall(0x458326, GetRealDudeTrait); // op_has_trait_ } } diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index 1fe06ea4c..f48044910 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -76,7 +76,7 @@ struct FakePerk { char Name[maxNameLen]; char Desc[maxDescLen]; char reserve[510]; // empty block - short id; // use last bytes of the description under the ID value for compatibility + short id; // perk id (use last bytes of the description under the ID value for compatibility) FakePerk() {} @@ -217,7 +217,11 @@ void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { } void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { - if (level < 0 || level > 100) return; + if (level > 100) { + level = 100; + } else if (level < 0 ) { + return; + } size_t size = fakePerks.size(); if (level == 0) { // remove perk from fakePerks for (size_t i = 0; i < size; i++) { @@ -242,7 +246,11 @@ void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { } void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { - if (active < 0 || active > 1) return; + if (active > 1) { + active = 1; + } else if (active < 0) { + return; + } size_t size = fakeTraits.size(); if (active == 0) { for (size_t i = 0; i < size; i++) { @@ -326,8 +334,8 @@ static DWORD HandleFakeTraits(int isSelect) { return isSelect; } -static long __fastcall PlayerHasPerk(int* isSelectPtr) { - *isSelectPtr = HandleFakeTraits(*isSelectPtr); +static long __fastcall PlayerHasPerk(int &isSelectPtr) { + isSelectPtr = HandleFakeTraits(isSelectPtr); for (int i = 0; i < PERK_count; i++) { if (fo::func::perk_level(fo::var::obj_dude, i)) return 0x43438A; // print perks @@ -335,7 +343,7 @@ static long __fastcall PlayerHasPerk(int* isSelectPtr) { return (!fakePerks.empty()) ? 0x43438A : 0x434446; // skip print perks } -static DWORD __fastcall HaveFakeTraits(int* isSelectPtr) { +static DWORD __fastcall HaveFakeTraits(int &isSelectPtr) { return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; } @@ -344,7 +352,7 @@ static void __declspec(naked) PlayerHasPerkHack() { push ecx; // isSelect mov ecx, esp; // ptr to isSelect call PlayerHasPerk; - pop ecx; // value from HandleFakeTraits + pop ecx; // isSelect value from HandleFakeTraits jmp eax; } } @@ -435,8 +443,7 @@ static void __declspec(naked) EndPerkLoopHack() { } } -// Build a table of perks ID numbers available for selection -// data buffer has limited size for 119 perks +// Build a table of perks ID numbers available for selection, data buffer has limited size for 119 perks static DWORD _stdcall HandleExtraSelectablePerks(DWORD available, DWORD* data) { size_t count = extPerks.size(); for (size_t i = 0; i < count; i++) { @@ -447,7 +454,7 @@ static DWORD _stdcall HandleExtraSelectablePerks(DWORD available, DWORD* data) { for (size_t i = 0; i < count; i++) { if (available >= 119) break; // for fake perks, their ID should start from 256 - data[available++] = startFakeID + i; //*(WORD*)(_name_sort_list + (offset+i)*8)=(WORD)(PERK_count+i); + data[available++] = startFakeID + i; } return available; // total number of perks available for selection } @@ -456,8 +463,7 @@ static void __declspec(naked) GetAvailablePerksHook() { __asm { push ecx; push edx; // arg data - mov ecx, IgnoringDefaultPerks; - test ecx, ecx; + cmp IgnoringDefaultPerks, 0; jnz skipDefaults; call fo::funcoffs::perk_make_list_; // return available count jmp next; @@ -587,7 +593,7 @@ static void _stdcall AddFakePerk(DWORD perkID) { } } if (!matched) { // add to fakePerks - RemovePerkID.push_back(count); // index of the added perk + RemovePerkID.push_back(count); // index of the added perk int index = PerkSearchID(perkID); fakePerks.emplace_back(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc, extPerks[index].id); // id same as perkID } @@ -626,8 +632,7 @@ static void _stdcall AddFakePerk(DWORD perkID) { } } if (addPerkMode & 4) { // delete from selectable perks - RemoveSelectableID.push_back(perkID); - //fakeSelectablePerks.remove_at(perkID); + RemoveSelectableID.push_back(perkID); //fakeSelectablePerks.remove_at(perkID); } } @@ -1282,15 +1287,15 @@ void _stdcall AddPerkMode(DWORD mode) { addPerkMode = mode; } -DWORD HasFakePerk(const char* name, long id) { - if (id < PERK_count && name[0] == 0) return 0; +DWORD HasFakePerk(const char* name, long perkId) { + if (perkId < PERK_count && name[0] == 0) return 0; for (DWORD i = 0; i < fakePerks.size(); i++) { - if (id) { - if (fakePerks[i].id == id) return fakePerks[i].Level; - } else { - if (!strcmp(name, fakePerks[i].Name)) { - return fakePerks[i].Level; // current perk level + if (perkId) { + if (fakePerks[i].id == perkId) { + return fakePerks[i].Level; } + } else if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; // current perk level } } return 0; diff --git a/sfall/Modules/Scripting/Handlers/Perks.cpp b/sfall/Modules/Scripting/Handlers/Perks.cpp index df01bedaf..04dd7e8c2 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.cpp +++ b/sfall/Modules/Scripting/Handlers/Perks.cpp @@ -511,28 +511,24 @@ void __declspec(naked) op_perk_add_mode() { void __declspec(naked) op_remove_trait() { __asm { - pushad; - mov ebp, eax; - call fo::funcoffs::interpretPopShort_; - mov edi, eax; - mov eax, ebp; - call fo::funcoffs::interpretPopLong_; - cmp di, VAR_TYPE_INT; - jnz end; - xor ebx, ebx; - dec ebx; - mov ecx, ds:[FO_VAR_pc_trait + 4]; - cmp eax, ds:[FO_VAR_pc_trait]; - jne next; - mov ds:[FO_VAR_pc_trait], ecx; - mov ds:[FO_VAR_pc_trait + 4], ebx; - jmp end; -next: - cmp eax, ds:[FO_VAR_pc_trait + 4]; - jne end; - mov ds:[FO_VAR_pc_trait + 4], ebx; + push ecx; + _GET_ARG_INT(end); + test eax, eax; + jl end; + mov edx, -1; + cmp eax, ds:[FO_VAR_pc_trait]; + jne next; + mov ecx, ds:[FO_VAR_pc_trait2]; + mov ds:[FO_VAR_pc_trait], ecx; + mov ds:[FO_VAR_pc_trait2], edx; end: - popad; + pop ecx; + retn; +next: + cmp eax, ds:[FO_VAR_pc_trait2]; + jne end; + mov ds:[FO_VAR_pc_trait2], edx; + pop ecx; retn; } } From 22cdc32d26f50acbaf75f9917ac276aea6c61362 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Fri, 24 May 2019 20:00:07 +0800 Subject: [PATCH 27/32] Simplified the ASM code of combat_ai_hack. --- sfall/Modules/AI.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index 070bb8bf1..d0d83e830 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -59,8 +59,7 @@ static void __declspec(naked) combat_ai_hook_FleeFix() { static const DWORD combat_ai_hack_Ret = 0x42B204; static void __declspec(naked) combat_ai_hack() { __asm { - mov edx, [ebx + 0x10]; // cap.min_hp - cmp eax, edx; + cmp eax, [ebx + 0x10]; // cap.min_hp - minimum hp, below which NPC will run away jl tryHeal; // curr_hp < min_hp end: add esp, 4; @@ -68,12 +67,10 @@ static void __declspec(naked) combat_ai_hack() { tryHeal: mov eax, esi; call fo::funcoffs::ai_check_drugs_; - push edx; mov eax, esi; mov edx, STAT_current_hp; call fo::funcoffs::stat_level_; - pop edx; - cmp eax, edx; // edx - minimum hp, below which NPC will run away + cmp eax, [ebx + 0x10]; // cap.min_hp jge end; retn; // flee } From aa221adea3139c6168b11f39f992070f97fc11fa Mon Sep 17 00:00:00 2001 From: NovaRain Date: Mon, 27 May 2019 12:01:09 +0800 Subject: [PATCH 28/32] Fix for fake perks related functions. * now fake perks are no longer added to all controlled critters. Added alternative functions that allow to add fake perks to party member NPC. Rewrote set_fake_perk/trait, set_selectable_perk, has_fake_perk/trait functions in C++. --- artifacts/scripting/headers/sfall.h | 10 + artifacts/scripting/sfall function notes.txt | 15 +- sfall/FalloutEngine/Enums.h | 4 +- sfall/Modules/Perks.cpp | 150 ++++++--- sfall/Modules/Perks.h | 18 +- sfall/Modules/Scripting/Handlers/Metarule.cpp | 6 + sfall/Modules/Scripting/Handlers/Perks.cpp | 317 ++++-------------- sfall/Modules/Scripting/Handlers/Perks.h | 18 +- sfall/Modules/Scripting/Opcodes.cpp | 8 +- 9 files changed, 224 insertions(+), 322 deletions(-) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index e367074af..65c085e8a 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -238,6 +238,11 @@ #define party_member_list_critters party_member_list(0) #define party_member_list_all party_member_list(1) +// fake perks/traits add mode flags +#define ADD_PERK_MODE_TRAIT (1) // add to the player's traits +#define ADD_PERK_MODE_PERK (2) // add to the player's perks +#define ADD_PERK_MODE_REMOVE (4) // remove from the list of selectable perks + // sfall_funcX macros #define add_iface_tag sfall_func0("add_iface_tag") #define art_cache_clear sfall_func0("art_cache_clear") @@ -264,6 +269,8 @@ #define get_object_data(obj, offset) sfall_func2("get_object_data", obj, offset) #define get_outline(obj) sfall_func1("get_outline", obj) #define get_string_pointer(text) sfall_func1("get_string_pointer", text) +#define has_fake_perk_npc(npc, perk) sfall_func2("has_fake_perk_npc", npc, perk) +#define has_fake_trait_npc(npc, trait) sfall_func2("has_fake_trait_npc", npc, trait) #define intface_hide sfall_func0("intface_hide") #define intface_is_hidden sfall_func0("intface_is_hidden") #define intface_redraw sfall_func0("intface_redraw") @@ -282,6 +289,8 @@ #define set_cursor_mode(mode) sfall_func1("set_cursor_mode", mode) #define set_drugs_data(type, pid, value) sfall_func3("set_drugs_data", type, pid, value) #define set_dude_obj(critter) sfall_func1("set_dude_obj", critter) +#define set_fake_perk_npc(npc, perk, level, image, desc) sfall_func5("set_fake_perk_npc", npc, perk, level, image, desc) +#define set_fake_trait_npc(npc, trait, active, image, desc) sfall_func5("set_fake_trait_npc", npc, trait, active, image, desc) #define set_flags(obj, flags) sfall_func2("set_flags", obj, flags) #define set_iface_tag_text(tag, text, color) sfall_func3("set_iface_tag_text", tag, text, color) #define set_ini_setting(setting, value) sfall_func2("set_ini_setting", setting, value) @@ -290,6 +299,7 @@ #define set_outline(obj, color) sfall_func2("set_outline", obj, color) #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_selectable_perk_npc(npc, perk, active, image, desc) sfall_func5("set_selectable_perk_npc", npc, perk, active, image, desc) #define set_unique_id(obj) sfall_func1("set_unique_id", obj) #define unset_unique_id(obj) sfall_func2("set_unique_id", obj, -1) #define set_unjam_locks_time(time) sfall_func1("set_unjam_locks_time", time) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 14e4de8e5..7bc484028 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -32,7 +32,7 @@ set_shader_mode tells sfall when to use a shader. The parameter is a set of 32 f force_graphics_refresh forces the screen to redraw at times when it normally wouldn't. If you're using animated shader, turning this option on is recommended. -The mapper manual lists the functions 'world_map_x_pos' and 'world_map_y_pos', which supposedly return the players x and y positions on the world map. get_world_map_x/y_pos are included here anyway, because I was unable to get those original functions to work, or even to find any evidence that they existed in game. +The mapper manual lists the functions 'world_map_x_pos' and 'world_map_y_pos', which supposedly return the player's x and y positions on the world map. get_world_map_x/y_pos are included here anyway, because I was unable to get those original functions to work, or even to find any evidence that they existed in game. set_pipboy_available will only accept 0 or 1 as an argument. Using any other value will cause the function to have no effect. Use 0 to disable the pipboy, and 1 to enable it. @@ -42,13 +42,13 @@ The 'type' value in the weapon knockback functions can be 0 or 1. If 0, the valu The get/set_sfall_global functions require an 8 characters long case sensitive string for the variable name. The variables behave the same as normal Fallout globals, except that they don't have to be declared beforehand in vault13.gam. Trying to get a variable which hasn't been set will always return 0. These functions are intended for use when a patch to a mod requires the addition of a new global variable, a case which would otherwise require the player to start a new game. -set_pickpocket_max and set_hit_chance_max effect all critters rather than just the player. set_skill_max can't be used to increase the skill cap above 300. set_perk_level_mod sets a modifier between +25 and -25 that is added/subtracted from the players level for the purposes of deciding which perks can be chosen. +set_pickpocket_max and set_hit_chance_max effect all critters rather than just the player. set_skill_max can't be used to increase the skill cap above 300. set_perk_level_mod sets a modifier between +25 and -25 that is added/subtracted from the player's level for the purposes of deciding which perks can be chosen. set_fake_trait and set_fake_perk can be used to add additional traits and perks to the character screen. They will be saved correctly when the player saves and reloads games, but by themselves they will have no further effect on the character. For perks, the allowed range for levels is between 0 and 100; setting the level to 0 removes that perk. For traits, the level must be 0 or 1. The image is a numeric id that corresponds to an entry in skilldex.lst. The name is limited to 63 characters and the description to 255 characters by sfall, but internal Fallout limits may be lower. has_fake_trait and has_fake_perk return the number of levels the player has of the perks/traits with the given name or ID of extra perk. -perk_add_mode, set_selectable_perk, set_perkbox_title, hide_real_perks, show_real_perks and clear_selectable_perks control the behaviour of the select a perk box. set_selectable_perk can be used to add additional items by setting the 'active' parameter to 1, and to remove them again by setting it to 0. set_perkbox_title can be used to change the title of the box, or by using "" it will be set back to the default. hide and show_real_perks can be used to prevent the dialog from displaying any of the original 119 perks. perk_add_mode modifies what happens when a fake perk is selected from the perks dialog. It is treated as a set of flags - if bit 1 is set then it is added to the players traits, if bit 2 is set it is added to the players perks, and if bit 3 is set it is removed from the list of selectable perks. The default is 0x2. clear_selectable_perks restores the dialog to its default state. +perk_add_mode, set_selectable_perk, set_perkbox_title, hide_real_perks, show_real_perks and clear_selectable_perks control the behaviour of the select a perk box. set_selectable_perk can be used to add additional items by setting the 'active' parameter to 1, and to remove them again by setting it to 0. set_perkbox_title can be used to change the title of the box, or by using "" it will be set back to the default. hide and show_real_perks can be used to prevent the dialog from displaying any of the original 119 perks. perk_add_mode modifies what happens when a fake perk is selected from the perks dialog. It is treated as a set of flags - if bit 1 is set then it is added to the player's traits, if bit 2 is set it is added to the player's perks, and if bit 3 is set it is removed from the list of selectable perks. The default is 0x2. clear_selectable_perks restores the dialog to its default state. show_iface_tag, hide_iface_tag and is_iface_tag_active relate to the boxes that appear above the interface such as SNEAK and LEVEL. You can use 3 for LEVEL and 4 for ADDICT, or the range from 5 to (4 + the value of BoxBarCount in ddraw.ini) for custom boxes. Remember to add your messages to intrface.msg and set up the font colours in ddraw.ini if you're going to use custom boxes. Starting from sfall 4.1, is_iface_tag_active can also be used to check 0 for SNEAK, 1 for POISONED, and 2 for RADIATED. @@ -591,7 +591,7 @@ Some utility/math functions are available: > void sfall_func6("draw_image_scaled", string/int pathFile/artId, int frame, int x, int y, int width, int height) - displays the specified FRM image in the active window created by vanilla CreateWin or sfall's create_win script function - pathFile/artId: path to the FRM file (e.g. "art\\inven\\5mmap.frm"), or its FRM ID number (e.g. 117440550, see specification of the FID format) -Optional arguments: +optional arguments: - frame: frame number, the first frame starts from zero - x/y: offset relative to the top-left corner of the window - width/height: image size, used to scale the image when displaying it. Pass -1 to either width or height to keep the aspect ratio when scaling @@ -599,6 +599,13 @@ Optional arguments: - NOTE: to omit optional arguments starting from the right, call the functions with different sfall_funcX (e.g. sfall_func4("draw_image", pathFile, frame, x, y)) - if draw_image_scaled is called without x/y/width/height arguments, the image will be scaled to fit the window without transparent background +> void sfall_func5("set_fake_perk_npc", object npc, string namePerk, int level, int image, string desc) +> void sfall_func5("set_fake_trait_npc", object npc, string nameTrait, int active, int image, string desc) +> void sfall_func5("set_selectable_perk_npc", object npc, string namePerk, int active, int image, string desc) +> int sfall_func2("has_fake_perk_npc", object npc, string namePerk) +> int sfall_func2("has_fake_trait_npc", object npc, string nameTrait) +- functions are similar to has_fake_*/set_fake_*/set_selectable_perk opcodes, but work on the specified party member NPC (including dude_obj) + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index e39aad6f0..74c9564ea 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -641,7 +641,9 @@ enum BodyType : long Robotic = 2 }; -#define OBJFLAG_CAN_WEAR_ITEMS (0xf000000) +#define PLAYER_ID (18000) + +#define OBJFLAG_CAN_WEAR_ITEMS (0xF000000) #define OBJFLAG_HELD_IN_RIGHT (0x10000) #define OBJFLAG_HELD_IN_LEFT (0x20000) diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index f48044910..738ab4986 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -21,6 +21,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "LoadGameHook.h" +#include "PartyControl.h" #include "Perks.h" @@ -75,12 +76,13 @@ struct FakePerk { int Image; char Name[maxNameLen]; char Desc[maxDescLen]; - char reserve[510]; // empty block + char reserve[506]; // empty block + int ownerId; // 0 = the player, or ID number of party member NPC short id; // perk id (use last bytes of the description under the ID value for compatibility) FakePerk() {} - FakePerk(char* _name, int _level, int _image, char* _desc, short _id = -1) : id(_id), Name {0}, Desc {0}, reserve {0} { + FakePerk(const char* _name, int _level, int _image, const char* _desc, int npcId, short _id = -1) : ownerId(npcId), id(_id), Name {0}, Desc {0}, reserve {0} { Level = _level; Image = _image; strncpy_s(this->Name, _name, _TRUNCATE); @@ -151,7 +153,7 @@ static long _stdcall LevelUp() { } } - int level = fo::var::Level_; // Get players level + int level = fo::var::Level_; // Get player's level if (!((level + 1) % eachLevel)) fo::var::free_perk++; // Increment the number of perks owed return level; } @@ -191,11 +193,21 @@ void _stdcall SetPerkboxTitle(char* name) { } } -void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { - if (active < 0 || active > 1) return; +static bool IsOwnedFake(int ownerId) { + return ((!ownerId && !PartyControl::IsNpcControlled()) || ownerId == fo::var::obj_dude->id); +} + +static bool IsNotOwnedFake(int ownerId1, int ownerId2) { + return (ownerId1 != ownerId2); +} + +void Perks::SetSelectablePerk(const char* name, int active, int image, const char* desc, int npcID) { + if (active < 0) return; + if (active > 1) active = 1; size_t size = fakeSelectablePerks.size(); if (active == 0) { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeSelectablePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks.erase(fakeSelectablePerks.begin() + i); return; @@ -203,6 +215,7 @@ void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { } } else { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeSelectablePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks[i].Level = active; fakeSelectablePerks[i].Image = image; @@ -212,19 +225,17 @@ void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { } } if (size == fakeSelectablePerks.capacity()) fakeSelectablePerks.reserve(size + 10); - fakeSelectablePerks.emplace_back(name, active, image, desc); + fakeSelectablePerks.emplace_back(name, active, image, desc, npcID); } } -void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { - if (level > 100) { - level = 100; - } else if (level < 0 ) { - return; - } +void Perks::SetFakePerk(const char* name, int level, int image, const char* desc, int npcID) { + if (level < 0) return; + if (level > 100) level = 100; size_t size = fakePerks.size(); if (level == 0) { // remove perk from fakePerks for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakePerks[i].Name)) { fakePerks.erase(fakePerks.begin() + i); return; @@ -232,6 +243,7 @@ void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { } } else { // add or change the existing fake perk in fakePerks for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakePerks[i].Name)) { fakePerks[i].Level = level; fakePerks[i].Image = image; @@ -241,19 +253,17 @@ void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { } } if (size == fakePerks.capacity()) fakePerks.reserve(size + 10); - fakePerks.emplace_back(name, level, image, desc); + fakePerks.emplace_back(name, level, image, desc, npcID); } } -void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { - if (active > 1) { - active = 1; - } else if (active < 0) { - return; - } +void Perks::SetFakeTrait(const char* name, int active, int image, const char* desc, int npcID) { + if (active < 0) return; + if (active > 1) active = 1; size_t size = fakeTraits.size(); if (active == 0) { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, npcID)) continue; if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits.erase(fakeTraits.begin() + i); return; @@ -261,6 +271,7 @@ void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { } } else { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, npcID)) continue; if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits[i].Level = active; fakeTraits[i].Image = image; @@ -270,7 +281,7 @@ void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { } } if (size == fakeTraits.capacity()) fakeTraits.reserve(size + 5); - fakeTraits.emplace_back(name, active, image, desc); + fakeTraits.emplace_back(name, active, image, desc, npcID); } } @@ -279,7 +290,8 @@ static DWORD _stdcall HaveFakePerks() { } static long __fastcall GetFakePerkLevel(int id) { - return fakePerks[id - PERK_count].Level; + int i = id - PERK_count; + return IsOwnedFake(fakePerks[i].ownerId) ? fakePerks[i].Level : 0; } static long __fastcall GetFakePerkImage(int id) { @@ -290,16 +302,24 @@ static FakePerk* __fastcall GetFakePerk(int id) { return &fakePerks[id - PERK_count]; } +// Get level of taken perk static DWORD __fastcall GetFakeSelectPerkLevel(int id) { if (id < startFakeID) { - for (DWORD i = 0; i < fakePerks.size(); i++) { - if (fakePerks[i].id == id) return fakePerks[i].Level; + if (!PartyControl::IsNpcControlled()) { // extra perks are not available for controlled NPC + for (DWORD i = 0; i < fakePerks.size(); i++) { + if (fakePerks[i].id == id) return fakePerks[i].Level; + } } return 0; } - char* name = fakeSelectablePerks[id - startFakeID].Name; + long n = id - startFakeID; + const char* name = fakeSelectablePerks[n].Name; + int ownerID = fakeSelectablePerks[n].ownerId; for (DWORD i = 0; i < fakePerks.size(); i++) { - if (!strcmp(name, fakePerks[i].Name)) return fakePerks[i].Level; + if (ownerID != fakePerks[i].ownerId) continue; + if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; + } } return 0; } @@ -321,8 +341,10 @@ static FakePerk* __fastcall GetFakeSelectPerk(int id) { return &fakeSelectablePerks[id - startFakeID]; } +// Print a list of fake traits static DWORD HandleFakeTraits(int isSelect) { for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (!IsOwnedFake(fakeTraits[i].ownerId)) continue; if (fo::func::folder_print_line(fakeTraits[i].Name) && !isSelect) { isSelect = 1; var::folder_card_fid = fakeTraits[i].Image; @@ -340,11 +362,13 @@ static long __fastcall PlayerHasPerk(int &isSelectPtr) { for (int i = 0; i < PERK_count; i++) { if (fo::func::perk_level(fo::var::obj_dude, i)) return 0x43438A; // print perks } - return (!fakePerks.empty()) ? 0x43438A : 0x434446; // skip print perks + return (!fakePerks.empty()) + ? 0x43438A // print perks + : 0x434446; // skip print perks } static DWORD __fastcall HaveFakeTraits(int &isSelectPtr) { - return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; + return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; // print traits } static void __declspec(naked) PlayerHasPerkHack() { @@ -362,7 +386,7 @@ static void __declspec(naked) PlayerHasTraitHook() { push ecx; // isSelect mov ecx, esp; // ptr to isSelect call HaveFakeTraits; - pop ecx; // value from HandleFakeTraits + pop ecx; // isSelect value from HandleFakeTraits jmp eax; } } @@ -445,16 +469,21 @@ static void __declspec(naked) EndPerkLoopHack() { // Build a table of perks ID numbers available for selection, data buffer has limited size for 119 perks static DWORD _stdcall HandleExtraSelectablePerks(DWORD available, DWORD* data) { - size_t count = extPerks.size(); - for (size_t i = 0; i < count; i++) { - if (available >= 119) return available; // exit if the buffer is overfull - if (fo::func::perk_can_add(fo::var::obj_dude, extPerks[i].id)) data[available++] = extPerks[i].id; + size_t count; + if (!PartyControl::IsNpcControlled()) { // extra perks are not available for controlled NPC + count = extPerks.size(); + for (size_t i = 0; i < count; i++) { + if (available >= 119) return available; // exit if the buffer is overfull + if (fo::func::perk_can_add(fo::var::obj_dude, extPerks[i].id)) data[available++] = extPerks[i].id; + } } count = fakeSelectablePerks.size(); for (size_t i = 0; i < count; i++) { - if (available >= 119) break; - // for fake perks, their ID should start from 256 - data[available++] = startFakeID + i; + if (IsOwnedFake(fakeSelectablePerks[i].ownerId)) { + if (available >= 119) break; + // for fake perks, their ID should start from 256 + data[available++] = startFakeID + i; + } } return available; // total number of perks available for selection } @@ -579,10 +608,10 @@ static void ApplyPerkEffect(long index, fo::GameObject* critter, long type) { } // Adds the selected perk to the player -static void _stdcall AddFakePerk(DWORD perkID) { +static long _stdcall AddFakePerk(DWORD perkID) { size_t count; bool matched = false; - if (perkID < startFakeID) { + if (perkID < startFakeID) { // extra perk can only be added to the player count = fakePerks.size(); for (size_t d = 0; d < count; d++) { if (fakePerks[d].id == perkID) { @@ -593,18 +622,20 @@ static void _stdcall AddFakePerk(DWORD perkID) { } } if (!matched) { // add to fakePerks - RemovePerkID.push_back(count); // index of the added perk int index = PerkSearchID(perkID); - fakePerks.emplace_back(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc, extPerks[index].id); // id same as perkID + if (index < 0) return -1; + RemovePerkID.push_back(count); // index of the added perk + fakePerks.emplace_back(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc, 0, extPerks[index].id); // id same as perkID } fo::func::perk_add_effect(fo::var::obj_dude, perkID); - return; + return 0; } // behavior for fake perk/trait perkID -= startFakeID; if (addPerkMode & 1) { // add perk to trait count = fakeTraits.size(); for (size_t d = 0; d < count; d++) { + if (IsNotOwnedFake(fakeTraits[d].ownerId, fakeSelectablePerks[perkID].ownerId)) continue; if (!strcmp(fakeTraits[d].Name, fakeSelectablePerks[perkID].Name)) { matched = true; break; @@ -619,6 +650,7 @@ static void _stdcall AddFakePerk(DWORD perkID) { matched = false; count = fakePerks.size(); for (size_t d = 0; d < count; d++) { + if (IsNotOwnedFake(fakePerks[d].ownerId, fakeSelectablePerks[perkID].ownerId)) continue; if (!strcmp(fakePerks[d].Name, fakeSelectablePerks[perkID].Name)) { RemovePerkID.push_back(d); fakePerks[d].Level++; @@ -634,6 +666,7 @@ static void _stdcall AddFakePerk(DWORD perkID) { if (addPerkMode & 4) { // delete from selectable perks RemoveSelectableID.push_back(perkID); //fakeSelectablePerks.remove_at(perkID); } + return 0; } // Adds perk from selection window to player @@ -645,7 +678,6 @@ static void __declspec(naked) AddPerkHook() { push edx; call AddFakePerk; pop ecx; - xor eax, eax; retn; normalPerk: push edx; @@ -1287,23 +1319,43 @@ void _stdcall AddPerkMode(DWORD mode) { addPerkMode = mode; } -DWORD HasFakePerk(const char* name, long perkId) { - if (perkId < PERK_count && name[0] == 0) return 0; +DWORD Perks::HasFakePerk(const char* name, long perkId) { + if ((perkId < PERK_count && name[0] == 0) || (perkId && PartyControl::IsNpcControlled())) return 0; for (DWORD i = 0; i < fakePerks.size(); i++) { if (perkId) { - if (fakePerks[i].id == perkId) { - return fakePerks[i].Level; - } - } else if (!strcmp(name, fakePerks[i].Name)) { - return fakePerks[i].Level; // current perk level + if (fakePerks[i].id == perkId) return fakePerks[i].Level; // current perk level + } else if (IsOwnedFake(fakePerks[i].ownerId) && !strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; + } + } + return 0; +} + +DWORD Perks::HasFakeTrait(const char* name) { + if (name[0] == 0) return 0; + for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (IsOwnedFake(fakeTraits[i].ownerId) && !strcmp(name, fakeTraits[i].Name)) { + return 1; + } + } + return 0; +} + +DWORD Perks::HasFakePerkOwner(const char* name, long objId) { + if (name[0] == 0) return 0; + for (DWORD i = 0; i < fakePerks.size(); i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, objId)) continue; + if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; } } return 0; } -DWORD _stdcall HasFakeTrait(const char* name) { +DWORD Perks::HasFakeTraitOwner(const char* name, long objId) { if (name[0] == 0) return 0; for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, objId)) continue; if (!strcmp(name, fakeTraits[i].Name)) { return 1; } diff --git a/sfall/Modules/Perks.h b/sfall/Modules/Perks.h index 4af5f7f8d..5b4482863 100644 --- a/sfall/Modules/Perks.h +++ b/sfall/Modules/Perks.h @@ -30,6 +30,15 @@ class Perks : public Module { static void save(HANDLE file); static bool load(HANDLE file); + + static void SetSelectablePerk(const char* name, int active, int image, const char* desc, int npcID = 0); + static void SetFakePerk(const char* name, int level, int image, const char* desc, int npcID = 0); + static void SetFakeTrait(const char* name, int active, int image, const char* desc, int npcID = 0); + + static DWORD HasFakePerk(const char* name, long perkId); + static DWORD HasFakeTrait(const char* name); + static DWORD HasFakePerkOwner(const char* name, long objId); + static DWORD HasFakeTraitOwner(const char* name, long objId); }; void PerksEnterCharScreen(); @@ -43,17 +52,12 @@ void _stdcall SetPerkName(int id, char* value); void _stdcall SetPerkDesc(int id, char* value); void _stdcall SetPerkboxTitle(char* title); -void _stdcall SetSelectablePerk(char* name, int level, int image, char* desc); -void _stdcall SetFakePerk(char* name, int level, int image, char* desc); -void _stdcall SetFakeTrait(char* name, int level, int image, char* desc); + void _stdcall IgnoreDefaultPerks(); void _stdcall RestoreDefaultPerks(); - void _stdcall AddPerkMode(DWORD mode); -DWORD HasFakePerk(const char* name, long id); -DWORD _stdcall HasFakeTrait(const char* name); -void _stdcall ClearSelectablePerks(); +void _stdcall ClearSelectablePerks(); void _stdcall SetPerkFreq(int i); } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 4f65b5599..7ee89042b 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -24,6 +24,7 @@ #include "Interface.h" #include "Misc.h" #include "Objects.h" +#include "Perks.h" #include "Utils.h" #include "Worldmap.h" @@ -83,6 +84,8 @@ static const SfallMetarule metarules[] = { {"get_object_data", sf_get_object_data, 2, 2, {ARG_OBJECT, ARG_INT}}, {"get_outline", sf_get_outline, 1, 1, {ARG_OBJECT}}, {"get_string_pointer", sf_get_string_pointer, 1, 1, {ARG_STRING}}, + {"has_fake_perk_npc", sf_has_fake_perk_npc, 2, 2, {ARG_OBJECT, ARG_STRING}}, + {"has_fake_trait_npc", sf_has_fake_trait_npc, 2, 2, {ARG_OBJECT, ARG_STRING}}, {"intface_hide", sf_intface_hide, 0, 0}, {"intface_is_hidden", sf_intface_is_hidden, 0, 0}, {"intface_redraw", sf_intface_redraw, 0, 0}, @@ -101,6 +104,8 @@ static const SfallMetarule metarules[] = { {"set_cursor_mode", sf_set_cursor_mode, 1, 1, {ARG_INT}}, {"set_drugs_data", sf_set_drugs_data, 3, 3, {ARG_INT, ARG_INT, ARG_INT}}, {"set_dude_obj", sf_set_dude_obj, 1, 1, {ARG_OBJECT}}, + {"set_fake_perk_npc", sf_set_fake_perk_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {"set_fake_trait_npc", sf_set_fake_trait_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"set_flags", sf_set_flags, 2, 2, {ARG_OBJECT, ARG_INT}}, {"set_iface_tag_text", sf_set_iface_tag_text, 3, 3, {ARG_INT, ARG_STRING, ARG_INT}}, {"set_ini_setting", sf_set_ini_setting, 2, 2, {ARG_STRING, ARG_INTSTR}}, @@ -109,6 +114,7 @@ static const SfallMetarule metarules[] = { {"set_outline", sf_set_outline, 2, 2, {ARG_OBJECT, ARG_INT}}, {"set_rest_heal_time", sf_set_rest_heal_time, 1, 1, {ARG_INT}}, {"set_rest_mode", sf_set_rest_mode, 1, 1, {ARG_INT}}, + {"set_selectable_perk_npc", sf_set_selectable_perk_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"set_unique_id", sf_set_unique_id, 1, 2, {ARG_OBJECT, ARG_INT}}, {"set_unjam_locks_time", sf_set_unjam_locks_time, 1, 1, {ARG_INT}}, {"spatial_radius", sf_spatial_radius, 1, 1, {ARG_OBJECT}}, diff --git a/sfall/Modules/Scripting/Handlers/Perks.cpp b/sfall/Modules/Scripting/Handlers/Perks.cpp index 04dd7e8c2..a470bfa8d 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.cpp +++ b/sfall/Modules/Scripting/Handlers/Perks.cpp @@ -27,7 +27,6 @@ namespace sfall { namespace script { -using namespace fo; void __declspec(naked) op_get_perk_owed() { __asm { @@ -183,218 +182,44 @@ void __declspec(naked) op_set_perk_value() { } } -void __declspec(naked) op_set_selectable_perk() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds:[esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds:[esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; +void sf_set_selectable_perk(OpcodeContext& ctx) { + Perks::SetSelectablePerk(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); +} - call SetSelectablePerk; -fail: - add esp, 32; - popad; - retn; - } +void sf_set_fake_perk(OpcodeContext& ctx) { + Perks::SetFakePerk(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); } -void __declspec(naked) op_set_fake_perk() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; +void sf_set_fake_trait(OpcodeContext& ctx) { + Perks::SetFakeTrait(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); +} - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds:[esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds:[esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; +const char* notPartyMemberErr = "%s() - the object is not a party member."; - call SetFakePerk; -fail: - add esp, 32; - popad; - retn; +void sf_set_selectable_perk_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetSelectablePerk(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } } -void __declspec(naked) op_set_fake_trait() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - push esi; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds:[esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds:[esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; +void sf_set_fake_perk_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetFakePerk(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); + } +} - call SetFakeTrait; -fail: - add esp, 32; - pop esi; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; +void sf_set_fake_trait_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetFakeTrait(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } } @@ -402,28 +227,24 @@ void __declspec(naked) op_set_perkbox_title() { __asm { push ebx; push ecx; - push edx; - push edi; - mov edi, eax; + mov ecx, eax; call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; + mov edx, eax; + mov eax, ecx; call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; + cmp dx, VAR_TYPE_STR2; + jz next; + cmp dx, VAR_TYPE_STR; + jnz end; next: - mov ebx, eax; - mov eax, edi; + mov ebx, eax; + mov eax, ecx; call fo::funcoffs::interpretGetString_; push eax; call SetPerkboxTitle; end: - pop edi; - pop edx; - pop ecx; - pop ebx; + pop ecx; + pop ebx; retn; } } @@ -456,43 +277,33 @@ void __declspec(naked) op_clear_selectable_perks() { } void sf_has_fake_perk(OpcodeContext& ctx) { - ctx.setReturn(HasFakePerk(ctx.arg(0).asString(), ctx.arg(0).asInt())); + ctx.setReturn(Perks::HasFakePerk(ctx.arg(0).asString(), ctx.arg(0).asInt())); } -void __declspec(naked) op_has_fake_trait() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov edi, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push eax; - call HasFakeTrait; -end: - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov eax, edi; - mov edx, VAR_TYPE_INT; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; +void sf_has_fake_trait(OpcodeContext& ctx) { + ctx.setReturn(Perks::HasFakeTrait(ctx.arg(0).strValue())); +} + +void sf_has_fake_perk_npc(OpcodeContext& ctx) { + long result = 0; + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + result = Perks::HasFakePerkOwner(ctx.arg(1).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } + ctx.setReturn(result); +} + +void sf_has_fake_trait_npc(OpcodeContext& ctx) { + long result = 0; + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + result = Perks::HasFakeTraitOwner(ctx.arg(1).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); + } + ctx.setReturn(result); } void __declspec(naked) op_perk_add_mode() { diff --git a/sfall/Modules/Scripting/Handlers/Perks.h b/sfall/Modules/Scripting/Handlers/Perks.h index ef7a693ac..1e2d6c84d 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.h +++ b/sfall/Modules/Scripting/Handlers/Perks.h @@ -37,11 +37,17 @@ void __declspec() op_set_perk_desc(); void __declspec() op_set_perk_value(); -void __declspec() op_set_selectable_perk(); +void sf_set_selectable_perk(OpcodeContext&); -void __declspec() op_set_fake_perk(); +void sf_set_fake_perk(OpcodeContext&); -void __declspec() op_set_fake_trait(); +void sf_set_fake_trait(OpcodeContext&); + +void sf_set_selectable_perk_npc(OpcodeContext&); + +void sf_set_fake_perk_npc(OpcodeContext&); + +void sf_set_fake_trait_npc(OpcodeContext&); void __declspec() op_set_perkbox_title(); @@ -53,7 +59,11 @@ void __declspec() op_clear_selectable_perks(); void sf_has_fake_perk(OpcodeContext&); -void __declspec() op_has_fake_trait(); +void sf_has_fake_trait(OpcodeContext&); + +void sf_has_fake_perk_npc(OpcodeContext&); + +void sf_has_fake_trait_npc(OpcodeContext&); void __declspec() op_perk_add_mode(); diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index 22694036e..673123137 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -86,7 +86,11 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x1a5, "inc_npc_level", sf_inc_npc_level, 1, false, {ARG_INTSTR}}, {0x1ac, "get_ini_setting", sf_get_ini_setting, 1, true, {ARG_STRING}}, + {0x1bb, "set_fake_perk", sf_set_fake_perk, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {0x1bc, "set_fake_trait", sf_set_fake_trait, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {0x1bd, "set_selectable_perk", sf_set_selectable_perk, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {0x1c1, "has_fake_perk", sf_has_fake_perk, 1, true, {ARG_INTSTR}}, + {0x1c2, "has_fake_trait", sf_has_fake_trait, 1, true, {ARG_STRING}}, {0x1dc, "show_iface_tag", sf_show_iface_tag, 1, false, {ARG_INT}}, {0x1dd, "hide_iface_tag", sf_hide_iface_tag, 1, false, {ARG_INT}}, @@ -330,13 +334,9 @@ void InitNewOpcodes() { opcodes[0x1b8] = op_set_pc_stat_min; opcodes[0x1b9] = op_set_npc_stat_max; opcodes[0x1ba] = op_set_npc_stat_min; - opcodes[0x1bb] = op_set_fake_perk; - opcodes[0x1bc] = op_set_fake_trait; - opcodes[0x1bd] = op_set_selectable_perk; opcodes[0x1be] = op_set_perkbox_title; opcodes[0x1bf] = op_hide_real_perks; opcodes[0x1c0] = op_show_real_perks; - opcodes[0x1c2] = op_has_fake_trait; opcodes[0x1c3] = op_perk_add_mode; opcodes[0x1c4] = op_clear_selectable_perks; opcodes[0x1c5] = op_set_critter_hit_chance_mod; From aa5bd95dfe4acf945b66ac2603ccd8a13934b7f4 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Tue, 28 May 2019 11:09:11 +0800 Subject: [PATCH 29/32] Fixed AI not checking min HP properly for using stims. Edited the code of InventoryApCost in Inventory.cpp. Minor edits to some other code. --- artifacts/scripting/sfall function notes.txt | 2 +- sfall/FalloutEngine/EngineUtils.cpp | 2 +- sfall/Modules/AI.cpp | 14 +++ sfall/Modules/BugFixes.cpp | 14 +-- sfall/Modules/Console.cpp | 4 +- sfall/Modules/Inventory.cpp | 109 +++++++++---------- sfall/Modules/Objects.cpp | 2 +- sfall/Modules/Scripting/Handlers/Objects.cpp | 7 +- 8 files changed, 85 insertions(+), 69 deletions(-) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 7bc484028..52f3eb35a 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -604,7 +604,7 @@ optional arguments: > void sfall_func5("set_selectable_perk_npc", object npc, string namePerk, int active, int image, string desc) > int sfall_func2("has_fake_perk_npc", object npc, string namePerk) > int sfall_func2("has_fake_trait_npc", object npc, string nameTrait) -- functions are similar to has_fake_*/set_fake_*/set_selectable_perk opcodes, but work on the specified party member NPC (including dude_obj) +- these functions are similar to has_fake_*/set_fake_*/set_selectable_perk functions, but apply to the specified party member NPC (including dude_obj) ------------------------ ------ MORE INFO ------- diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index 59adc0ceb..72d56ebd7 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -150,7 +150,7 @@ long IsPartyMemberByPid(long pid) { } bool IsPartyMember(fo::GameObject* critter) { - if (critter->id < 18000) return false; + if (critter->id < PLAYER_ID) return false; return (IsPartyMemberByPid(critter->protoId) > 0); } diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index d0d83e830..e479c0b9e 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -76,6 +76,17 @@ static void __declspec(naked) combat_ai_hack() { } } +static void __declspec(naked) ai_check_drugs_hook() { + __asm { + call fo::funcoffs::stat_level_; // current hp + mov edx, dword ptr [esp + 0x34 - 0x1C + 4]; // ai cap + mov edx, [edx + 0x10]; // min_hp + cmp eax, edx; // curr_hp < cap.min_hp + cmovl edi, edx; + retn; + } +} + //////////////////////////////////////////////////////////////////////////////// static DWORD RetryCombatLastAP; @@ -194,6 +205,9 @@ void AI::init() { // Fix to allow fleeing NPC to use drugs MakeCall(0x42B1DC, combat_ai_hack); + // Fix for AI not checking minimum hp properly for using stimpaks (prevents premature fleeing) + HookCall(0x428579, ai_check_drugs_hook); + // Fix for NPC stuck in fleeing mode when the hit chance of a target was too low HookCall(0x42B1E3, combat_ai_hook_FleeFix); HookCalls(ai_try_attack_hook_FleeFix, {0x42ABA8, 0x42ACE5}); diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index d2ee6b0fd..8f96731ca 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -801,19 +801,19 @@ static void __declspec(naked) PipStatus_AddHotLines_hook() { static void __declspec(naked) perform_withdrawal_start_display_print_hook() { __asm { - test eax, eax - jz end - jmp fo::funcoffs::display_print_ + test eax, eax; + jz end; + jmp fo::funcoffs::display_print_; end: - retn + retn; } } static void __declspec(naked) op_wield_obj_critter_adjust_ac_hook() { __asm { - call fo::funcoffs::adjust_ac_ - xor eax, eax // not animated - jmp fo::funcoffs::intface_update_ac_ + call fo::funcoffs::adjust_ac_; + xor eax, eax; // not animated + jmp fo::funcoffs::intface_update_ac_; } } diff --git a/sfall/Modules/Console.cpp b/sfall/Modules/Console.cpp index 980a8eac8..f6246b7e0 100644 --- a/sfall/Modules/Console.cpp +++ b/sfall/Modules/Console.cpp @@ -36,10 +36,10 @@ static void _stdcall ConsoleFilePrint(const char* msg) { static const DWORD ConsoleHookRet = 0x431871; static void __declspec(naked) ConsoleHook() { __asm { - pushad; + pushadc; push eax; call ConsoleFilePrint; - popad; + popadc; push ebx; push ecx; push edx; diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index ea819e1b6..85b9b5538 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -63,6 +63,7 @@ void InventoryKeyPressedHook(DWORD dxKey, bool pressed) { } ///////////////////////////////////////////////////////////////// + DWORD __stdcall sf_item_total_size(fo::GameObject* critter) { int totalSize = fo::func::item_c_curr_size(critter); @@ -85,19 +86,6 @@ DWORD __stdcall sf_item_total_size(fo::GameObject* critter) { return totalSize; } -/*static const DWORD ObjPickupFail=0x49B70D; -static const DWORD ObjPickupEnd=0x49B6F8; -static __declspec(naked) void ObjPickupHook() { - __asm { - cmp edi, ds:[FO_VAR_obj_dude]; - jnz end; -end: - lea edx, [esp+0x10]; - mov eax, ecx; - jmp ObjPickupEnd; - } -}*/ - static int __stdcall CritterGetMaxSize(fo::GameObject* critter) { if (critter == fo::var::obj_dude) return invSizeMaxLimit; @@ -198,8 +186,8 @@ static __declspec(naked) void barter_attempt_transaction_hack_pc() { /* cmp eax, edx */ jg fail; // if there's no available weight //------ - mov ecx, edi; // source (pc) - mov edx, ebp; // npc table + mov ecx, edi; // source (pc) + mov edx, ebp; // npc table call BarterAttemptTransaction; test eax, eax; jz fail; @@ -232,19 +220,19 @@ static __declspec(naked) void barter_attempt_transaction_hack_pm() { static __declspec(naked) void loot_container_hook_btn() { __asm { push ecx; - push edx; // source current weight - mov edx, eax; // target - mov ecx, [esp + 0x150 - 0x1C + 12]; // source + push edx; // source current weight + mov edx, eax; // target + mov ecx, [esp + 0x150 - 0x1C + 12]; // source call BarterAttemptTransaction; pop edx; pop ecx; test eax, eax; jz fail; - mov eax, ebp; // target + mov eax, ebp; // target jmp fo::funcoffs::item_total_weight_; fail: mov eax, edx; - inc eax; // weight + 1 + inc eax; // weight + 1 retn; } } @@ -340,51 +328,32 @@ static void __declspec(naked) gdControlUpdateInfo_hack() { ///////////////////////////////////////////////////////////////// static std::string superStimMsg; -static int __fastcall SuperStimFix2(fo::GameObject* item, fo::GameObject* target) { +static int __fastcall SuperStimFix(fo::GameObject* item, fo::GameObject* target) { if (item->protoId != fo::PID_SUPER_STIMPAK || !target || target->Type() != fo::OBJ_TYPE_CRITTER) { return 0; } - long curr_hp, max_hp; - curr_hp = fo::func::stat_level(target, fo::STAT_current_hp); - max_hp = fo::func::stat_level(target, fo::STAT_max_hit_points); - if (curr_hp < max_hp) return 0; + long max_hp = fo::func::stat_level(target, fo::STAT_max_hit_points); + if (target->critter.health < max_hp) return 0; fo::func::display_print(superStimMsg.c_str()); return -1; } -static const DWORD UseItemHookRet = 0x49C5F4; -static void __declspec(naked) SuperStimFix() { +static const DWORD protinst_use_item_on_Ret = 0x49C5F4; +static void __declspec(naked) protinst_use_item_on_hack() { __asm { push ecx; - mov ecx, ebx; // ecx - item - call SuperStimFix2; // edx - target + mov ecx, ebx; // ecx - item + call SuperStimFix; // edx - target pop ecx; test eax, eax; jnz end; - mov ebp, -1; // overwritten engine code + mov ebp, -1; // overwritten engine code retn; end: - add esp, 4; // destroy ret - jmp UseItemHookRet; // exit - } -} - -static int invenApCost, invenApCostDef; -static char invenApQPReduction; -void _stdcall SetInvenApCost(int cost) { - invenApCost = cost; -} -static const DWORD inven_ap_cost_hack_ret = 0x46E816; -static void __declspec(naked) inven_ap_cost_hack() { - _asm { - movzx ebx, byte ptr invenApQPReduction; - mul bl; - mov edx, invenApCost; - sub edx, eax; - mov eax, edx; - jmp inven_ap_cost_hack_ret; + add esp, 4; // destroy ret + jmp protinst_use_item_on_Ret; // exit } } @@ -570,7 +539,7 @@ DWORD __stdcall Inventory::adjust_fid_replacement() { using namespace fo; DWORD fid; - if (var::inven_dude->TypeFid() == fo::OBJ_TYPE_CRITTER) { + if (var::inven_dude->TypeFid() == ObjType::OBJ_TYPE_CRITTER) { DWORD frameNum; DWORD weaponAnimCode = 0; if (PartyControl::IsNpcControlled()) { @@ -652,6 +621,34 @@ static void __declspec(naked) do_move_timer_hook() { } } +static int invenApCost, invenApCostDef; +static char invenApQPReduction; +static const DWORD inven_ap_cost_Ret = 0x46E812; +static void __declspec(naked) inven_ap_cost_hack() { + _asm { + mul byte ptr invenApQPReduction; + mov edx, invenApCost; + jmp inven_ap_cost_Ret; + } +} + +static bool onlyOnceAP = false; +inline static void ApplyInvenApCostPatch() { + MakeJump(0x46E80B, inven_ap_cost_hack); + onlyOnceAP = true; +} + +void _stdcall SetInvenApCost(int cost) { + invenApCost = cost; + if (!onlyOnceAP) ApplyInvenApCostPatch(); +} + +// TODO: Make GetInvenApCost() function +/*long GetInvenApCost() { + long plevel = fo::func::perk_level(fo::var::obj_dude, fo::PERK_quick_pockets); + return invenApCost - (invenApQPReduction * plevel); +}*/ + void InventoryReset() { invenApCost = invenApCostDef; } @@ -712,17 +709,19 @@ void Inventory::init() { } } - invenApCost = invenApCostDef = GetConfigInt("Misc", "InventoryApCost", 4); - invenApQPReduction = GetConfigInt("Misc", "QuickPocketsApCostReduction", 2); - MakeJump(0x46E80B, inven_ap_cost_hack); - if (GetConfigInt("Misc", "SuperStimExploitFix", 0)) { superStimMsg = Translate("sfall", "SuperStimExploitMsg", "You cannot use a super stim on someone who is not injured!"); - MakeCall(0x49C3D9, SuperStimFix); + MakeCall(0x49C3D9, protinst_use_item_on_hack); } reloadWeaponKey = GetConfigInt("Input", "ReloadWeaponKey", 0); + invenApCost = invenApCostDef = GetConfigInt("Misc", "InventoryApCost", 4); + invenApQPReduction = GetConfigInt("Misc", "QuickPocketsApCostReduction", 2); + if (invenApCostDef != 4 || invenApQPReduction != 2) { + ApplyInvenApCostPatch(); + } + if (GetConfigInt("Misc", "StackEmptyWeapons", 0)) { MakeCall(0x4736C6, inven_action_cursor_hack); HookCall(0x4772AA, item_add_mult_hook); diff --git a/sfall/Modules/Objects.cpp b/sfall/Modules/Objects.cpp index d296f6dee..7f73f6d58 100644 --- a/sfall/Modules/Objects.cpp +++ b/sfall/Modules/Objects.cpp @@ -35,7 +35,7 @@ long Objects::uniqueID = UniqueID::Start; // current counter id, saving to sfall // player ID = 18000, all party members have ID = 18000 + its pid (file number of prototype) long Objects::SetObjectUniqueID(fo::GameObject* obj) { long id = obj->id; - if (id > UniqueID::Start || obj == fo::var::obj_dude || (id >= 18000 && id < 83536)) return id; // 65535 maximum possible number of prototypes + if (id > UniqueID::Start || obj == fo::var::obj_dude || (id >= PLAYER_ID && id < 83536)) return id; // 65535 maximum possible number of prototypes if ((DWORD)uniqueID >= UniqueID::End) uniqueID = UniqueID::Start; obj->id = ++uniqueID; diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index 82814d330..2994180a0 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.cpp +++ b/sfall/Modules/Scripting/Handlers/Objects.cpp @@ -365,10 +365,13 @@ void sf_item_weight(OpcodeContext& ctx) { void sf_set_dude_obj(OpcodeContext& ctx) { auto obj = ctx.arg(0).asObject(); if (obj->Type() == fo::OBJ_TYPE_CRITTER) { - PartyControl::SwitchToCritter(obj); + //if (!InCombat && obj != PartyControl::RealDudeObject()) { + // ctx.printOpcodeError("%s() - controlling of the critter is only allowed in combat mode.", ctx.getMetaruleName()); + //} else { + PartyControl::SwitchToCritter(obj); + //} } else { ctx.printOpcodeError("%s() - the object is not a critter.", ctx.getMetaruleName()); - ctx.setReturn(-1); } } From 4ee20e06b4d9b86e08ea686e6f659a718807569c Mon Sep 17 00:00:00 2001 From: NovaRain Date: Wed, 29 May 2019 09:35:38 +0800 Subject: [PATCH 30/32] Fixed the code for removal of party member's corpse when loading the map. --- sfall/Modules/BugFixes.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 8f96731ca..467da48d0 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -1955,9 +1955,9 @@ static void __declspec(naked) map_age_dead_critters_hack() { static void __declspec(naked) partyFixMultipleMembers_hook() { __asm { - mov edx, [esi + protoId]; - sar edx, 24; - cmp edx, OBJ_TYPE_CRITTER; + mov ebx, [esi + protoId]; + sar ebx, 24; + cmp ebx, OBJ_TYPE_CRITTER; jne noDrop; // not critter test [esi + damageFlags], DAM_DEAD; jnz isDead; @@ -1973,11 +1973,13 @@ static void __declspec(naked) partyFixMultipleMembers_hook() { cmp eax, -1; je noBlood; mov eax, [esp - 4]; // object + push ebx; // mov [eax + frm], 3; // set frame - mov ebx, [esi + elevation]; mov edx, [esi + tile]; + mov ebx, [esi + elevation]; xor ecx, ecx; call fo::funcoffs::obj_move_to_tile_; + pop ebx; noBlood: mov eax, [esi + protoId]; mov edx, 0x40; // noDrop flag @@ -1991,6 +1993,11 @@ static void __declspec(naked) partyFixMultipleMembers_hook() { noDrop: xor edx, edx; call fo::funcoffs::obj_erase_object_; + cmp ebx, OBJ_TYPE_CRITTER; + jnz skip; + mov eax, esi; + jmp fo::funcoffs::combat_delete_critter_; +skip: retn; } } From 8778d1c695d9fde311353fb6500dfb0df52f75f4 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 30 May 2019 10:30:25 +0800 Subject: [PATCH 31/32] Improved the fix for the removal of PM corpses. * to prevent save file corruption (one of bugs listed in #228). Now party member's corpse is removed in the same way as all other critter corpses. Simplified the ASM code of combat_ai_hack (take 2). --- sfall/Modules/AI.cpp | 8 +++--- sfall/Modules/BugFixes.cpp | 52 +++++++------------------------------- 2 files changed, 12 insertions(+), 48 deletions(-) diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index e479c0b9e..9e4f79c6c 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -59,7 +59,8 @@ static void __declspec(naked) combat_ai_hook_FleeFix() { static const DWORD combat_ai_hack_Ret = 0x42B204; static void __declspec(naked) combat_ai_hack() { __asm { - cmp eax, [ebx + 0x10]; // cap.min_hp - minimum hp, below which NPC will run away + mov edx, [ebx + 0x10]; // cap.min_hp + cmp eax, edx; jl tryHeal; // curr_hp < min_hp end: add esp, 4; @@ -67,10 +68,7 @@ static void __declspec(naked) combat_ai_hack() { tryHeal: mov eax, esi; call fo::funcoffs::ai_check_drugs_; - mov eax, esi; - mov edx, STAT_current_hp; - call fo::funcoffs::stat_level_; - cmp eax, [ebx + 0x10]; // cap.min_hp + cmp [esi + health], edx; // edx - minimum hp, below which NPC will run away jge end; retn; // flee } diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 467da48d0..a3d283a7f 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -1953,50 +1953,16 @@ static void __declspec(naked) map_age_dead_critters_hack() { } } -static void __declspec(naked) partyFixMultipleMembers_hook() { +static void __declspec(naked) partyFixMultipleMembers_hack() { __asm { - mov ebx, [esi + protoId]; - sar ebx, 24; - cmp ebx, OBJ_TYPE_CRITTER; - jne noDrop; // not critter - test [esi + damageFlags], DAM_DEAD; - jnz isDead; - cmp dword ptr [esi + health], 0; - jg noBlood; // is not dead -isDead: - // create generic blood - sub esp, 4; - mov eax, esp // object buf - mov edx, 0x5000004; // pid - call fo::funcoffs::obj_pid_new_; - add esp, 4; - cmp eax, -1; - je noBlood; - mov eax, [esp - 4]; // object - push ebx; -// mov [eax + frm], 3; // set frame - mov edx, [esi + tile]; - mov ebx, [esi + elevation]; - xor ecx, ecx; - call fo::funcoffs::obj_move_to_tile_; - pop ebx; -noBlood: + cmp esi, edx; + je skip; mov eax, [esi + protoId]; - mov edx, 0x40; // noDrop flag - call fo::funcoffs::critter_flag_check_; - test eax, eax; - mov eax, esi; - jnz noDrop; // flag is set - mov edx, [esi + tile]; - call fo::funcoffs::item_drop_all_; - mov eax, esi; -noDrop: - xor edx, edx; - call fo::funcoffs::obj_erase_object_; - cmp ebx, OBJ_TYPE_CRITTER; - jnz skip; - mov eax, esi; - jmp fo::funcoffs::combat_delete_critter_; + sar eax, 24; + cmp eax, OBJ_TYPE_CRITTER; + jne skip; + test [esi + damageFlags], DAM_DEAD; + cmovnz edx, esi; skip: retn; } @@ -2654,7 +2620,7 @@ void BugFixes::init() dlogr(" Done", DL_INIT); // Fix for the removal of party member's corpse when loading the map - HookCall(0x4957B8, partyFixMultipleMembers_hook); + MakeCall(0x495769, partyFixMultipleMembers_hack, 1); // Fix for unexplored areas being revealed on the automap when entering a map MakeCall(0x48A76B, obj_move_to_tile_hack_seen, 1); From e5e2ef5ab03437843fa2a46bc601c5b4564bedec Mon Sep 17 00:00:00 2001 From: NovaRain Date: Fri, 31 May 2019 09:10:36 +0800 Subject: [PATCH 32/32] Minor edit to gl_ammomod example script. --- artifacts/example_mods/AmmoMod/gl_ammomod.int | Bin 1288 -> 1238 bytes artifacts/example_mods/AmmoMod/gl_ammomod.ssl | 12 ++++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/artifacts/example_mods/AmmoMod/gl_ammomod.int b/artifacts/example_mods/AmmoMod/gl_ammomod.int index 3572190caa64ee7cf794c5b86ce1652bce4246b9..2730de62ef6f5f92be294fc900a7fe77f1d59eaf 100644 GIT binary patch delta 168 zcmeC+y2d%7ld)%G7aubdOUdK~%o1MA?+!3B06~Kgm}YD+M&UCx7&q8A=uZHtNrTD= zgJ>451{N?a15-a)k42VIYqAfEzGMklj1g)kLxV+wK3Hwh0K E0I+E$B>(^b delta 241 zcmcb{*}*lTlX2h1E 0) then call set_ammo_mod;