Divergent/mods/Better Smoke Grenades/gamedata/scripts/working_smoke_grenades.script

231 lines
7.6 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
-- OPTIONS
-- Dont set this to 0
-- Visibility will be reduce by this value each update
visibility_reduce_coefficient = 0.02
-- Visibility will start to reduce after this amount of updates
-- Each update is 200ms
visibility_reduce_tick_threshold = 6
-- Smoke field registry
smoke_fields = {}
function spawn_smoke_field(pos, radius, lifetime)
local f = SmokeField(pos, radius, lifetime)
smoke_fields[#smoke_fields + 1] = f
return f
end
function update_smoke_fields()
local tg = time_global()
for i = #smoke_fields, 1, -1 do
local f = smoke_fields[i]
if tg > f:getEndTime() then
table.remove(smoke_fields, i)
printf("%s: SmokeField destroy", f.init_time)
end
end
end
-- Smoke field class
class "SmokeField"
function SmokeField:__init(pos, radius, lifetime)
self.position = vector():set(pos)
self.radius = radius
self.lifetime = lifetime
self.init_time = time_global()
-- Adjust position by radius up
self.position.y = self.position.y + 1
printf("%s: SmokeField spawn", self.init_time)
end
function SmokeField:getEndTime()
return self.init_time + self.lifetime * 1000
end
function bind(obj)
obj:bind_object(smoke_grenade_binder(obj))
end
class "smoke_grenade_binder" (object_binder)
function smoke_grenade_binder:__init(obj) super(obj)
self.sec = self.object:section()
-- Flag of fused
self.fused = false
self.time_event_done = false
-- Get fuse time
self.fuse_time = SYS_GetParam(2, self.sec, "destroy_time", 0) / 1000
-- Smoke params
self.smoke_radius = SYS_GetParam(2, self.sec, "smoke_radius", 0)
self.smoke_lifetime = SYS_GetParam(2, self.sec, "smoke_lifetime", 0)
-- Fastcall to collision detection check
self.object:set_fastcall(self.fastcall, self)
end
function smoke_grenade_binder:fastcall()
local obj = self.object
local id = obj:id()
-- If object is in someone hands, dont check it
if obj:parent() then
if self.position then
self.position = nil
end
self.fused = nil
return
end
if self.fused == nil then
printf("%s: check for fuse", obj:name())
self.fused = not alife_object(obj:id())
end
if not self.fused then
return
end
self.position = obj:position()
if not self.time_event_done then
self.time_event_done = true
printf("%s: fused %s", obj:name(), self.fused)
CreateTimeEvent(obj:name(), "spawn_smoke_field", self.fuse_time, function()
printf("%s: spawn smoke field at %s", obj:name(), self.position)
spawn_smoke_field(self.position, self.smoke_radius, self.smoke_lifetime)
return true
end)
end
end
function actor_on_first_update()
AddUniqueCall(update_smoke_fields)
end
function on_game_start()
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
end
function get_npc_pos(npc)
return utils_obj.safe_bone_pos(npc, "bip01_spine") or npc:position():add(vector():set(0, 0.4, 0))
end
if AddScriptCallback then
AddScriptCallback("npc_behind_smoke")
end
function is_smoke_field_between_npcs(field, npc, who)
-- Check if field is between npcs
local npc_pos = get_npc_pos(npc)
local who_pos = get_npc_pos(who)
local field_to_npc = vector():set(field.position):sub(npc_pos)
local field_to_who = vector():set(field.position):sub(who_pos)
if field_to_npc:dotproduct(field_to_who) > 0 then
-- Vectors are in the same direction, field is not in between
-- printf("check %s against %s, field is not in between", npc:name(), who:name())
return false
end
-- Construct invisibility cone
local apex = npc_pos
local pos = vector():set(who_pos):sub(apex):normalize()
local axis_direction = vector():set(field_to_npc):normalize()
local dot = pos:dotproduct(axis_direction)
local cos_half_angle
do
local height = apex:distance_to(field.position)
local half_angle = math.atan(field.radius / height)
cos_half_angle = math.cos(half_angle)
end
-- Check if inside invisibility cone
local res = dot >= cos_half_angle
-- Visualize
-- local function lerpVec(v1, v2, f)
-- return vector():lerp(v1, v2, f)
-- end
-- local t = {}
-- t[1] = vector_rotate_y(axis_direction, 90)
-- t[2] = vector():set(t[1]):invert()
-- t[3] = vector():crossproduct(axis_direction, t[1])
-- t[4] = vector():set(t[3]):invert()
-- t[5] = lerpVec(t[1], t[3], 0.5)
-- t[6] = vector():set(t[5]):invert()
-- t[7] = lerpVec(t[2], t[3], 0.5)
-- t[8] = vector():set(t[7]):invert()
-- for i, v in ipairs(t) do
-- local v1 = vector():mad(field.position, v, field.radius)
-- demonized_geometry_ray.VisualizeRay(
-- npc_pos,
-- vector():mad(npc_pos, vector():set(v1):sub(npc_pos):normalize(), vector():set(v1):sub(npc_pos):magnitude() + 10),
-- nil,
-- 199
-- )
-- end
-- if res then
-- print_tip("check %s against %s, %s is in invisibility cone", npc:name(), who:name(), who:name())
-- end
return res
end
function IsValidMonster(o)
local c = o:clsid()
return c == clsid.burer
or c == clsid.burer_s
or c == clsid.controller
or c == clsid.controller_s
end
local vis_decrease_ticks = {}
get_visible_value = visual_memory_manager.get_visible_value
visual_memory_manager.get_visible_value = function(npc,who,time_delta,time_quant,luminocity,velocity_factor,velocity,distance,object_distance,always_visible_distance)
local res = get_visible_value(npc,who,time_delta,time_quant,luminocity,velocity_factor,velocity,distance,object_distance,always_visible_distance)
if #smoke_fields > 0
and npc
and who
and (IsStalker(npc) or IsValidMonster(npc))
and (who:id() == AC_ID or IsStalker(who) or IsMonster(who))
then
local under_field = false
for i = 1, #smoke_fields do
local f = smoke_fields[i]
if is_smoke_field_between_npcs(f, npc, who) then
under_field = true
-- Add an update threshold
local npc_name = npc:name()
local who_name = who:name()
vis_decrease_ticks[npc_name] = vis_decrease_ticks[npc_name] or {}
vis_decrease_ticks[npc_name][who_name] = (vis_decrease_ticks[npc_name][who_name] or 0) + 1
if vis_decrease_ticks[npc_name][who_name] >= visibility_reduce_tick_threshold then
-- print_tip("check %s against %s, %s is in invisibility cone, start reducing visibility %s/%s", npc:name(), who:name(), who:name(), vis_decrease_ticks[npc_name][who_name], visibility_reduce_tick_threshold)
res = -visibility_reduce_coefficient
else
-- print_tip("check %s against %s, %s is in invisibility cone, under threshold %s/%s", npc:name(), who:name(), who:name(), vis_decrease_ticks[npc_name][who_name], visibility_reduce_tick_threshold)
end
-- Send callback
-- Npc is the one who tries to see enemy behind smoke
-- Who is the enemy behind smoke
if AddScriptCallback then
SendScriptCallback("npc_behind_smoke", npc, who)
end
end
end
if not under_field then
vis_decrease_ticks[npc:name()] = vis_decrease_ticks[npc:name()] or {}
vis_decrease_ticks[npc:name()][who:name()] = 0
end
elseif npc and who then
vis_decrease_ticks[npc:name()] = vis_decrease_ticks[npc:name()] or {}
vis_decrease_ticks[npc:name()][who:name()] = 0
end
return res
end