diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h
index db849818..a491927b 100644
--- a/artifacts/scripting/headers/sfall.h
+++ b/artifacts/scripting/headers/sfall.h
@@ -261,6 +261,7 @@
#define set_outline(obj, color) sfall_func2("set_outline", obj, color)
#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)
#define spatial_radius(obj) sfall_func1("spatial_radius", obj)
#define tile_refresh_display sfall_func0("tile_refresh_display")
#define unjam_lock(obj) sfall_func1("unjam_lock", obj)
diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt
index b258f5cb..ac6ef963 100644
--- a/artifacts/scripting/sfall function notes.txt
+++ b/artifacts/scripting/sfall function notes.txt
@@ -443,6 +443,12 @@ Some utility/math functions are available:
- unjams a lock immediately without having to wait until the next day, or leave the map and then return after 24 hours
- does not work in use_skill_on_p_proc procedure
+> void sfall_func1("set_unjam_locks_time", int time)
+- sets after how many hours (up to 127 hours) jammed locks will be unjammed if the player leaves the map
+- also disables the auto unjam that occurs at midnight when the player is on the map
+- passing 0 will disable the auto unjam mechanism completely
+- The auto unjam mechanism will be reset each time the player reloads the game
+
> array sfall_func0("get_map_enter_position")
- returns an array of the player's position data (index 0 - tile, 1 - elevation, 2 - rotation) when entering the map through exit grids
- if entering from the world map, the tile value will be -1
diff --git a/sfall/Combat.cpp b/sfall/Combat.cpp
index 0bd54201..763c41d7 100644
--- a/sfall/Combat.cpp
+++ b/sfall/Combat.cpp
@@ -23,7 +23,7 @@
#include "FalloutEngine.h"
#include "HookScripts.h"
-#include "ScriptExtender.h"
+#include "Objects.h"
#include "Combat.h"
diff --git a/sfall/LoadGameHook.cpp b/sfall/LoadGameHook.cpp
index 6fb63f3a..a7e52f94 100644
--- a/sfall/LoadGameHook.cpp
+++ b/sfall/LoadGameHook.cpp
@@ -36,6 +36,7 @@
#include "Logging.h"
#include "Message.h"
#include "movies.h"
+#include "Objects.h"
#include "PartyControl.h"
#include "perks.h"
#include "ScriptExtender.h"
@@ -99,6 +100,7 @@ static void _stdcall ResetState(DWORD onLoad) {
ForceEncounterRestore(); // restore if the encounter did not happen
AfterAttackCleanup();
ResetExplosionRadius();
+ RestoreObjUnjamAllLocks();
PartyControlReset();
NpcEngineLevelUpReset();
}
diff --git a/sfall/Objects.cpp b/sfall/Objects.cpp
new file mode 100644
index 00000000..5499d32d
--- /dev/null
+++ b/sfall/Objects.cpp
@@ -0,0 +1,227 @@
+/*
+ * sfall
+ * Copyright (C) 2008-2018 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 "Define.h"
+#include "FalloutEngine.h"
+
+#include "Objects.h"
+
+static int unjamTimeState;
+static int maxCountLoadProto = 512;
+
+long objUniqueID = UID_START; // current counter id, saving to sfallgv.sav
+
+// Assigns a new unique identifier to an object if it has not been previously assigned
+// the identifier is saved with the object in the saved game and this can used in various script
+// player ID = 18000, all party members have ID = 18000 + its pid (file number of prototype)
+long __fastcall SetObjectUniqueID(TGameObj* obj) {
+ long id = obj->ID;
+ if (id > UID_START || (id >= PLAYER_ID && id < 83536)) return id; // 65535 maximum possible number of prototypes
+
+ if ((DWORD)objUniqueID >= (DWORD)UID_END) objUniqueID = UID_START;
+ obj->ID = ++objUniqueID;
+ return objUniqueID;
+}
+
+// Assigns a unique ID in the negative range (0xFFFFFFF6 - 0x8FFFFFF7)
+long __fastcall SetSpecialID(TGameObj* obj) {
+ long id = obj->ID;
+ if (id <= -10 || id > UID_START) return id;
+
+ if ((DWORD)objUniqueID >= (DWORD)UID_END) objUniqueID = UID_START;
+ id = -9 - (++objUniqueID - UID_START);
+ obj->ID = id;
+ return id;
+}
+
+void SetNewEngineID(TGameObj* obj) {
+ if (obj->ID > UID_START) return;
+ obj->ID = NewObjId();
+}
+
+static void __declspec(naked) item_identical_hack() {
+ __asm {
+ mov ecx, [edi]; // item id
+ cmp ecx, UID_START; // start unique ID
+ jg notIdentical;
+ mov eax, [esi + 0x78]; // scriptID
+ cmp eax, ebx;
+notIdentical:
+ retn; // if ZF == 0 then item is not identical
+ }
+}
+
+static void __declspec(naked) new_obj_id_hook() {
+ __asm {
+ mov eax, 83535;
+ cmp dword ptr ds:[_cur_id], eax;
+ jle pickNewID;
+ retn;
+pickNewID: // skip PM range (18000 - 83535)
+ mov ds:[_cur_id], eax;
+ jmp new_obj_id_;
+ }
+}
+
+// Reassigns object IDs to all critters upon first loading a map
+static void __stdcall map_fix_critter_id() {
+ long npcStartID = 4096;
+ TGameObj* obj = ObjFindFirst();
+ while (obj) {
+ if (obj->pid >> 24 == OBJ_TYPE_CRITTER && obj->ID < PLAYER_ID) {
+ obj->ID = npcStartID++;
+ }
+ obj = ObjFindNext();
+ }
+}
+
+static void __declspec(naked) map_load_file_hack() {
+ __asm {
+ jz mapVirgin;
+ retn;
+mapVirgin:
+ test eax, eax;
+ jl skip; // check map index > -1
+ call wmMapIsSaveable_;
+ test eax, eax;
+ jnz saveable;
+ retn;
+saveable:
+ call map_fix_critter_id;
+skip:
+ xor eax, eax; // set ZF
+ retn;
+ }
+}
+
+static void __declspec(naked) queue_add_hack() {
+ __asm {
+ mov [edx + 8], edi; // queue.object
+ mov [edx], esi; // queue.time
+ test edi, edi;
+ jnz fix;
+ retn;
+fix:
+ mov eax, [edi + 0x64];
+ and eax, 0x0F000000;
+ jnz notItem; // object is not an item?
+ push ecx;
+ push edx;
+ mov ecx, edi;
+ call SetSpecialID;
+ pop edx;
+ pop ecx;
+ retn;
+notItem:
+ cmp ecx, script_timer_event; // QueueType
+ je end;
+ cmp eax, OBJ_TYPE_CRITTER << 24;
+ jne end;
+ push ecx;
+ push edx;
+ mov ecx, edi;
+ call SetObjectUniqueID;
+ pop edx;
+ pop ecx;
+end:
+ xor edi, edi; // fix: don't set "Used" flag for non-item objects
+ retn;
+ }
+}
+
+void SetAutoUnjamLockTime(DWORD time) {
+ if (!unjamTimeState) {
+ BlockCall(0x4A364A); // disable auto unjam at midnight
+ }
+
+ if (time > 0) {
+ SafeWrite8(0x4831D9, (BYTE)time);
+ if (unjamTimeState == 2) {
+ SafeWrite8(0x4831DA, 0x7C);
+ }
+ unjamTimeState = 1;
+ } else {
+ SafeWrite8(0x4831DA, 0xEB); // disable auto unjam
+ unjamTimeState = 2;
+ }
+}
+
+void RestoreObjUnjamAllLocks() {
+ if (unjamTimeState) {
+ SafeWrite8(0x4A364A, 0xE8);
+ SafeWrite32(0x4A364B, 0xFFFF9E69);
+ SafeWrite8(0x4831DA, 0x7C);
+ SafeWrite8(0x4831D9, 24);
+ unjamTimeState = 0;
+ }
+}
+
+static void __declspec(naked) proto_ptr_hack() {
+ __asm {
+ mov ecx, maxCountLoadProto;
+ cmp ecx, 4096;
+ jae skip;
+ cmp eax, ecx;
+ jb end;
+ add ecx, 256;
+ mov maxCountLoadProto, ecx;
+skip:
+ cmp eax, ecx;
+end:
+ retn;
+ }
+}
+
+void LoadProtoAutoMaxLimit() {
+ MakeCall(0x4A21B2, proto_ptr_hack);
+}
+
+static void __declspec(naked) obj_insert_hack() {
+ __asm {
+ mov edi, [ebx];
+ mov [esp + 0x38 - 0x1C + 4], esi; // 0
+ test edi, edi;
+ jnz insert;
+ retn;
+insert:
+ mov esi, [ecx]; // esi - inserted object
+ cmp dword ptr [esi + 0x64], PID_CORPSE_BLOOD;
+ jnz skip;
+ xor edi, edi;
+skip:
+ retn;
+ }
+}
+
+void ObjectsInit() {
+ HookCall(0x4A38A5, new_obj_id_hook);
+ SafeWrite8(0x4A38B3, 0x90); // fix ID increment
+
+ MakeCall(0x477A0E, item_identical_hack); // don't put item with unique ID to items stack
+
+ // Fix mapper bug by reassigning object IDs to critters (for unvisited maps)
+ MakeCall(0x482E6B, map_load_file_hack);
+ SafeWrite8(0x482E71, 0x85); // jz > jnz
+ // Additionally fix object IDs for queued events
+ MakeCall(0x4A25BA, queue_add_hack);
+
+ // Place some objects on the tile to the lower z-layer
+ MakeCall(0x48D918, obj_insert_hack, 1);
+}
diff --git a/sfall/Objects.h b/sfall/Objects.h
new file mode 100644
index 00000000..5c242d28
--- /dev/null
+++ b/sfall/Objects.h
@@ -0,0 +1,18 @@
+#pragma once
+
+enum UniqueID : long {
+ UID_START = 0x0FFFFFFF, // start at 0x10000000
+ UID_END = 0x7FFFFFFF
+};
+
+extern long objUniqueID;
+
+void ObjectsInit();
+
+long __fastcall SetObjectUniqueID(TGameObj* obj);
+long __fastcall SetSpecialID(TGameObj* obj);
+void SetNewEngineID(TGameObj* obj);
+
+void SetAutoUnjamLockTime(DWORD time);
+void RestoreObjUnjamAllLocks();
+void LoadProtoAutoMaxLimit();
diff --git a/sfall/ScriptExtender.cpp b/sfall/ScriptExtender.cpp
index 98e8b758..67887376 100644
--- a/sfall/ScriptExtender.cpp
+++ b/sfall/ScriptExtender.cpp
@@ -407,6 +407,7 @@ static const SfallOpcodeMetadata opcodeMetaArray[] = {
{sf_set_object_data, "set_object_data", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT, DATATYPE_MASK_INT}},
{sf_set_outline, "set_outline", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}},
{sf_set_unique_id, "set_unique_id", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}},
+ {sf_set_unjam_locks_time, "set_unjam_locks_time", {DATATYPE_MASK_INT}},
{sf_spatial_radius, "spatial_radius", {DATATYPE_MASK_VALID_OBJ}},
{sf_unjam_lock, "unjam_lock", {DATATYPE_MASK_VALID_OBJ}},
#ifndef NDEBUG
@@ -1195,165 +1196,6 @@ end:
}
}
-static int maxCountLoadProto = 512;
-
-long objUniqueID = UID_START; // current counter id, saving to sfallgv.sav
-
-// Assigns a new unique identifier to an object if it has not been previously assigned
-// the identifier is saved with the object in the saved game and this can used in various script
-// player ID = 18000, all party members have ID = 18000 + its pid (file number of prototype)
-long __fastcall SetObjectUniqueID(TGameObj* obj) {
- long id = obj->ID;
- if (id > UID_START || (id >= PLAYER_ID && id < 83536)) return id; // 65535 maximum possible number of prototypes
-
- if ((DWORD)objUniqueID >= (DWORD)UID_END) objUniqueID = UID_START;
- obj->ID = ++objUniqueID;
- return objUniqueID;
-}
-
-// Assigns a unique ID in the negative range (0xFFFFFFF6 - 0x8FFFFFF7)
-long __fastcall SetSpecialID(TGameObj* obj) {
- long id = obj->ID;
- if (id <= -10 || id > UID_START) return id;
-
- if ((DWORD)objUniqueID >= (DWORD)UID_END) objUniqueID = UID_START;
- id = -9 - (++objUniqueID - UID_START);
- obj->ID = id;
- return id;
-}
-
-void SetNewEngineID(TGameObj* obj) {
- if (obj->ID > UID_START) return;
- obj->ID = NewObjId();
-}
-
-static void __declspec(naked) item_identical_hack() {
- __asm {
- mov ecx, [edi]; // item id
- cmp ecx, UID_START; // start unique ID
- jg notIdentical;
- mov eax, [esi + 0x78]; // scriptID
- cmp eax, ebx;
-notIdentical:
- retn; // if ZF == 0 then item is not identical
- }
-}
-
-static void __declspec(naked) new_obj_id_hook() {
- __asm {
- mov eax, 83535;
- cmp dword ptr ds:[_cur_id], eax;
- jle pickNewID;
- retn;
-pickNewID: // skip PM range (18000 - 83535)
- mov ds:[_cur_id], eax;
- jmp new_obj_id_;
- }
-}
-
-// Reassigns object IDs to all critters upon first loading a map
-static void __stdcall map_fix_critter_id() {
- long npcStartID = 4096;
- TGameObj* obj = ObjFindFirst();
- while (obj) {
- if (obj->pid >> 24 == OBJ_TYPE_CRITTER && obj->ID < PLAYER_ID) {
- obj->ID = npcStartID++;
- }
- obj = ObjFindNext();
- }
-}
-
-static void __declspec(naked) map_load_file_hack() {
- __asm {
- jz mapVirgin;
- retn;
-mapVirgin:
- test eax, eax;
- jl skip; // check map index > -1
- call wmMapIsSaveable_;
- test eax, eax;
- jnz saveable;
- retn;
-saveable:
- call map_fix_critter_id;
-skip:
- xor eax, eax; // set ZF
- retn;
- }
-}
-
-static void __declspec(naked) queue_add_hack() {
- __asm {
- mov [edx + 8], edi; // queue.object
- mov [edx], esi; // queue.time
- test edi, edi;
- jnz fix;
- retn;
-fix:
- mov eax, [edi + 0x64];
- and eax, 0x0F000000;
- jnz notItem; // object is not an item?
- push ecx;
- push edx;
- mov ecx, edi;
- call SetSpecialID;
- pop edx;
- pop ecx;
- retn;
-notItem:
- cmp ecx, script_timer_event; // QueueType
- je end;
- cmp eax, OBJ_TYPE_CRITTER << 24;
- jne end;
- push ecx;
- push edx;
- mov ecx, edi;
- call SetObjectUniqueID;
- pop edx;
- pop ecx;
-end:
- xor edi, edi; // fix: don't set "Used" flag for non-item objects
- retn;
- }
-}
-
-static void __declspec(naked) proto_ptr_hack() {
- __asm {
- mov ecx, maxCountLoadProto;
- cmp ecx, 4096;
- jae skip;
- cmp eax, ecx;
- jb end;
- add ecx, 256;
- mov maxCountLoadProto, ecx;
-skip:
- cmp eax, ecx;
-end:
- retn;
- }
-}
-
-void LoadProtoAutoMaxLimit() {
- MakeCall(0x4A21B2, proto_ptr_hack);
-}
-
-static void __declspec(naked) obj_insert_hack() {
- __asm {
- mov edi, [ebx];
- mov [esp + 0x38 - 0x1C + 4], esi; // 0
- test edi, edi;
- jnz insert;
- retn;
-insert:
- mov esi, [ecx]; // esi - inserted object
- cmp dword ptr [esi + 0x64], PID_CORPSE_BLOOD;
- jnz skip;
- xor edi, edi;
-skip:
- retn;
- }
-}
-
static void __declspec(naked) map_save_in_game_hook() {
__asm {
test cl, 1;
@@ -1395,20 +1237,6 @@ void ScriptExtenderSetup() {
SafeWrite8(0x4C9F13, idle);
}
- HookCall(0x4A38A5, new_obj_id_hook);
- SafeWrite8(0x4A38B3, 0x90); // fix ID increment
-
- MakeCall(0x477A0E, item_identical_hack); // don't put item with unique ID to items stack
-
- // Fix mapper bug by reassigning object IDs to critters (for unvisited maps)
- MakeCall(0x482E6B, map_load_file_hack);
- SafeWrite8(0x482E71, 0x85); // jz > jnz
- // Additionally fix object IDs for queued events
- MakeCall(0x4A25BA, queue_add_hack);
-
- // Place some objects on the tile to the lower z-layer
- MakeCall(0x48D918, obj_insert_hack, 1);
-
arraysBehavior = GetPrivateProfileIntA("Misc", "arraysBehavior", 1, ini);
if (arraysBehavior > 0) {
arraysBehavior = 1; // only 1 and 0 allowed at this time
diff --git a/sfall/ScriptExtender.h b/sfall/ScriptExtender.h
index a99f7aac..1275f9fc 100644
--- a/sfall/ScriptExtender.h
+++ b/sfall/ScriptExtender.h
@@ -28,11 +28,6 @@ enum SfallDataType {
DATATYPE_STR
};
-enum UniqueID : long {
- UID_START = 0x0FFFFFFF, // start at 0x10000000
- UID_END = 0x7FFFFFFF
-};
-
#pragma pack(push, 8)
struct sGlobalVar {
__int64 id;
@@ -99,14 +94,6 @@ static char reg_anim_combat_check = 1;
extern DWORD isGlobalScriptLoading;
extern DWORD AvailableGlobalScriptTypes;
-void LoadProtoAutoMaxLimit();
-
-// object's unique id
-extern long objUniqueID;
-long __fastcall SetObjectUniqueID(TGameObj* obj);
-long __fastcall SetSpecialID(TGameObj* obj);
-void SetNewEngineID(TGameObj* obj);
-
// Script data types
#define VAR_TYPE_INT (0xC001)
#define VAR_TYPE_FLOAT (0xA001)
diff --git a/sfall/ScriptOps/MetaruleOp.hpp b/sfall/ScriptOps/MetaruleOp.hpp
index 8d79c773..f5625553 100644
--- a/sfall/ScriptOps/MetaruleOp.hpp
+++ b/sfall/ScriptOps/MetaruleOp.hpp
@@ -153,6 +153,7 @@ static const SfallMetarule metaruleArray[] = {
{"set_object_data", sf_set_object_data, 3, 3},
{"set_outline", sf_set_outline, 2, 2},
{"set_unique_id", sf_set_unique_id, 1, 2},
+ {"set_unjam_locks_time", sf_set_unjam_locks_time, 1, 1},
{"spatial_radius", sf_spatial_radius, 1, 1},
{"tile_refresh_display", sf_tile_refresh_display, 0, 0},
{"unjam_lock", sf_unjam_lock, 1, 1},
diff --git a/sfall/ScriptOps/ObjectsOps.hpp b/sfall/ScriptOps/ObjectsOps.hpp
index 5f35408d..abe67c13 100644
--- a/sfall/ScriptOps/ObjectsOps.hpp
+++ b/sfall/ScriptOps/ObjectsOps.hpp
@@ -21,8 +21,8 @@
#include "main.h"
#include "Inventory.h"
+#include "Objects.h"
#include "PartyControl.h"
-#include "ScriptExtender.h"
//script control functions
@@ -619,6 +619,15 @@ static void sf_unjam_lock() {
}
}
+static void sf_set_unjam_locks_time() {
+ int time = opHandler.arg(0).asInt();
+ if (time < 0 || time > 127) {
+ opHandler.printOpcodeError("set_unjam_locks_time() - time argument must be in the range of 0 to 127.");
+ } else {
+ SetAutoUnjamLockTime(time);
+ }
+}
+
static void sf_get_current_inven_size() {
opHandler.setReturn(sf_item_total_size(opHandler.arg(0).asObject()), DATATYPE_INT);
}
diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj
index 2431989a..406d6852 100644
--- a/sfall/ddraw.vcxproj
+++ b/sfall/ddraw.vcxproj
@@ -301,6 +301,7 @@
+
@@ -367,6 +368,7 @@
+
diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters
index 742e4e97..0eda4fe2 100644
--- a/sfall/ddraw.vcxproj.filters
+++ b/sfall/ddraw.vcxproj.filters
@@ -199,6 +199,9 @@
Headers
+
+ Headers
+
Headers
@@ -340,6 +343,9 @@
Source
+
+ Source
+
Source
diff --git a/sfall/main.cpp b/sfall/main.cpp
index c59fb79f..edd3ac00 100644
--- a/sfall/main.cpp
+++ b/sfall/main.cpp
@@ -48,6 +48,7 @@
#include "MainMenu.h"
#include "Message.h"
#include "movies.h"
+#include "Objects.h"
#include "PartyControl.h"
#include "perks.h"
#include "Premade.h"
@@ -646,6 +647,9 @@ static void DllMain2() {
MainMenuInit();
dlogr(" Done", DL_INIT);
+ dlogr("Running ObjectsInit().", DL_INIT);
+ ObjectsInit();
+
mapName[64] = 0;
if (GetPrivateProfileString("Misc", "StartingMap", "", mapName, 64, ini)) {
dlog("Applying starting map patch.", DL_INIT);
diff --git a/sfall/skills.cpp b/sfall/skills.cpp
index c9d5e12c..2c059dcb 100644
--- a/sfall/skills.cpp
+++ b/sfall/skills.cpp
@@ -25,7 +25,7 @@
#include "Define.h"
#include "FalloutEngine.h"
#include "Combat.h"
-#include "ScriptExtender.h"
+#include "Objects.h"
#define SKILL_MIN_LIMIT (-128)