diff --git a/artifacts/mods/gl_npcarmor.int b/artifacts/mods/gl_npcarmor.int index 23bfdaa7..6bc7c7ca 100644 Binary files a/artifacts/mods/gl_npcarmor.int and b/artifacts/mods/gl_npcarmor.int differ diff --git a/artifacts/mods/gl_npcarmor.ssl b/artifacts/mods/gl_npcarmor.ssl index 4d00e48a..d3b43df1 100644 --- a/artifacts/mods/gl_npcarmor.ssl +++ b/artifacts/mods/gl_npcarmor.ssl @@ -65,7 +65,7 @@ procedure check_weapon_change(variable critter, variable weapon, variable isWiel weaponAnimList := npc["WeaponAnims"]; if (weaponAnimList != 0) then begin foreach (i in string_split(weaponAnimList, ",")) begin - if (newWeaponAnim == atoi(i)) then return -1; // can use (engine default) + if (newWeaponAnim == atoi(i) and not(weapon_is_unusable(weapon))) then return -1; // can use (engine default) end end return 0; // can't use @@ -226,10 +226,10 @@ end procedure canuseweapon_handler begin variable critter, canWield; critter := get_sfall_arg; - //if (critter) then begin + if (critter != dude_obj) then begin canWield := check_weapon_change(critter, get_sfall_arg, true); // override result set_sfall_arg(3, canWield); set_sfall_return(canWield); - //end + end end diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 9acc5d4e..83ba8cd4 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -289,9 +289,11 @@ // checks if the specified PID number exists in the list of registered protos #define check_pid(pid) (get_proto_data(pid, 0) != -1) -// sets the status of a broken weapon that cannot be used in combat (the hand slot will not be available for use) -#define set_broken_weapon_state_on(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwor 0x00000010) -#define set_broken_weapon_state_off(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0xFFFFFFEF) +// sets the status of an unusable weapon that cannot be used in combat +// use the HOOK_CANUSEWEAPON hook with weapon_is_unusable macro to override the engine value +#define set_weapon_unusable(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwor 0x00000010) +#define set_weapon_usable(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0xFFFFFFEF) +#define weapon_is_unusable(item) (get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0x00000010) /* SFALL_FUNCX MACROS */ diff --git a/artifacts/scripting/hookscripts.md b/artifacts/scripting/hookscripts.md index 6096a4ba..6d5007d5 100644 --- a/artifacts/scripting/hookscripts.md +++ b/artifacts/scripting/hookscripts.md @@ -897,13 +897,14 @@ Item ret0 - overrides the chosen best weapon #### `HOOK_CANUSEWEAPON (hs_canuseweapon.int)` -Run when the AI checks whether it can use a weapon. -This mostly happens when NPCs try to find weapons in their inventory or on the map. +Run when the AI checks whether it can use a weapon, or when the game checks whether the player can use a weapon. +For AI, this mostly happens when NPCs try to find weapons in their inventory or on the map. +For the player, this happens when the game redraws the interface bar. ``` Critter arg0 - the critter doing the check -Item arg1 - the weapon being checked -int arg2 - attack type (see ATKTYPE_* constants) +Item arg1 - the item being checked +int arg2 - attack type (see ATKTYPE_* constants), or -1 for dude_obj int arg3 - original result of engine function: 1 - can use, 0 - can't use int ret0 - overrides the result of engine function. Any non-zero value allows using the weapon diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index c61d28d8..ec6bdfd2 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -616,6 +616,7 @@ FUNC(calculateColor_, 0x4C72B4) FUNC(calloc_, 0x4EAFEC) FUNC(can_see_, 0x412BEC) FUNC(can_talk_to_, 0x413420) +FUNC(can_use_weapon_, 0x477F3C) FUNC(cget_, 0x4F2250) FUNC(cget_string_, 0x4F0EA1) FUNC(cgetw_, 0x4F2E5B) diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 66dc65b7..f8cafccf 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -626,17 +626,17 @@ static bool __stdcall CanUseWeaponHook_Script(bool result, fo::GameObject* sourc args[0] = (DWORD)source; args[1] = (DWORD)weapon; args[2] = hitMode; - args[3] = 0 | result; + args[3] = result; RunHookScript(HOOK_CANUSEWEAPON); - if (cRet > 0) result = rets[0] ? true : false; + if (cRet > 0) result = (rets[0]) ? true : false; EndHook(); return result; // only 0 and 1 } -static void __declspec(naked) CanUseWeaponHook() { +static void __declspec(naked) AICanUseWeaponHook() { __asm { push ecx; push ebx; // hitMode @@ -645,7 +645,25 @@ static void __declspec(naked) CanUseWeaponHook() { call fo::funcoffs::ai_can_use_weapon_; push eax; // result call CanUseWeaponHook_Script; - and eax, 1; + //and eax, 1; + pop ecx; + retn; + } +} + +static void __declspec(naked) CanUseWeaponHook() { + __asm { + push ecx; + push edx; + push -1; // hitMode (indefinite) + push eax; // weapon + push ds:[FO_VAR_obj_dude]; // source + call fo::funcoffs::can_use_weapon_; // 1 - can't use + xor al, 1; + push eax; // result + call CanUseWeaponHook_Script; + xor al, 1; // invert + pop edx; pop ecx; retn; } @@ -750,11 +768,12 @@ void Inject_BestWeaponHook() { } void Inject_CanUseWeaponHook() { - HookCalls(CanUseWeaponHook, { + HookCalls(AICanUseWeaponHook, { 0x429A1B, // ai_search_inven_weap_ 0x429CF2, // ai_search_environ_ 0x429E1C // ai_pick_hit_mode_ }); + HookCalls(CanUseWeaponHook, { 0x45F05E, 0x45F1C1, 0x45F203, 0x45F36A }); // intface_update_items_ } void InitCombatHookScripts() { diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index d474b0fa..afed09e1 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -595,25 +595,6 @@ static void __declspec(naked) do_move_timer_hack() { } } -static void __declspec(naked) can_use_weapon_hook() { - static const DWORD can_use_weapon_Ret = 0x477F9F; - using namespace fo; - using namespace fo::Fields; - __asm { - call fo::funcoffs::item_get_type_; - cmp eax, item_type_weapon; - je checkFlag; - retn; // eax - type -checkFlag: - test dword ptr [edx + miscFlags], CantUse; - jnz cantUse; - retn; // eax - type -cantUse: - add esp, 4; - jmp can_use_weapon_Ret; - } -} - static int invenApCost, invenApCostDef; static char invenApQPReduction; @@ -758,11 +739,6 @@ void Inventory::init() { // Note: the flag is not checked for the metarule(METARULE_INVEN_UNWIELD_WHO, x) function HookCall(0x45B0CE, op_inven_unwield_hook); // with fix to update interface slot after unwielding HookCall(0x45693C, op_wield_obj_critter_hook); - - // Add an additional "Can't Use" flag to the misc flags of item objects (offset 0x0038) - // Misc Flags: - // 0x00000010 - Can't Use (makes the weapon object unusable in combat) - HookCall(0x477F4C, can_use_weapon_hook); } void Inventory::InvokeAdjustFid(long fid) { diff --git a/sfall/Modules/SubModules/CombatBlock.cpp b/sfall/Modules/SubModules/CombatBlock.cpp index bc69bb28..f3309de2 100644 --- a/sfall/Modules/SubModules/CombatBlock.cpp +++ b/sfall/Modules/SubModules/CombatBlock.cpp @@ -60,6 +60,57 @@ end: } } +static void __declspec(naked) ai_can_use_weapon_hack() { + using namespace fo; + using namespace Fields; + __asm { + test dword ptr [esi + miscFlags], CantUse; + jnz cantUse; + mov eax, [edi + damageFlags]; + retn; +cantUse: + mov al, 0xFF; + retn; + } +} + +static void __declspec(naked) can_use_weapon_hook() { + static const DWORD cant_use_weapon_Ret = 0x477F9F; + using namespace fo; + using namespace Fields; + __asm { + call fo::funcoffs::item_get_type_; + cmp eax, item_type_weapon; + je checkFlag; + retn; // eax - type +checkFlag: + test dword ptr [edx + miscFlags], CantUse; + jnz cantUse; + retn; // eax - type +cantUse: + add esp, 4; + jmp cant_use_weapon_Ret; + } +} + +// Note: in ai_try_attack_, the attacker will not be able to change unusable weapon, as it happens with crippled arms +static void __declspec(naked) combat_check_bad_shot_hack() { + static const DWORD combat_check_bad_shot_Ret = 0x42673A; + using namespace fo; + using namespace Fields; + __asm { + test dword ptr [ecx + miscFlags], CantUse; + jnz cantUse; + mov eax, [esi + damageFlags]; + test al, DAM_CRIP_ARM_LEFT; + retn; +cantUse: + mov eax, 4; // result same as TargetDead + add esp, 4; + jmp combat_check_bad_shot_Ret; + } +} + void __stdcall CombatBlock::SetBlockCombat(long toggle) { combatDisabled = toggle != 0; } @@ -67,6 +118,13 @@ void __stdcall CombatBlock::SetBlockCombat(long toggle) { void CombatBlock::init() { HookCall(0x45F626, intface_use_item_hook); // jnz hook HookCall(0x4432A6, game_handle_input_hook); + + // Add an additional "Can't Use" flag to the misc flags of item objects (offset 0x0038) + // Misc Flags: + // 0x00000010 - Can't Use (makes the weapon object unusable in combat) + HookCall(0x477F4C, can_use_weapon_hook); + MakeCall(0x4298F4, ai_can_use_weapon_hack); + MakeCall(0x426669, combat_check_bad_shot_hack); } }