diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 53f4e06f..0be063f6 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -350,6 +350,7 @@ #define set_object_data(obj, offset, value) sfall_func3("set_object_data", obj, offset, value) #define set_outline(obj, color) sfall_func2("set_outline", obj, color) #define set_quest_failure_value(gvar, threshold) sfall_func2("set_quest_failure_value", gvar, threshold) +#define set_scr_name(name) sfall_func1("set_scr_name", name) #define set_terrain_name(x, y, name) sfall_func3("set_terrain_name", x, y, name) #define set_town_title(areaID, title) sfall_func2("set_town_title", areaID, title) #define set_unique_id(obj) sfall_func1("set_unique_id", obj) @@ -366,6 +367,7 @@ #define tile_by_position(x, y) sfall_func2("tile_by_position", x, y) #define tile_refresh_display sfall_func0("tile_refresh_display") #define unjam_lock(obj) sfall_func1("unjam_lock", obj) +#define unset_scr_name sfall_func0("set_scr_name") /* sets the name of the script object from pro_*.msg instead of scrname.msg */ #define unset_unique_id(obj) sfall_func2("set_unique_id", obj, -1) #define unwield_slot(critter, slot) sfall_func2("unwield_slot", critter, slot) #define win_fill_color(x, y, width, height, color) sfall_func5("win_fill_color", x, y, width, height, color) diff --git a/artifacts/scripting/sfall function notes.md b/artifacts/scripting/sfall function notes.md index 448d1366..75688759 100644 --- a/artifacts/scripting/sfall function notes.md +++ b/artifacts/scripting/sfall function notes.md @@ -963,6 +963,15 @@ sfall_funcX metarule functions - `gvarNumber`: the number of the global variable controlling the quest - `thresholdValue`: the value of the global variable at which the quest is counted as a failure +---- +#### set_scr_name +`void sfall_func1("set_scr_name", string name)` +- Overrides the name of the script object that was set from **scrname.msg** +- The changed name will be reset each time the player leaves the map or reloads the game +- Passing an empty string ("") to the `name` argument or omitting it will allow the game to get the name for the object from pro_*.msg files +- The function can override the name only once for the same object until reset +- __NOTE:__ this function is intended for use in normal game scripts + **** _See other documentation files (arrays.md, hookscripts.md) for related functions reference._ diff --git a/sfall/FalloutFuncOffs_def.h b/sfall/FalloutFuncOffs_def.h index 494f4a7a..557b8ddb 100644 --- a/sfall/FalloutFuncOffs_def.h +++ b/sfall/FalloutFuncOffs_def.h @@ -330,6 +330,7 @@ FUNC(item_move_all_, 0x4776AC) FUNC(item_move_all_hidden_, 0x4776E0) FUNC(item_move_force_, 0x4776A4) FUNC(item_mp_cost_, 0x478040) +FUNC(item_name_, 0x477AE4) FUNC(item_remove_mult_, 0x477490) FUNC(item_size_, 0x477B68) FUNC(item_total_cost_, 0x477DAC) @@ -487,6 +488,7 @@ FUNC(protinst_use_item_, 0x49BF38) FUNC(protinst_use_item_on_, 0x49C3CC) FUNC(proto_dude_init_, 0x49FA64) FUNC(proto_dude_update_gender_, 0x49F984) +FUNC(proto_get_msg_info_, 0x49EAA4) FUNC(proto_list_str_, 0x49E758) FUNC(proto_make_path_, 0x49E270) FUNC(proto_ptr_, 0x4A2108) // eax - PID, edx - int** - pointer to a pointer to a proto struct diff --git a/sfall/FalloutFuncs_def.h b/sfall/FalloutFuncs_def.h index 9ec34b61..156b1b35 100644 --- a/sfall/FalloutFuncs_def.h +++ b/sfall/FalloutFuncs_def.h @@ -176,6 +176,7 @@ WRAP_WATCOM_FUNC2(long, perk_can_add, TGameObj*, critter, long, perkId) WRAP_WATCOM_FUNC2(long, perk_level, TGameObj*, critter, long, perkId) WRAP_WATCOM_FUNC6(long, pick_death, TGameObj*, attacker, TGameObj*, target, TGameObj*, weapon, long, amount, long, anim, long, hitFromBack) WRAP_WATCOM_FUNC0(void, process_bk) +WRAP_WATCOM_FUNC2(const char*, proto_get_msg_info, long, pid, long, msgType) // msgType: 0 - name, 1 - desc WRAP_WATCOM_FUNC0(void, proto_dude_update_gender) WRAP_WATCOM_FUNC2(void, proto_make_path, char*, buffer, long, pid) // Places pointer to a prototype structure into ptrPtr and returns 0 on success or -1 on failure diff --git a/sfall/LoadGameHook.cpp b/sfall/LoadGameHook.cpp index 2492a5fe..c19e3e15 100644 --- a/sfall/LoadGameHook.cpp +++ b/sfall/LoadGameHook.cpp @@ -393,6 +393,10 @@ static void __stdcall GameClose() { // OnBeforeGameClose WipeSounds(); } +static void __stdcall MapLoadHook() { // OnBeforeMapLoad + ObjectNameReset(); +} + static void __declspec(naked) main_init_system_hook() { __asm { pushadc; @@ -470,8 +474,13 @@ static void __declspec(naked) map_load_hook() { mov ecx, 4; rep movsd; // copy the name of the loaded map to gameMapLoadingName mov onLoadingMap, 1; + push eax; + push edx; + call MapLoadHook; + pop edx; + pop eax; call map_load_file_; - mov onLoadingMap, cl; // unset + mov onLoadingMap, 0; retn; } } diff --git a/sfall/Message.cpp b/sfall/Message.cpp index c4e736b0..f21744eb 100644 --- a/sfall/Message.cpp +++ b/sfall/Message.cpp @@ -170,7 +170,7 @@ void ReadExtraGameMsgFiles() { path += ".msg"; MSGList* list = new MSGList(); if (fo_message_load(list, path.c_str()) == 1) { - gExtraGameMsgLists.insert(std::make_pair(0x2000 + number, list)); + gExtraGameMsgLists.insert(std::pair(0x2000 + number, list)); } else { delete list; } @@ -198,7 +198,7 @@ long __stdcall Message_AddExtraMsgFile(const char* msgName, long msgNumber) { //} } if (msgNumber == 0) msgNumber = msgNumCounter++; - gExtraGameMsgLists.insert(std::make_pair(msgNumber, list)); + gExtraGameMsgLists.insert(std::pair(msgNumber, list)); return msgNumber; } diff --git a/sfall/Objects.cpp b/sfall/Objects.cpp index 1257af08..42ffe7cb 100644 --- a/sfall/Objects.cpp +++ b/sfall/Objects.cpp @@ -27,6 +27,10 @@ static int maxCountLoadProto = 512; long Objects_uniqueID = UID_START; // current counter id, saving to sfallgv.sav +bool Objects_IsUniqueID(long id) { + return (id > UID_START || (id >= PLAYER_ID && id < 83536)); // 65535 maximum possible number of prototypes +} + static void SetScriptObjectID(TGameObj* obj) { TScript* script; if (fo_scr_ptr(obj->scriptId, &script) != -1) { @@ -39,7 +43,7 @@ static void SetScriptObjectID(TGameObj* obj) { // player ID = 18000, all party members have ID = 18000 + its pid (file number of prototype) long __fastcall Objects_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 (Objects_IsUniqueID(id)) return id; if ((DWORD)Objects_uniqueID >= (DWORD)UID_END) Objects_uniqueID = UID_START; obj->id = ++Objects_uniqueID; @@ -155,7 +159,7 @@ end: } } -void Objects_SetAutoUnjamLockTime(DWORD time) { +void __stdcall Objects_SetAutoUnjamLockTime(DWORD time) { if (!unjamTimeState) BlockCall(0x4A364A); // disable auto unjam at midnight if (time > 0) { @@ -196,7 +200,7 @@ end: } } -void Objects_LoadProtoAutoMaxLimit() { +void __stdcall Objects_LoadProtoAutoMaxLimit() { MakeCall(0x4A21B2, proto_ptr_hack); } diff --git a/sfall/Objects.h b/sfall/Objects.h index a8012a1f..9ea7eb77 100644 --- a/sfall/Objects.h +++ b/sfall/Objects.h @@ -10,9 +10,11 @@ extern long Objects_uniqueID; void Objects_Init(); void Objects_OnGameLoad(); +bool Objects_IsUniqueID(long id); + long __fastcall Objects_SetObjectUniqueID(TGameObj* obj); long __fastcall Objects_SetSpecialID(TGameObj* obj); void Objects_SetNewEngineID(TGameObj* obj); -void Objects_SetAutoUnjamLockTime(DWORD time); -void Objects_LoadProtoAutoMaxLimit(); +void __stdcall Objects_SetAutoUnjamLockTime(DWORD time); +void __stdcall Objects_LoadProtoAutoMaxLimit(); diff --git a/sfall/ScriptExtender.cpp b/sfall/ScriptExtender.cpp index 5ff9db2b..8383fe39 100644 --- a/sfall/ScriptExtender.cpp +++ b/sfall/ScriptExtender.cpp @@ -603,6 +603,7 @@ static const SfallOpcodeMetadata opcodeMetaArray[] = { {mf_set_object_data, "set_object_data", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT, DATATYPE_MASK_INT}}, {mf_set_outline, "set_outline", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}}, {mf_set_quest_failure_value, "set_quest_failure_value", {DATATYPE_MASK_INT, DATATYPE_MASK_INT}}, + {mf_set_scr_name, "set_scr_name", {DATATYPE_MASK_STR}}, {mf_set_terrain_name, "set_terrain_name", {DATATYPE_MASK_INT, DATATYPE_MASK_INT, DATATYPE_MASK_STR}}, {mf_set_town_title, "set_town_title", {DATATYPE_MASK_INT, DATATYPE_MASK_STR}}, {mf_set_unique_id, "set_unique_id", {DATATYPE_MASK_VALID_OBJ, DATATYPE_MASK_INT}}, @@ -1510,11 +1511,114 @@ void BuildSortedIndexList() { //devlog_f("\nCount: %d\n", DL_MAIN, scriptsIndexList.size()); } +///////////////////////////////// OBJECT NAME ////////////////////////////////// + +static std::tr1::unordered_map overrideScrName; + +static long lastNamePid = -1; +static long lastNameSid = -1; +static long lastItemPid = -1; + +void __stdcall ObjectName_SetName(long sid, const char* name) { + if (sid == lastNameSid) lastNameSid = -1; + if (!name) name = ""; + overrideScrName.insert(std::pair(sid, name)); +} + +const char* __stdcall ObjectName_GetName(TGameObj* object) { + if (!overrideScrName.empty()) { + std::tr1::unordered_map::iterator name = overrideScrName.find(object->scriptId); + if (name != overrideScrName.cend()) { + return (name->second.length() > 0) + ? name->second.c_str() + : fo_proto_get_msg_info(object->protoId, 0); + } + } + return nullptr; +} + +static void __declspec(naked) critter_name_hack() { + static DWORD critter_name_hack_ret = 0x42D125; + using namespace Fields; + __asm { + push ebx; // object + call ObjectName_GetName; + test eax, eax; + jnz override; + mov edi, [ebx + scriptIndex]; + retn; +override: + add esp, 4; + jmp critter_name_hack_ret; + } +} + +static void __declspec(naked) critter_name_hack_check() { + static DWORD critter_name_hack_ret = 0x42D12F; + using namespace Fields; + __asm { + mov ecx, [ebx + scriptId]; + cmp ecx, -1; + je checkPid; // has no script, check only the PID + cmp ecx, lastNameSid; + jne default; + add esp, 4; + mov eax, ds:[FO_VAR_name_critter]; + jmp critter_name_hack_ret; +checkPid: + mov ecx, [ebx + protoId]; + cmp ecx, lastNamePid; + jne default; + add esp, 4; + mov eax, ds:[FO_VAR_name_critter]; + jmp critter_name_hack_ret; +default: + mov ecx, [ebx + scriptIndex]; + retn; + } +} + +static void __declspec(naked) critter_name_hack_end() { + using namespace Fields; + __asm { + mov edx, [ebx + protoId]; + mov lastNamePid, edx; + mov ecx, [ebx + scriptId]; + mov lastNameSid, ecx; + retn; + } +} + +static void __declspec(naked) object_name_hook() { + using namespace Fields; + __asm { + mov edx, [eax + protoId]; + cmp edx, lastItemPid; + je getLast; + mov lastItemPid, edx; + jmp item_name_; +getLast: + mov eax, ds:[FO_VAR_name_item]; + retn; + } +} + +void ObjectNameReset() { + overrideScrName.clear(); + lastNameSid = -1; +} + +//////////////////////////////////////////////////////////////////////////////// + void ScriptExtender_OnGameLoad() { ClearGlobalScripts(); ClearGlobals(); RegAnimCombatCheck(1); ForceEncounterRestore(); // restore if the encounter did not happen + + ObjectNameReset(); + lastNamePid = -1; + lastItemPid = -1; } void ScriptExtender_Init() { @@ -1892,4 +1996,12 @@ void ScriptExtender_Init() { InitOpcodeMetaTable(); InitMetaruleTable(); + + // Returns the redefined object name + MakeCall(0x42D0F2, critter_name_hack, 1); + + // Tweak for quickly getting last object name + MakeCall(0x42D0C4, critter_name_hack_check, 1); + MakeCall(0x42D12A, critter_name_hack_end); + HookCall(0x48C901, object_name_hook); } diff --git a/sfall/ScriptExtender.h b/sfall/ScriptExtender.h index c08a8a8c..a2dafd7c 100644 --- a/sfall/ScriptExtender.h +++ b/sfall/ScriptExtender.h @@ -96,6 +96,10 @@ int __stdcall ScriptHasLoaded(TProgram* script); // loads and initializes script file (for normal game scripts) long __fastcall InitScript(long sid); +const char* __stdcall ObjectName_GetName(TGameObj* object); +void __stdcall ObjectName_SetName(long sid, const char* name); +void ObjectNameReset(); + // variables extern DWORD availableGlobalScriptTypes; extern bool alwaysFindScripts; diff --git a/sfall/ScriptOps/MetaruleOps.hpp b/sfall/ScriptOps/MetaruleOps.hpp index cb409101..5aed6b57 100644 --- a/sfall/ScriptOps/MetaruleOps.hpp +++ b/sfall/ScriptOps/MetaruleOps.hpp @@ -175,6 +175,7 @@ static const SfallMetarule metaruleArray[] = { {"set_object_data", mf_set_object_data, 3, 3}, {"set_outline", mf_set_outline, 2, 2}, {"set_quest_failure_value", mf_set_quest_failure_value, 2, 2}, + {"set_scr_name", mf_set_scr_name, 0, 1}, {"set_terrain_name", mf_set_terrain_name, 3, 3}, {"set_town_title", mf_set_town_title, 2, 2}, {"set_unique_id", mf_set_unique_id, 1, 2}, diff --git a/sfall/ScriptOps/MiscOps.hpp b/sfall/ScriptOps/MiscOps.hpp index bc8bda41..155d8390 100644 --- a/sfall/ScriptOps/MiscOps.hpp +++ b/sfall/ScriptOps/MiscOps.hpp @@ -678,3 +678,10 @@ static void mf_get_ini_section() { static void mf_set_quest_failure_value() { QuestList_AddQuestFailureValue(opHandler.arg(0).rawValue(), opHandler.arg(1).rawValue()); } + +static void mf_set_scr_name() { + long sid = fo_scr_find_sid_from_program(opHandler.program()); + if (sid == -1) return; + + ObjectName_SetName(sid, opHandler.arg(0).strValue()); +} diff --git a/sfall/TalkingHeads.cpp b/sfall/TalkingHeads.cpp index 3a16d326..42e07b6b 100644 --- a/sfall/TalkingHeads.cpp +++ b/sfall/TalkingHeads.cpp @@ -141,7 +141,7 @@ static bool LoadFrm(Frm* frm) { if (GetFileAttributes(buf) != INVALID_FILE_ATTRIBUTES) frm->bakedBackground = 1; // fills entire frame surface with a key-color } frm->textures = textures; - texMap.insert(std::make_pair(frm->key, TextureData(textures, frm->showHighlights, frm->bakedBackground, frm->frames))); + texMap.insert(std::pair(frm->key, TextureData(textures, frm->showHighlights, frm->bakedBackground, frm->frames))); var_setInt(FO_VAR_bk_disabled) = 0; } else { // Use preloaded textures