diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h
index e6633df4..75fd0d32 100644
--- a/artifacts/scripting/headers/sfall.h
+++ b/artifacts/scripting/headers/sfall.h
@@ -52,7 +52,7 @@
#define HOOK_INVENTORYMOVE (24)
#define HOOK_INVENWIELD (25)
#define HOOK_ADJUSTFID (26)
-//#define HOOK_COMBATTURN (27) // unimplemented
+//#define HOOK_COMBATTURN (27) // unimplemented
#define HOOK_CARTRAVEL (28)
#define HOOK_SETGLOBALVAR (29)
#define HOOK_RESTTIMER (30)
@@ -348,6 +348,7 @@
#define intface_redraw sfall_func0("intface_redraw")
#define intface_show sfall_func0("intface_show")
#define inventory_redraw(invSide) sfall_func1("inventory_redraw", invSide)
+#define item_make_explosive(pid, activePid, min, max) sfall_func4("item_make_explosive", pid, activePid, min, max)
#define item_weight(obj) sfall_func1("item_weight", obj)
#define lock_is_jammed(obj) sfall_func1("lock_is_jammed", obj)
#define loot_obj sfall_func0("loot_obj")
diff --git a/artifacts/scripting/sfall function notes.md b/artifacts/scripting/sfall function notes.md
index e7b981ab..3c54010b 100644
--- a/artifacts/scripting/sfall function notes.md
+++ b/artifacts/scripting/sfall function notes.md
@@ -286,7 +286,7 @@ FUNCTION REFERENCE
-----
##### `array get_explosion_damage(itemPid)`
-- Returns an array of the minimum and maximum damage of Dynamite or Plastic Explosives.
+- Returns an array of the minimum and maximum damage of the explosive item.
-----
##### `void set_dynamite_damage(minDmg, maxDmg)`
@@ -682,6 +682,17 @@ sfall_funcX metarule functions
- Redraws inventory list in the inventory/use inventory item on/loot/barter screens
- `invSide` specifies which side needs to be redrawn: 0 - the player, 1 - target (container/NPC in loot/barter screens), -1 - both sides (same as without argument)
+----
+#### item_make_explosive
+`void sfall_func3("item_make_explosive", int pid, int activePid, int damage)`\
+`void sfall_func4("item_make_explosive", int pid, int activePid, int min, int max)`
+- Makes the specified item (pid) an explosive item like Dynamite or Plastic Explosives
+- `activePid` is for an item with an active timer, can be the same as the `pid` argument
+- The item proto must be the **Misc Item** type and have the **Use** action flag
+- `min` and `max` are the minimum and maximum explosion damage
+- Using the function on an item that is already set as an explosive will override its previous settings
+- __NOTE:__ this function does not work for pid's of Dynamite and Plastic Explosives
+
----
#### get_string_pointer
`int sfall_func1("get_string_pointer", string text)`
diff --git a/sfall/Modules/Explosions.cpp b/sfall/Modules/Explosions.cpp
index 1477d171..c797b2f4 100644
--- a/sfall/Modules/Explosions.cpp
+++ b/sfall/Modules/Explosions.cpp
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+#include
+
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
@@ -31,6 +33,15 @@ namespace sfall
using namespace fo::Fields;
+struct explosiveInfo {
+ DWORD pid;
+ DWORD pidActive;
+ DWORD minDamage;
+ DWORD maxDamage;
+};
+
+std::vector explosives;
+
static bool lightingEnabled = false;
static bool explosionsMetaruleReset = false;
static bool explosionsDamageReset = false;
@@ -152,6 +163,162 @@ static void __declspec(naked) fire_dance_lighting_fix1() {
}
}
+////////////////////////////////////////////////////////////////////////////////
+
+static DWORD __fastcall CheckExplosives(DWORD pid) {
+ for (std::vector::const_iterator it = explosives.begin(); it != explosives.end(); ++it) {
+ if (it->pid == pid) return it->pidActive;
+ }
+ return 0;
+}
+
+static DWORD __fastcall CheckActiveExplosives(DWORD pid) {
+ for (std::vector::const_iterator it = explosives.begin(); it != explosives.end(); ++it) {
+ if (it->pidActive == pid) return 0;
+ }
+ return 1;
+}
+
+static DWORD __fastcall GetDamage(DWORD pid, DWORD &min, DWORD &max) {
+ DWORD result = 0;
+ for (std::vector::const_iterator it = explosives.begin(); it != explosives.end(); ++it) {
+ if (it->pidActive == pid) {
+ min = it->minDamage;
+ max = it->maxDamage;
+ result = 1;
+ break;
+ }
+ }
+ return result;
+}
+
+static DWORD __fastcall SetQueueExplosionDamage(DWORD pid) {
+ DWORD min, max;
+ DWORD result = GetDamage(pid, min, max);
+
+ if (result) {
+ __asm mov edx, min;
+ __asm mov ecx, max;
+ }
+ return result;
+}
+
+static void __declspec(naked) obj_use_explosive_hack() {
+ using namespace fo;
+ __asm {
+ cmp edx, PID_PLASTIC_EXPLOSIVES;
+ jz end;
+ // end engine code
+ push edx;
+ mov ecx, edx;
+ call CheckExplosives;
+ pop edx;
+ test eax, eax;
+ jnz end;
+ retn;
+end:
+ mov dword ptr [esp], 0x49BCE0;
+ retn;
+ }
+}
+
+static void __declspec(naked) obj_use_explosive_active_hack() {
+ using namespace fo;
+ __asm {
+ cmp eax, PID_PLASTIC_EXPLOSIVES;
+ jz end;
+ // end engine code
+ mov ecx, eax;
+ call CheckExplosives;
+ test eax, eax;
+ jz skipSet;
+ mov dword ptr [esi + protoId], eax; // change item pid to active;
+skipSet:
+ mov dword ptr [esp], 0x49BD62;
+end:
+ retn;
+ }
+}
+
+static void __declspec(naked) queue_do_explosion_hack() {
+ using namespace fo;
+ __asm {
+ cmp edx, PID_ACTIVE_DYNAMITE;
+ jz dynamite;
+ cmp edx, PID_ACTIVE_PLASTIC_EXPLOSIVE;
+ jz end;
+ push edx;
+ mov ecx, edx;
+ call SetQueueExplosionDamage;
+ test eax, eax;
+ mov ebx, edx;
+ pop edx;
+ jz end;
+ mov dword ptr [esp], 0x4A2888;
+end:
+ retn;
+dynamite:
+ mov dword ptr [esp], 0x4A2872;
+ retn;
+ }
+}
+
+static void __declspec(naked) inven_action_cursor_drop_hack() {
+ using namespace fo;
+ __asm {
+ cmp ebx, PID_ACTIVE_PLASTIC_EXPLOSIVE;
+ jz end;
+ // end engine code
+ push eax;
+ mov ecx, ebx;
+ call CheckActiveExplosives;
+ test eax, eax; // check in engine
+ pop eax;
+end:
+ retn;
+ }
+}
+
+static void __declspec(naked) protinstTestDroppedExplosive_hack() {
+ __asm {
+ jz end;
+ mov ecx, edx;
+ call CheckActiveExplosives;
+ test eax, eax;
+ jz end;
+ mov dword ptr [esp], 0x49C112; // exit, no active explosive item
+end:
+ retn;
+ }
+}
+
+static void apply_expl_hack() {
+ MakeCall(0x49BCC7, obj_use_explosive_hack); // check explosives
+ MakeCall(0x49BD56, obj_use_explosive_active_hack); // set active explosive
+ MakeCall(0x4A2865, queue_do_explosion_hack); // set damage explosive
+ MakeCall(0x4737F2, inven_action_cursor_drop_hack, 1); // check drop explosives
+ MakeCall(0x49C005, protinstTestDroppedExplosive_hack, 1); // check drop explosives
+}
+
+void Explosions::AddToExplosives(DWORD pid, DWORD activePid, DWORD minDmg, DWORD maxDmg) {
+ static bool onlyOnce = false;
+ for (unsigned int i = 0; i < explosives.size(); i++) {
+ if (explosives[i].pid == pid) {
+ explosives.erase(explosives.begin() + i);
+ break;
+ }
+ }
+ explosiveInfo expl = { pid, activePid, minDmg, maxDmg };
+ explosives.push_back(expl);
+
+ if (!onlyOnce) {
+ apply_expl_hack();
+ onlyOnce = true;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
static const DWORD explosion_dmg_check_adr[] = {0x411709, 0x4119FC, 0x411C08, 0x4517C1, 0x423BC8, 0x42381A};
static const DWORD explosion_art_adr[] = {0x411A19, 0x411A29, 0x411A35, 0x411A3C};
static const DWORD explosion_art_defaults[] = {10, 2, 31, 29};
@@ -203,6 +370,8 @@ static int GetExplosionDamage(int pid) {
min = *(DWORD*)plastic_min_dmg_addr;
max = *(DWORD*)plastic_max_dmg_addr;
break;
+ default:
+ GetDamage(pid, min, max);
}
DWORD arrayId = script::CreateTempArray(2, 0);
@@ -298,6 +467,8 @@ void ResetExplosionRadius() {
}
static void ResetExplosionDamage() {
+ explosives.clear();
+
if (!explosionsDamageReset) return;
SafeWrite32(dynamite_min_dmg_addr, dynamite_minDmg);
SafeWrite32(dynamite_max_dmg_addr, dynamite_maxDmg);
diff --git a/sfall/Modules/Explosions.h b/sfall/Modules/Explosions.h
index fe34840f..c9f62296 100644
--- a/sfall/Modules/Explosions.h
+++ b/sfall/Modules/Explosions.h
@@ -27,6 +27,8 @@ public:
static void init();
static void OnGameLoad();
+
+ static void AddToExplosives(DWORD pid, DWORD activePid, DWORD minDmg, DWORD maxDmg);
};
void ResetExplosionSettings();
diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp
index 4cd478c6..e2591dee 100644
--- a/sfall/Modules/HookScripts/MiscHs.cpp
+++ b/sfall/Modules/HookScripts/MiscHs.cpp
@@ -11,7 +11,7 @@ namespace sfall
{
// The hook is executed twice when entering the barter screen and after transaction: the first time is for the player; the second time is for NPC
-static DWORD __fastcall BarterPriceHook_Script(register fo::GameObject* source, register fo::GameObject* target, DWORD callAddr) {
+static DWORD __fastcall BarterPriceHook_Script(fo::GameObject* source, fo::GameObject* target, DWORD callAddr) {
bool barterIsParty = (*fo::ptr::dialog_target_is_party != 0);
long computeCost = fo::func::barter_compute_value(source, target);
@@ -100,7 +100,7 @@ void SourceUseSkillOnInit() { sourceSkillOn = *fo::ptr::obj_dude; }
static char resultSkillOn; // -1 - cancel handler, 1 - replace user
static long bakupCombatState;
-static void __fastcall UseSkillOnHook_Script(DWORD source, DWORD target, register DWORD skillId) {
+static void __fastcall UseSkillOnHook_Script(DWORD source, DWORD target, DWORD skillId) {
BeginHook();
argCount = 3;
@@ -389,7 +389,7 @@ static void __declspec(naked) CarTravelHack() {
}
}
-static long __fastcall GlobalVarHook_Script(register DWORD number, register int value) {
+static long __fastcall GlobalVarHook_Script(DWORD number, int value) {
int old = (*fo::ptr::game_global_vars)[number];
if (IsGameLoaded() && HookScripts::HookHasScript(HOOK_SETGLOBALVAR)) { // IsGameLoaded - don't execute hook until loading sfall scripts
diff --git a/sfall/Modules/Interface.cpp b/sfall/Modules/Interface.cpp
index 687563be..f3f17376 100644
--- a/sfall/Modules/Interface.cpp
+++ b/sfall/Modules/Interface.cpp
@@ -683,7 +683,7 @@ static void InterfaceWindowPatch() {
// Increase the max text width of the information card on the character screen
const DWORD drawCardAddr[] = {0x43ACD5, 0x43DD37}; // 136, 133 (DrawCard_, DrawCard2_)
- SafeWriteBatch(145, drawCardAddr);
+ SafeWriteBatch(146, drawCardAddr);
// Increase the width of the mouse drop area from 64px to 80px for the PC's and NPC's inventory on the barter screen
// barter_move_from_table_inventory_
diff --git a/sfall/Modules/MainLoopHook.cpp b/sfall/Modules/MainLoopHook.cpp
index 64df1c89..e2020b41 100644
--- a/sfall/Modules/MainLoopHook.cpp
+++ b/sfall/Modules/MainLoopHook.cpp
@@ -42,7 +42,7 @@ static void __stdcall CombatLoop() { // OnCombatLoop
}
static void __stdcall AfterCombatAttack() { // OnAfterCombatAttack
- ResetExplosionSettings();
+ ResetExplosionSettings(); // after each combat attack, reset metarule_explosions settings
}
static void __declspec(naked) MainGameLoopHook() {
diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp
index 4aa12b6d..41730a90 100644
--- a/sfall/Modules/Scripting/Handlers/Metarule.cpp
+++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp
@@ -109,6 +109,7 @@ static const SfallMetarule metarules[] = {
{"intface_redraw", mf_intface_redraw, 0, 1},
{"intface_show", mf_intface_show, 0, 0},
{"inventory_redraw", mf_inventory_redraw, 0, 1, -1, {ARG_INT}},
+ {"item_make_explosive", mf_item_make_explosive, 3, 4, -1, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}},
{"item_weight", mf_item_weight, 1, 1, 0, {ARG_OBJECT}},
{"lock_is_jammed", mf_lock_is_jammed, 1, 1, 0, {ARG_OBJECT}},
{"loot_obj", mf_get_loot_object, 0, 0},
diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp
index 90983c18..b3439894 100644
--- a/sfall/Modules/Scripting/Handlers/Objects.cpp
+++ b/sfall/Modules/Scripting/Handlers/Objects.cpp
@@ -398,6 +398,25 @@ void mf_set_unjam_locks_time(OpcodeContext& ctx) {
}
}
+void mf_item_make_explosive(OpcodeContext& ctx) {
+ DWORD pid = ctx.arg(0).rawValue();
+ DWORD pidActive = ctx.arg(1).rawValue();
+ DWORD min = ctx.arg(2).rawValue();
+ DWORD max = (ctx.numArgs() == 4) ? ctx.arg(3).rawValue() : min;
+
+ if (min > max) {
+ max = min;
+ 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("%s() - invalid PID number, must be greater than 0.", ctx.getMetaruleName());
+ ctx.setReturn(-1);
+ }
+}
+
void mf_get_dialog_object(OpcodeContext& ctx) {
ctx.setReturn(InDialog() ? *fo::ptr::dialog_target : 0);
}
diff --git a/sfall/Modules/Scripting/Handlers/Objects.h b/sfall/Modules/Scripting/Handlers/Objects.h
index 438683fe..e09ceeb1 100644
--- a/sfall/Modules/Scripting/Handlers/Objects.h
+++ b/sfall/Modules/Scripting/Handlers/Objects.h
@@ -79,6 +79,8 @@ void mf_unjam_lock(OpcodeContext&);
void mf_set_unjam_locks_time(OpcodeContext&);
+void mf_item_make_explosive(OpcodeContext&);
+
void mf_get_dialog_object(OpcodeContext&);
void mf_obj_under_cursor(OpcodeContext&);