From ff1c87f0c514aebf2cde4b007080967c50fb5910 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Mon, 15 May 2023 12:14:46 +0800 Subject: [PATCH] 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.) --- .../ComputeDamage/gl_compute_damage.ssl | 227 ++++++++++++++---- artifacts/scripting/headers/define_extra.h | 15 +- artifacts/scripting/headers/sfall.h | 1 + sfall/FalloutEngine/EngineUtils.cpp | 6 +- sfall/FalloutEngine/Enums.h | 2 +- sfall/FalloutEngine/Structs.h | 4 +- sfall/Game/items.cpp | 2 +- sfall/Modules/BugFixes.cpp | 2 +- sfall/Modules/Perks.cpp | 2 +- 9 files changed, 199 insertions(+), 62 deletions(-) diff --git a/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl b/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl index 44d32934..ea98cf7f 100644 --- a/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl +++ b/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl @@ -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,27 +135,31 @@ procedure compute_damage_F2 begin difficulty := 125; end - // 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 %) - if (dmg_resist < 100) then begin - if (dmg_resist < 0) then dmg_resist := 0; + 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 %) + if (dmg_resist < 100) then begin + if (dmg_resist < 0) then dmg_resist := 0; - dmg_mult := damageMultiplier * get_ammo_value(weapon, PROTO_AM_DMG_MULT); // item_w_dam_mult_ (Dmg mod A) - dmg_div := get_ammo_value(weapon, PROTO_AM_DMG_DIV); // item_w_dam_div_ (Dmg mod B) + dmg_mult := damageMultiplier * get_ammo_value(weapon, PROTO_AM_DMG_MULT); // item_w_dam_mult_ (Dmg mod A) + 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) - if dmg_div then damage /= dmg_div; + for (i := 1; i <= rounds; i++) begin + 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; - if (damage > 0) then begin - damage := damage - ((damage * dmg_resist) / 100); // reduce damage by the percentage of DR_armor + DR_Ammo - if (damage > 0) then amount += damage; + damage := (((damage / 2) * difficulty) / 100) - dmg_thresh; + if (damage > 0) then begin + damage := damage - ((damage * dmg_resist) / 100); // reduce damage by the percentage of DR_armor + DR_Ammo + if (damage > 0) then amount += damage; + end end end + // F2 default end end - // F2 default 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; diff --git a/artifacts/scripting/headers/define_extra.h b/artifacts/scripting/headers/define_extra.h index da9016e4..0533dbaa 100644 --- a/artifacts/scripting/headers/define_extra.h +++ b/artifacts/scripting/headers/define_extra.h @@ -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) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index c562ca98..afeeecda 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -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 */ diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index 898cc43f..b827c850 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -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; diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index f4674824..bb8130ef 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -650,7 +650,7 @@ enum AttackSubType : long UNARMED = 1, MELEE = 2, THROWING = 3, - GUNS = 4 + RANGED = 4 }; enum BodyType : long diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index b481db34..69daa7cf 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -564,9 +564,9 @@ struct CritInfo { long statMod; // Another bit field, using the same values as EffectFlags. If the stat check is failed, these are applied in addition to the earlier ones. long failureEffect; - // The message to show when this critical occurs, taken from combat.msg . + // 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]; diff --git a/sfall/Game/items.cpp b/sfall/Game/items.cpp index ddb5951c..c79f84a0 100644 --- a/sfall/Game/items.cpp +++ b/sfall/Game/items.cpp @@ -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; diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 5e4aff75..6f14b368 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -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; } diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index fb54c68e..ec3fc9c3 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -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: