Updated gl_compute_damage example script from 4.x

* with script-based YAAM implementation.

Sync header changes with develop branch.

Changed AttackSubType enum "GUNS" to "RANGED".
(to be in line with define_extra.h, the naming is from RE/CE.)
This commit is contained in:
NovaRain
2023-05-15 12:16:23 +08:00
parent 66c0ed8cb2
commit ff1c87f0c5
9 changed files with 199 additions and 62 deletions
@@ -1,28 +1,69 @@
/*
Example algorithm of how the game engine calculates combat damage
This script reimplements the combat damage calculation of the original game.
Use this to implement your own damage formula!
*/
#include "..\headers\define.h"
#include "..\headers\command.h"
#include "..\headers\sfall\sfall.h"
#include "..\headers\sfall\define_extra.h"
#include "../sfall/define_lite.h"
#include "../sfall/define_extra.h"
#include "../sfall/sfall.h"
#define DAMAGE_FORMULA_VANILLA (0)
#define DAMAGE_FORMULA_GLOVZ (1)
#define DAMAGE_FORMULA_GLOVZ2 (2)
#define DAMAGE_FORMULA_YAAM (5)
procedure start;
procedure compute_damage_F2;
procedure item_w_damage_hook;
procedure combatdamage_handler;
procedure itemdamage_handler;
procedure calc_damage_YAAM(variable weapon, variable rounds, variable armorDT, variable armorDR, variable bonusRangedDamage, variable multiplyDamage, variable difficulty);
procedure item_w_subtype(variable weapon, variable hit_mode);
procedure get_ammo_value(variable weapon, variable param);
variable item_w_damage;
variable
damage_formula,
item_damage_min,
item_damage_max,
item_damage_weapon,
item_damage_attacker;
procedure start begin
if game_loaded then begin
register_hook_proc(HOOK_COMBATDAMAGE, compute_damage_F2);
register_hook_proc(HOOK_ITEMDAMAGE, item_w_damage_hook);
damage_formula := get_ini_setting("ddraw.ini|Misc|DamageFormula");
register_hook_proc(HOOK_COMBATDAMAGE, combatdamage_handler);
register_hook_proc(HOOK_ITEMDAMAGE, itemdamage_handler);
end
end
procedure compute_damage_F2 begin
/*
HOOK_COMBATDAMAGE
Runs when:
1) Game calculates how much damage each target will get. This includes primary target as well as all extras (explosions and bursts). This happens BEFORE the actual attack animation.
2) AI decides whether it is safe to use area attack (burst, grenades), if he might hit friendlies.
Does not run for misses, or non-combat damage like dynamite explosions.
Critter arg0 - The target
Critter arg1 - The attacker
int arg2 - The amount of damage to the target
int arg3 - The amount of damage to the attacker
int arg4 - The special effect flags for the target (use bwand DAM_* to check specific flags)
int arg5 - The special effect flags for the attacker (use bwand DAM_* to check specific flags)
Item arg6 - The weapon used in the attack
int arg7 - The bodypart that was struck
int arg8 - Damage Multiplier (this is divided by 2, so a value of 3 does 1.5x damage, and 8 does 4x damage. Usually it's 2; for critical hits, the value is taken from the critical table; with Silent Death perk and the corresponding attack conditions, the value will be doubled)
int arg9 - Number of bullets actually hit the target (1 for melee attacks)
int arg10 - The amount of knockback to the target
int arg11 - Attack Type (see ATKTYPE_* constants)
mixed arg12 - computed attack data (see C_ATTACK_* for offsets and use get/set_object_data functions to get/set the data)
int ret0 - The damage to the target
int ret1 - The damage to the attacker
int ret2 - The special effect flags for the target
int ret3 - The special effect flags for the attacker
int ret4 - The amount of knockback to the target
*/
procedure combatdamage_handler begin
variable dmg_type, weapon_perk, dmg_thresh, dmg_resist, weapon_subtype, bonus_ranged, difficulty, i, dmg_mult, dmg_div, damage;
variable target, flags, knockback, amount;
@@ -40,6 +81,11 @@ procedure compute_damage_F2 begin
amountKnockback := get_sfall_arg,
hit_mode := get_sfall_arg;
if (ctdSource != item_damage_attacker or weapon != item_damage_weapon) then begin
debug_msg("! ERROR ! compute_damage: Expected attacker or weapon differs!");
return;
end
if (flagsSource bwand DAM_HIT) then begin
target := ctdTarget;
flags := flagsTarget;
@@ -50,7 +96,6 @@ procedure compute_damage_F2 begin
knockback := 0;
end
//amount := 0;
if target and (obj_type(target) == OBJ_TYPE_CRITTER) then begin
if weapon then begin
dmg_type := weapon_dmg_type(weapon);
@@ -77,7 +122,7 @@ procedure compute_damage_F2 begin
end
weapon_subtype := item_w_subtype(weapon, hit_mode); // item_w_subtype_
if (ctdSource != dude_obj) or (weapon_subtype != WEAPON_TYPE_GUNS) then
if (ctdSource != dude_obj) or (weapon_subtype != WEAPON_TYPE_RANGED) then
bonus_ranged := 0;
else
bonus_ranged := has_trait(TRAIT_PERK, ctdSource, PERK_bonus_ranged_damage) * 2;
@@ -90,6 +135,9 @@ procedure compute_damage_F2 begin
difficulty := 125;
end
if (damage_formula == DAMAGE_FORMULA_YAAM) then begin
amount := calc_damage_YAAM(weapon, rounds, dmg_thresh, dmg_resist, bonus_ranged, damageMultiplier, difficulty);
end else begin
// F2 default start
// Damage = (1 - (DR_armor + DR_ammo_adjust) * (((raw_damage * (dmg_mult * damageMultiplier)) / dmg_div) - dmg_thresh)
dmg_resist += get_ammo_value(weapon, PROTO_AM_DR_MOD); // item_w_dr_adjust_ (DR Adjust %)
@@ -100,7 +148,7 @@ procedure compute_damage_F2 begin
dmg_div := get_ammo_value(weapon, PROTO_AM_DMG_DIV); // item_w_dam_div_ (Dmg mod B)
for (i := 1; i <= rounds; i++) begin
damage := (item_w_damage + bonus_ranged) * dmg_mult; // item_w_damage_ (raw_damage)
damage := (random(item_damage_min, item_damage_max) + bonus_ranged) * dmg_mult; // item_w_damage_ (raw_damage)
if dmg_div then damage /= dmg_div;
damage := (((damage / 2) * difficulty) / 100) - dmg_thresh;
@@ -111,6 +159,7 @@ procedure compute_damage_F2 begin
end
end
// F2 default end
end
if (ctdSource == dude_obj) then begin
if has_trait(TRAIT_PERK, ctdSource, PERK_living_anatomy_perk) and (critter_kill_type(ctdTarget) != KILL_TYPE_robot_kills)
@@ -140,11 +189,11 @@ procedure compute_damage_F2 begin
end
if (flagsSource bwand DAM_HIT) then begin
display_msg("amountTarget = " + amountTarget+ ", amount = " + amount);
display_msg("COMBATDAMAGE amountTarget = " + amountTarget+ ", amount = " + amount);
amountTarget := amount;
flagsTarget := flags;
end else begin
display_msg("amountSource = " + amountSource+ ", amount = " + amount);
display_msg("COMBATDAMAGE amountSource = " + amountSource+ ", amount = " + amount);
amountSource := amount;
flagsSource := flags;
end
@@ -154,40 +203,118 @@ procedure compute_damage_F2 begin
set_sfall_return(flagsTarget);
set_sfall_return(flagsSource);
set_sfall_return(amountKnockback);
set_sfall_arg(2, amountTarget);
set_sfall_arg(3, amountSource);
set_sfall_arg(4, flagsTarget);
set_sfall_arg(5, flagsSource);
set_sfall_arg(10, amountKnockback);
end
procedure item_w_damage_hook begin
variable
item_w_damage_min := get_sfall_arg,
item_w_damage_max := get_sfall_arg;
//weapon := get_sfall_arg,
//source := get_sfall_arg,
procedure calc_damage_YAAM(variable weapon, variable rounds, variable armorDT, variable armorDR, variable bonusRangedDamage, variable multiplyDamage, variable difficulty) begin
variable accumulatedDamage, ammoDiv, ammoMult, ammoDT, calcDT, _calcDT, calcDR, i, rawDamage, resistedDamage;
if (rounds <= 0) then begin
return 0;
end
ammoDiv := get_ammo_value(weapon, PROTO_AM_DMG_DIV);
ammoMult := get_ammo_value(weapon, PROTO_AM_DMG_MULT);
// Damage Multipler = Critical Multipler * Ammo Dividend
multiplyDamage *= ammoMult;
// Retrieve ammo DT (well, it's really Retrieve ammo DR, but since we're treating ammo DR as ammo DT...)
ammoDT := get_ammo_value(weapon, PROTO_AM_DR_MOD);
calcDT := armorDT - ammoDT;
_calcDT := calcDT;
if (calcDT >= 0) then begin
_calcDT := 0;
end else begin
// note that this should be a negative value
_calcDT *= 10;
calcDT := 0;
end
// DR = armor DR + DT (note that DT should be less than or equal to zero)
calcDR = armorDR + _calcDT;
if (calcDR < 0) then begin
calcDR = 0;
end else if (calcDR >= 100) then begin
return 0;
end
display_msg("YAAM: AmmoDT=" + ammoDT + ", DT=" + calcDT + "/" + armorDT + ", DR=" + calcDR + "/" + armorDR);
// Start of damage calculation loop
for (i := 0; i < rounds; i++) begin
rawDamage = random(item_damage_min, item_damage_max);
rawDamage += bonusRangedDamage;
rawDamage -= calcDT;
if (rawDamage <= 0) then
continue;
rawDamage *= multiplyDamage;
if (ammoDiv != 0) then begin
rawDamage /= ammoDiv;
end
rawDamage /= 2; // related to critical hit damage multiplier bonus
rawDamage *= difficulty; // combat difficulty setting (75 if wimpy, 100 if normal or if attacker is player, 125 if rough)
rawDamage /= 100;
resistedDamage = calcDR * rawDamage;
resistedDamage /= 100;
rawDamage -= resistedDamage;
if (rawDamage > 0) then begin
display_msg("YAAM: Bullet " + i + " dmg=" + rawDamage);
accumulatedDamage += rawDamage;
end
end
return accumulatedDamage;
end
/*
HOOK_ITEMDAMAGE
Runs when retrieving the damage rating of the player's used weapon. (Which may be their fists.)
int arg0 - The default min damage
int arg1 - The default max damage
Item arg2 - The weapon used (0 if unarmed)
Critter arg3 - The critter doing the attacking
int arg4 - The type of attack
int arg5 - non-zero if this is an attack using a melee weapon
int ret0 - Either the damage to be used, if ret1 isn't given, or the new minimum damage if it is
int ret1 - The new maximum damage
*/
procedure itemdamage_handler begin
item_damage_min := get_sfall_arg;
item_damage_max := get_sfall_arg;
item_damage_weapon := get_sfall_arg;
item_damage_attacker := get_sfall_arg;
//hit_mode := get_sfall_arg,
//isMelee := get_sfall_arg;
item_w_damage := (item_w_damage_min + (item_w_damage_max - item_w_damage_min) / 2);
display_msg("item_w_damage_ = " + item_w_damage);
set_sfall_return(item_w_damage);
display_msg("itemdamage: " + item_damage_min + "-" + item_damage_max);
end
procedure item_w_subtype(variable weapon, variable hit_mode) begin
variable attack_mode, type := WEAPON_TYPE_UNARMED;
if weapon and (hit_mode <= ATKTYPE_RWEP2) then begin
attack_mode := (get_proto_data(obj_pid(weapon), PROTO_IT_FLAGS));
attack_mode := weapon_attack_mode(obj_pid(weapon), hit_mode);
if (hit_mode == ATKTYPE_LWEP2) or (hit_mode == ATKTYPE_RWEP2) then
attack_mode := (attack_mode bwand 0xF0) / 16; // shift 4 bits to the right
else
attack_mode := (attack_mode bwand 0x0F);
if (attack_mode > ATKMODE_PRI_THROW) then
type := WEAPON_TYPE_GUNS;
else if (attack_mode == ATKMODE_PRI_THROW) then
if (attack_mode >= ATTACK_MODE_SINGLE) then
type := WEAPON_TYPE_RANGED;
else if (attack_mode == ATTACK_MODE_THROW) then
type := WEAPON_TYPE_THROWN;
else if (attack_mode > ATKMODE_PRI_KICK) then
else if (attack_mode >= ATTACK_MODE_SWING) then
type := WEAPON_TYPE_MELEE;
else if (attack_mode == ATTACK_MODE_NONE) then
type := WEAPON_TYPE_NONE;
end
return type;
+12 -3
View File
@@ -35,7 +35,7 @@
#define WEAPON_TYPE_UNARMED (1)
#define WEAPON_TYPE_MELEE (2)
#define WEAPON_TYPE_THROWN (3)
#define WEAPON_TYPE_GUNS (4)
#define WEAPON_TYPE_RANGED (4)
/* Item Flags (FlagsExt in proto) */
#define HEALING_ITEM 0x04000000 // Healing Item (item will be used by NPCs for healing in combat) [sfall 4.3.1/3.8.31]
@@ -49,7 +49,7 @@
#define WEAPON_2HAND 0x00000200 // 2Hnd (weapon is two-handed)
#define WEAPON_ENERGY 0x00000400 // Energy Weapon (forces weapon to use Energy Weapons skill) [sfall 4.2/3.8.20]
// The attack types returned by get_attack_type or as fifth argument of HOOK_ITEMDAMAGE
// The attack types returned by get_attack_type or as the fifth argument of HOOK_ITEMDAMAGE
#define ATKTYPE_LWEP1 (0)
#define ATKTYPE_LWEP2 (1)
#define ATKTYPE_RWEP1 (2)
@@ -140,7 +140,7 @@
#define MSGBOX_YESNO (0x10) // use YES/NO buttons instead of DONE
#define MSGBOX_CLEAN (0x20) // no buttons
// Some possible defines for the 4th argument to HOOK_REMOVEINVOBJ
// Some possible defines for the fourth argument of HOOK_REMOVEINVENOBJ
#define RMOBJ_ITEM_REMOVED_INVEN 4831349 // removing or destroying an item (obj_remove_from_inven_)
#define RMOBJ_ITEM_REMOVED 4548572 // (op_rm_obj_from_inven_)
#define RMOBJ_ITEM_REMOVED_MULTI 4563866 // (op_rm_mult_objs_from_inven_)
@@ -429,6 +429,15 @@
#define C_ATTACK_KNOCKBACK_VALUE5 (0xB0)
#define C_ATTACK_KNOCKBACK_VALUE6 (0xB4)
#define CRITICAL_VALUE_MULT (0) // This is divided by 2, so a value of 3 does 1.5x damage, and 8 does 4x damage.
#define CRITICAL_VALUE_EFFECTS (1) // This is a flag bit field (DAM_*) controlling what effects the critical causes.
#define CRITICAL_VALUE_STAT_CHECK (2) // This makes a check against a (SPECIAL) stat. Values of 2 (endurance), 5 (agility), and 6 (luck) are used, but other stats will probably work as well. A value of -1 indicates that no check is to be made.
#define CRITICAL_VALUE_STAT_MOD (3) // Affects the outcome of the stat check, if one is made. Positive values make it easier to pass the check, and negative ones make it harder.
#define CRITICAL_VALUE_FAIL_EFFECT (4) // Another bit field, using the same values as EFFECTS. If the stat check is failed, these are applied in addition to the earlier ones.
#define CRITICAL_VALUE_MSG (5) // The message to show when this critical occurs, taken from combat.msg.
#define CRITICAL_VALUE_FAIL_MSG (6) // This is shown instead of Message if the stat check fails.
/* Playback mode defines for the soundplay function */
#define soundraw (0x80000000) // sfall flag
#define Stereo8bit (soundstereo)
+1
View File
@@ -267,6 +267,7 @@
#define weapon_attack_mode1(pid) (get_proto_data(pid, PROTO_FLAG_EXT) bwand 0x0000000F)
#define weapon_attack_mode2(pid) ((get_proto_data(pid, PROTO_FLAG_EXT) bwand 0x000000F0) / 0x10)
#define weapon_attack_mode(pid, attackType) (weapon_attack_mode1(pid) if (attackType == ATKTYPE_LWEP1 or attackType == ATKTYPE_RWEP1) else weapon_attack_mode2(pid))
/* SFALL_FUNCX MACROS */
+3 -3
View File
@@ -193,9 +193,9 @@ fo::AttackSubType GetWeaponType(DWORD weaponFlag) {
fo::AttackSubType::MELEE,
fo::AttackSubType::MELEE,
fo::AttackSubType::THROWING,
fo::AttackSubType::GUNS,
fo::AttackSubType::GUNS,
fo::AttackSubType::GUNS
fo::AttackSubType::RANGED,
fo::AttackSubType::RANGED,
fo::AttackSubType::RANGED
};
DWORD type = weaponFlag & 0xF;
return (type < 9) ? weapon_types[type] : fo::AttackSubType::NONE;
+1 -1
View File
@@ -650,7 +650,7 @@ enum AttackSubType : long
UNARMED = 1,
MELEE = 2,
THROWING = 3,
GUNS = 4
RANGED = 4
};
enum BodyType : long
+1 -1
View File
@@ -566,7 +566,7 @@ struct CritInfo {
long failureEffect;
// The message to show when this critical occurs, taken from combat.msg.
long message;
// Shown instead of Message if the stat check is failed.
// This is shown instead of Message if the stat check fails.
long failMessage;
};
long values[7];
+1 -1
View File
@@ -142,7 +142,7 @@ static long item_w_mp_cost_sub(fo::GameObject* source, fo::GameObject* item, lon
if ((type == fo::AttackSubType::MELEE || type == fo::AttackSubType::UNARMED) && Stats::perk_level(source, fo::Perk::PERK_bonus_hth_attacks)) {
cost--;
}
if (type == fo::AttackSubType::GUNS && Stats::perk_level(source, fo::Perk::PERK_bonus_rate_of_fire)) {
if (type == fo::AttackSubType::RANGED && Stats::perk_level(source, fo::Perk::PERK_bonus_rate_of_fire)) {
cost--;
}
if (cost < 1) cost = 1;
+1 -1
View File
@@ -2283,7 +2283,7 @@ fix:
mov edx, [esi + ammoPid];
test edx, edx;
js skip;
mov eax, GUNS; // set GUNS if has ammo pid
mov eax, RANGED; // set RANGED if has ammo pid
skip:
retn;
}
+1 -1
View File
@@ -921,7 +921,7 @@ static void __declspec(naked) item_w_called_shot_hack() {
call fo::funcoffs::item_hit_with_; // get pointer to weapon
mov edx, ecx;
call fo::funcoffs::item_w_subtype_;
cmp eax, THROWING; // is weapon type GUNS or THROWING?
cmp eax, THROWING; // is weapon type RANGED or THROWING?
jge checkRange; // yes
jmp FastShotTraitFix_End; // continue processing called shot attempt
checkRange: