diff --git a/artifacts/config_files/Tweaks.ini b/artifacts/config_files/Tweaks.ini
new file mode 100644
index 00000000..44f5609b
--- /dev/null
+++ b/artifacts/config_files/Tweaks.ini
@@ -0,0 +1,13 @@
+;Game engine tweaks
+
+[Main]
+
+
+[Items]
+;Override the PIDs of the standard healing drugs that AI will use in combat
+;Set to -1 if you don't want AI to identify this item as a healing drug
+;Note: Starting from sfall 4.3.1/3.8.31, you can set a new flag (FlagsExt: 0x04000000)
+;in the proto for other items, so AI will use them for healing in combat as well
+STIMPAK=40
+SUPER_STIMPAK=144
+HEALING_POWDER=237
diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini
index b179ade4..de1174aa 100644
--- a/artifacts/ddraw.ini
+++ b/artifacts/ddraw.ini
@@ -444,7 +444,7 @@ NPCsTryToSpendExtraAP=0
;Note that enabling this option can affect the weapon of choice for some NPCs
AIBestWeaponFix=1
-;Set to 1 to fix NPCs not taking chem_primary_desire in AI.txt as drug use preference when using drugs in their inventory
+;Set to 1 to fix NPCs not taking chem_primary_desire in AI.txt as a preference list when using drugs in their inventory
;Set to 2 to allow NPCs to use only the drugs listed in chem_primary_desire and healing drugs (stimpaks and healing powder)
;Note: chem_primary_desire w/o fixes prevents the specified item PID from being consumed if all three values in the list are the same PID
;chem_primary_desire also works as a priority list of drug items the NPC will try to pick up in combat when they are on the ground
@@ -782,7 +782,8 @@ CreditsAtBottom=0
;Point the next line to an ini file containing the replacement skill data
;SkillsFile=sfall\Skills.ini
-;To add additional perks to the game, uncomment the next line and set it to point to a file containing perk information
+;To add additional perks to the game, uncomment the next line and point to a file containing perk information
+;See the Perks.ini in the modders pack for an example file
;PerksFile=sfall\Perks.ini
;To add additional books to the game, uncomment the next line and point to a file containing book information
@@ -792,6 +793,10 @@ CreditsAtBottom=0
;Point to an ini file containing elevator data
;ElevatorsFile=sfall\Elevators.ini
+;Allows you to change some engine parameters for the game mechanics
+;See the Tweaks.ini in the modders pack for an example file
+;TweaksFile=sfall\Tweaks.ini
+
;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
[Debugging]
;Extra sfall configuration settings that can be used by modders
diff --git a/sfall/AI.cpp b/sfall/AI.cpp
index fea57c1f..3299b4d7 100644
--- a/sfall/AI.cpp
+++ b/sfall/AI.cpp
@@ -103,28 +103,30 @@ static void __declspec(naked) ai_try_attack_hook_runFix() {
static void __declspec(naked) combat_ai_hack() {
static const DWORD combat_ai_hack_Ret = 0x42B204;
__asm {
- mov edx, [ebx + 0x10]; // cap.min_hp
+ mov edx, [ebx + 0x10]; // cap.min_hp
cmp eax, edx;
- jl tryHeal; // curr_hp < min_hp
+ jl tryHeal; // curr_hp < min_hp
end:
add esp, 4;
- jmp combat_ai_hack_Ret;
+ jmp combat_ai_hack_Ret; // jump to call ai_check_drugs_
tryHeal:
- mov eax, esi;
- call ai_check_drugs_;
- cmp [esi + health], edx; // edx - minimum hp, below which NPC will run away
+ push ecx;
+ push esi; // mov eax, esi;
+ call sfgame_ai_check_drugs; // call ai_check_drugs_;
+ pop ecx;
+ cmp [esi + health], edx; // edx - minimum hp, below which NPC will run away
jge end;
retn; // flee
}
}
-static void __declspec(naked) ai_check_drugs_hook() {
+/*static void __declspec(naked) ai_check_drugs_hook() {
__asm {
call 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;
+ cmovl edi, edx; // min_hp <- cap.min_hp
retn;
}
}
@@ -163,7 +165,7 @@ dontUse:
xor eax, eax;
retn;
}
-}
+}*/
////////////////////////////////////////////////////////////////////////////////
@@ -657,12 +659,12 @@ 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);
+ //HookCall(0x428579, ai_check_drugs_hook);
// Fix to prevent the use of healing drugs when not necessary
- HookCall(0x4287D7, ai_check_drugs_hook_healing);
- SafeWrite8(0x4285A8, 2); // set noInvenItem = 2
- SafeWrite8(0x4287A0, 0x8C); // jnz > jl (noInvenItem < 1)
+ //HookCall(0x4287D7, ai_check_drugs_hook_healing);
+ //SafeWrite8(0x4285A8, 2); // set noInvenItem = 2
+ //SafeWrite8(0x4287A0, 0x8C); // jnz > jl (noInvenItem < 1)
// Fix for NPC stuck in fleeing mode when the hit chance of a target was too low
HookCall(0x42B1E3, combat_ai_hook_FleeFix);
diff --git a/sfall/BugFixes.cpp b/sfall/BugFixes.cpp
index 37cd4740..49dbd575 100644
--- a/sfall/BugFixes.cpp
+++ b/sfall/BugFixes.cpp
@@ -2012,7 +2012,7 @@ skip:
}
}
-static DWORD firstItemDrug = -1;
+/*static DWORD firstItemDrug = -1;
// when there are no more items in the inventory
static void __declspec(naked) ai_check_drugs_hack_break() {
@@ -2061,7 +2061,7 @@ skip:
add esp, 4;
jmp ai_check_drugs_hack_Loop; // goto begin loop, search next item
}
-}
+}*/
static void __declspec(naked) cai_cap_save_hook() {
__asm {
@@ -3628,20 +3628,20 @@ void BugFixes_Init()
// Display a pop-up message box about death from radiation
HookCall(0x42D733, process_rads_hook_msg);
- int drugUsePerfFix = GetConfigInt("Misc", "AIDrugUsePerfFix", 0);
- if (drugUsePerfFix > 0) {
- dlog("Applying AI drug use preference fix.", DL_FIX);
- if (drugUsePerfFix == 1) {
- // Fix for AI not taking chem_primary_desire in AI.txt as a preference list when using drugs in the inventory
- MakeCall(0x42869D, ai_check_drugs_hack_break);
- MakeCall(0x4286AB, ai_check_drugs_hack_check, 1);
- MakeCall(0x4286C7, ai_check_drugs_hack_use);
- }
- // Fix to allow using only the drugs listed in chem_primary_desire and healing drugs (stimpaks and healing powder)
- SafeWrite8(0x4286B1, CODETYPE_JumpZ); // jnz > jz (ai_check_drugs_)
- SafeWrite8(0x4286C5, CODETYPE_JumpNZ); // jz > jnz (ai_check_drugs_)
- dlogr(" Done", DL_FIX);
- }
+ //int drugUsePerfFix = GetConfigInt("Misc", "AIDrugUsePerfFix", 0);
+ //if (drugUsePerfFix > 0) {
+ // dlog("Applying AI drug use preference fix.", DL_FIX);
+ // if (drugUsePerfFix == 1) {
+ // // Fix for AI not taking chem_primary_desire in AI.txt as a preference list when using drugs in the inventory
+ // MakeCall(0x42869D, ai_check_drugs_hack_break);
+ // MakeCall(0x4286AB, ai_check_drugs_hack_check, 1);
+ // MakeCall(0x4286C7, ai_check_drugs_hack_use);
+ // }
+ // // Fix to allow using only the drugs listed in chem_primary_desire and healing drugs (stimpaks and healing powder)
+ // SafeWrite8(0x4286B1, CODETYPE_JumpZ); // jnz > jz (ai_check_drugs_)
+ // SafeWrite8(0x4286C5, CODETYPE_JumpNZ); // jz > jnz (ai_check_drugs_)
+ // dlogr(" Done", DL_FIX);
+ //}
// Fix for chem_primary_desire values in party member AI packets not being saved correctly
HookCall(0x42803E, cai_cap_save_hook);
diff --git a/sfall/EngineTweaks.cpp b/sfall/EngineTweaks.cpp
new file mode 100644
index 00000000..32dd7444
--- /dev/null
+++ b/sfall/EngineTweaks.cpp
@@ -0,0 +1,36 @@
+/*
+ * sfall
+ * Copyright (C) 2008-2021 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.h"
+
+#include "ReplacementFuncs.h"
+
+void EngineTweaks_Init() {
+ std::string tweaksFile = GetConfigString("Misc", "TweaksFile", "", MAX_PATH);
+ if (!tweaksFile.empty()) {
+ const char* cTweaksFile = tweaksFile.insert(0, ".\\").c_str();
+
+ sfgame_SetHealingPID(0, IniGetInt("Items", "STIMPAK", PID_STIMPAK, cTweaksFile));
+ sfgame_SetHealingPID(1, IniGetInt("Items", "SUPER_STIMPAK", PID_SUPER_STIMPAK, cTweaksFile));
+ sfgame_SetHealingPID(2, IniGetInt("Items", "HEALING_POWDER", PID_HEALING_POWDER, cTweaksFile));
+ }
+}
+
+void EngineTweaks_Exit() {
+}
diff --git a/sfall/EngineTweaks.h b/sfall/EngineTweaks.h
new file mode 100644
index 00000000..5f85cc72
--- /dev/null
+++ b/sfall/EngineTweaks.h
@@ -0,0 +1,22 @@
+/*
+ * sfall
+ * Copyright (C) 2008-2021 The sfall team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+void EngineTweaks_Init();
+void EngineTweaks_Exit();
diff --git a/sfall/HookScripts.cpp b/sfall/HookScripts.cpp
index abf83735..0daabce8 100644
--- a/sfall/HookScripts.cpp
+++ b/sfall/HookScripts.cpp
@@ -620,18 +620,47 @@ defaultHandler:
}
}
+/*
+static void RemoveInvenObjHook_Script(TGameObj* source, TGameObj* item, long count, long rmType) {
+ BeginHook();
+ argCount = 5;
+
+ args[0] = (DWORD)source;
+ args[1] = (DWORD)item;
+ args[2] = count;
+ args[3] = rmType; // RMOBJ_*
+ args[4] = 0; // target only from item_move_func_
+
+ RunHookScript(HOOK_REMOVEINVENOBJ);
+ EndHook();
+}
+
+void RemoveInvenObjHook_Invoke(TGameObj* source, TGameObj* item, long count, long rmType) {
+ if (HookHasScript(HOOK_REMOVEINVENOBJ)) RemoveInvenObjHook_Script(source, item, count, rmType);
+}
+*/
+
+static long rmObjType = -1;
+
+void __stdcall SetRemoveObjectType(long rmType) {
+ rmObjType = rmType;
+}
+
static void __declspec(naked) RemoveObjHook() {
static const DWORD RemoveObjHookRet = 0x477497;
__asm {
- mov ecx, [esp + 8]; // call addr
+ mov ecx, [esp + 8]; // call addr
+ cmp rmObjType, -1;
+ cmovne ecx, rmObjType;
+ mov rmObjType, -1;
HookBegin;
- mov args[0], eax; // source
- mov args[4], edx; // item
- mov args[8], ebx; // count
- mov args[12], ecx; // called func
- xor esi, esi;
- xor ecx, 0x47761D; // from item_move_func_
- cmovz esi, ebp; // target
+ mov args[0], eax; // source
+ mov args[4], edx; // item
+ mov args[8], ebx; // count
+ mov args[12], ecx; // RMOBJ_* (called func)
+ xor esi, esi;
+ xor ecx, 0x47761D; // from item_move_func_
+ cmovz esi, ebp; // target
mov args[16], esi;
push edi;
push ebp;
@@ -1893,9 +1922,9 @@ void HookScripts_Init() {
HookCalls(UseObjOnHook, useObjOnHkAddr);
// the following hooks allows to catch drug use of AI and from action cursor
const DWORD drugUseObjOnHkAddr[] = {
- 0x4285DF, // ai_check_drugs
- 0x4286F8, // ai_check_drugs
- 0x4287F8, // ai_check_drugs
+ //0x4285DF, // ai_check_drugs
+ //0x4286F8, // ai_check_drugs
+ //0x4287F8, // ai_check_drugs
0x473573 // inven_action_cursor
};
HookCalls(Drug_UseObjOnHook, drugUseObjOnHkAddr);
diff --git a/sfall/HookScripts.h b/sfall/HookScripts.h
index f3655744..46de8f38 100644
--- a/sfall/HookScripts.h
+++ b/sfall/HookScripts.h
@@ -81,6 +81,8 @@ int __fastcall AmmoCostHook_Script(DWORD hookType, TGameObj* weapon, DWORD &roun
void InvenUnwield_HookDrop();
void InvenUnwield_HookMove();
+void __stdcall SetRemoveObjectType(long rmType);
+
void __stdcall AdjustFidHook(DWORD vanillaFid);
long __stdcall CalcApCostHook_Invoke(TGameObj* source, long hitMode, long isCalled, long cost, TGameObj* weapon);
diff --git a/sfall/Inventory.cpp b/sfall/Inventory.cpp
index 508e2eb9..47f5bfc7 100644
--- a/sfall/Inventory.cpp
+++ b/sfall/Inventory.cpp
@@ -304,8 +304,10 @@ static void __declspec(naked) gdControlUpdateInfo_hack() {
////////////////////////////////////////////////////////////////////////////////
static char superStimMsg[128];
+static const long SUPER_STIMPAK = 1;
+
static int __fastcall SuperStimFix(TGameObj* item, TGameObj* target) {
- if (item->protoId != PID_SUPER_STIMPAK || !target || target->IsNotCritter()) {
+ if (item->protoId != sfgame_GetHealingPID(SUPER_STIMPAK) || !target || target->IsNotCritter()) {
return 0;
}
@@ -671,7 +673,7 @@ void Inventory_Init() {
SafeWrite32(0x472632, widthWeight);
if (GetConfigInt("Misc", "SuperStimExploitFix", 0)) {
- Translate_Get("sfall", "SuperStimExploitMsg", "You cannot use a super stim on someone who is not injured!", superStimMsg);
+ Translate_Get("sfall", "SuperStimExploitMsg", "You cannot use this item on someone who is not injured!", superStimMsg);
MakeCall(0x49C3D9, protinst_use_item_on_hack);
}
diff --git a/sfall/ReplacementFuncs.cpp b/sfall/ReplacementFuncs.cpp
index cb3e26e0..4cbf4cf2 100644
--- a/sfall/ReplacementFuncs.cpp
+++ b/sfall/ReplacementFuncs.cpp
@@ -84,8 +84,8 @@ void __stdcall sfgame_ai_check_drugs(TGameObj* source) {
break;
}
- if (sfgame_IsHealingItem(itemFind) && !fo_item_remove_mult(source, itemFind, 1)) { // HOOK_REMOVEINVENOBJ
- if (!sfgame_UseDrugItemFunc(source, itemFind)) { // HOOK_USEOBJON
+ if (sfgame_IsHealingItem(itemFind) && !sfgame_item_remove_mult(source, itemFind, 1, 0x4286F0)) { // HOOK_REMOVEINVENOBJ
+ if (!sfgame_UseDrugItemFunc(source, itemFind)) { // HOOK_USEOBJON
drugWasUsed = true;
}
@@ -134,8 +134,8 @@ void __stdcall sfgame_ai_check_drugs(TGameObj* source) {
// if the preference counter is less than 3, then AI can use the drug
if (counter < 3) {
// if the item is NOT a healing drug
- if (!sfgame_IsHealingItem(item) && !fo_item_remove_mult(source, item, 1)) { // HOOK_REMOVEINVENOBJ
- if (!sfgame_UseDrugItemFunc(source, item)) { // HOOK_USEOBJON
+ if (!sfgame_IsHealingItem(item) && !sfgame_item_remove_mult(source, item, 1, 0x4286F0)) { // HOOK_REMOVEINVENOBJ
+ if (!sfgame_UseDrugItemFunc(source, item)) { // HOOK_USEOBJON
drugWasUsed = true;
usedCount++;
}
@@ -170,8 +170,8 @@ void __stdcall sfgame_ai_check_drugs(TGameObj* source) {
}
}
- if (lastItem && !fo_item_remove_mult(source, lastItem, 1)) { // HOOK_REMOVEINVENOBJ
- if (!sfgame_UseDrugItemFunc(source, lastItem)) lastItem = nullptr; // HOOK_USEOBJON
+ if (lastItem && !sfgame_item_remove_mult(source, lastItem, 1, 0x4286F0)) { // HOOK_REMOVEINVENOBJ
+ if (!sfgame_UseDrugItemFunc(source, lastItem)) lastItem = nullptr; // HOOK_USEOBJON
source->critter.decreaseAP(aiUseItemAPCost);
}
@@ -340,6 +340,11 @@ long __stdcall sfgame_item_d_take_drug(TGameObj* source, TGameObj* item) {
return -1; // cancel the drug use
}
+long __stdcall sfgame_item_remove_mult(TGameObj* source, TGameObj* item, long count, long rmType) {
+ SetRemoveObjectType(rmType);
+ return fo_item_remove_mult(source, item, count);
+}
+
long __stdcall sfgame_item_count(TGameObj* who, TGameObj* item) {
for (int i = 0; i < who->invenSize; i++) {
TGameObj::InvenItem* tableItem = &who->invenTable[i];
@@ -887,15 +892,15 @@ static void __declspec(naked) tile_num_beyond_hack() {
void InitReplacementHacks() {
// Replace ai_check_drugs_ function for code fixes and checking healing items
- //MakeJump(ai_check_drugs_, ai_check_drugs_hack); // 0x428480
+ MakeJump(ai_check_drugs_, ai_check_drugs_hack); // 0x428480
// Change ai_can_use_drug_ function code to check healing items
- //MakeCall(0x429BDE, ai_can_use_drug_hack, 6);
- //SafeWrite8(0x429BE9, CODETYPE_JumpNZ); // jz > jnz
- //SafeWrite8(0x429BF1, CODETYPE_JumpShort); // jnz > jmp
+ MakeCall(0x429BDE, ai_can_use_drug_hack, 6);
+ SafeWrite8(0x429BE9, CODETYPE_JumpNZ); // jz > jnz
+ SafeWrite8(0x429BF1, CODETYPE_JumpShort); // jnz > jmp
- //drugUsePerfFixMode = GetConfigInt("Misc", "AIDrugUsePerfFix", 0);
- //if (drugUsePerfFixMode > 0) dlogr("Applying AI drug use preference fix.", DL_FIX);
+ drugUsePerfFixMode = GetConfigInt("Misc", "AIDrugUsePerfFix", 0);
+ if (drugUsePerfFixMode > 0) dlogr("Applying AI drug use preference fix.", DL_FIX);
// Replace adjust_fid_ function
MakeJump(adjust_fid_, adjust_fid_hack); // 0x4716E8
diff --git a/sfall/ReplacementFuncs.h b/sfall/ReplacementFuncs.h
index 2866e0b4..e3e2f599 100644
--- a/sfall/ReplacementFuncs.h
+++ b/sfall/ReplacementFuncs.h
@@ -47,6 +47,8 @@ bool __stdcall sfgame_UseDrugItemFunc(TGameObj* source, TGameObj* item);
// Implementation of item_d_take_ engine function with the HOOK_USEOBJON hook
long __stdcall sfgame_item_d_take_drug(TGameObj* source, TGameObj* item);
+long __stdcall sfgame_item_remove_mult(TGameObj* source, TGameObj* item, long count, long rmType);
+
long __stdcall sfgame_item_count(TGameObj* who, TGameObj* item);
long __stdcall sfgame_item_weapon_range(TGameObj* source, TGameObj* weapon, long hitMode);
diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj
index 2e153eec..4321e628 100644
--- a/sfall/ddraw.vcxproj
+++ b/sfall/ddraw.vcxproj
@@ -220,6 +220,7 @@
+
@@ -304,6 +305,7 @@
+
diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters
index 6a5397ca..2abb8fdf 100644
--- a/sfall/ddraw.vcxproj.filters
+++ b/sfall/ddraw.vcxproj.filters
@@ -262,6 +262,9 @@
Headers
+
+ Headers
+
@@ -439,6 +442,9 @@
Source
+
+ Source
+
diff --git a/sfall/main.cpp b/sfall/main.cpp
index cb6f208a..4ca8fc81 100644
--- a/sfall/main.cpp
+++ b/sfall/main.cpp
@@ -36,6 +36,7 @@
#include "DamageMod.h"
#include "DebugEditor.h"
#include "Elevators.h"
+#include "EngineTweaks.h"
#include "Explosions.h"
#include "ExtraSaveSlots.h"
#include "FileSystem.h"
@@ -111,6 +112,9 @@ static void InitModules() {
dlogr("Running LoadGameHook_Init().", DL_INIT);
LoadGameHook_Init();
+ dlogr("Running EngineTweaks_Init().", DL_INIT);
+ EngineTweaks_Init();
+
dlogr("Running Books_Init().", DL_INIT);
Books_Init();
@@ -237,6 +241,7 @@ static void InitModules() {
static void __stdcall OnExit() {
Graphics_Exit();
+ EngineTweaks_Exit();
Books_Exit();
Movies_Exit();
Interface_Exit();