218 lines
7.8 KiB
Plaintext
218 lines
7.8 KiB
Plaintext
-- Core file for ballistics. When someone is shot, the hit will be intercepted and deferred to a custom config defined in ammo importer based on bullet type.
|
|
|
|
ini_ammo = ini_file("ammo\\importer.ltx")
|
|
|
|
local dbg_log
|
|
function print_dbg( text , ...)
|
|
if get_config("debug") or false then
|
|
dbg_log = dbg_log or mcm_log and mcm_log.new("DBG")
|
|
if dbg_log then
|
|
dbg_log.enabled = true
|
|
dbg_log:log( text , ...)
|
|
else
|
|
printf( "ballistics: | %s | "..text ,time_global(), ...)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- checking if actor shot the gun at npc or vice versa and if gun is gun
|
|
function validate_hit(s_hit, is_actor)
|
|
if s_hit.draftsman and ((not is_actor and s_hit.draftsman:id() ~= AC_ID) or is_actor and s_hit.type ~= hit.fire_wound) then return end
|
|
if bone_id == 65535 then return end
|
|
if s_hit.power >= 50 then return end
|
|
local wpn = level.object_by_id(s_hit.weapon_id)
|
|
if not wpn or IsGrenade(wpn) or (not IsWeapon(wpn)) then return end
|
|
local sec = wpn:section()
|
|
if ini_sys:r_string_ex(sec, "class") == "S_EXPLO" then return end
|
|
if IsItem("fake_ammo_wpn",sec) then return end
|
|
ammo_map = utils_item.get_ammo(nil, s_hit.weapon_id)
|
|
local ammo = ammo_map[wpn:get_ammo_type() + 1]
|
|
return {
|
|
["wpn"] = wpn,
|
|
["ammo"] = ammo,
|
|
["wpn_sec"] = sec
|
|
} -- returning stuff we parsed, so we don't parse it again
|
|
end
|
|
|
|
function get_handler(ammo_sec)
|
|
if ini_ammo:section_exist(ammo_sec) then
|
|
local is_grenade = SYS_GetParam(0, ammo_sec, "fake_grenade_name")
|
|
local default = is_grenade and "bind_grenade.explode" or "ballistic_handlers.default"
|
|
local proc = ini_ammo:r_string_ex(ammo_sec, "handler") or ("ballistic_handlers.default")
|
|
proc = str_explode(proc, "%.")
|
|
return proc
|
|
end
|
|
end
|
|
|
|
-- for the most part calculations will be deferred to function-specifics for each ammo type
|
|
-- Generically it will call a function specified in config. Function determines how damage is applied.
|
|
-- is_npc is either 0 (actor), 1 (npc), 2 (mutant)
|
|
-- Final damage should be stored in s_hit.power field.
|
|
-- Return false or nothing in order to pass the original hit.
|
|
function on_before_hit(npc,s_hit,bone_id, is_npc)
|
|
local valid_hit_data = validate_hit(s_hit, npc:id() == AC_ID)
|
|
if not (valid_hit_data) then -- no data, so we don't care about the hit
|
|
return false
|
|
end
|
|
local wpn = valid_hit_data.wpn
|
|
local ammo = valid_hit_data.ammo
|
|
local sec = valid_hit_data.wpn_sec
|
|
|
|
local func = get_handler(ammo)
|
|
if not func then return false end
|
|
-- print_dbg("Applying custom hit for %s on %s using function %s.%s. Prev hit power %s", ammo, bone_id, func[1], func[2], s_hit.power)
|
|
local hit_ctx = {
|
|
hit_data = valid_hit_data,
|
|
bone_id = bone_id,
|
|
is_npc = is_npc
|
|
}
|
|
-- pass to the ammo processor
|
|
-- return value should be the new hit
|
|
if func and func[1] and func[2] and _G[func[1]] and _G[func[1]][func[2]] then
|
|
|
|
ret_val = _G[func[1]][func[2]](npc, s_hit, hit_ctx)
|
|
if ret_val then s_hit = ret_val end
|
|
end
|
|
|
|
return {
|
|
["s_hit"] = s_hit,
|
|
["bone_id"] = bone_id
|
|
}
|
|
end
|
|
|
|
-- Overriding hit callbacks so our custom hits can communicate with other scripts
|
|
local flags = { ret_value = true }
|
|
|
|
_G.CActor__BeforeHitCallback = function(actor,s_hit,bone_id)
|
|
if (s_hit.type ~= hit.strike) then
|
|
if (bind_stalker_ext.invulnerable_time and time_global() < bind_stalker_ext.invulnerable_time) then
|
|
bind_stalker_ext.invulnerable_time = bind_stalker_ext.invulnerable_time - 500
|
|
return false
|
|
end
|
|
end
|
|
|
|
if (s_hit.power > 0) then
|
|
if (s_hit.draftsman and s_hit.draftsman:id() ~= 0 and IsStalker(s_hit.draftsman) and s_hit.draftsman:relation(db.actor) == game_object.friend) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
flags.ret_value = true
|
|
-- go to original
|
|
if not get_config("incoming") then
|
|
SendScriptCallback("actor_on_before_hit",s_hit,bone_id,flags)
|
|
return flags.ret_value
|
|
end
|
|
|
|
-- pass through processor
|
|
local custom_hit = on_before_hit(actor,s_hit,bone_id, true)
|
|
custom_hit = custom_hit and custom_hit.s_hit or s_hit
|
|
if custom_hit then
|
|
SendScriptCallback("actor_on_before_hit",custom_hit,bone_id,flags)
|
|
if flags.ret_value and get_config("incoming") then
|
|
s_hit.power = custom_hit.power
|
|
end
|
|
end
|
|
return flags.ret_value
|
|
end
|
|
|
|
_G.CAI_Stalker__BeforeHitCallback = function(npc,s_hit,bone_id)
|
|
flags.ret_value = true
|
|
local custom_hit = on_before_hit(npc,s_hit,bone_id, true)
|
|
if custom_hit then
|
|
SendScriptCallback("npc_on_before_hit",npc,custom_hit.s_hit,custom_hit.bone_id, flags)
|
|
if flags.ret_value then
|
|
if custom_hit.s_hit.type ~= hit.fire_wound then
|
|
s_hit.power = custom_hit.s_hit.power
|
|
else
|
|
npc:set_health_ex(npc.health - custom_hit.s_hit.power)
|
|
s_hit.power = 0.0001 -- removing engine stuff
|
|
end
|
|
end
|
|
else
|
|
SendScriptCallback("npc_on_before_hit",npc,s_hit,bone_id,flags)
|
|
end
|
|
return flags.ret_value
|
|
end
|
|
|
|
-- bone mult is unknown for monster, so simply assume skin armor vs ap calcs
|
|
_G.CBaseMonster__BeforeHitCallback = function(monster,s_hit,bone_id)
|
|
flags.ret_value = true
|
|
local custom_hit = on_before_hit(monster,s_hit,bone_id, false)
|
|
if custom_hit then
|
|
SendScriptCallback("monster_on_before_hit",monster,custom_hit.s_hit,custom_hit.bone_id, flags)
|
|
if flags.ret_value then
|
|
s_hit.power = custom_hit.s_hit.power
|
|
s_hit.ap = 0
|
|
end
|
|
else
|
|
SendScriptCallback("monster_on_before_hit",monster,s_hit,bone_id,flags)
|
|
end
|
|
return flags.ret_value
|
|
end
|
|
|
|
local ini_ammo = ini_file("ammo\\importer.ltx")
|
|
gc = game.translate_string
|
|
|
|
GetFirstName = ui_item.build_name_first
|
|
function ui_item.build_name_first(obj, sec, str)
|
|
if ini_ammo:section_exist(sec) and ini_ammo:r_string_ex(sec, "name") then
|
|
return gc(ini_ammo:r_string_ex(sec, "name"))
|
|
else
|
|
return GetFirstName(obj, sec, str)
|
|
end
|
|
end
|
|
|
|
ShortName = ui_item.build_short_name_first
|
|
function ui_item.build_short_name_first(obj, sec, str)
|
|
if ini_ammo:section_exist(sec) and ini_ammo:r_string_ex(sec, "name") then
|
|
return gc(ini_ammo:r_string_ex(sec, "name") .. "_s")
|
|
else
|
|
return ShortName(obj, sec, str)
|
|
end
|
|
end
|
|
-- custom ammo desc, inject descriptions later
|
|
BuildFooter = ui_item.build_desc_footer
|
|
function ui_item.build_desc_footer(obj, sec, str)
|
|
if ini_ammo:section_exist(sec) and ini_ammo:r_string_ex(sec, "name") then
|
|
return gc(ini_ammo:r_string_ex(sec, "name") .. "_descr")
|
|
else
|
|
return BuildFooter(obj, sec, str)
|
|
end
|
|
end
|
|
|
|
|
|
-- If you don't use MCM, change your defaults from here.
|
|
local defaults = {
|
|
["debug"] = true,
|
|
["incoming"] = false,
|
|
["cost"] = true,
|
|
["impair"] = true,
|
|
["mutant"] = true,
|
|
["burer"] = 0,
|
|
["snork"] = 0,
|
|
["legs"] = true,
|
|
}
|
|
|
|
function get_config(key)
|
|
if ui_mcm then return ui_mcm.get("ballistics/"..key) else return defaults[key] end
|
|
end
|
|
|
|
function on_mcm_load()
|
|
op = { id= "ballistics",sh=true ,gr={
|
|
{ id= "title",type= "slide",link= "ui_options_slider_player",text="ui_mcm_ballistics_title",size= {512,50},spacing= 20 },
|
|
{id = "debug", type = "check", val = 1, def=true},
|
|
{id = "incoming", type = "check", val = 1, def=false},
|
|
-- {id = "osp", type = "check", val = 1, def=false},
|
|
{id = "cost", type = "check", val = 1, def=true},
|
|
{id = "impair", type = "check", val = 1, def=true},
|
|
{id = "mutant", type = "check", val = 1, def=true},
|
|
{id = "burer", type = "list", val = 2, def=0, content= {{0,"bclassic"} , {1,"bmeaty"} , {2,"bsith"}}},
|
|
{id = "snork", type = "list", val = 2, def=0, content= {{0,"snormal"} , {1,"sfat"} , {2,"ssfat"}}},
|
|
|
|
{id = "legs", type = "check", val = 1, def=true},
|
|
}
|
|
}
|
|
return op
|
|
end |