844 lines
29 KiB
Plaintext
844 lines
29 KiB
Plaintext
|
get_config = ballistics_mcm.get_config
|
||
|
print_dbg = ballistics_mcm.print_dbg
|
||
|
npc_bone_mult = ballistics_utils.npc_bone_mult
|
||
|
npc_bone_protection = ballistics_utils.npc_bone_protection
|
||
|
npc_bone_data = ballistics_utils.npc_bone_data
|
||
|
play_particle = ballistics_utils.play_particle
|
||
|
play_particle_firepoint = ballistics_utils.play_particle_firepoint
|
||
|
mutant_hp = ballistics_utils.mutant_hp
|
||
|
mutant_prot_values = ballistics_utils.mutant_prot_values
|
||
|
modify_bone = ballistics_utils.modify_bone
|
||
|
modify_velocity = ballistics_utils.modify_velocity
|
||
|
modify_distance = ballistics_utils.modify_distance
|
||
|
get_distance = ballistics_utils.get_distance
|
||
|
get_cur_vel = ballistics_utils.get_cur_vel
|
||
|
read_npc_bone_sec = ballistics_utils.read_npc_bone_sec
|
||
|
add_effect = arti_timed_events.add_effect
|
||
|
play_sound_on_location = ballistics_utils.play_sound_on_location
|
||
|
math_floor = math.floor
|
||
|
math_abs = math.abs
|
||
|
math_random = math.random
|
||
|
|
||
|
Bone_IDs = ballistics_utils.Bone_IDs
|
||
|
head_bones = ballistics_utils.head_bones
|
||
|
ini_ammo = ballistics_utils.ini_ammo
|
||
|
ini_damage = ballistics_utils.ini_damage
|
||
|
|
||
|
local anims = {
|
||
|
"norm_2_critical_hit_torso_0", -- good
|
||
|
"norm_1_critical_hit_head_0", -- good
|
||
|
}
|
||
|
|
||
|
|
||
|
-- pulls custom ap and hit power values from code
|
||
|
function get_ammo_data(ammo_sec)
|
||
|
if ini_ammo:section_exist(ammo_sec) then
|
||
|
return {
|
||
|
sec = ammo_sec,
|
||
|
k_hit = ini_ammo:r_float_ex(ammo_sec, "k_hit") or SYS_GetParam(2, ammo_sec, "k_hit") or 1,
|
||
|
k_ap = ini_ammo:r_float_ex(ammo_sec, "k_ap") or SYS_GetParam(2, ammo_sec, "k_ap") or 0,
|
||
|
k_pen = ini_ammo:r_float_ex(ammo_sec, "k_pen") or 0,
|
||
|
k_imp = ini_ammo:r_float_ex(ammo_sec, "k_imp") or 0
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function is_actor(npc)
|
||
|
return npc and npc:id() == AC_ID
|
||
|
end
|
||
|
|
||
|
-- deal residual impact damage only
|
||
|
-- 2-40%
|
||
|
function impact(diff, ap_frac, ammo_data, bone_data)
|
||
|
-- print_dbg("Impact")
|
||
|
if head_bones[bone_data.id] then
|
||
|
bone_data.hit_fraction = clamp(bone_data.hit_fraction * 1.1, 0, 1)
|
||
|
end
|
||
|
local frac_adj = 1 - bone_data.hit_fraction
|
||
|
return 0.2 + clamp(ammo_data.k_imp - frac_adj, -0.18, 0.2)
|
||
|
end
|
||
|
|
||
|
-- reduction based on AP frac and diff
|
||
|
-- base 40-80%
|
||
|
-- diff is 0-2
|
||
|
function reduce(diff, ap_frac, ammo_data, bone_data)
|
||
|
local modifier = 0.8 - (0.2 * (diff - ap_frac))
|
||
|
return modifier
|
||
|
end
|
||
|
|
||
|
-- deal full damage (does nothing lol)
|
||
|
function full(diff, ap_frac, ammo_data, bone_data)
|
||
|
return 1
|
||
|
end
|
||
|
|
||
|
-- overpenetrate and lose damage (based on diff and pen)
|
||
|
-- default is reducing down to pen
|
||
|
-- only matters if diff > 3, or more than 0.2
|
||
|
function penetrate(diff, ap_frac, ammo_data, bone_data)
|
||
|
adj = clamp((diff - 4)/4, 0.05, ammo_data.k_pen)
|
||
|
-- print_dbg("Overkill, reduce by %s", adj)
|
||
|
return 1 - adj
|
||
|
end
|
||
|
|
||
|
local damage_subcalcs = {
|
||
|
[-7] = impact,
|
||
|
[-6] = impact,
|
||
|
[-5] = impact,
|
||
|
[-4] = impact,
|
||
|
[-3] = impact,
|
||
|
[-2] = reduce,
|
||
|
[-1] = reduce,
|
||
|
[0] = reduce,
|
||
|
[1] = full,
|
||
|
[2] = full,
|
||
|
[3] = full,
|
||
|
[4] = full,
|
||
|
[5] = penetrate,
|
||
|
[6] = penetrate,
|
||
|
[7] = penetrate,
|
||
|
}
|
||
|
|
||
|
-- input:
|
||
|
function tbl_reduce(x)
|
||
|
local acc = 1
|
||
|
for k,v in pairs(x) do
|
||
|
print_dbg("Applying key %s, val %s", k ,v)
|
||
|
acc = acc * v
|
||
|
end
|
||
|
return acc
|
||
|
end
|
||
|
|
||
|
function get_tier(val)
|
||
|
return clamp(math_floor(val * 20), 0, 7)
|
||
|
end
|
||
|
|
||
|
function get_frac(val)
|
||
|
frac = math.fmod(val * 20, 1)
|
||
|
if frac > 0.95 then frac = 1 end
|
||
|
return frac
|
||
|
end
|
||
|
|
||
|
local function change_damage_type(s_hit, hit_type, bone_data, is_actor)
|
||
|
s_hit.type = hit_type
|
||
|
-- undo bone mult as elemental hits shouldn't get any bonus/malus from body part
|
||
|
-- actor always receives full hits to allow for armor damage calc
|
||
|
if not is_actor then
|
||
|
s_hit.power = s_hit.power / bone_data.mult
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--[[
|
||
|
precalculate all the crap we need
|
||
|
returns:
|
||
|
{
|
||
|
ammo_data: data on ammo (sec, overridden values for hit and ap)
|
||
|
bone_data: data on bones (bone id, name, bone armor, if it's been modified, etc)
|
||
|
}
|
||
|
]]
|
||
|
function prework(npc, s_hit, ctx)
|
||
|
local precalc = {}
|
||
|
|
||
|
precalc.base_power = ctx.hit_data.wpn:cast_Weapon():GetHitPower()
|
||
|
precalc.ammo_data = get_ammo_data(ctx.hit_data.ammo)
|
||
|
precalc.bone_data = ctx.is_npc and npc_bone_data(npc, ctx.bone_id) or mutant_prot_values(npc, ctx.bone_id)
|
||
|
|
||
|
return precalc
|
||
|
end
|
||
|
|
||
|
function apply_std_calcs(tbl, npc, s_hit, precalc)
|
||
|
tbl.base_power = precalc.base_power
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
tbl.k_hit = ammo_data.k_hit
|
||
|
tbl.velocity = is_actor(npc) and 1 or modify_velocity()
|
||
|
tbl.distance = modify_distance(s_hit.draftsman, npc, ammo_data.sec)
|
||
|
tbl.bone_mult = is_actor(npc) and 1 or bone_data.mult
|
||
|
tbl.hit_scale = SYS_GetParam(2, ammo_data.sec, "hit_scale") or 1
|
||
|
end
|
||
|
|
||
|
-- determine if a hit fully penetrates either mutant or npc
|
||
|
local function full_pen_modifier(precalc, is_npc)
|
||
|
if not is_npc then
|
||
|
return 1 - clamp(2 * (precalc.bone_data.armor - precalc.ammo_data.k_ap), 0, 1)
|
||
|
else
|
||
|
return clamp(precalc.ammo_data.k_ap - precalc.bone_data.armor, 0, 1)
|
||
|
end
|
||
|
return 0
|
||
|
end
|
||
|
|
||
|
function process_hp(precalc, is_npc)
|
||
|
local hp_mult = ini_ammo:r_float_ex(precalc.ammo_data.sec, "k_hp")
|
||
|
if not hp_mult then return 1 end
|
||
|
local modifier = full_pen_modifier(precalc, is_npc)
|
||
|
-- mutants get partial hp expansion
|
||
|
if not is_npc then
|
||
|
local hp_frac = hp_mult - 1
|
||
|
if modifier > 0 then hp_mult = 1 + (hp_frac * modifier) end
|
||
|
print_dbg("Mutant with armor %s, bullet with AP %s, HP modifier %s, final HP %s", precalc.bone_data.armor, precalc.ammo_data.k_ap, modifier, hp_mult)
|
||
|
return hp_mult
|
||
|
else
|
||
|
if modifier > 0 then return hp_mult end
|
||
|
end
|
||
|
return 1
|
||
|
end
|
||
|
|
||
|
function npc_standard(s_hit, precalc)
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
-- print_dbg("[NPC] Ammo %s: Hit %s, AP %s. Bone: ID %s, Armor %s", ammo_data.sec, ammo_data.k_hit, ammo_data.k_ap, bone_data.id, bone_data.armor)
|
||
|
|
||
|
local armor_tier = get_tier(bone_data.armor)
|
||
|
local ap_tier = get_tier(ammo_data.k_ap)
|
||
|
local ap_frac = get_frac(ammo_data.k_ap)
|
||
|
|
||
|
local diff = ap_tier - armor_tier
|
||
|
local mult = damage_subcalcs[diff](math_abs(diff), ap_frac, ammo_data, bone_data)
|
||
|
print_dbg("Computed AP multipler is %s", mult)
|
||
|
-- modify bone multiplier if above 1, to limit effectiveness of headshots
|
||
|
-- if diff < 1 and mult < 1 and bone_data.mult > 1 then
|
||
|
-- bone_adj = bone_data.mult - 1
|
||
|
-- bone_adj = bone_adj * mult
|
||
|
-- local mult_adj = clamp((1 + bone_adj) / bone_data.mult, 0.6, 1)
|
||
|
-- print_dbg("AP insufficient, reducing multiplier by %s", mult_adj)
|
||
|
-- mult = mult * mult_adj
|
||
|
-- end
|
||
|
|
||
|
-- tweak multiplier based on heavy/exo armor
|
||
|
if armor_tier == 7 then
|
||
|
local tank_coef = 10 * clamp(bone_data.armor - 0.4, 0.1, 0.3)
|
||
|
-- print_dbg("Exo tank coef %s", tank_coef)
|
||
|
mult = mult * (1/tank_coef)
|
||
|
end
|
||
|
return mult
|
||
|
end
|
||
|
|
||
|
function mutant_standard(s_hit, precalc)
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
-- print_dbg("[MUT] Ammo %s: Hit %s, AP %s. Bone: ID %s, Armor %s", ammo_data.sec, ammo_data.k_hit, ammo_data.k_ap, bone_data.id, bone_data.armor)
|
||
|
|
||
|
-- chance to zero damage, capped at 50% and play particle
|
||
|
if bone_data.deflect then
|
||
|
local air_res = SYS_GetParam(2, ammo_data.sec, "k_air_resistance") or 0.05
|
||
|
air_res = clamp(air_res, 0, 0.5)
|
||
|
air_res = air_res * (1 - get_cur_vel())
|
||
|
if math_random() < air_res then
|
||
|
local snd = xr_sound.get_safe_sound_object("anomaly\\anomaly_gravy_hit1")
|
||
|
if snd then
|
||
|
snd.volume = 1
|
||
|
snd:play(db.actor, 0, sound_object.s2d)
|
||
|
end
|
||
|
return 0
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local ap_val = ammo_data.k_ap * 2
|
||
|
if ap_val > bone_data.armor then
|
||
|
return clamp((ap_val - bone_data.armor)/ap_val, bone_data.hit_fraction, 1)
|
||
|
else
|
||
|
return bone_data.hit_fraction
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local npc_stun = {}
|
||
|
function stun_npc(npc)
|
||
|
if is_actor(npc) then
|
||
|
level.add_cam_effector("camera_effects\\fusker.anm",959,false,"")
|
||
|
else
|
||
|
play_anim(npc)
|
||
|
npc_stun[npc:id()] = time_global() + 750
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
function play_anim(npc)
|
||
|
if npc:movement_type() == move.walk or npc:movement_type() == move.run then
|
||
|
npc:set_movement_type(move.stand)
|
||
|
end
|
||
|
npc:play_cycle(anims[math_random(#anims)], true)
|
||
|
state_mgr.set_state(npc, "idle")
|
||
|
end
|
||
|
|
||
|
function on_enemy_eval(obj, enemy, flags)
|
||
|
if npc_stun[obj:id()] then
|
||
|
if time_global() > npc_stun[obj:id()] then
|
||
|
npc_stun[obj:id()] = false
|
||
|
else
|
||
|
flags.override = true
|
||
|
flags.ret_value = false
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function npc_on_death_callback(victim, who)
|
||
|
npc_stun[victim:id()] = nil
|
||
|
if marked == victim:id() then
|
||
|
RemoveTimeEvent("clear_mark", "clear_mark")
|
||
|
marked = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- refer to ballistics_mcm.txt for logic details
|
||
|
-- handles basic bullets, flinching and hollowpoint expansion
|
||
|
function ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
local calcs = {}
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
|
||
|
-- add standard calcs for hit, bone mult, etc
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
|
||
|
if not ctx.is_npc then
|
||
|
calcs.ap_calc = mutant_standard(s_hit, precalc)
|
||
|
else
|
||
|
-- tier based formula with overpen, and flinch
|
||
|
calcs.ap_calc = npc_standard(s_hit, precalc)
|
||
|
-- process_flinch(npc, precalc)
|
||
|
end
|
||
|
if not is_actor(npc) then
|
||
|
calcs.hollowpoint = process_hp(precalc, ctx.is_npc)
|
||
|
end
|
||
|
|
||
|
local final_hit = tbl_reduce(calcs)
|
||
|
print_dbg("Bone: %s. BA: %s. BM: %s. AP: %s. Vis: %s. Original: %s. Base hit: %s. Final hit: %s", bone_data.name, bone_data.armor, bone_data.mult, ammo_data.k_ap, npc:get_visual_name(), s_hit.power, calcs.base_power, final_hit)
|
||
|
s_hit.power = final_hit
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
function apply_flinch(npc, chance, health_dmg)
|
||
|
if math_random() < chance then
|
||
|
stun_npc(npc)
|
||
|
npc.health = clamp(npc.health - health_dmg, 0.05, 1)
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- flinch applies up to 3 levels above the tier
|
||
|
function disruptor(npc, s_hit, ctx)
|
||
|
if ctx.is_npc then
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
local ammo_data = precalc.ammo_data
|
||
|
local bone_data = precalc.bone_data
|
||
|
local flinch_adj = (bone_data.armor - ammo_data.k_ap) * 5
|
||
|
local flinch_chance = clamp(0.95 - flinch_adj, 0.01, 0.8)
|
||
|
local health_damage = (5 + math_random(10)) / 100
|
||
|
if head_bones[ctx.bone_id] then
|
||
|
flinch_chance = clamp(flinch_chance + 0.1, 0, 1)
|
||
|
health_damage = health_damage * 2
|
||
|
end
|
||
|
|
||
|
print_dbg("Stagger adj %s, chance %s", flinch_adj, flinch_chance)
|
||
|
|
||
|
local chest_armor = npc_bone_protection(npc, 11).armor
|
||
|
local health_damage = (5 + math_random(10)) / 100
|
||
|
local flinch = apply_flinch(npc, flinch_chance, health_damage)
|
||
|
end
|
||
|
return ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
end
|
||
|
|
||
|
function head_disruptor(npc, s_hit, ctx)
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
local ammo_data = precalc.ammo_data
|
||
|
local bone_data = precalc.bone_data
|
||
|
if ctx.is_npc and head_bones[ctx.bone_id] then
|
||
|
local chest_armor = npc_bone_protection(npc, 11).armor
|
||
|
apply_flinch(npc, clamp(0.9 + ammo_data.k_ap - chest_armor, 0, 1), 0.75 - chest_armor)
|
||
|
end
|
||
|
return ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
end
|
||
|
local boar_clsid = {
|
||
|
[clsid.boar] = true,
|
||
|
[clsid.boar_s] = true,
|
||
|
}
|
||
|
|
||
|
-- buckshot ignores up to 20 armor on limb hit
|
||
|
function buckshot_damage(npc, s_hit, ctx)
|
||
|
if boar_clsid[get_clsid(npc)] or (ctx.is_npc and ballistics_utils.limbs[ctx.bone_id] and not is_actor(npc)) then
|
||
|
local calcs = {}
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
bone_data = precalc.bone_data
|
||
|
ammo_data = precalc.ammo_data
|
||
|
-- reduce armor
|
||
|
|
||
|
if boar_clsid[get_clsid(npc)] then
|
||
|
bone_data.mult = 1.3
|
||
|
else
|
||
|
bone_data.armor = clamp(bone_data.armor - 0.1, 0, 1)
|
||
|
ammo_data.k_imp = ammo_data.k_imp + 0.15
|
||
|
end
|
||
|
|
||
|
calcs.base_power = precalc.base_power
|
||
|
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
calcs.ap_calc = ctx.is_npc and npc_standard(s_hit, precalc) or mutant_standard(s_hit, precalc)
|
||
|
|
||
|
local final_hit = tbl_reduce(calcs)
|
||
|
print_dbg("Bone: %s. BA: %s. BM: %s. AP: %s. Vis: %s. Original: %s. Base hit: %s. Final hit: %s", bone_data.name, bone_data.armor, bone_data.mult, ammo_data.k_ap, npc:get_visual_name(), s_hit.power, calcs.base_power, final_hit)
|
||
|
s_hit.power = final_hit
|
||
|
else
|
||
|
s_hit = ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
end
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
function fragment(npc, s_hit, ctx)
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
s_hit = ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
|
||
|
if not is_actor(npc) and full_pen_modifier(precalc, ctx.is_npc) > 0 and math_random(3) == 3 then
|
||
|
s_hit.power = s_hit.power * ini_ammo:r_float_ex(ctx.hit_data.ammo, "frag") or 1.5
|
||
|
end
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
function fragment_sound(npc, s_hit, ctx)
|
||
|
s_hit = fragment(npc, s_hit, ctx)
|
||
|
play_sound_on_location("elite", npc)
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
function headsplode(npc)
|
||
|
CreateTimeEvent("headsplode", npc:id(), 0, function()
|
||
|
if not npc:alive() then
|
||
|
npc:set_bone_visible("bip01_head", false, true)
|
||
|
play_particle(npc, 15, "anomaly2\\effects\\body_tear_blood_05")
|
||
|
play_particle(npc, 15, "anomaly2\\effects\\body_tear_blood_01")
|
||
|
play_sound_on_location("headshot", npc)
|
||
|
end
|
||
|
return true
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
-- slugs are effective at merely crushing people to death (based on their hit fraction)
|
||
|
function slug(npc, s_hit, ctx)
|
||
|
if not ctx.is_npc then return ballistic_handlers.default(npc, s_hit, ctx) end
|
||
|
local calcs = {}
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
|
||
|
-- ignore some armor on headshot
|
||
|
if head_bones[ctx.bone_id] then
|
||
|
local chest_armor = npc_bone_protection(npc, 11)
|
||
|
if chest_armor.armor <= ammo_data.k_ap * 3 then
|
||
|
bone_data.armor = clamp(bone_data.armor - ammo_data.k_ap, 0, 1)
|
||
|
headsplode(npc)
|
||
|
end
|
||
|
elseif ballistics_utils.center_mass[ctx.bone_id] and math_random() < 0.9 then
|
||
|
stun_npc(npc)
|
||
|
npc.health = clamp(npc.health - 0.1, 0.1, 1)
|
||
|
end
|
||
|
|
||
|
calcs.ap_calc = npc_standard(s_hit, precalc)
|
||
|
|
||
|
local final_hit = tbl_reduce(calcs)
|
||
|
print_dbg("Bone: %s. BA: %s. BM: %s. AP: %s. Vis: %s. Original: %s. Base hit: %s. Final hit: %s", bone_data.name, bone_data.armor, bone_data.mult, ammo_data.k_ap, npc:get_visual_name(), s_hit.power, calcs.base_power, final_hit)
|
||
|
s_hit.power = final_hit
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
-- jfp headshots
|
||
|
function headshot(npc, s_hit, ctx)
|
||
|
if not ctx.is_npc or is_actor(npc) then return ballistic_handlers.default(npc, s_hit, ctx) end
|
||
|
local calcs = {}
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
-- ignore armor on headshot, set armor to one below ap to guarantee full damage
|
||
|
if head_bones[ctx.bone_id] and bone_data.armor < 0.35 and not dialogs_axr_companion.is_actor_companion(db.actor, npc) then
|
||
|
print_dbg("Ignoring armor on headshot")
|
||
|
calcs.ap_calc = 1
|
||
|
headsplode(npc)
|
||
|
else
|
||
|
calcs.ap_calc = npc_standard(s_hit, precalc)
|
||
|
end
|
||
|
|
||
|
calcs.hollowpoint = process_hp(precalc, ctx.is_npc)
|
||
|
|
||
|
local final_hit = tbl_reduce(calcs)
|
||
|
|
||
|
print_dbg("Bone: %s. BA: %s. BM: %s. AP: %s. Vis: %s. Original: %s. Base hit: %s. Final hit: %s", bone_data.name, bone_data.armor, bone_data.mult, ammo_data.k_ap, npc:get_visual_name(), s_hit.power, calcs.base_power, final_hit)
|
||
|
s_hit.power = final_hit
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
-- for 5.56 varmageddon
|
||
|
function varmageddon(npc, s_hit, ctx)
|
||
|
s_hit = ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
if not ctx.is_npc then
|
||
|
-- varma mult always applies
|
||
|
local v_mult = ini_ammo:r_float_ex(ctx.hit_data.ammo, "v_mult") or 1.5
|
||
|
s_hit.power = s_hit.power * v_mult
|
||
|
end
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
function ambush(npc, s_hit, ctx)
|
||
|
|
||
|
local calcs = {}
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
-- hitting distracted npc with suppressed weapon confers 20% damage bonus and ups pen one class (SP6 eq)
|
||
|
if ctx.hit_data.wpn:weapon_is_silencer() and(not npc:best_enemy() or npc:best_enemy():id() ~= 0) then
|
||
|
calcs.ambush = 1.2
|
||
|
ammo_data.k_ap = ammo_data.k_ap + 0.05
|
||
|
end
|
||
|
if not ctx.is_npc then
|
||
|
calcs.ap_calc = mutant_standard(s_hit, precalc)
|
||
|
else
|
||
|
-- tier based formula with overpen, and flinch
|
||
|
calcs.ap_calc = npc_standard(s_hit, precalc)
|
||
|
end
|
||
|
|
||
|
local final_hit = tbl_reduce(calcs)
|
||
|
print_dbg("Bone: %s. BA: %s. BM: %s. AP: %s. Vis: %s. Original: %s. Base hit: %s. Final hit: %s", bone_data.name, bone_data.armor, bone_data.mult, ammo_data.k_ap, npc:get_visual_name(), s_hit.power, calcs.base_power, final_hit)
|
||
|
s_hit.power = final_hit
|
||
|
return s_hit
|
||
|
end
|
||
|
-- 0-200 no damage falloff
|
||
|
function match(npc, s_hit, ctx)
|
||
|
local distance = get_distance(db.actor, npc)
|
||
|
|
||
|
local calcs = {}
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
calcs.distance = nil
|
||
|
-- distance based ap increase
|
||
|
local distance_mult = clamp(1 + (distance / 200), 1, 2)
|
||
|
ammo_data.k_ap = clamp(ammo_data.k_ap * distance_mult, 0, 0.399)
|
||
|
if not ctx.is_npc then
|
||
|
calcs.ap_calc = mutant_standard(s_hit, precalc)
|
||
|
else
|
||
|
-- tier based formula with overpen, and flinch
|
||
|
calcs.ap_calc = npc_standard(s_hit, precalc)
|
||
|
end
|
||
|
|
||
|
local final_hit = tbl_reduce(calcs)
|
||
|
print_dbg("Bone: %s. BA: %s. BM: %s. AP: %s. Vis: %s. Original: %s. Base hit: %s. Final hit: %s", bone_data.name, bone_data.armor, bone_data.mult, ammo_data.k_ap, npc:get_visual_name(), s_hit.power, calcs.base_power, final_hit)
|
||
|
s_hit.power = final_hit
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
local marked
|
||
|
|
||
|
function tracer(npc, s_hit, ctx)
|
||
|
if marked == npc:id() then return ballistic_handlers.default(npc, s_hit, ctx) end
|
||
|
marked = npc:id()
|
||
|
play_sound_on_location("elite", db.actor)
|
||
|
-- aggro companions
|
||
|
for k,v in pairs(axr_companions.list_actor_squad_by_id()) do
|
||
|
companion = get_object_by_id(v)
|
||
|
if companion then
|
||
|
local h = hit()
|
||
|
h.type = hit.fire_wound
|
||
|
h.power = 0.0
|
||
|
h.impulse = 0.0
|
||
|
h.direction = VEC_Y
|
||
|
h.draftsman = npc
|
||
|
companion:hit(h)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
RemoveTimeEvent("clear_mark", "clear_mark")
|
||
|
CreateTimeEvent("clear_mark", "clear_mark", 5, function()
|
||
|
marked = nil
|
||
|
return true
|
||
|
end)
|
||
|
return ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
end
|
||
|
|
||
|
function on_before_hit(npc, s_hit, bone_id, flags)
|
||
|
if npc:id() == marked then
|
||
|
local companions = axr_companions.companion_squads
|
||
|
if s_hit.draftsman:id() == AC_ID then
|
||
|
s_hit.power = clamp(s_hit.power * 1.2, 0, 0.99)
|
||
|
elseif companions[s_hit.draftsman:id()] then
|
||
|
s_hit.power = clamp(s_hit.power * 1.3, 0, 0.99)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function rip(npc, s_hit, ctx)
|
||
|
s_hit = ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
if is_actor(npc) then return s_hit end
|
||
|
|
||
|
if s_hit.power > 0.5 and db.actor.health < 1 then
|
||
|
local s = xr_sound.get_safe_sound_object("Soul_"..math.random(5))
|
||
|
s:play(db.actor, 0, sound_object.s2d)
|
||
|
end
|
||
|
db.actor.health = clamp(db.actor.health + 0.05 + (0.2 * s_hit.power), 0, 1)
|
||
|
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
function ignite_npc(npc, bone_id, damage, stack, duration, hit_frac)
|
||
|
play_particle(npc, bone_id, "artefact\\af_thermal_idle")
|
||
|
|
||
|
stack = stack or 1
|
||
|
duration = duration or 5
|
||
|
dmg_low = damage[1] or 5
|
||
|
dmg_high = damage[2] or 15
|
||
|
dmg_low = clamp(dmg_low, 0, dmg_high)
|
||
|
if hit_frac then
|
||
|
dmg_low = dmg_low * hit_frac
|
||
|
dmg_high = dmg_high * hit_frac
|
||
|
end
|
||
|
print_dbg("Ignited %s", npc:id())
|
||
|
play_sound_on_location("incendiary", npc)
|
||
|
play_particle(npc, 11, "damage_fx\\effects\\body_burn_01", duration)
|
||
|
add_effect("ignite"..npc:id()..math.random(stack), duration,
|
||
|
function()
|
||
|
if not npc:alive() then return end
|
||
|
local h = hit()
|
||
|
h.type = hit.burn
|
||
|
h.draftsman = db.actor
|
||
|
h.power = math_random(dmg_low, dmg_high) / 100
|
||
|
h.bone = "bip01_spine"
|
||
|
npc:hit(h)
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
-- ignite + reduce armor
|
||
|
function dragonsbreath(npc, s_hit, ctx)
|
||
|
s_hit = ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
-- shred 0.06 off each hit
|
||
|
local bone_data = ctx.is_npc and npc_bone_data(npc, ctx.bone_id) or mutant_prot_values(npc, ctx.bone_id)
|
||
|
local hit_frac = 0
|
||
|
if ctx.is_npc then
|
||
|
modify_bone(npc, ctx.bone_id, clamp(bone_data.armor - 0.03, 0, 1))
|
||
|
else
|
||
|
hit_frac = bone_data.hit_fraction
|
||
|
end
|
||
|
if not is_actor(npc) then
|
||
|
ignite_npc(npc, ctx.bone_id, {5, 12}, 8, math_random(8, 16), hit_frac)
|
||
|
end
|
||
|
s_hit.type = hit.burn
|
||
|
change_damage_type(s_hit, hit.burn, bone_data, is_actor(npc))
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
-- ignite
|
||
|
function fire_damage(npc, s_hit, ctx)
|
||
|
s_hit = ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
local ignite_chance = ini_ammo:r_float_ex(ctx.hit_data.ammo, "ignite") or 0
|
||
|
if math_random() < ignite_chance then
|
||
|
if not is_actor(npc) then
|
||
|
ignite_npc(npc, ctx.bone_id, {5, 15}, 15, math.random(5, 20))
|
||
|
end
|
||
|
end
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
-- for corrosive ammo
|
||
|
function acid_damage(npc, s_hit, ctx)
|
||
|
s_hit = ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
change_damage_type(s_hit, hit.chemical_burn, bone_data, is_actor(npc))
|
||
|
if not ctx.is_npc or is_actor(npc) then return s_hit end
|
||
|
-- shred ONCE per bone
|
||
|
local bone_data = npc_bone_data(npc, ctx.bone_id)
|
||
|
play_particle(npc, ctx.bone_id, "artefact\\effects\\af_acidic_idle_color")
|
||
|
modify_bone(npc, ctx.bone_id, clamp(bone_data.armor * 0.8, 0, 1))
|
||
|
play_sound_on_location("acid", npc)
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
-- for 5.7 chaos sx
|
||
|
function chaos_damage(npc, s_hit, ctx)
|
||
|
local calcs = {}
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
ammo_data = precalc.ammo_data
|
||
|
bone_data = precalc.bone_data
|
||
|
|
||
|
-- these work for mutant or man
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
|
||
|
random = math_random()
|
||
|
|
||
|
-- ap +/- 2 tiers randomly
|
||
|
ammo_data.k_ap = clamp(ammo_data.k_ap + random/9, 0, 1)
|
||
|
if not ctx.is_npc then
|
||
|
calcs.ap_calc = mutant_standard(s_hit, precalc)
|
||
|
else
|
||
|
calcs.ap_calc = npc_standard(s_hit, precalc)
|
||
|
end
|
||
|
-- damage inverse of ap
|
||
|
calcs.chaos = 0.6 + (1 - random)
|
||
|
|
||
|
local extra_effect = math_random(15)
|
||
|
if not is_actor(npc) then
|
||
|
if extra_effect == 3 then
|
||
|
ignite_npc(npc, ctx.bone_id, {1, 20}, 3, 10, ctx.is_npc and 1 or bone_data.hit_fraction)
|
||
|
elseif extra_effect == 4 and ctx.is_npc then
|
||
|
stun_npc(npc)
|
||
|
elseif extra_effect == 5 then
|
||
|
db.actor.health = clamp(db.actor.health + (0.1 * s_hit.power), 0, 1)
|
||
|
elseif extra_effect == 6 then
|
||
|
-- random full AP
|
||
|
calcs.ap_calc = 1
|
||
|
elseif extra_effect == 7 then
|
||
|
play_particle(npc, ctx.bone_id, "artefact\\effects\\af_acidic_idle_color")
|
||
|
modify_bone(npc, ctx.bone_id, clamp(bone_data.armor * 0.8, 0, 1))
|
||
|
play_sound_on_location("acid", npc)
|
||
|
elseif extra_effect == 8 then
|
||
|
local explode_obj = alife_create_item("bullet_blow", npc)
|
||
|
CreateTimeEvent(explode_obj.id, explode_obj.id, 0,
|
||
|
function(id)
|
||
|
local explode_obj = get_object_by_id(id)
|
||
|
if explode_obj then
|
||
|
explode_obj:explode(0)
|
||
|
return true
|
||
|
end
|
||
|
return false
|
||
|
end, explode_obj.id)
|
||
|
end
|
||
|
end
|
||
|
-- add random effect
|
||
|
local final_hit = tbl_reduce(calcs)
|
||
|
print_dbg("Bone: %s. BA: %s. BM: %s. AP: %s. Vis: %s. Original: %s. Base hit: %s. Final hit: %s", bone_data.name, bone_data.armor, bone_data.mult, ammo_data.k_ap, npc:get_visual_name(), s_hit.power, calcs.base_power, final_hit)
|
||
|
s_hit.power = final_hit
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
local shock_effect = particles_object("artefact\\effects\\af_electra_show_flash_00")
|
||
|
-- for shock anti armor
|
||
|
function shock_damage(npc, s_hit, ctx)
|
||
|
local precalc = prework(npc, s_hit, ctx)
|
||
|
change_damage_type(s_hit, hit.shock, precalc.bone_data, is_actor(npc))
|
||
|
if not ctx.is_npc then return ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
end
|
||
|
local calcs = {}
|
||
|
|
||
|
local armor_tier = get_tier(precalc.bone_data.armor)
|
||
|
|
||
|
apply_std_calcs(calcs, npc, s_hit, precalc)
|
||
|
calcs.lightning_bonus = 1 + (armor_tier * 0.6)
|
||
|
snd = "electric"
|
||
|
if armor_tier > 3 then
|
||
|
snd = "electric_big"
|
||
|
play_particle(npc, ctx.bone_id, "amik\\anomaly\\electra\\electra_dust_distort")
|
||
|
local h = hit()
|
||
|
h.type = hit.shock
|
||
|
h.draftsman = db.actor
|
||
|
h.power = armor_tier * 0.3
|
||
|
h.bone = "bip01_spine"
|
||
|
radius = armor_tier / 2
|
||
|
|
||
|
level.iterate_nearest(npc:position(), radius, function(obj)
|
||
|
if obj and obj:alive() and obj:id() ~= AC_ID then
|
||
|
obj:hit(h)
|
||
|
shock_effect:play_at_pos(obj:position())
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
s_hit.power = tbl_reduce(calcs)
|
||
|
play_sound_on_location(snd, npc)
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
function soulripper(npc, s_hit, ctx)
|
||
|
s_hit = head_bones[ctx.bone_id] and rip(npc, s_hit, ctx) or ballistic_handlers.default(npc, s_hit, ctx)
|
||
|
return s_hit
|
||
|
end
|
||
|
|
||
|
-- get position of where actor is aiming
|
||
|
function get_point_of_aim()
|
||
|
gvid = db.actor:game_vertex_id()
|
||
|
local r = level.get_target_dist and level.get_target_dist() or 3
|
||
|
if r > 1000 then return end
|
||
|
pos = vector():set(db.actor:position())
|
||
|
pos:add(device().cam_dir:mul(r))
|
||
|
lvid = level.vertex_id(pos)
|
||
|
return {pos, lvid, gvid}
|
||
|
end
|
||
|
|
||
|
local mine_id = 0
|
||
|
function explode_handler()
|
||
|
local aim_point = get_point_of_aim()
|
||
|
if not aim_point then return end
|
||
|
local explode_obj = alife_create_item("bullet_blow", aim_point)
|
||
|
CreateTimeEvent(explode_obj.id, explode_obj.id, 0,
|
||
|
function(id)
|
||
|
local explode_obj = get_object_by_id(id)
|
||
|
if explode_obj then
|
||
|
explode_obj:explode(0)
|
||
|
explode_obj:destroy_object()
|
||
|
return true
|
||
|
end
|
||
|
return false
|
||
|
end, explode_obj.id)
|
||
|
end
|
||
|
|
||
|
-- randomly rip the durability
|
||
|
function pab()
|
||
|
local rand = math_random(4)
|
||
|
local wpn = db.actor:active_item()
|
||
|
if wpn and rand == 1 then
|
||
|
wpn:set_condition(clamp(wpn:condition() - 0.01, 0, 1))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function play_acid()
|
||
|
play_particle_firepoint("artefact\\effects\\af_acidic_idle_color_trail")
|
||
|
end
|
||
|
function play_shock()
|
||
|
play_particle_firepoint("anomaly2\\effects\\electra_entrance_small")
|
||
|
end
|
||
|
function play_grav_sound()
|
||
|
play_particle_firepoint("anomaly2\\bloodsucker_shield")
|
||
|
play_sound_on_location("soulripper", db.actor)
|
||
|
end
|
||
|
function play_grav()
|
||
|
play_particle_firepoint("anomaly2\\bloodsucker_shield")
|
||
|
end
|
||
|
function play_fire_big()
|
||
|
play_particle_firepoint("artefact\\af_thermal_idle")
|
||
|
end
|
||
|
|
||
|
local custom_proj_table = {
|
||
|
["explodes"] = explode_handler,
|
||
|
["impair"] = pab,
|
||
|
["soulripper"] = play_grav_sound,
|
||
|
["chaos"] = play_grav,
|
||
|
["shock"] = play_shock,
|
||
|
["fire_melt"] = play_fire_big,
|
||
|
["acid"] = play_acid
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
function actor_on_weapon_fired(obj, wpn, ammo_elapsed, grenade_elapsed, ammo_type, grenade_type)
|
||
|
local ammo_table = utils_item.get_ammo(wpn:section(), wpn:id())
|
||
|
ammo_type = wpn:get_ammo_type()
|
||
|
if ammo_table[ammo_type + 1] and custom_proj_table[ini_ammo:r_string_ex(ammo_table[ammo_type + 1], "special")] then
|
||
|
custom_proj_table[ini_ammo:r_string_ex(ammo_table[ammo_type + 1], "special")]()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- enhanced recoil patch
|
||
|
if enhanced_recoil then
|
||
|
RecoilMult = enhanced_recoil.recoil_multiplier
|
||
|
function enhanced_recoil.recoil_multiplier(wpn, shotc)
|
||
|
local sect = wpn:section()
|
||
|
local ammo_list = utils_item.get_ammo(sect, wpn:id())
|
||
|
local ammo_type = ammo_list[wpn:get_ammo_type() + 1]
|
||
|
return RecoilMult(wpn, shotc) * (ini_ammo:r_float_ex(ammo_type, "recoil") or 1)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function on_game_start()
|
||
|
RegisterScriptCallback("actor_on_weapon_fired", actor_on_weapon_fired)
|
||
|
RegisterScriptCallback("on_enemy_eval", on_enemy_eval)
|
||
|
RegisterScriptCallback("npc_on_death_callback", npc_on_death_callback)
|
||
|
RegisterScriptCallback("monster_on_death_callback",npc_on_death_callback)
|
||
|
RegisterScriptCallback("npc_on_before_hit", on_before_hit)
|
||
|
RegisterScriptCallback("monster_on_before_hit", on_before_hit)
|
||
|
|
||
|
end
|