Rewrote several fixes for attack_complex in C++ and merged them

Added a default fix for attack_complex to untie target_results from
attacker_results.
This commit is contained in:
NovaRain
2020-08-01 10:22:30 +08:00
parent 694d97e123
commit 435180d157
5 changed files with 63 additions and 70 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -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)
+13
View File
@@ -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 {
+2 -2
View File
@@ -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)
+42 -63
View File
@@ -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;
}
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_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::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_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;
}
}
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);