/* This script reimplements the combat damage calculation of the original game. Use this to implement your own damage formula! */ #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 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 damage_formula, item_damage_min, item_damage_max, item_damage_weapon, item_damage_attacker; procedure start begin if game_loaded then begin 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 /* 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; variable ctdTarget := get_sfall_arg, ctdSource := get_sfall_arg, amountTarget := get_sfall_arg, amountSource := get_sfall_arg, flagsTarget := get_sfall_arg, flagsSource := get_sfall_arg, weapon := get_sfall_arg, bodypart := get_sfall_arg, damageMultiplier := get_sfall_arg, rounds := get_sfall_arg, 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; knockback := 1; end else begin target := ctdSource; flags := flagsSource; knockback := 0; end if target and (obj_type(target) == OBJ_TYPE_CRITTER) then begin if weapon then begin dmg_type := weapon_dmg_type(weapon); weapon_perk := get_proto_data(obj_pid(weapon), PROTO_WP_PERK); end else begin dmg_type := DMG_normal_dam; weapon_perk := -1; end dmg_thresh := get_critter_stat(target, STAT_dmg_thresh + dmg_type); dmg_resist := get_critter_stat(target, STAT_dmg_resist + dmg_type); if (flags bwand DAM_BYPASS) and (dmg_type != DMG_emp) then begin dmg_thresh := dmg_thresh * 20 / 100; dmg_resist := dmg_resist * 20 / 100; end else begin if (weapon_perk == PERK_weapon_penetrate) or (hit_mode == ATKTYPE_PALMSTRIKE or hit_mode == ATKTYPE_PIERCINGSTRIKE or hit_mode == ATKTYPE_HOOKKICK or hit_mode == ATKTYPE_PIERCINGKICK) then dmg_thresh := dmg_thresh * 20 / 100; if ctdSource == dude_obj and has_trait(TRAIT_TRAIT, ctdSource, TRAIT_finesse) then dmg_resist += 30; end weapon_subtype := item_w_subtype(weapon, hit_mode); // item_w_subtype_ 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; difficulty := 100; if (has_trait(TRAIT_OBJECT, ctdSource, OBJECT_TEAM_NUM) != has_trait(TRAIT_OBJECT, dude_obj, OBJECT_TEAM_NUM)) then begin if (combat_difficulty == 0) then difficulty := 75; else if (combat_difficulty == 2) then 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 %) 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) 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; end 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) and (critter_kill_type(ctdTarget) != KILL_TYPE_alien_kills) then amount += 5; if has_trait(TRAIT_PERK, ctdSource, PERK_pyromaniac_perk) and (dmg_type == DMG_fire) then amount += 5; end if knockback and ((get_flags(target) bwand FLAG_MULTIHEX) == 0) and (dmg_type == DMG_explosion or weapon == 0 or weapon_subtype == WEAPON_TYPE_MELEE) and ((get_proto_data(obj_pid(target), PROTO_CR_FLAGS) bwand CFLG_NOKNOCKDOWN) == 0) then begin // critter_flag_check_ damage := 0; if (target == dude_obj) and has_trait(TRAIT_PERK, target, PERK_stonewall_perk) then begin damage := 1; if (random(0, 100) < 50) then knockback := 0; end if knockback then begin if (weapon_perk == PERK_weapon_knockback) then amountKnockback := amount / 5; else amountKnockback := amount / 10; if damage then amountKnockback /= 2; end end end if (flagsSource bwand DAM_HIT) then begin display_msg("COMBATDAMAGE amountTarget = " + amountTarget+ ", amount = " + amount); amountTarget := amount; flagsTarget := flags; end else begin display_msg("COMBATDAMAGE amountSource = " + amountSource+ ", amount = " + amount); amountSource := amount; flagsSource := flags; end set_sfall_return(amountTarget); set_sfall_return(amountSource); 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 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; 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 := weapon_attack_mode(obj_pid(weapon), hit_mode); 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 >= ATTACK_MODE_SWING) then type := WEAPON_TYPE_MELEE; else if (attack_mode == ATTACK_MODE_NONE) then type := WEAPON_TYPE_NONE; end return type; end procedure get_ammo_value(variable weapon, variable param) begin variable pid := -1, value := 0; // default DR value if weapon then begin pid := get_weapon_ammo_pid(weapon); if (pid > -1) then value := get_proto_data(pid, param); end if (pid == -1 and param != PROTO_AM_DR_MOD) then value := 1; // default value for Mult/Div return value; end