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

474 lines
16 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
-- Utility scripts to help calculate things.
get_config = ballistics_mcm.get_config
print_dbg = ballistics_mcm.print_dbg
ini_ammo = ini_file("ammo\\importer.ltx")
ini_damage = ini_file("creatures\\damages.ltx")
ini_sounds = ini_file("ammo\\sounds.ltx")
Bone_IDs = {
[1] = "bip01_pelvis",
[2] = "bip01_pelvis",
[3] = "bip01_l_thigh",
[4] = "bip01_l_calf",
[5] = "bip01_l_foot",
[6] = "bip01_l_foot",
[7] = "bip01_r_thigh",
[8] = "bip01_r_calf",
[9] = "bip01_r_foot",
[10] = "bip01_r_foot",
[11] = "bip01_spine",
[12] = "bip01_spine1",
[13] = "bip01_spine2",
-- head
[14] = "bip01_neck",
[15] = "bip01_head",
[16] = "eye_left",
[17] = "eye_right",
[18] = "eyelid_1",
[19] = "jaw_1",
-- endhead
[20] = "bip01_l_clavicle",
[21] = "bip01_l_upperarm",
[22] = "bip01_l_forearm",
[23] = "bip01_l_hand",
[24] = "bip01_l_hand",
[25] = "bip01_l_hand",
[26] = "bip01_l_hand",
[27] = "bip01_l_hand",
[28] = "bip01_l_hand",
[29] = "bip01_l_hand",
[30] = "bip01_l_hand",
[31] = "bip01_l_hand",
[32] = "bip01_l_hand",
[33] = "bip01_r_clavicle",
[34] = "bip01_r_upperarm",
[35] = "bip01_r_forearm",
[36] = "bip01_r_hand",
[37] = "bip01_r_hand",
[38] = "bip01_r_hand",
[39] = "bip01_r_hand",
[40] = "bip01_r_hand",
[41] = "bip01_r_hand",
[42] = "bip01_r_hand",
[43] = "bip01_r_hand",
[44] = "bip01_r_hand",
[45] = "bip01_r_hand",
[46] = "bip01_pelvis",
}
head_bones = {
[14] = "bip01_neck",
[15] = "bip01_head",
[16] = "eye_left",
[17] = "eye_right",
[18] = "eyelid_1",
[19] = "jaw_1",
}
center_mass = {
[1] = "bip01_pelvis",
[2] = "bip01_pelvis",
[11] = "bip01_spine",
[12] = "bip01_spine1",
[13] = "bip01_spine2",
[20] = "bip01_l_clavicle",
[33] = "bip01_r_clavicle",
[46] = "bip01_pelvis",
}
limbs = {
[3] = "bip01_l_thigh",
[4] = "bip01_l_calf",
[6] = "bip01_l_toe0",
[7] = "bip01_r_thigh",
[8] = "bip01_r_calf",
[10] = "bip01_r_toe0",
[22] = "bip01_l_forearm",
[24] = "bip01_l_finger0",
[25] = "bip01_l_finger01",
[26] = "bip01_l_finger02",
[27] = "bip01_l_finger1",
[28] = "bip01_l_finger11",
[29] = "bip01_l_finger12",
[30] = "bip01_l_finger2",
[31] = "bip01_l_finger21",
[32] = "bip01_l_finger22",
[35] = "bip01_r_forearm",
[37] = "bip01_r_finger0",
[38] = "bip01_r_finger01",
[39] = "bip01_r_finger02",
[40] = "bip01_r_finger1",
[41] = "bip01_r_finger11",
[42] = "bip01_r_finger12",
[43] = "bip01_r_finger2",
[44] = "bip01_r_finger21",
[45] = "bip01_r_finger22",
}
-- play particle at npc bone
function play_particle(npc, bone_id, name, duration)
bone_id = (not bone_id or bone_id == 65535) and 1 or bone_id
bone_name = Bone_IDs[bone_id] or "bip01_spine"
duration = duration or 1
local particle = particles_object(name)
local bone_pos = npc:bone_position(bone_name)
if particle and not particle:playing() then
particle:play_at_pos(bone_pos)
end
local timeout = time_global() + (duration * 1000)
CreateTimeEvent(npc:id(), name, 0, function(npc, bone, particle, timeout)
if time_global() > timeout then
return true
end
local bp = npc:bone_position(bone)
if not particle:playing() then
particle:play_at_pos(bp)
end
particle:move_to(bp, VEC_Z)
end, npc, bone_name, particle, timeout)
end
-- play a particle at the weapon bone firepoint. good for emulating fancier muzzle fx
function play_particle_firepoint(name)
local wpn = db.actor:active_item()
if not wpn then return end
last_particle = particles_object(name)
if last_particle and not last_particle:playing() then
local hud = utils_data.read_from_ini(nil,wpn:section(),"hud","string",nil)
local fire_bone = utils_data.read_from_ini(nil,hud,"fire_bone","string",nil) or "wpn_body"
local offset = utils_data.read_from_ini(nil,hud,"fire_point","string",nil) or VEC_ZERO
offset = offset and utils_data.string_to_vector(offset)
last_particle:play_at_pos( wpn:bone_position(fire_bone, true), offset )
CreateTimeEvent("particle_stop", math.random(30), 0.7,
function()
last_particle:stop()
return true
end)
end
end
-- play some sounds that scale based on distance
function play_sound_on_location(snd, npc, distance)
if not snd or not npc or not ini_sounds:section_exist(snd) then
return
end
distance = distance or get_distance(db.actor, npc)
play_sound_distance(snd, distance)
end
function play_sound_distance(snd, distance)
to_use = distance > 10 and ini_sounds:r_string_ex(snd, "far") and "far" or "near"
snd_to_play = ini_sounds:r_string_ex(snd, to_use)
if not snd_to_play then return end
snd_to_play = snd_to_play .. math.random(ini_sounds:r_float_ex(snd, to_use.."_amt") or 1)
local s = xr_sound.get_safe_sound_object(snd_to_play)
if s then
-- print_dbg("Playing %s", snd_to_play)
s:play(db.actor, 0, sound_object.s2d)
-- snd.volume = 1
s.volume = (distance > 10) and 0.3 + (0.7 * (1 - (clamp(distance, 2, 100) - 2)/98)) or 1
return s
end
end
-- pull the bone_mult from [stalker_damage] section, cache the values to prevent excessive stringsplitting
local stalker_damage = {}
function npc_bone_mult(bone_id)
local damage_mult = stalker_damage[bone_id]
if damage_mult then return damage_mult
else
-- pull from data and cache
local bone_name = Bone_IDs[bone_id] or "bip01_spine"
local damages = SYS_GetParam(0, "stalker_damage", bone_name)
damages = str_explode(damages, ",")
stalker_damage[bone_id] = tonumber(damages[1])
return stalker_damage[bone_id]
end
end
-- Pull bone protection for a given NPC's bone. Can use custom bone data.
-- returns two values, second one indicates if this has already been modified
local bone_cache = {}
local custom_bone_data = {}
function npc_bone_protection(npc, bone_id)
local id = npc:id()
if id == AC_ID then return {
armor = actor_bone_prot(bone_id),
modified = false
} end
if custom_bone_data[id] and custom_bone_data[id][bone_id] then return {
armor = custom_bone_data[id][bone_id],
modified = false
}
else
local bone_name = Bone_IDs[bone_id] or "bip01_spine"
local bone_sec = read_npc_bone_sec(npc) or "stalker_hero_1"
if not bone_cache[bone_sec] then
bone_cache[bone_sec] = {}
end
if not bone_cache[bone_sec][bone_id] then
local bone_armor_str = SYS_GetParam(0, bone_sec, bone_name) or "0, 0.15"
local bone_armor = str_explode(bone_armor_str, ",")
bone_cache[bone_sec][bone_id] = tonumber(bone_armor[2]) or 0.15
end
return {
armor = bone_cache[bone_sec][bone_id],
modified = false
}
end
end
function hit_frac(npc, bone_id)
local bone_sec = read_npc_bone_sec(npc) or "stalker_hero_1"
return SYS_GetParam(2, bone_sec, "hit_fraction_npc") or 0.5
end
-- compute actor bone prot based on active suit
function actor_bone_prot(bone_id)
local helmet = db.actor:item_in_slot(12)
if head_bones[bone_id] and helmet then
return helmet:cast_Helmet():GetBoneArmor(bone_id)
else
local outfit = db.actor:item_in_slot(7)
if not outfit then return 0
else return outfit:cast_CustomOutfit():GetBoneArmor(bone_id) end
end
end
function read_npc_bone_sec(npc)
local visual_data = game.get_visual_userdata(npc:get_visual_name())
if not visual_data then
printf("!!! Visual %s is lacking data !!!", npc:get_visual_name())
return "stalker_hero_1"
end
local bone_sec = visual_data:r_string_ex("bone_protection", "bones_protection_sect", "stalker_hero_1")
visual_data:close()
return bone_sec
end
-- Used to modify the custom values of a stalker's bone armors (to account for reductions, etc)
function modify_bone(npc, bone_id, new_val)
local npc_id = npc:id()
if npc_id == AC_ID then return end
if not custom_bone_data[npc_id] then custom_bone_data[npc_id] = {} end
custom_bone_data[npc_id][bone_id] = new_val
end
-- util to collect information about a bone - damage multiplier, id, name
function npc_bone_data(npc, bone_id)
bone_prot = npc_bone_protection(npc, bone_id)
local data = {
id = bone_id,
name = Bone_IDs[bone_id],
armor = bone_prot.armor,
modified = bone_prot.modified,
mult = npc_bone_mult(bone_id),
hit_fraction = hit_frac(npc, bone_id)
}
-- halve leg armor and damage multiplier
if get_config("legs") and limbs[bone_id] then
data.armor = data.armor / 2
data.mult = data.mult / 2
end
return data
end
function npc_on_death_callback(npc, who)
if custom_bone_data[npc:id()] then
custom_bone_data[npc:id()] = nil
end
end
-- zombies have an inexplicable 0.1x damage multiplier that isn't reflected anywhere, so I added it here
local undead_clsid = {
SM_IZLOM = true,
SM_ZOMBI = true
}
local mutant_hp = {
SM_BLOOD = 1.5,
SM_CONTR = 3,
SM_CHIMS = 3,
SM_IZLOM = 10,
SM_ZOMBI = 10,
}
local burer = {
SM_BURER = 3
}
function mutant_prot_values(mutant, id)
local sec = mutant:section()
local skin_armor = SYS_GetParam(2, sec, "skin_armor")
local clsid = SYS_GetParam(0, sec, "kind")
if not clsid or clsid == "" then clsid = SYS_GetParam(0, sec, "class") or "" end
local prot_sec = SYS_GetParam(0, sec, "protections_sect")
local burer_lv = get_config("burer") or 0
if not skin_armor then
if prot_sec then
skin_armor = SYS_GetParam(2, prot_sec, "skin_armor") or 0
else skin_armor = 0 end
end
local hit_frac = SYS_GetParam(2, sec, "hit_fraction_monster")
if not hit_frac and prot_sec then
hit_frac = SYS_GetParam(2, prot_sec, "hit_fraction_monster")
end
if not hit_frac then hit_frac = 0.5 end
local mult = mutant_hp[clsid] and (get_config("mutant") or undead_clsid[clsid]) and 1/mutant_hp[clsid] or 1
local snork = get_config("snork")
if snork > 0 and clsid == SM_SNORK then
skin_armor = skin_armor / (snork * 2)
mult = mult * (1 + snork)
end
if burer[clsid] then
if burer_lv > 0 then
skin_armor = skin_armor / 2
end
if burer_lv == 1 then
mult = 0.4
end
end
return {
id = id,
hit_fraction = hit_frac,
armor = skin_armor,
deflect = (burer[clsid] and burer_lv == 2) and true or false,
mult = mult
}
end
-- Utils to handle storing velocity. Before weapon fired, compute and cache velocity modifier
local cur_wep_data = {
id = 0
}
-- check and update current weapon to cache hit power, velocity and recoil (for recoil module)
function actor_on_weapon_before_fire(flags)
local current_wpn = db.actor:active_item()
if not current_wpn then return end
-- cache
local id = current_wpn:id()
local sec = current_wpn:section()
if not cur_wep_data.id or cur_wep_data.id ~= id then
if IsWeapon(current_wpn) and not IsItem("fake_ammo_wpn",sec) then
cur_wep_data.id = current_wpn:id()
base_vel = SYS_GetParam(2, sec, "bullet_speed")
base_degrade = SYS_GetParam(2, sec, "condition_shot_dec") or 0
base_recoil = SYS_GetParam(2, sec, "cam_step_angle_horz")
local vel_add = 0
local recoil_add = 0
local upgrades = utils_item.get_upgrades_installed(current_wpn)
for _, upgrade in pairs(upgrades) do
local section = ini_sys:r_string_ex(upgrade, "section")
local speed_adj = ini_sys:r_string_ex(section,"bullet_speed")
if speed_adj then
local op = string.sub(speed_adj, 1, 1)
local val = tonumber(string.sub(speed_adj, 2))
vel_add = op == "+" and vel_add + val or vel_add - val
end
local shot_dec = ini_sys:r_string_ex(section,"condition_shot_dec")
if shot_dec then
local op = string.sub(shot_dec, 1, 1)
local val = tonumber(string.sub(shot_dec, 2))
base_degrade = op == "+" and base_degrade + val or base_degrade - val
end
end
cur_wep_data.vel_mod = vel_add/base_vel
cur_wep_data.dec = base_degrade
-- print_dbg("Stored velocity mod %s, recoil %s, degradation %s for wpn %s", cur_wep_data.vel_mod, cur_wep_data.recoil, base_degrade, sec)
else
-- clear
-- print_dbg("Clearing cwd")
cur_wep_data.id = 0
cur_wep_data.vel_mod = 0
cur_wep_data.dec = 0
end
end
end
-- return the velocity modifier for the current weapon, or 1
function get_cur_vel()
return cur_wep_data.vel_mod or 1
end
function get_recoil()
return cur_wep_data.recoil or 1
end
-- Provide a damage modifier based on distance, adjusted by bullet velocity and flatness
function modify_distance(shooter, npc, ammo)
local dist = get_distance(shooter, npc)
local air_res = SYS_GetParam(2, ammo, "k_air_resistance") or 0.05
air_res = clamp(air_res, 0, 1.1)
local mod = clamp( 1 / ( 1 + (dist / 600) * air_res / ( 1.3 - air_res)), 0.02, 1)
-- print_dbg("applying distance modifier %s", mod)
return mod
end
function get_distance(shooter, npc)
local npc_pos = npc:position()
local shooter_pos = shooter:position()
return npc_pos:distance_to(shooter_pos)
end
-- One way to modify damage based on velocity. 20% of bonus velocity (e.g. taken from flatness upgrades) is added as hit damage.
-- Of course, you don't need to use this
function modify_velocity()
-- 20% of velocity bonus becomes damage
local velocity_mod = (1 + (get_cur_vel() / 5))
-- print_dbg("Applying velocity damage multiplier of %s", velocity_mod)
return velocity_mod
end
-- Modify weapon degradation based on ammo type to be correct, if needed. Leverage stored data
-- eg. if ammo degrade in custom config is 1.5 and is 1 in weapon_ammo, reduce weapon dura by 0.5 x condition_shot_dec
function actor_on_weapon_fired(obj, weapon, ammo_elapsed, grenade_elapsed, ammo_type, grenade_type)
if weapon:id() ~= cur_wep_data.id then return end
if not get_config("impair") then return end
local sec = weapon:section()
ammo_map = utils_item.get_ammo(nil, cur_wep_data.id)
local ammo = ammo_map[weapon:get_ammo_type() + 1]
local base_degrade = SYS_GetParam(2, ammo, "impair")
local actual_degrade = ini_ammo:r_float_ex(ammo, "impair") or base_degrade
if base_degrade == actual_degrade then return end
local diff = (actual_degrade - base_degrade) * cur_wep_data.dec
-- print_dbg("Adjusting weapon cond by %s", diff)
weapon:set_condition(clamp(weapon:condition() - diff, 0.001, 0.999))
end
function on_get_item_cost(kind, obj, profile, calculated_cost, ret)
if not (obj and profile) then
return nil
end
if not get_config("cost") then return end
local sec = obj:section()
local discount = profile.discount
local ammo = IsItem("ammo",sec)
if ammo and ini_ammo:section_exist(sec) and ini_ammo:r_float_ex(sec, "cost") then
ret.new_cost = ini_ammo:r_float_ex(sec, "cost") * discount * (obj:ammo_get_count() / ammo)
end
end
function on_game_start()
RegisterScriptCallback("actor_on_weapon_before_fire", actor_on_weapon_before_fire)
RegisterScriptCallback("actor_on_weapon_fired", actor_on_weapon_fired)
RegisterScriptCallback("on_get_item_cost", on_get_item_cost)
RegisterScriptCallback("npc_on_death_callback",npc_on_death_callback)
RegisterScriptCallback("monster_on_hit_callback",npc_on_death_callback)
end