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)