Divergent/mods/Anomaly Ballistics Overhaul/gamedata/scripts/arti_handlers.script

844 lines
29 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
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