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