Files
sfall/sfall/Modules/DamageMod.cpp
T
NovaRain c8de86d58f Improved the ASM code of Glovz's formula and YAAM
Changed OverrideArtCacheSize to set the art cache size to 261.
Fixed typo in ddraw.ini.
2021-10-27 22:05:29 +08:00

528 lines
26 KiB
C++

/*
* sfall
* Copyright (C) 2008, 2009, 2010, 2013, 2014 The sfall team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
#include "..\Logging.h"
#include "Unarmed.h"
#include "..\Game\stats.h"
#include "DamageMod.h"
namespace sfall
{
static int formula;
static const DWORD DamageFunctionReturn = 0x424A63;
// Damage Fix v5 (with v5.1 Damage Multiplier tweak) by Glovz 2014.04.16.xx.xx
static __declspec(naked) void DamageGlovz() {
static long ammoY, ammoX, ammoDRM;
__asm {
mov ebx, dword ptr ss:[esp + 0x1C]; // get the number of hits
xor ecx, ecx; // set the loop counter to 0
test ebx, ebx; // compare the number of hits to 0
jle end; // exit
mov eax, dword ptr ds:[esi + 0x8]; // get pointer to critter's weapon
call fo::funcoffs::item_w_dam_div_; // get the ammoY value
mov ammoY, eax; // store to the variable for later use
test eax, eax; // compare the ammoY value to 0
jg setAmX; // if the ammoY value is greater than 0 then goto setAmX
mov ammoY, 1; // set the ammoY value to 1
setAmX:
mov eax, dword ptr ds:[esi + 0x8]; // get pointer to critter's weapon
call fo::funcoffs::item_w_dam_mult_; // get the ammoX value
mov ammoX, eax; // store to the variable for later use
test eax, eax; // compare the ammoX value to 0
jg setAmDRM; // if the ammoX value is greater than 0 then goto setAmDRM
mov ammoX, 1; // set the ammoX value to 1
setAmDRM:
mov eax, dword ptr ds:[esi + 0x8]; // get pointer to critter's weapon
call fo::funcoffs::item_w_dr_adjust_; // get the ammoDRM value
mov ammoDRM, eax; // store to the variable for later use
test eax, eax; // compare the ammoDRM value to 0
jle begin; // if the ammoDRM value is less than or equal to 0 then goto begin
neg eax; // subtract ammoDRM value from 0
mov ammoDRM, eax; // set new ammoDRM value
begin:
mov dword ptr ss:[esp + 0x30], 0; // clear value
mov edx, dword ptr ds:[esi + 0x4]; // get the hit mode of weapon being used by the attacker
mov eax, dword ptr ds:[esi]; // get pointer to critter attacking
call fo::funcoffs::item_w_damage_; // get the raw damage value
add eax, dword ptr ss:[esp + 0x18]; // add the bonus ranged damage value to the RD value
test eax, eax; // compare the new damage value to 0
jle noDamageJmp; // goto noDamageJmp
mov ebx, eax; // set the ND value
mov edx, dword ptr ss:[esp + 0x28]; // get the armorDT value
test edx, edx; // compare the armorDT value to 0
jle bJmp; // if the armorDT value is less than or equal to 0 then goto bJmp
mov eax, ammoY; // get the ammoY value
aJmp:
xor ebp, ebp; // clear value
cmp edx, eax; // compare the dividend value to the divisor value
jl lrThan; // the dividend is less than the divisor then goto lrThan
jg grThan; // the dividend is greater than the divisor then goto grThan
jmp setOne; // if the two values are equal then goto setOne
lrThan:
shl edx, 1; // multiply dividend value by 2
cmp edx, eax; // compare the dividend value to the divisor value
jle setZero; // if the dividend value is less than or equal to the divisor value then goto setZero
jmp setOne; // if the dividend value is greater than the divisor value then goto setOne
grThan:
mov ebp, eax; // assign the divisor value
mov eax, edx; // assign the dividend value
cdq; // prepare for signed division
idiv ebp; // perform signed integer division
test edx, edx; // compare the remainder value to 0
jz endDiv; // if the remainder value is equal to 0 then goto endDiv
shl edx, 1; // multiply the remainder value by 2
cmp edx, ebp; // compare the remainder value to the divisor value
jl endDiv; // if the remainder value is less than the divisor value then goto endDiv
jg addOne; // if the remainder value is greater than the divisor value then goto addOne
test al, 1; // check if the quotient value is even or odd
jz endDiv; // if the quotient value is even goto endDiv
addOne:
inc eax; // add 1 to the quotient value
jmp endDiv; // goto endDiv
setOne:
mov eax, 1; // set the quotient value to 1
jmp endDiv; // goto endDiv
setZero:
xor eax, eax; // clear value
endDiv:
cmp dword ptr ss:[esp + 0x30], 2; // compare value to 2
je divTwo; // goto divTwo
cmp dword ptr ss:[esp + 0x30], 3; // compare value to 3
je divThree; // goto divThree
cmp dword ptr ss:[esp + 0x30], 4; // compare value to 4
je divFour; // goto divFour
cmp dword ptr ss:[esp + 0x30], 5; // compare value to 5
je divFive; // goto divFive
cmp dword ptr ss:[esp + 0x30], 6; // compare value to 6
je divSix; // goto divSix
cmp dword ptr ss:[esp + 0x30], 7; // compare value to 7 (added for v5.1 tweak)
je divSeven; // goto divSeven
sub ebx, eax; // subtract the new armorDT value from the ND value
test ebx, ebx; // compare the ND value to 0
jle noDamageJmp; // goto noDamageJmp
jmp cJmp; // goto cJmp
bJmp:
mov edx, dword ptr ss:[esp + 0x2C]; // get the armorDR value
test edx, edx; // compare the armorDR value to 0
jle dJmp; // if the armorDR value is less than or equal to 0 then goto dJmp
jmp cAltJmp; // goto cAltJmp (skip duplicate instructions)
cJmp:
mov edx, dword ptr ss:[esp + 0x2C]; // get the armorDR value
test edx, edx; // compare the armorDR value to 0
jle eJmp; // if the armorDR value is less than or equal to 0 then goto eJmp
cAltJmp:
mov eax, dword ptr ss:[esp + 0x20]; // get the Combat Difficulty (CD) value
cmp eax, 100; // compare the CD value to 100
jg sdrJmp; // if the CD value is greater than 100 then goto sdrJmp
je aSubCJmp; // if the CD value is equal to 100 then goto aSubCJmp
add edx, 20; // add 20 to the armorDR value
jmp aSubCJmp; // goto aSubCJmp
sdrJmp:
sub edx, 20; // subtract 20 from the armorDR value
aSubCJmp:
add edx, ammoDRM; // add the ammoDRM value to the armorDR value
mov eax, ammoX; // get the ammoX value
mov dword ptr ss:[esp + 0x30], 2; // set value to 2
jmp aJmp; // goto aJmp
divTwo:
cmp eax, 100; // compare the armorDR value to 100
jge noDamageJmp; // if the armorDR value is greater than or equal to 100 then goto noDamageJmp
mov edx, ebx; // set temp value
imul edx, eax; // multiply the ND value by the armorDR value
mov eax, 100; // set divisor value to 100
mov dword ptr ss:[esp + 0x30], 3; // set value to 3
jmp aJmp; // goto aJmp
divThree:
sub ebx, eax; // subtract the damage resisted value from the ND value
test ebx, ebx; // compare the ND value to 0
jle noDamageJmp; // goto noDamageJmp
jmp eJmp; // goto eJmp
dJmp:
mov eax, ammoX; // get the ammoX value
cmp eax, 1; // compare the ammoX value to 1
jle bSubDJmp; // if the ammoX value is less than or equal to 1 then goto bSubDJmp;
mov eax, ammoY; // get the ammoY value
cmp eax, 1; // compare the ammoY value to 1
jle aSubDJmp; // if the ammoY value is less than or equal to 1 then goto aSubDJmp
mov edx, ebx; // set temp value
imul edx, 15; // multiply the ND value by 15
mov eax, 100; // set divisor value to 100
mov dword ptr ss:[esp + 0x30], 4; // set value to 4
jmp aJmp; // goto aJmp
divFour:
add ebx, eax; // add the quotient value to the ND value
jmp eJmp; // goto eJmp
aSubDJmp:
mov edx, ebx; // set temp value
imul edx, 20; // multiply the ND value by 20
mov eax, 100; // set divisor value to 100
mov dword ptr ss:[esp + 0x30], 5; // set value to 5
jmp aJmp; // goto aJmp
divFive:
add ebx, eax; // add the quotient value to the ND value
jmp eJmp; // goto eJmp
bSubDJmp:
mov eax, ammoY; // get the ammoY value
cmp eax, 1; // compare the ammoY value to 1
jle eJmp; // goto eJmp
mov edx, ebx; // set temp value
imul edx, 10; // multiply the ND value by 10
mov eax, 100; // set divisor value to 100
mov dword ptr ss:[esp + 0x30], 6; // set value to 6
jmp aJmp; // goto aJmp
divSix:
add ebx, eax; // add the quotient value to the ND value
eJmp:
mov eax, dword ptr ss:[esp + 0x24]; // get the Critical Multiplier (CM) value
cmp eax, 2; // compare the CM value to 2
jle addNDJmp; // if the CM value is less than or equal to 2 then goto addNDJmp
cmp formula, 2; // check selected damage formula (added for v5.1 tweak)
je tweak;
imul ebx, eax; // multiply the ND value by the CM value
sar ebx, 1; // divide the result by 2
jmp addNDJmp;
//////// begin v5.1 tweak ////////
tweak:
mov edx, ebx; // set temp ND value
imul edx, eax; // multiply the temp ND value by the CM value
imul edx, 25; // multiply the temp ND value by 25
mov eax, 100; // set divisor value to 100
mov dword ptr ss:[esp + 0x30], 7; // set value to 7
jmp aJmp; // goto aJmp
divSeven:
add ebx, eax; // add the critical damage value to the ND value
//////// end v5.1 tweak //////////
addNDJmp:
test ebx, ebx; // compare the ND value to 0
jle noDamageJmp; // goto noDamageJmp
add dword ptr ds:[edi], ebx; // accumulate damage
noDamageJmp:
mov eax, dword ptr ss:[esp + 0x1C]; // get the number of hits
inc ecx; // the counter value increments by one
cmp ecx, eax; // is the counter value less than the number of hits
jl begin; // goto begin
end:
jmp DamageFunctionReturn; // exit
}
}
// YAAM v1.1a by Haenlomal 2010.05.13
static __declspec(naked) void DamageYAAM() {
static long ammoDT;
__asm {
mov ebx, dword ptr ss:[esp + 0x1C]; // Get number of hits
xor ecx, ecx; // Set loop counter to zero
test ebx, ebx; // Is number of hits <= 0?
jle end; // If yes, jump beyond damage calculation loop
mov eax, dword ptr ds:[esi + 0x8]; // Get pointer to critter's weapon
mov edx, dword ptr ss:[esp + 0x24]; // Get Critical Multiplier (passed in as argument to function)
call fo::funcoffs::item_w_dam_mult_; // Retrieve Ammo Dividend
imul edx, eax; // Damage Multipler = Critical Multipler * Ammo Dividend
mov eax, dword ptr ds:[esi + 0x8]; // Get pointer to critter's weapon
call fo::funcoffs::item_w_dam_div_; // Retrieve Ammo Divisor
imul ebp, eax; // Ammo Divisor = 1 * Ammo Divisor (ebp set to 1 earlier in function)
mov dword ptr ss:[esp + 0x24], edx; // Store Damage Multiplier
mov eax, dword ptr ds:[esi + 0x8]; // Get pointer to critter's weapon
call fo::funcoffs::item_w_dr_adjust_; // Retrieve ammo DT (well, it's really Retrieve ammo DR, but since we're treating ammo DR as ammo DT...)
mov ammoDT, eax; // Store to the variable for later use
ajmp: // Start of damage calculation loop
mov edx, dword ptr ds:[esi + 0x4]; // Get hit mode of weapon being used by attacker
mov eax, dword ptr ds:[esi]; // Get pointer to critter
mov ebx, dword ptr ss:[esp + 0x18]; // Get Bonus Ranged Damage
call fo::funcoffs::item_w_damage_; // Retrieve Raw Damage
add ebx, eax; // Raw Damage = Raw Damage + Bonus Ranged Damage
mov edx, dword ptr ss:[esp + 0x28]; // Get armor DT
mov eax, ammoDT; // Retrieve ammo DT
sub edx, eax; // DT = armor DT - ammo DT
mov dword ptr ss:[esp + 0x30], edx; // Store DT
test edx, edx; // Is DT <= 0?
jle bjmp; // If yes, skip the next instruction
sub ebx, edx; // Raw Damage = Raw Damage - DT
bjmp:
test ebx, ebx; // Is Raw Damage <= 0?
jle ijmp; // If yes, skip damage calculation and go to bottom of loop
imul ebx, dword ptr ss:[esp + 0x24]; // Otherwise, Raw Damage = Raw Damage * Damage Multiplier
test ebp, ebp; // Is Ammo Divisor == 0?
jz djmp; // If yes, avoid divide by zero error
mov eax, ebx;
cdq;
idiv ebp;
mov ebx, eax; // Otherwise, Raw Damage = Raw Damage / Ammo Divisor
djmp:
mov eax, ebx;
cdq;
sub eax, edx;
sar eax, 1; // Raw Damage = Raw Damage / 2 (related to critical hit damage multiplier bonus)
mov edx, dword ptr ss:[esp + 0x20]; // Get combat difficulty setting (75 if wimpy, 100 if normal or if attacker is player, 125 if rough)
imul edx, eax; // Raw Damage = Raw Damage * combat difficulty setting
mov ebx, 100;
mov eax, edx;
cdq;
idiv ebx;
mov ebx, eax; // Raw Damage = Raw Damage / 100
mov edx, dword ptr ss:[esp + 0x30]; // Get stored DT
test edx, edx; // Is DT >= 0?
jns ejmp; // If yes, set DT = 0
mov eax, 10;
imul eax, edx; // Otherwise, DT = DT * 10 (note that this should be a negative value)
jmp fjmp;
ejmp:
xor eax, eax;
fjmp:
mov edx, dword ptr ss:[esp + 0x2C]; // Get armor DR
add edx, eax; // DR = armor DR + DT (note that DT should be less than or equal to zero)
cmp edx, 100; // Is DR >= 100?
jge ijmp; // If yes, damage will be zero, so stop calculating and go to bottom of loop
test edx, edx; // Otherwise, is DR >= 0?
jns hjmp;
xor edx, edx; // If no, set DR = 0
hjmp:
imul edx, ebx; // Otherwise, Resisted Damage = DR * Raw Damage
mov dword ptr ss:[esp + 0x30], 100;
mov eax, edx;
cdq;
idiv dword ptr ss:[esp + 0x30]; // Resisted Damage = Resisted Damage / 100
sub ebx, eax; // Raw Damage = Raw Damage - Resisted Damage
test ebx, ebx; // Is Raw Damage <= 0?
jle ijmp; // If yes, don't accumulate damage
add dword ptr ds:[edi], ebx; // Otherwise, Accumulated Damage = Accumulated Damage + Raw Damage
ijmp:
mov eax, dword ptr ss:[esp + 0x1C]; // Get number of hits
inc ecx; // counter += 1
cmp ecx, eax; // Is counter < number of hits?
jl ajmp; // If yes, go back to start of damage calcuation loop (calculate damage for next hit)
end:
jmp DamageFunctionReturn; // Otherwise, exit loop
}
}
////////////////////////////////////////////////////////////////////////////////
// Display melee damage w/o PERK_bonus_hth_damage bonus
static __declspec(naked) void MeleeDmgDisplayPrintFix_hook() {
using namespace fo;
__asm {
mov ecx, eax; // Store pointer to critter
call fo::funcoffs::stat_level_; // Get Melee Damage
xchg ecx, eax; // Store Melee Damage value
mov edx, PERK_bonus_hth_damage; // perk_level_ argument: PERK_bonus_hth_damage
call fo::funcoffs::perk_level_; // Get rank of Bonus HtH Damage
shl eax, 1; // Multiply by 2
sub ecx, eax; // Subtract from Melee Damage
mov edx, STAT_melee_dmg;
mov eax, ds:[FO_VAR_obj_dude]; // Get pointer to PC
call fo::funcoffs::stat_get_base_; // Get Melee Damage w/o bonuses
cmp ecx, eax; // HtH Damage vs Base Melee Damage
cmovg eax, ecx; // Move back to eax in preparation of push
retn;
}
}
// Display max melee damage w/o PERK_bonus_hth_damage bonus
static __declspec(naked) void CommonDmgRngDispFix_hook() {
using namespace fo;
__asm {
mov ebx, eax; // Store pointer to critter
call fo::funcoffs::stat_level_; // Get Melee Damage
xchg ebx, eax; // Store Melee Damage value
mov edx, PERK_bonus_hth_damage; // perk_level_ argument: PERK_bonus_hth_damage
call fo::funcoffs::perk_level_; // Get rank of Bonus HtH Damage
shl eax, 1; // Multiply by 2
sub ebx, eax; // Subtract from Melee Damage
mov edx, STAT_melee_dmg;
mov eax, ds:[FO_VAR_stack];
call fo::funcoffs::stat_get_base_; // Get Melee Damage w/o bonuses
cmp ebx, eax; // HtH Damage vs Base Melee Damage
cmovg eax, ebx; // Move back to eax in preparation of push
retn;
}
}
/*
static __declspec(naked) void HtHDamageFix1a_hack() {
using namespace fo;
__asm {
cmp ecx, dword ptr ds:[FO_VAR_obj_dude]; // Is the critter == PC?
je fix; // Skip if no
mov edx, 1; // Min_Damage = 1
retn;
fix:
mov edx, PERK_bonus_hth_damage; // perk_level_ argument: PERK_bonus_hth_damage
mov eax, ecx; // pointer to PC
call fo::funcoffs::perk_level_; // Return Rank_of_Bonus_HtH_Damage_perk
shl eax, 1; // Rank_of_Bonus_HtH_Damage_perk *= 2
lea edx, [eax + 1]; // Min_Damage = 1 + Rank_of_Bonus_HtH_Damage_perk
retn;
}
}
*/
static __declspec(naked) void HtHDamageFix1b_hook() {
using namespace fo;
__asm {
call fo::funcoffs::stat_level_; // Get Total_Melee_Damage
cmp ecx, dword ptr ds:[FO_VAR_obj_dude]; // Is the critter == PC?
je fix; // Skip to exit if no
retn;
fix:
push eax;
mov edx, PERK_bonus_hth_damage; // perk_level_ argument: PERK_bonus_hth_damage
mov eax, ecx; // pointer to PC
call fo::funcoffs::perk_level_; // Return Rank_of_Bonus_HtH_Damage_perk
shl eax, 1; // Rank_of_Bonus_HtH_Damage_perk *= 2
add dword ptr [esp + 0x24 - 0x20 + 8], eax; // Min_Damage += Rank_of_Bonus_HtH_Damage_perk
pop eax;
retn;
}
}
static void __declspec(naked) DisplayBonusRangedDmg_hook() {
using namespace fo;
__asm {
mov edx, PERK_bonus_ranged_damage;
mov eax, dword ptr ds:[FO_VAR_stack];
call fo::funcoffs::perk_level_;
shl eax, 1; // Multiply by 2
add dword ptr [esp + 4 * 4], eax; // min_dmg + perk bonus
add dword ptr [esp + 4 * 5], eax; // max_dmg + perk bonus
jmp fo::funcoffs::sprintf_;
}
}
static void __declspec(naked) DisplayBonusHtHDmg1_hook() {
using namespace fo;
__asm {
mov edx, PERK_bonus_hth_damage;
mov eax, dword ptr ds:[FO_VAR_stack];
call fo::funcoffs::perk_level_;
shl eax, 1; // Multiply by 2
add dword ptr [esp + 4 * 4], eax; // min_dmg + perk bonus
jmp fo::funcoffs::sprintf_;
}
}
static bool bonusHtHDamageFix = true;
static bool displayBonusDamage = false;
static long __fastcall GetHtHDamage(fo::GameObject* source, long &meleeDmg, long handOffset) {
long min, max;
fo::AttackType hit = Unarmed_GetStoredHitMode((handOffset == 0) ? fo::HandSlot::Left : fo::HandSlot::Right);
long bonus = Unarmed_GetDamage(hit, min, max);
meleeDmg += max + bonus;
long perkBonus = game::Stats::perk_level(source, fo::Perk::PERK_bonus_hth_damage) << 1;
if (!displayBonusDamage) meleeDmg -= perkBonus;
if (displayBonusDamage && bonusHtHDamageFix) min += perkBonus;
return min + bonus;
}
static const char* __fastcall GetHtHName(long handOffset) {
fo::AttackType hit = Unarmed_GetStoredHitMode((handOffset == 0) ? fo::HandSlot::Left : fo::HandSlot::Right);
return Unarmed_GetName(hit);
}
static void __declspec(naked) DisplayBonusHtHDmg2_hack() {
static const DWORD DisplayBonusHtHDmg2Exit = 0x47254F;
static const DWORD DisplayBonusHtHDmg2Exit2 = 0x472556;
__asm {
mov ecx, eax;
call fo::funcoffs::stat_level_; // get STAT_melee_dmg
push eax; // max dmg (meleeDmg)
mov edx, esp; // meleeDmg ref
push edi; // handOffset
call GetHtHDamage;
push eax; // min dmg
mov ecx, edi;
call GetHtHName;
test eax, eax;
jnz customName;
jmp DisplayBonusHtHDmg2Exit;
customName:
jmp DisplayBonusHtHDmg2Exit2;
}
}
long DamageMod_GetHtHMinDamageBonus(fo::GameObject* source) {
return (bonusHtHDamageFix)
? game::Stats::perk_level(source, fo::Perk::PERK_bonus_hth_damage) << 1 // Multiply by 2
: 0;
}
void DamageMod_Init() {
if (formula = GetConfigInt("Misc", "DamageFormula", 0)) {
switch (formula) {
case 1:
case 2:
MakeJump(0x424995, DamageGlovz);
break;
case 5:
MakeJump(0x424995, DamageYAAM);
break;
}
}
bonusHtHDamageFix = GetConfigInt("Misc", "BonusHtHDamageFix", 1) != 0;
displayBonusDamage = GetConfigInt("Misc", "DisplayBonusDamage", 0) != 0;
if (bonusHtHDamageFix) {
dlog("Applying Bonus HtH Damage Perk fix.", DL_INIT);
// Subtract damage from perk bonus (vanilla displaying)
if (!displayBonusDamage) {
const DWORD meleeDmgDispPrtAddr[] = {
0x435C0C, // DisplayFix (ListDrvdStats_)
0x439921 // PrintFix (Save_as_ASCII_)
};
HookCalls(MeleeDmgDisplayPrintFix_hook, meleeDmgDispPrtAddr);
const DWORD commonDmgRngDispAddr[] = {
0x472266, // MeleeWeap (display_stats_)
//0x472546 // Unarmed (display_stats_)
};
HookCalls(CommonDmgRngDispFix_hook, commonDmgRngDispAddr);
}
//MakeCall(0x478492, HtHDamageFix1a_hack); // Unarmed (item_w_damage_)
HookCall(0x47854C, HtHDamageFix1b_hook); // MeleeWeap (item_w_damage_)
dlogr(" Done", DL_INIT);
}
if (displayBonusDamage) {
dlog("Applying Display Bonus Damage patch.", DL_INIT);
HookCall(0x4722DD, DisplayBonusRangedDmg_hook); // display_stats_
if (bonusHtHDamageFix) {
HookCall(0x472309, DisplayBonusHtHDmg1_hook); // MeleeWeap (display_stats_)
}
dlogr(" Done", DL_INIT);
}
// Display the actual damage values of unarmed attacks (display_stats_ hacks)
MakeJump(0x472546, DisplayBonusHtHDmg2_hack);
SafeWrite32(0x472558, 0x509EDC); // fmt: '%s %d-%d'
SafeWrite8(0x472552, 0x98 + 4);
SafeWrite8(0x47255F, 0x0C + 4);
SafeWrite8(0x472568, 0x10 + 4);
}
}