diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 55070953..4925a067 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -589,7 +589,7 @@ StartGDialogFix=0 ;Set to 1 to fix attacker_results/target_results arguments and repurpose the unused called_shot/num_attacks arguments of attack_complex script function ;New behavior of the arguments: -;called_shot - additional damage, when the damage received by the target is above the specified minimum damage (minDmg argument) +;called_shot - additional damage when hitting the target ;num_attacks - the number of free action points on the first turn only AttackComplexFix=0 diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index ab7a753a..facd5f9a 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -17,6 +17,7 @@ // For functions that have 3 or more arguments, it is preferable to use the fastcall calling convention // because the compiler builds the better/optimized code when calling the engine functions WRAP_WATCOM_FFUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, buf, BYTE*, count) +WRAP_WATCOM_FFUNC3(void, check_for_death, GameObject*, critter, long, amountDamage, long*, flags) WRAP_WATCOM_FFUNC3(void, correctFidForRemovedItem, GameObject*, critter, GameObject*, item, long, slotFlag) WRAP_WATCOM_FFUNC7(long, createWindow, const char*, winName, DWORD, x, DWORD, y, DWORD, width, DWORD, height, long, color, long, flags) WRAP_WATCOM_FFUNC4(long, determine_to_hit, GameObject*, source, GameObject*, target, long, bodyPart, long, hitMode) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index eb404643..428af28d 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -166,6 +166,19 @@ struct ComputeAttackResult { long extraKnockbackValue[6]; }; +struct CombatGcsd { + GameObject* source; + GameObject* target; + long freeAP; + long bonusToHit; + long bonusDamage; + long minDamage; + long maxDamage; + long changeFlags; + DWORD flagsSource; + DWORD flagsTarget; +}; + // Script instance attached to an object or tile (spatial script). #pragma pack(1) struct ScriptInstance { diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 7dddfc34..88312af2 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -64,7 +64,7 @@ VAR_(frame_time, DWORD) VAR_(free_perk, char) VAR_(game_global_vars, long*) // dynamic array of size == num_game_global_vars VAR_(game_user_wants_to_quit, DWORD) -VAR_(gcsd, DWORD) +VAR_(gcsd, CombatGcsd*) VAR_(gdBarterMod, DWORD) VAR_(gdNumOptions, DWORD) VAR_(gIsSteal, DWORD) @@ -109,7 +109,7 @@ VAR_(list_total, DWORD) VAR_(loadingGame, DWORD) VAR_(LSData, DWORD) VAR_(lsgwin, DWORD) -VAR_(main_ctd, DWORD) +VAR_(main_ctd, ComputeAttackResult) VAR_(main_death_voiceover_done, DWORD) VAR_(main_window, DWORD) VAR_(map_elevation, DWORD) diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 90262cab..2ea791e8 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -2006,8 +2006,8 @@ skip: static void __declspec(naked) op_attack_hook() { __asm { - mov esi, dword ptr [esp + 0x3C + 4]; // free_move - mov ebx, dword ptr [esp + 0x40 + 4]; // add amount damage to target + mov esi, dword ptr [esp + 0x3C + 4]; // free_move + mov ebx, dword ptr [esp + 0x40 + 4]; // add amount damage to target jmp fo::funcoffs::gdialogActive_; } } @@ -2015,9 +2015,10 @@ static void __declspec(naked) op_attack_hook() { static void __declspec(naked) op_attack_hook_flags() { __asm { and eax, 0xFF; + shl al, 1; // shift to bit 2 test ebp, ebp; jz skip; - or al, 2; // set bit 2 + or al, 1; skip: // EAX: gcsd.changeFlags contains the attribute value for setting the flags // bit 1 - set result flags for target, bit 2 - set result flags for attacker @@ -2026,65 +2027,44 @@ skip: } } -static void __declspec(naked) combat_attack_hack_gcsdFlags() { - __asm { - mov ebp, [eax + 0x24]; // gcsd.flagsTarget - mov ebx, [eax + 0x1C]; // gcsd.changeFlags - test bl, 2; - jz checkTarget; - // set source - mov eax, ds:[FO_VAR_main_ctd + 0x14]; // flagsSource - and eax, DAM_DEAD; - or edx, eax; // don't unset DAM_DEAD flag - mov ds:[FO_VAR_main_ctd + 0x14], edx; // flagsSource -checkTarget: - mov eax, ds:[FO_VAR_main_ctd + 0x30]; // flagsTarget - test bl, 1; - jnz setTarget; - retn; -setTarget: - and eax, DAM_DEAD; - or ebp, eax; // don't unset DAM_DEAD flag - mov eax, ebp; - retn; +static void __stdcall combat_attack_gcsd() { + if (fo::var::gcsd->changeFlags & 2) { // only for AttackComplexFix + long flags = fo::var::gcsd->flagsSource; + flags |= fo::var::main_ctd.attackerFlags & (fo::DamageFlag::DAM_HIT | fo::DamageFlag::DAM_DEAD); // don't unset DAM_HIT and DAM_DEAD flags + fo::var::main_ctd.attackerFlags = flags; } -} - -static void __declspec(naked) combat_attack_hack_gcsdMinDamage() { - __asm { - mov ds:[FO_VAR_main_ctd + 0x2C], ecx; // amountTarget (min) - mov edx, ecx; - lea ebx, ds:[FO_VAR_main_ctd + 0x30]; // flagsTarget - mov eax, ds:[FO_VAR_main_ctd + 0x20]; // Target - jmp fo::funcoffs::check_for_death_; // set DAM_DEAD + if (fo::var::gcsd->changeFlags & 1) { + long flags = fo::var::gcsd->flagsTarget; + flags |= fo::var::main_ctd.targetFlags & fo::DamageFlag::DAM_DEAD; // don't unset DAM_DEAD flag + fo::var::main_ctd.targetFlags = flags; } -} -static void __declspec(naked) combat_attack_hack_gcsdMaxDamage() { - __asm { - mov ds:[FO_VAR_main_ctd + 0x2C], ebp; // amountTarget (max) - mov eax, ds:[FO_VAR_main_ctd + 0x20]; // Target - call fo::funcoffs::critter_get_hits_; - cmp eax, ebp; // curr.HP <= max.DMG - jle skip; - cmp eax, edx; // curr.HP > curr.DMG - jg skip; - and byte ptr ds:[FO_VAR_main_ctd + 0x30], ~DAM_DEAD; // flagsTarget (unset) -skip: - retn; + if (fo::var::main_ctd.attackerFlags & fo::DamageFlag::DAM_HIT) { + long damage = fo::var::main_ctd.targetDamage; + fo::var::main_ctd.targetDamage += fo::var::gcsd->bonusDamage; + if (fo::var::main_ctd.targetDamage < fo::var::gcsd->minDamage) { + fo::var::main_ctd.targetDamage = fo::var::gcsd->minDamage; + } + // check the hit points and set the DAM_DEAD flag + if (damage != fo::var::main_ctd.targetDamage) { + fo::func::check_for_death(fo::var::main_ctd.target, fo::var::main_ctd.targetDamage, &fo::var::main_ctd.targetFlags); + } + if (fo::var::main_ctd.targetDamage > fo::var::gcsd->maxDamage) { + fo::var::main_ctd.targetDamage = fo::var::gcsd->maxDamage; + if (fo::var::main_ctd.target->Type() == fo::ObjType::OBJ_TYPE_CRITTER) { + long cHP = fo::var::main_ctd.target->critter.health; + if (cHP > fo::var::gcsd->maxDamage && cHP <= damage) { + fo::var::main_ctd.targetFlags &= ~fo::DamageFlag::DAM_DEAD; // unset + } + } + } } } static void __declspec(naked) combat_attack_hack() { __asm { - mov ebx, ds:[FO_VAR_main_ctd + 0x2C]; // amountTarget - test ebx, ebx; - jz end; - retn; -end: - add esp, 4; - mov ebx, 0x423039; - jmp ebx; + push 0x423039; // return addr + jmp combat_attack_gcsd; } } @@ -3280,31 +3260,30 @@ void BugFixes::init() HookCall(0x4C6162, db_freadInt_hook); // Fix and repurpose the unused called_shot/num_attack arguments of attack_complex function - // called_shot - additional damage, when the damage received by the target is above the specified minimum + // called_shot - additional damage when hitting the target // num_attacks - the number of free action points on the first turn only if (GetConfigInt("Misc", "AttackComplexFix", 0)) { dlog("Applying attack_complex arguments fix.", DL_INIT); HookCall(0x456D4A, op_attack_hook); - SafeWrite8(0x456D61, 0x74); // mov [esp+x], esi - SafeWrite8(0x456D92, 0x5C); // mov [esp+x], ebx + SafeWrite8(0x456D61, 0x74); // mov [gcsd.free_move], esi + SafeWrite8(0x456D92, 0x5C); // mov [gcsd.amount], ebx - // Fix setting result flags arguments for the attacker and the target (now work independently of each other) - SafeWrite16(0x456D95, 0xC085); // cmp eax, ebx > test eax, eax + // Allow setting result flags arguments for the attacker and the target (now work independently of each other) + SafeWrite16(0x456D95, 0xC085); // cmp eax, ebp > test eax, eax MakeCall(0x456D9A, op_attack_hook_flags); - SafeWrite8(0x456D9F, CodeType::JumpNZ); // jz > jnz SafeWrite16(0x456DA7, 0x8489); // mov [gcsd.changeFlags], 1 > mov [gcsd.changeFlags], eax SafeWrite8(0x456DAB, 0); SafeWrite8(0x456DAE, CodeType::Nop); dlogr(" Done", DL_INIT); + } else { + // Fix setting result flags argument for the target + SafeWrite16(0x456D95, 0xED85); // cmp eax, ebp > test ebp, ebp } + SafeWrite8(0x456D9F, CodeType::JumpNZ); // jz > jnz // Fix result flags for the attacker and the target when calling attack_complex function - MakeCall(0x42302B, combat_attack_hack_gcsdFlags, 4); - // Set/Unset the DAM_DEAD flag when changing the minimum/maximum damage to the target - MakeCall(0x422FFF, combat_attack_hack_gcsdMinDamage, 1); - MakeCall(0x423017, combat_attack_hack_gcsdMaxDamage, 1); - - // Fix for attack_complex still causing minimum damage to the target when the attacker misses - MakeCall(0x422FE5, combat_attack_hack, 1); + // also set/unset the DAM_DEAD flag when changing the minimum/maximum damage to the target + // and fix minimum damage still being applied to the target when the attacker misses + MakeJump(0x422FE5, combat_attack_hack, 1); // Fix for critter_mod_skill taking a negative amount value as a positive dlog("Applying critter_mod_skill fix.", DL_INIT);