diff --git a/MPClient/DebugEditor.Designer.cs b/MPClient/DebugEditor.Designer.cs index 73b61bc9e..c8af1d466 100644 --- a/MPClient/DebugEditor.Designer.cs +++ b/MPClient/DebugEditor.Designer.cs @@ -180,6 +180,7 @@ private void InitializeComponent() { this.Controls.Add(this.dataGridView1); this.MinimumSize = new System.Drawing.Size(450, 300); this.Name = "DebugEditor"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "sfall Debug Editor"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DebugEditor_FormClosing); ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); diff --git a/MPClient/EditorWindow.Designer.cs b/MPClient/EditorWindow.Designer.cs index 052916b1b..fa6b3804c 100644 --- a/MPClient/EditorWindow.Designer.cs +++ b/MPClient/EditorWindow.Designer.cs @@ -111,6 +111,7 @@ private void InitializeComponent() { this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; this.MinimumSize = new System.Drawing.Size(300, 200); this.Name = "EditorWindow"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Edit Values"; ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); this.ResumeLayout(false); diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index c7e8ceb5f..1b0a086c0 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -324,12 +324,12 @@ HOOK_AMMOCOST (hs_ammocost.int) Runs when calculating ammo cost for a weapon. Doesn't affect damage, only how much ammo is spent. By default, weapon will shoot when at least 1 round is left, regardless of ammo cost calculations. -To add proper check for ammo before shooting and proper calculation of number of burst rounds, set Misc.CheckWeaponAmmoCost=1 in ddraw.ini +To add proper check for ammo before shooting and proper calculation of number of burst rounds (hook type 1 and 2 in arg4), set Misc.CheckWeaponAmmoCost=1 in ddraw.ini Item arg1 - weapon int arg2 - Number of bullets in burst (1 for single shots) int arg3 - Ammo cost calculated by original function (this is basically 2 for Super Cattle Prod and Mega Power Fist) -int arg4 - Type of hook (0 - when subtracting ammo after attack, 1 - when checking for "out of ammo" before attack) +int arg4 - Type of hook (0 - when subtracting ammo after single shot attack, 1 - when checking for "out of ammo" before attack, 2 - when calculating number of burst rounds, 3 - when subtracting ammo after burst attack) int ret1 - new ammo cost value (set to 0 for unlimited ammo) diff --git a/sfall/DebugEditor.cpp b/sfall/DebugEditor.cpp index afebb7eee..d8fdebf8e 100644 --- a/sfall/DebugEditor.cpp +++ b/sfall/DebugEditor.cpp @@ -49,7 +49,7 @@ struct sArray { }; static void DEGameWinRedraw() { - __asm call process_bk_; + ProcessBk(); } static bool SetBlocking(SOCKET s, bool block) { diff --git a/sfall/FalloutEngine.cpp b/sfall/FalloutEngine.cpp index ccd208e02..90e7bee4e 100644 --- a/sfall/FalloutEngine.cpp +++ b/sfall/FalloutEngine.cpp @@ -437,7 +437,7 @@ const DWORD item_w_anim_code_ = 0x478DA8; const DWORD item_w_anim_weap_ = 0x47860C; const DWORD item_w_can_reload_ = 0x478874; const DWORD item_w_compute_ammo_cost_ = 0x4790AC; -const DWORD item_w_cur_ammo_ = 0x4786A0; +const DWORD item_w_curr_ammo_ = 0x4786A0; const DWORD item_w_dam_div_ = 0x479294; const DWORD item_w_dam_mult_ = 0x479230; const DWORD item_w_damage_ = 0x478448; @@ -791,6 +791,10 @@ void InterfaceRedraw() { __asm call intface_redraw_; } +void __stdcall ProcessBk() { + __asm call process_bk_; +} + // pops value type from Data stack (must be followed by InterpretPopLong) DWORD __stdcall InterpretPopShort(TProgram* scriptPtr) { __asm { @@ -981,3 +985,34 @@ long __stdcall QueueFindFirst(TGameObj* object, long qType) { call queue_find_first_; } } + +// for the backported AmmoCostHook from 4.x +long __stdcall ItemWAnimWeap(TGameObj* item, DWORD hitMode) { + __asm { + mov edx, hitMode; + mov eax, item; + call item_w_anim_weap_; + } +} + +long __stdcall ItemWComputeAmmoCost(TGameObj* item, DWORD* rounds) { + __asm { + mov edx, rounds; + mov eax, item; + call item_w_compute_ammo_cost_; + } +} + +long __stdcall ItemWCurrAmmo(TGameObj* item) { + __asm { + mov eax, item; + call item_w_curr_ammo_; + } +} + +long __stdcall ItemWRounds(TGameObj* item) { + __asm { + mov eax, item; + call item_w_rounds_; + } +} diff --git a/sfall/FalloutEngine.h b/sfall/FalloutEngine.h index 9727dbee4..0165e4485 100644 --- a/sfall/FalloutEngine.h +++ b/sfall/FalloutEngine.h @@ -456,10 +456,11 @@ extern const DWORD add_bar_box_; extern const DWORD AddHotLines_; extern const DWORD adjust_ac_; extern const DWORD adjust_fid_; +extern const DWORD ai_can_use_weapon_; // (TGameObj *aCritter, int aWeapon, int a2Or3) returns 1 or 0 extern const DWORD ai_search_inven_armor_; extern const DWORD ai_try_attack_; extern const DWORD art_alias_num_; -extern const DWORD art_exists_; +extern const DWORD art_exists_; // eax - frameID, used for critter FIDs extern const DWORD art_flush_; extern const DWORD art_frame_data_; extern const DWORD art_frame_length_; @@ -574,7 +575,7 @@ extern const DWORD gdProcess_; extern const DWORD GetSlotList_; extern const DWORD get_input_; extern const DWORD get_time_; -extern const DWORD getmsg_; +extern const DWORD getmsg_; // eax - msg file addr, ebx - message ID, edx - int[4] - loads string from MSG file preloaded in memory extern const DWORD gmouse_3d_get_mode_; extern const DWORD gmouse_3d_set_mode_; extern const DWORD gmouse_is_scrolling_; @@ -592,6 +593,8 @@ extern const DWORD handle_inventory_; extern const DWORD inc_game_time_; extern const DWORD inc_stat_; extern const DWORD insert_withdrawal_; +extern const DWORD interface_disable_; +extern const DWORD interface_enable_; extern const DWORD interpret_; // - programPtr, - ??? (-1) extern const DWORD interpretAddString_; extern const DWORD interpretFindProcedure_; // get proc number (different for each script) by name: * - scriptPtr, char* - proc name @@ -602,25 +605,26 @@ extern const DWORD interpretPopShort_; extern const DWORD interpretPushLong_; extern const DWORD interpretPushShort_; extern const DWORD interpretError_; +extern const DWORD intface_get_attack_; +extern const DWORD intface_hide_; +extern const DWORD intface_is_hidden_; +extern const DWORD intface_item_reload_; // no args extern const DWORD intface_redraw_; // no args -extern const DWORD intface_toggle_item_state_; +extern const DWORD intface_show_; +extern const DWORD intface_toggle_item_state_; // no args extern const DWORD intface_toggle_items_; extern const DWORD intface_update_ac_; extern const DWORD intface_update_hit_points_; extern const DWORD intface_update_items_; extern const DWORD intface_update_move_points_; -extern const DWORD intface_use_item_; -extern const DWORD intface_show_; -extern const DWORD intface_hide_; -extern const DWORD intface_is_hidden_; -extern const DWORD intface_get_attack_; +extern const DWORD intface_use_item_; // no args extern const DWORD invenUnwieldFunc_; // (int critter@, int slot@, int a3@) - int result (-1 on error, 0 on success) extern const DWORD invenWieldFunc_; // (int who@, int item@, int a3@, int slot@) - int result (-1 on error, 0 on success) extern const DWORD inven_display_msg_; extern const DWORD inven_find_id_; -extern const DWORD inven_left_hand_; +extern const DWORD inven_left_hand_; // eax - object extern const DWORD inven_pid_is_carried_ptr_; -extern const DWORD inven_right_hand_; +extern const DWORD inven_right_hand_; // eax - object extern const DWORD inven_unwield_; extern const DWORD inven_wield_; extern const DWORD inven_worn_; @@ -651,13 +655,13 @@ extern const DWORD item_w_anim_code_; extern const DWORD item_w_anim_weap_; extern const DWORD item_w_can_reload_; extern const DWORD item_w_compute_ammo_cost_; // signed int aWeapon, int *aRoundsSpent -extern const DWORD item_w_cur_ammo_; +extern const DWORD item_w_curr_ammo_; // eax - object extern const DWORD item_w_dam_div_; extern const DWORD item_w_dam_mult_; extern const DWORD item_w_damage_; extern const DWORD item_w_damage_type_; extern const DWORD item_w_dr_adjust_; -extern const DWORD item_w_max_ammo_; +extern const DWORD item_w_max_ammo_; // eax - object extern const DWORD item_w_mp_cost_; extern const DWORD item_w_perk_; extern const DWORD item_w_range_; @@ -721,6 +725,8 @@ extern const DWORD obj_find_first_at_tile_; // (int elevation, int ti extern const DWORD obj_find_next_at_; extern const DWORD obj_find_next_at_tile_; // no args extern const DWORD obj_lock_is_jammed_; +extern const DWORD obj_move_to_tile_; // int aObj, int aTile, int aElev +extern const DWORD obj_new_; // int aObj*, int aPid extern const DWORD obj_new_sid_inst_; extern const DWORD obj_outline_object_; extern const DWORD obj_pid_new_; @@ -731,6 +737,7 @@ extern const DWORD obj_set_light_; // (int aObj, signed int aDist extern const DWORD obj_shoot_blocking_at_; extern const DWORD obj_sight_blocking_at_; extern const DWORD obj_top_environment_; +extern const DWORD obj_turn_off_; // int aObj, int ??? extern const DWORD obj_unjam_lock_; extern const DWORD obj_use_book_; extern const DWORD obj_use_power_on_car_; @@ -768,7 +775,7 @@ extern const DWORD process_bk_; extern const DWORD protinst_use_item_; extern const DWORD protinst_use_item_on_; extern const DWORD proto_dude_update_gender_; -extern const DWORD proto_ptr_; +extern const DWORD proto_ptr_; // eax - PID, edx - int** - pointer to a pointer to a proto struct extern const DWORD pushLongStack_; extern const DWORD qsort_; extern const DWORD queue_add_; @@ -895,32 +902,6 @@ extern const DWORD xvfprintf_; * 2) Call _stdcall functions from (1), write those entirely in C++ (with little ASM blocks only to call engine functions, when you are too lazy to add wrapper) */ -extern const DWORD item_get_type_; -extern const DWORD inven_left_hand_; // eax - object -extern const DWORD inven_right_hand_; // eax - object -extern const DWORD proto_ptr_; // eax - PID, edx - int** - pointer to a pointer to a proto struct -extern const DWORD ai_can_use_weapon_; // (TGameObj *aCritter, int aWeapon, int a2Or3) returns 1 or 0 -extern const DWORD item_w_max_ammo_; // eax - object -extern const DWORD item_w_cur_ammo_; // eax - object - - -// Interface -extern const DWORD interface_disable_; -extern const DWORD interface_enable_; -extern const DWORD intface_toggle_items_; -extern const DWORD intface_item_reload_; // no args -extern const DWORD intface_toggle_item_state_; // no args -extern const DWORD intface_use_item_; // no args - -// objects -extern const DWORD obj_new_; // int aObj*, int aPid -extern const DWORD obj_turn_off_; // int aObj, int ??? -extern const DWORD obj_move_to_tile_; // int aObj, int aTile, int aElev - -extern const DWORD art_exists_; // eax - frameID, used for critter FIDs - -extern const DWORD getmsg_; // eax - msg file addr, ebx - message ID, edx - int[4] - loads string from MSG file preloaded in memory - #define MSG_FILE_COMBAT (0x56D368) #define MSG_FILE_AI (0x56D510) #define MSG_FILE_SCRNAME (0x56D754) @@ -970,6 +951,8 @@ void SkillSetTags(int* tags, DWORD num); // redraws the main game interface windows (useful after changing some data like active hand, etc.) void InterfaceRedraw(); +void __stdcall ProcessBk(); + // critter worn item (armor) TGameObj* __stdcall InvenWorn(TGameObj* critter); @@ -1024,3 +1007,9 @@ void __stdcall DisplayTargetInventory(long inventoryOffset, long visibleOffset, long __stdcall StatLevel(TGameObj* critter, long statId); long __stdcall QueueFindFirst(TGameObj* object, long qType); + +// for the backported AmmoCostHook from 4.x +long __stdcall ItemWAnimWeap(TGameObj* item, DWORD hitMode); +long __stdcall ItemWComputeAmmoCost(TGameObj* item, DWORD* rounds); +long __stdcall ItemWCurrAmmo(TGameObj* item); +long __stdcall ItemWRounds(TGameObj* item); diff --git a/sfall/HookScripts.cpp b/sfall/HookScripts.cpp index 3871b3ed6..435caf17e 100644 --- a/sfall/HookScripts.cpp +++ b/sfall/HookScripts.cpp @@ -777,51 +777,46 @@ static void __declspec(naked) ItemDamageHook() { } } -static void __declspec(naked) AmmoCostHook_internal() { - __asm { - pushad; - mov args[0], eax; //weapon - mov ebx, [edx]; - mov args[4], ebx; //rounds in attack - call item_w_compute_ammo_cost_; - cmp eax, -1; - je fail; - mov ebx, [edx]; - mov args[8], ebx; //rounds as computed by game +// code backported from 4.x +int __fastcall AmmoCostHook_Script(DWORD hookType, TGameObj* weapon, DWORD* rounds) { + int result = 0; - push HOOK_AMMOCOST; - call RunHookScript; - popad; - cmp cRet, 0; - je end; - mov eax, rets[0]; - mov [edx], eax; // override result - xor eax, eax; - jmp end; -fail: - popad; -end: - hookend; - retn; - } -} + BeginHook(); + argCount = 4; -static void __declspec(naked) AmmoCostHook() { - __asm { - hookbegin(4); - mov args[12], 0; // type of hook - jmp AmmoCostHook_internal; + args[0] = (DWORD)weapon; + args[1] = *rounds; // rounds in attack + args[3] = hookType; + + if (hookType == 2) { // burst hook + *rounds = 1; // set default multiply for check burst attack + } else { + result = ItemWComputeAmmoCost(weapon, rounds); + if (result == -1) goto failed; // failed computed } + args[2] = *rounds; // rounds as computed by game (cost) + + RunHookScript(HOOK_AMMOCOST); + + if (cRet > 0) *rounds = rets[0]; // override rounds + +failed: + EndHook(); + return result; } -void __declspec(naked) AmmoCostHookWrapper() { +static void __declspec(naked) AmmoCostHook() { __asm { - hookbegin(4); - push eax; - mov eax, [esp+8]; // hook type - mov args[12], eax; - pop eax; - call AmmoCostHook_internal; + xor ecx, ecx; // type of hook (0) + cmp dword ptr [esp + 0x1C + 4], 46; // ANIM_fire_burst + jl skip; + cmp dword ptr [esp + 0x1C + 4], 47; // ANIM_fire_continuous + jg skip; + mov ecx, 3; // hook type burst +skip: + xchg eax, edx; + push eax; // rounds in attack + call AmmoCostHook_Script; // edx - weapon retn; } } diff --git a/sfall/HookScripts.h b/sfall/HookScripts.h index 9ae27eb0d..0eeb54efa 100644 --- a/sfall/HookScripts.h +++ b/sfall/HookScripts.h @@ -61,7 +61,7 @@ void HookScriptInit(); void HookScriptClear(); extern DWORD InitingHookScripts; -extern void __declspec() AmmoCostHookWrapper(); +extern int __fastcall AmmoCostHook_Script(DWORD hookType, TGameObj* weapon, DWORD* rounds); void _stdcall MouseClickHook(DWORD button, bool pressed); DWORD _stdcall KeyPressHook(DWORD dxKey, bool pressed, DWORD vKey); -void _stdcall RunHookScriptsAtProc(DWORD procId); \ No newline at end of file +void _stdcall RunHookScriptsAtProc(DWORD procId); diff --git a/sfall/Inventory.cpp b/sfall/Inventory.cpp index ad2a98922..e8daf1b2c 100644 --- a/sfall/Inventory.cpp +++ b/sfall/Inventory.cpp @@ -67,7 +67,7 @@ void InventoryKeyPressedHook(DWORD dxKey, bool pressed, DWORD vKey) { call item_w_max_ammo_; mov maxAmmo, eax; mov eax, item; - call item_w_cur_ammo_; + call item_w_curr_ammo_; mov curAmmo, eax; } if (maxAmmo != curAmmo) { @@ -420,66 +420,93 @@ static void __declspec(naked) inven_ap_cost_hack() { } } -static const DWORD add_check_for_item_ammo_cost_back = 0x4266EE; -// adds check for weapons which require more than 1 ammo for single shot (super cattle prod & mega power fist) -static void __declspec(naked) add_check_for_item_ammo_cost() { +static DWORD __fastcall add_check_for_item_ammo_cost(register TGameObj* weapon, DWORD hitMode) { + DWORD rounds = 1; + + DWORD anim = ItemWAnimWeap(weapon, hitMode); + if (anim == 46 || anim == 47) { // ANIM_fire_burst or ANIM_fire_continuous + rounds = ItemWRounds(weapon); // ammo in burst + } + AmmoCostHook_Script(1, weapon, &rounds); // get rounds cost from hook + DWORD currAmmo = ItemWCurrAmmo(weapon); + + DWORD cost = 1; // default cost + if (currAmmo > 0) { + cost = rounds / currAmmo; + if (rounds % currAmmo) cost++; // round up + } + return (cost > currAmmo) ? 0 : 1; // 0 - this will force "Out of ammo", 1 - this will force success (enough ammo) +} + +// adds check for weapons which require more than 1 ammo for single shot (super cattle prod & mega power fist) and burst rounds +static void __declspec(naked) combat_check_bad_shot_hook() { __asm { - push edx - push ebx - sub esp, 4 - call item_w_cur_ammo_ - mov ebx, eax - mov eax, ecx // weapon - mov edx, esp - mov dword ptr [esp], 1 - pushad - push 1 // hook type - call AmmoCostHookWrapper - add esp, 4 - popad - mov eax, [esp] - cmp eax, ebx - jle enoughammo - xor eax, eax // this will force "Out of ammo" - jmp end -enoughammo: - mov eax, 1 // this will force success -end: - add esp, 4 - pop ebx - pop edx - jmp add_check_for_item_ammo_cost_back; // jump back + push edx; + push ecx; // weapon + mov edx, edi; // hitMode + call add_check_for_item_ammo_cost; + pop ecx; + pop edx; + retn; } } -static const DWORD divide_burst_rounds_by_ammo_cost_back = 0x4234B9; -static void __declspec(naked) divide_burst_rounds_by_ammo_cost() { +// check if there is enough ammo to shoot +static void __declspec(naked) ai_search_inven_weap_hook() { __asm { - // ecx - current ammo, eax - burst rounds; need to set ebp - push edx - sub esp, 4 - mov ebp, eax - mov eax, edx // weapon - mov dword ptr [esp], 1 - mov edx, esp // *rounds - pushad - push 2 - call AmmoCostHookWrapper - add esp, 4 - popad - mov edx, 0 - mov eax, ebp // rounds in burst - imul dword ptr [esp] // so much ammo is required for this burst - cmp eax, ecx - jle skip - mov eax, ecx // if more than current ammo, set it to current -skip: - idiv dword ptr [esp] // divide back to get proper number of rounds for damage calculations - mov ebp, eax - add esp, 4 - pop edx - // end overwriten code - jmp divide_burst_rounds_by_ammo_cost_back; // jump back + push ecx; + mov ecx, eax; // weapon + mov edx, 2; // hitMode - ATKTYPE_RWEAPON_PRIMARY + call add_check_for_item_ammo_cost; // enough ammo? + pop ecx; + retn; + } +} + +// switch weapon mode from secondary to primary if there is not enough ammo to shoot +static const DWORD ai_try_attack_search_ammo = 0x42AA1E; +static const DWORD ai_try_attack_continue = 0x42A929; +static void __declspec(naked) ai_try_attack_hook() { + __asm { + mov ebx, [esp + 0x364 - 0x38]; // hit mode + cmp ebx, 3; // ATKTYPE_RWEAPON_SECONDARY + jne searchAmmo; + mov edx, [esp + 0x364 - 0x3C]; // weapon + mov eax, [edx + 0x3C]; // curr ammo + test eax, eax; + jnz tryAttack; // have ammo +searchAmmo: + jmp ai_try_attack_search_ammo; +tryAttack: + mov ebx, 2; // ATKTYPE_RWEAPON_PRIMARY + mov [esp + 0x364 - 0x38], ebx; // change hit mode + jmp ai_try_attack_continue; + } +} + +static DWORD __fastcall divide_burst_rounds_by_ammo_cost(TGameObj* weapon, register DWORD currAmmo, DWORD burstRounds) { + DWORD rounds = 1; // default multiply + + rounds = burstRounds; // rounds in burst + AmmoCostHook_Script(2, weapon, &rounds); + + DWORD cost = burstRounds * rounds; // so much ammo is required for this burst + if (cost > currAmmo) cost = currAmmo; // if cost ammo more than current ammo, set it to current + + return (cost / rounds); // divide back to get proper number of rounds for damage calculations +} + +static void __declspec(naked) compute_spray_hack() { + __asm { + push edx; // weapon + push ecx; // current ammo in weapon + xchg ecx, edx; + push eax; // eax - rounds in burst attack, need to set ebp + call divide_burst_rounds_by_ammo_cost; + mov ebp, eax; // overwriten code + pop ecx; + pop edx; + retn; } } @@ -745,8 +772,10 @@ void InventoryInit() { } if(GetPrivateProfileInt("Misc", "CheckWeaponAmmoCost", 0, ini)) { - MakeJump(0x4266E9, add_check_for_item_ammo_cost); - MakeJump(0x4234B3, divide_burst_rounds_by_ammo_cost); + HookCall(0x4266E9, combat_check_bad_shot_hook); + HookCall(0x429A37, ai_search_inven_weap_hook); + HookCall(0x42A95D, ai_try_attack_hook); // jz func + MakeCall(0x4234B3, compute_spray_hack, 1); } ReloadWeaponKey = GetPrivateProfileInt("Input", "ReloadWeaponKey", 0, ini);