481 lines
15 KiB
Plaintext
481 lines
15 KiB
Plaintext
|
-- Visual Memory Manager exports
|
||
|
-- by Alundaio
|
||
|
|
||
|
-- called from engine
|
||
|
-- This occurs during the visible check. If value >= visiblity_threshold then object is considered visible
|
||
|
-- warning npc and who can be nil sometimes
|
||
|
|
||
|
local jacket_t = {
|
||
|
["o_medium"] = 2,
|
||
|
["o_sci"] = 2,
|
||
|
["o_light"] = 1.35,
|
||
|
["o_heavy"] = 4.5,
|
||
|
}
|
||
|
|
||
|
local jacketmult = 1
|
||
|
local summ = 0
|
||
|
local renderer = get_console_cmd(0, "renderer")
|
||
|
local is_r1 = (renderer == "renderer_r1")
|
||
|
local correct_game_ver = GAME_VERSION and string.find(GAME_VERSION, "1.5.2")
|
||
|
local campfire_npcs = {}
|
||
|
local muzzle_t = {}
|
||
|
local get_camo_k = 0
|
||
|
local vision_memory = {}
|
||
|
stealth_light_ind = { 0, false }
|
||
|
local ind_t = {}
|
||
|
|
||
|
function get_visible_value(npc,who,time_delta,time_quant,luminocity,velocity_factor,velocity,distance,object_distance,always_visible_distance)
|
||
|
|
||
|
distance = distance <= 0 and 0.00001 or distance
|
||
|
|
||
|
local function obj_is_alive(obj)
|
||
|
return (IsStalker(obj) or IsMonster(obj)) and obj:alive()
|
||
|
end
|
||
|
|
||
|
local npc_fits = npc and obj_is_alive(npc)
|
||
|
local who_fits = who and obj_is_alive(who)
|
||
|
|
||
|
if not (npc_fits and who_fits) then
|
||
|
local def_lum = luminocity <= 0 and 0.0001 or luminocity
|
||
|
local def_step_incr = time_delta / time_quant * def_lum * (1 + velocity_factor * velocity) * (distance - object_distance) / distance
|
||
|
return def_step_incr
|
||
|
end
|
||
|
|
||
|
luminocity = get_luminocity_mult(npc, who, luminocity, object_distance, distance)
|
||
|
local lum_dist = (distance - object_distance / 1.35) / distance
|
||
|
local Luminocity = (33 * luminocity * lum_dist) * stealth_mcm.get_config("luminocity")
|
||
|
|
||
|
local Velocity = (1 + velocity_factor * velocity) * stealth_mcm.get_config("velocity")
|
||
|
|
||
|
local eq_dist = ((distance - object_distance / 1.15) / distance) * stealth_mcm.get_config("distance")
|
||
|
|
||
|
local camo_x = get_camo_mult(velocity, object_distance, who) -- actor only
|
||
|
|
||
|
local weight = get_weight_mult(object_distance, who) -- actor only
|
||
|
|
||
|
local crouch = get_crouch_mult(who)
|
||
|
|
||
|
local danger_mult = get_danger_mult(npc, time_quant)
|
||
|
|
||
|
local muzzle_flash = muzzle_t[who:id()] or 0
|
||
|
|
||
|
local scheme_mult = get_scheme_mult(npc)
|
||
|
|
||
|
local memory_factor = get_memory_val(npc, who)
|
||
|
|
||
|
local nvg_factor = get_nvg_val(npc)
|
||
|
|
||
|
--============== total =============================
|
||
|
local step_incr = Luminocity * Velocity * eq_dist * camo_x * weight * crouch * danger_mult * scheme_mult + muzzle_flash + memory_factor + nvg_factor
|
||
|
-------------------------------------------------------------------------
|
||
|
|
||
|
-- alt icon
|
||
|
stealth_icon_alt(npc, who, step_incr)
|
||
|
|
||
|
-- dbg
|
||
|
stealth_dbg(npc, who, step_incr, Luminocity, luminocity, Velocity, distance, weight, crouch, danger_mult, camo_x, scheme_mult, memory_factor, eq_dist, nvg_factor)
|
||
|
-------------------------------------------------------------------------
|
||
|
|
||
|
return step_incr
|
||
|
end
|
||
|
|
||
|
------- Luminocity
|
||
|
-- main
|
||
|
function lights_lum()
|
||
|
local function andruxa(animegif)
|
||
|
return 0.21 * animegif.x + 0.72 * animegif.y + 0.07 * animegif.z
|
||
|
end
|
||
|
|
||
|
local andsun = andruxa(weather.get_value_vector("sun_color"))
|
||
|
local andhem = andruxa(weather.get_value_vector("hemisphere_color"))
|
||
|
local andlum = math.max(andhem, andsun)
|
||
|
|
||
|
return andlum
|
||
|
end
|
||
|
|
||
|
function get_luminocity_mult(npc, who, luminocity, object_distance, distance)
|
||
|
local michiko_patch = stealth_mcm.get_config("michiko_patch")
|
||
|
local hrs = level.get_time_hours() + level.get_time_minutes() / 60
|
||
|
local who_lum = who:get_luminocity()
|
||
|
|
||
|
-- dx8
|
||
|
local lumin_r1 = luminocity <= 0 and 0.0001 or luminocity
|
||
|
if is_r1 then
|
||
|
if level_weathers.bLevelUnderground then
|
||
|
return lumin_r1 + 0.35
|
||
|
end
|
||
|
return lumin_r1
|
||
|
end
|
||
|
|
||
|
-- colors, time, lum
|
||
|
local lumin_r2 = (who_lum + who_lum^0.5 + lights_lum()) / 3.02
|
||
|
local is_night = (hrs > 21 or hrs < 4)
|
||
|
local lumin_night = clamp((lumin_r2 * 2)^1.6, 0.01, 0.99)
|
||
|
if michiko_patch then
|
||
|
is_night = (hrs > 21 or hrs < 5)
|
||
|
lumin_night = clamp((lumin_r2 * 7)^3, 0.01, 0.99)
|
||
|
end
|
||
|
|
||
|
-- campfire / underground
|
||
|
lumin_r2 = lumin_r2 + (campfire_npcs[who:id()] or 0)
|
||
|
|
||
|
if is_night then
|
||
|
lumin_r2 = lumin_night
|
||
|
end
|
||
|
|
||
|
if level_weathers.bLevelUnderground then
|
||
|
lumin_r2 = lumin_r2 + 0.35
|
||
|
end
|
||
|
|
||
|
-- flashlights
|
||
|
local torch = db.actor:item_in_slot(10)
|
||
|
local flash = db.actor:item_in_slot(9)
|
||
|
local npc_torch = IsStalker(npc) and npc:object("device_torch")
|
||
|
local who_torch = IsStalker(who) and who:object("device_torch")
|
||
|
local who_dist = 20 / object_distance
|
||
|
local npc_dist = 10 / object_distance
|
||
|
|
||
|
local actor_torch_active = (who:id() == db.actor:id()) and ( (torch and torch:torch_enabled()) or (flash and (flash:section() == "device_flashlight") and db.actor:active_detector()) )
|
||
|
local npc_to_npc_torch_active = who_torch and who_torch:attachable_item_enabled()
|
||
|
local npc_torch_active = npc_torch and npc_torch:attachable_item_enabled() and (object_distance <= 25 and distance > 90)
|
||
|
|
||
|
if actor_torch_active or npc_to_npc_torch_active then
|
||
|
lumin_r2 = lumin_r2 + who_dist
|
||
|
elseif npc_torch_active then
|
||
|
lumin_r2 = lumin_r2 + npc_dist
|
||
|
end
|
||
|
|
||
|
return lumin_r2
|
||
|
end
|
||
|
|
||
|
-- campfires
|
||
|
local cf_tmr = 0
|
||
|
function set_campfire_val()
|
||
|
local tg = time_global()
|
||
|
if tg < cf_tmr then return end
|
||
|
cf_tmr = tg + 1000
|
||
|
|
||
|
empty_table(campfire_npcs)
|
||
|
|
||
|
for id, binder in pairs(bind_campfire.campfires_all) do
|
||
|
if (binder and binder.campfire and binder.campfire:is_on()) then
|
||
|
level.iterate_nearest(binder.object:position(), 7, function(who)
|
||
|
if (who and (IsStalker(who) or IsMonster(who)) and who.alive and who:alive()) then
|
||
|
campfire_npcs[who:id()] = 0.4
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
-- icon
|
||
|
function icon_lum()
|
||
|
local act_lum = db.actor:get_luminocity()
|
||
|
local dblum = (act_lum + act_lum^0.5 + lights_lum()) / 3 + (campfire_npcs[AC_ID] or 0)
|
||
|
local luminocity_icon = is_r1 and (act_lum)^0.7 or dblum
|
||
|
|
||
|
return luminocity_icon
|
||
|
end
|
||
|
|
||
|
-- alt icon
|
||
|
function stealth_icon_alt(npc, who, incr)
|
||
|
if who:id() ~= db.actor:id() then return end
|
||
|
|
||
|
local is_enemy_to_ac = game_relations.get_npcs_relation(npc, who) == game_object.enemy
|
||
|
if not (is_enemy_to_ac) then return end
|
||
|
|
||
|
local in_danger = (db.storage[npc:id()] and db.storage[npc:id()].danger_flag) or (npc:best_enemy() and true) or false
|
||
|
local vis_t = npc:visibility_threshold()
|
||
|
ind_t[npc:id()] = ind_t[npc:id()] or {}
|
||
|
ind_t[npc:id()].val = ind_t[npc:id()].val or 0
|
||
|
ind_t[npc:id()].norm = ind_t[npc:id()].norm or false
|
||
|
ind_t[npc:id()].danger = in_danger
|
||
|
|
||
|
if (npc:see(who)) then
|
||
|
ind_t[npc:id()].val = 1
|
||
|
else
|
||
|
incr = normalize(incr, 0, vis_t)
|
||
|
if not (ind_t[npc:id()].norm) then
|
||
|
ind_t[npc:id()].val = normalize(ind_t[npc:id()].val, 0, vis_t)
|
||
|
ind_t[npc:id()].norm = true
|
||
|
end
|
||
|
ind_t[npc:id()].val = (ind_t[npc:id()].val < 1) and ind_t[npc:id()].val + incr or 1
|
||
|
end
|
||
|
|
||
|
local function del_elem(te_id)
|
||
|
ind_t[te_id] = nil
|
||
|
return true
|
||
|
end
|
||
|
ResetTimeEvent("stealth_hud_e" .. npc:id(), "stealth_hud_a" .. npc:id(), 1)
|
||
|
CreateTimeEvent("stealth_hud_e" .. npc:id(), "stealth_hud_a" .. npc:id(), 1, del_elem, npc:id())
|
||
|
|
||
|
end
|
||
|
|
||
|
function update_icon()
|
||
|
local max_id = next(ind_t)
|
||
|
local max_val = ind_t[max_id] and ind_t[max_id].val or 0
|
||
|
|
||
|
for id, t in pairs(ind_t) do
|
||
|
if t.val > max_val then
|
||
|
max_id, max_val = id, t.val
|
||
|
end
|
||
|
end
|
||
|
|
||
|
stealth_light_ind[1] = max_val
|
||
|
stealth_light_ind[2] = ind_t[max_id] and ind_t[max_id].danger or false
|
||
|
|
||
|
end
|
||
|
--------------------------------------------------------------------------------
|
||
|
|
||
|
------- Camo and weight
|
||
|
function actor_on_first_update()
|
||
|
local outfit = db.actor:item_in_slot(7)
|
||
|
if not (outfit) then return end
|
||
|
|
||
|
for i = 1, 13 do
|
||
|
local obj = db.actor:item_in_slot(i)
|
||
|
slot_in_out(obj)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
function slot_in_out(obj)
|
||
|
if not IsOutfit(obj) then return end
|
||
|
local outfit = db.actor:item_in_slot(7)
|
||
|
local kind = outfit and ini_sys:r_string_ex(outfit:section(),"kind")
|
||
|
local get_camo_get_line = outfit and ini_sys:line_exist(outfit:section(), "npc_blindness_koeff")
|
||
|
get_camo_k = get_camo_get_line and ini_sys:r_float(outfit:section(), "npc_blindness_koeff") or 0
|
||
|
jacketmult = kind and jacket_t[kind] or 1
|
||
|
|
||
|
end
|
||
|
|
||
|
function get_camo_mult(velocity, object_distance, who)
|
||
|
local camo_mult = 1
|
||
|
local camo_checks = get_camo_k > 0 and velocity == 0 and object_distance >= 30
|
||
|
|
||
|
if (who:id() ~= AC_ID) or (not camo_checks) then
|
||
|
return camo_mult
|
||
|
end
|
||
|
|
||
|
local camo_dist = 1 - object_distance * 0.01
|
||
|
local camo_mult = get_camo_k / 20 * camo_dist
|
||
|
camo_mult = clamp(camo_mult, 0.02, 1.0)
|
||
|
|
||
|
return camo_mult
|
||
|
end
|
||
|
|
||
|
function get_weight_mult(object_distance, who)
|
||
|
if (who:id() ~= AC_ID) then return 1 end
|
||
|
|
||
|
local actor_weight = db.actor:get_total_weight()
|
||
|
local weight_factor = math.exp(actor_weight / 100)
|
||
|
local dist = 1.1 - 0.02 * object_distance
|
||
|
local weight_mult = weight_factor * jacketmult * dist * stealth_mcm.get_config("weight")
|
||
|
weight_mult = weight_mult > 1 and weight_mult or 1
|
||
|
|
||
|
return weight_mult
|
||
|
end
|
||
|
--------------------------------------------------------------------------------
|
||
|
|
||
|
------- Body state / Danger / Scheme / Memory / Muzzle flash
|
||
|
-- body state
|
||
|
function get_crouch_mult(who)
|
||
|
local crouch_mult = 1
|
||
|
local crouch_state = IsMoveState('mcCrouch')
|
||
|
local accel_state = IsMoveState('mcAccel')
|
||
|
|
||
|
if who:id() == AC_ID then
|
||
|
if crouch_state then
|
||
|
crouch_mult = accel_state and stealth_mcm.get_config("low_crouch") or stealth_mcm.get_config("crouch")
|
||
|
end
|
||
|
elseif IsStalker(who) and who:body_state() == move.crouch then
|
||
|
crouch_mult = stealth_mcm.get_config("crouch")
|
||
|
end
|
||
|
|
||
|
return crouch_mult
|
||
|
end
|
||
|
|
||
|
-- danger
|
||
|
function get_danger_mult(npc, time_quant)
|
||
|
local danger_mult = 1
|
||
|
|
||
|
if IsMonster(npc) then
|
||
|
danger_mult = 0.003 / time_quant
|
||
|
elseif IsStalker(npc) then
|
||
|
danger_mult = time_quant * 2
|
||
|
end
|
||
|
|
||
|
return danger_mult
|
||
|
end
|
||
|
|
||
|
-- scheme
|
||
|
function get_scheme_mult(npc)
|
||
|
local scheme_mult = 1
|
||
|
local st = db.storage[npc:id()]
|
||
|
|
||
|
if st and ((st.active_scheme == "guard") or (st.active_scheme == "sniper")) then
|
||
|
scheme_mult = 0.25
|
||
|
end
|
||
|
|
||
|
return scheme_mult
|
||
|
end
|
||
|
|
||
|
-- memory
|
||
|
function get_memory_val(npc, who)
|
||
|
local tg = time_global()
|
||
|
local memory_val = 0
|
||
|
local mcm_memory = stealth_mcm.get_config("memory")
|
||
|
|
||
|
local who_fits = IsStalker(who) or IsMonster(who)
|
||
|
if not (who_fits) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local see = npc:see(who)
|
||
|
local vis_memory_t = vision_memory[npc:id()] and vision_memory[npc:id()][who:id()] or nil
|
||
|
if (see) and (not (vis_memory_t)) then
|
||
|
vision_memory[npc:id()] = {}
|
||
|
vision_memory[npc:id()][who:id()] = true
|
||
|
elseif (not (see)) and (vis_memory_t) then
|
||
|
local mem_time = (tg - npc:memory_time(who)) * 0.001
|
||
|
local mem_time_factor = 7.5 * (1 / mem_time)^2
|
||
|
local vis_threshold = npc:visibility_threshold()
|
||
|
memory_val = mcm_memory * mem_time_factor * vis_threshold -- 35 threshold: 100 sec = 0.025 ; 80 sec = 0.04 ; 60 sec = 0.08 ; 40 sec = 0.17 ; 20 sec = 0.66 ; 10 sec = 2.6
|
||
|
end
|
||
|
|
||
|
memory_val = memory_val > 0.01 and memory_val or 0
|
||
|
|
||
|
return memory_val
|
||
|
end
|
||
|
|
||
|
-- nvg
|
||
|
function get_nvg_val(npc)
|
||
|
local nvg_val = stealth_mcm.get_config("nvg_val")
|
||
|
local hrs = level.get_time_hours() + level.get_time_minutes() / 60
|
||
|
local is_night = (hrs > 21 or hrs < 5)
|
||
|
|
||
|
if not (nvg_val > 0 and is_night and stealth_nvg.stealth_nvg_ps[npc:id()]) then
|
||
|
return 0
|
||
|
end
|
||
|
|
||
|
return nvg_val
|
||
|
end
|
||
|
|
||
|
-- muzzle flash
|
||
|
if correct_game_ver then
|
||
|
AddScriptCallback("npc_on_weapon_fired")
|
||
|
function xr_motivator.motivator_binder:weapon_fired()
|
||
|
SendScriptCallback("npc_on_weapon_fired", self.object, self.object:active_item())
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function actor_on_weapon_fired()
|
||
|
local wpn = db.actor:active_item()
|
||
|
if not (wpn) then return end
|
||
|
if not (IsWeapon(wpn)) or IsMelee(wpn) or wpn:weapon_is_silencer() then return end
|
||
|
|
||
|
muzzle_t[db.actor:id()] = 7
|
||
|
|
||
|
local function wpn_fired_stop(id)
|
||
|
muzzle_t[id] = 0
|
||
|
return true
|
||
|
end
|
||
|
CreateTimeEvent("stealth_wpn_fired_e_" .. db.actor:id(), "stealth_wpn_fired_a_" .. db.actor:id(), 0.2, wpn_fired_stop, db.actor:id())
|
||
|
|
||
|
end
|
||
|
|
||
|
function npc_on_net_spawn(npc)
|
||
|
if not (npc and IsStalker(npc) and npc.alive and npc:alive()) then return end
|
||
|
local binder = npc.binded_object and npc:binded_object()
|
||
|
if not binder then return end
|
||
|
npc:set_callback(callback.weapon_fired, binder.weapon_fired, binder)
|
||
|
end
|
||
|
|
||
|
function npc_on_weapon_fired(npc, wpn)
|
||
|
if not (wpn) then return end
|
||
|
if not (IsWeapon(wpn)) or IsMelee(wpn) or wpn:weapon_is_silencer() then return end
|
||
|
if not (npc and IsStalker(npc) and npc.alive and npc:alive()) then return end
|
||
|
|
||
|
muzzle_t[npc:id()] = 7
|
||
|
|
||
|
local function wpn_fired_stop(id)
|
||
|
muzzle_t[id] = 0
|
||
|
return true
|
||
|
end
|
||
|
CreateTimeEvent("stealth_wpn_fired_e_" .. npc:id(), "stealth_wpn_fired_a_" .. npc:id(), 0.2, wpn_fired_stop, npc:id())
|
||
|
|
||
|
end
|
||
|
--------------------------------------------------------------------------------
|
||
|
|
||
|
------- Debug
|
||
|
function stealth_dbg(npc, who, step_incr, Luminocity, luminocity, Velocity, distance, weight, crouch, danger_mult, camo_x, scheme_mult, memory_factor, eq_dist, nvg_factor)
|
||
|
if not (stealth_mcm.get_config("debugx")) then return end
|
||
|
|
||
|
local wthrs = level_weathers.get_weather_manager():get_curr_weather_preset()
|
||
|
local obj = level.get_target_obj()
|
||
|
|
||
|
local obj_fits = obj and (IsStalker(obj) or IsMonster(obj)) and obj.alive and obj:alive()
|
||
|
|
||
|
if obj_fits then
|
||
|
if (npc and npc:id() == obj:id()) and (who and who:id() == 0) and (not obj:see(db.actor)) then
|
||
|
summ = summ + step_incr
|
||
|
elseif obj:see(db.actor) then
|
||
|
summ = 0
|
||
|
news_manager.send_tip(db.actor, "Spotted", 0, nil, 1000)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function r(val)
|
||
|
return round_idp(val, 2)
|
||
|
end
|
||
|
|
||
|
local function pr(var, val, ...)
|
||
|
if var ~= val then
|
||
|
printf(..., var)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if obj_fits and (npc and npc:id() == obj:id()) and (who and who:id() == 0) then
|
||
|
printf('---------------------------------------------------------------')
|
||
|
printf("Weather is: " .. (wthrs))
|
||
|
printf("1. Luminocity ---------- %s", Luminocity)
|
||
|
printf("1.a) luminocity --------- %s", luminocity)
|
||
|
printf("1.b) lights_lum() ------- %s", lights_lum())
|
||
|
printf("1.c) who:get_lum() ---- %s", who:get_luminocity())
|
||
|
pr(Velocity, 1, ("2. Velocity ------------- %s"))
|
||
|
printf("3. dist_original --------- %s", distance)
|
||
|
pr(weight, 1, ("4. weight&outfit ------- %s"))
|
||
|
pr(crouch, 1, ("5. crouch ------------- %s"))
|
||
|
pr(muzzle_t[who:id()] or 0, 0, ("6.a) shot_mult --------- %s"))
|
||
|
pr(danger_mult, 1, ("6.b) danger ------------ %s"))
|
||
|
pr(camo_x, 1, ("6.c) camo ------------- %s"))
|
||
|
pr(scheme_mult, 1, ("6.d) ai scheme --------- %s"))
|
||
|
pr(get_camo_k, 0, ("6.e) get_camo_k ------ %s"))
|
||
|
pr(jacketmult, 1, ("6.f) jacketmult --------- %s"))
|
||
|
pr(memory_factor, 0, ("7. memory ------------- %s"))
|
||
|
pr(nvg_factor, 0, ("8. NVG ------------- %s"))
|
||
|
printf("8. Step incr --- %s", step_incr)
|
||
|
actor_menu.set_msg(1, strformat("Threshold: %s/%s || (Lumin: %s | Veloc: %s | Dist: %s | Weight: %s | Memory: %s || Total per update: %s)", r(summ), obj:visibility_threshold(), r(Luminocity), r(Velocity), r(eq_dist), r(weight), r(memory_factor), r(step_incr) ))
|
||
|
end
|
||
|
|
||
|
if obj == nil then
|
||
|
summ = 0
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
--===================================
|
||
|
function on_game_start()
|
||
|
RegisterScriptCallback("actor_on_update", set_campfire_val)
|
||
|
RegisterScriptCallback("actor_on_update", update_icon)
|
||
|
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
|
||
|
RegisterScriptCallback("actor_item_to_slot", slot_in_out)
|
||
|
RegisterScriptCallback("actor_item_to_ruck", slot_in_out)
|
||
|
RegisterScriptCallback("actor_on_item_drop", slot_in_out)
|
||
|
RegisterScriptCallback("actor_on_weapon_fired", actor_on_weapon_fired)
|
||
|
if correct_game_ver then
|
||
|
RegisterScriptCallback("npc_on_net_spawn", npc_on_net_spawn)
|
||
|
RegisterScriptCallback("npc_on_weapon_fired", npc_on_weapon_fired)
|
||
|
end
|
||
|
end
|