diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index aafe9127..dccfc845 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -473,10 +473,13 @@ Some utility/math functions are available: - clears the cache of FRM image files loaded into memory > int sfall_func1("set_unique_id", object) -- assigns an unique ID number to the object and returns it. If an unique ID number has already been assigned to an object, then ID number is returned without reassignment +> int sfall_func2("set_unique_id", object, int flag) +- assigns a unique ID number to the object and returns it. If a unique ID number has already been assigned to an object, then ID number is returned without reassignment +- items with unique IDs will not stack with other items of the same type in the inventory - to just get the current ID number of an object, use sfall_func2("get_object_data", object, OBJ_DATA_ID) - unique ID numbers are saved in your savegame, and have a range from 0x10000000 to 0x7FFFFFFF -- there is also an unique ID number range for the player and party members from 18000 to 83535 +- 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 ------------------------ ------ MORE INFO ------- diff --git a/sfall/Knockback.cpp b/sfall/Knockback.cpp index 70e4ac97..546f665e 100644 --- a/sfall/Knockback.cpp +++ b/sfall/Knockback.cpp @@ -27,15 +27,9 @@ static std::vector NoBursts; // object id -struct KnockbackModifier { - long id; - DWORD type; - double value; -}; - static std::vector mTargets; static std::vector mAttackers; -static std::vector mWeapons; +std::vector Knockback_mWeapons; struct ChanceModifier { long id; @@ -73,7 +67,7 @@ static double ApplyModifiers(std::vector* mods, TGameObj* obj static DWORD __fastcall CalcKnockbackMod(int knockValue, int damage, TGameObj* weapon, TGameObj* attacker, TGameObj* target) { double result = (double)damage / (double)knockValue; - result = ApplyModifiers(&mWeapons, weapon, result); + result = ApplyModifiers(&Knockback_mWeapons, weapon, result); result = ApplyModifiers(&mAttackers, attacker, result); result = ApplyModifiers(&mTargets, target, result); return (DWORD)floor(result); @@ -177,19 +171,25 @@ void _stdcall KnockbackSetMod(TGameObj* object, DWORD type, float val, DWORD on) std::vector* mods; switch (on) { case 0: - mods = &mWeapons; + if (object->pid >> 24 != OBJ_TYPE_ITEM) return; + mods = &Knockback_mWeapons; break; case 1: + if (object->pid >> 24 != OBJ_TYPE_CRITTER) return; mods = &mTargets; break; case 2: + if (object->pid >> 24 != OBJ_TYPE_CRITTER) return; mods = &mAttackers; break; default: return; } - long id = SetObjectUniqueID(object); + long id = (on == 0) + ? SetSpecialID(object) + : SetObjectUniqueID(object); + KnockbackModifier mod = { id, type, (double)val }; for (DWORD i = 0; i < mods->size(); i++) { if ((*mods)[i].id == id) { @@ -202,11 +202,9 @@ void _stdcall KnockbackSetMod(TGameObj* object, DWORD type, float val, DWORD on) void _stdcall KnockbackRemoveMod(TGameObj* object, DWORD on) { std::vector* mods; - int id = object->ID; switch (on) { case 0: - object->ID = NewObjId(); // revert to engine range id - mods = &mWeapons; + mods = &Knockback_mWeapons; break; case 1: mods = &mTargets; @@ -218,8 +216,9 @@ void _stdcall KnockbackRemoveMod(TGameObj* object, DWORD on) { return; } for (DWORD i = 0; i < mods->size(); i++) { - if ((*mods)[i].id == id) { + if ((*mods)[i].id == object->ID) { mods->erase(mods->begin() + i); + if (on == 0) SetNewEngineID(object); // revert to engine range id return; } } @@ -231,7 +230,7 @@ void _stdcall SetHitChanceMax(TGameObj* critter, DWORD maximum, DWORD mod) { BaseHitChance.mod = mod; return; } - + if (critter->pid >> 24 != OBJ_TYPE_CRITTER) return; long id = SetObjectUniqueID(critter); for (DWORD i = 0; i < HitChanceMods.size(); i++) { if (id == HitChanceMods[i].id) { @@ -270,7 +269,7 @@ void _stdcall SetPickpocketMax(TGameObj* critter, DWORD maximum, DWORD mod) { } void _stdcall SetNoBurstMode(TGameObj* critter, DWORD on) { - if (critter == *ptr_obj_dude) return; + if (critter == *ptr_obj_dude || critter->pid >> 24 != OBJ_TYPE_CRITTER) return; long id = SetObjectUniqueID(critter); for (DWORD i = 0; i < NoBursts.size(); i++) { @@ -349,7 +348,7 @@ void Knockback_OnGameLoad() { BasePickpocket.mod = 0; mTargets.clear(); mAttackers.clear(); - mWeapons.clear(); + Knockback_mWeapons.clear(); HitChanceMods.clear(); PickpocketMods.clear(); NoBursts.clear(); diff --git a/sfall/Knockback.h b/sfall/Knockback.h index 7fb72c25..03e0cd22 100644 --- a/sfall/Knockback.h +++ b/sfall/Knockback.h @@ -18,6 +18,14 @@ #pragma once +struct KnockbackModifier { + long id; + DWORD type; + double value; +}; + +extern std::vector Knockback_mWeapons; + void _stdcall SetPickpocketMax(TGameObj* critter, DWORD maximum, DWORD mod); void _stdcall SetHitChanceMax(TGameObj* critter, DWORD maximum, DWORD mod); void _stdcall KnockbackSetMod(TGameObj* object, DWORD type, float val, DWORD on); diff --git a/sfall/ScriptExtender.cpp b/sfall/ScriptExtender.cpp index 21cede4e..405ee01d 100644 --- a/sfall/ScriptExtender.cpp +++ b/sfall/ScriptExtender.cpp @@ -31,6 +31,7 @@ #include "FalloutEngine.h" #include "HookScripts.h" #include "input.h" +#include "Knockback.h" #include "LoadGameHook.h" #include "Logging.h" #include "ScriptExtender.h" @@ -402,7 +403,7 @@ static const SfallOpcodeMetadata opcodeMetaArray[] = { {sf_set_map_enter_position, "set_map_enter_position", {DATATYPE_MASK_INT, DATATYPE_MASK_INT, DATATYPE_MASK_INT}}, {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}}, + {sf_set_unique_id, "set_unique_id", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}}, {sf_spatial_radius, "spatial_radius", {DATATYPE_MASK_VALID_OBJ}}, {sf_unjam_lock, "unjam_lock", {DATATYPE_MASK_VALID_OBJ}}, #ifndef NDEBUG @@ -1269,6 +1270,45 @@ long SetObjectUniqueID(TGameObj* obj) { return objUniqueID; } +// Assigns a unique ID in the negative range (0x8FFFFFFF - 0xFFFFFFFE) +long SetSpecialID(TGameObj* obj) { + long id = obj->ID; + if (id < -1 || id > UID_START) return id; + + if ((DWORD)objUniqueID >= UID_END) objUniqueID = UID_START; + id = ++objUniqueID + UID_END; + obj->ID = id; + return id; +} + +void SetNewEngineID(TGameObj* obj) { + if (obj->ID > UID_START) return; + obj->ID = NewObjId(); +} + +static bool __fastcall CheckSpecialID(long id) { + if (Knockback_mWeapons.empty()) return false; + for (std::vector::iterator it = Knockback_mWeapons.begin(); it != Knockback_mWeapons.end(); ++it) { + if (id == it->id) return true; + } + return false; +} + +static void __declspec(naked) item_identical_hack() { + __asm { + mov ecx, [edi]; // item id + cmp ecx, UID_START; // start unique ID + jg notIdentical; + call CheckSpecialID; + test al, al; + jnz 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; @@ -1316,6 +1356,10 @@ void ScriptExtenderSetup() { HookCall(0x4A38A5, new_obj_id_hook); SafeWrite8(0x4A38B3, 0x90); // fix ID increment + //if (GetPrivateProfileIntA("Misc", "StackableUniqueItems", 0, ini) == 0) { + MakeCall(0x477A0E, item_identical_hack); // don't put to item stack + //} + 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 3beca0bb..a51fa0b7 100644 --- a/sfall/ScriptExtender.h +++ b/sfall/ScriptExtender.h @@ -98,6 +98,8 @@ extern DWORD AvailableGlobalScriptTypes; // object's unique id extern long objUniqueID; long SetObjectUniqueID(TGameObj* obj); +long SetSpecialID(TGameObj* obj); +void SetNewEngineID(TGameObj* obj); // types for script variables #define VAR_TYPE_INT (0xC001) diff --git a/sfall/ScriptOps/MetaruleOp.hpp b/sfall/ScriptOps/MetaruleOp.hpp index 10c77725..329d5219 100644 --- a/sfall/ScriptOps/MetaruleOp.hpp +++ b/sfall/ScriptOps/MetaruleOp.hpp @@ -133,7 +133,7 @@ static const SfallMetarule metaruleArray[] = { {"set_map_enter_position", sf_set_map_enter_position, 3, 3}, {"set_object_data", sf_set_object_data, 3, 3}, {"set_outline", sf_set_outline, 2, 2}, - {"set_unique_id", sf_set_unique_id, 1, 1}, + {"set_unique_id", sf_set_unique_id, 1, 2}, {"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 74757db4..e05bc983 100644 --- a/sfall/ScriptOps/ObjectsOps.hpp +++ b/sfall/ScriptOps/ObjectsOps.hpp @@ -645,5 +645,13 @@ static void sf_set_object_data() { } static void sf_set_unique_id() { - opHandler.setReturn(SetObjectUniqueID(opHandler.arg(0).asObject()), DATATYPE_INT); + TGameObj* obj = opHandler.arg(0).asObject(); + long id; + if (opHandler.numArgs() == 2 && opHandler.arg(1).asInt() == -1) { + id = NewObjId(); + obj->ID = id; + } else { + id = SetObjectUniqueID(obj); + } + opHandler.setReturn(id, DATATYPE_INT); }