Divergent/mods/NPCs Die in Emissions/gamedata/scripts/surge_manager.script

1875 lines
60 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
-- Surge manager class
-- Made by Peacemaker
-- 05.03.07
-- Edited for AtmosFear 3
-- by Cromm Cruac
-- 25.06.2011
-- Edited by Alundaio for Call of Chernobyl
--[[
Tronex
2019/9/13
Modified for Anomaly 1.5
--]]
-- Edited by demonized, 2022
-- Cleanup and reformat
-- NPC Die in Emissions
-- Faster emissions
local alife, math_random, pairs, tostring = alife, math.random, pairs, tostring
-- class instance
SurgeManager = nil
function get_surge_manager()
SurgeManager = SurgeManager or CSurgeManager()
return SurgeManager
end
local a_ini = ini_file("alife.ltx")
local normal_time_factor = a_ini:r_float_ex("alife", "time_factor") or 6
local surge_time_factor = 10
local surge_shock_pp_eff = 1
local earthquake_cam_eff = 2
local sleep_cam_eff = 3
local sleep_fade_pp_eff = 4
local prev_sec = -1
local prev_game_sec = -1
local currentPPEfactor = 0.001
local _tmr
local presurge_played = { false, false, false, false, false }
--[[Amount of time before a surge in in-game seconds (6x real life seconds)
Note that the script updates events only once per real life second]]
local presurge_time = 0
local function main_loop()
local tg = time_global()
if _tmr and tg < _tmr then
return false
end
_tmr = tg + 1000
if not (db.actor and db.actor.afterFirstUpdate) then
return false
end
get_surge_manager():update()
psi_storm_manager.get_psi_storm_manager():update()
if level.get_time_factor() ~= normal_time_factor and not is_started() and not psi_storm_manager.is_started() then
if is_finished() and psi_storm_manager.is_finished() then
level.set_time_factor(normal_time_factor)
end
end
return false
end
local function on_level_changing()
local tm = task_manager.get_task_manager()
local task_info = tm.task_info["faction_base_defense"]
if task_info and task_info.status ~= "completed" and task_info.status ~= "fail" then
stop_surge()
tm:set_task_cancelled("faction_base_defense")
end
end
local function actor_on_sleep(hours)
get_surge_manager().time_forwarded = true
psi_storm_manager.get_psi_storm_manager().time_forwarded = true
if is_started() and level_weathers.get_weather_manager().weather_fx then
level.stop_weather_fx()
end
level_weathers.get_weather_manager():forced_weather_change()
end
function on_game_start()
AddUniqueCall(main_loop)
RegisterScriptCallback("on_level_changing", on_level_changing)
RegisterScriptCallback("actor_on_sleep", actor_on_sleep)
end
local enable_debug = false
function print_dbg(fmt, ...)
if enable_debug then
printf("Surge manager | " .. fmt, ...)
end
end
local function file_exists(filename)
local file = io.open(filename, "r")
if file then
file:close()
return true
end
return false
end
-- ###############################################################################################
-- SURGE MANAGER
-- ###############################################################################################
class("CSurgeManager")
function CSurgeManager:__init()
self.ini = ini_file("misc\\surge_manager.ltx")
self.blowout_sounds = {
begin01 = sound_object("ambient\\blowout\\blowout_begin"),
begin02 = sound_object("ambient\\blowout\\blowout_begin_02"),
impact01 = sound_object("ambient\\blowout\\blowout_impact"),
impact02 = sound_object("ambient\\blowout\\blowout_impact_02"),
wave01 = sound_object("ambient\\blowout\\blowout_wave_01"),
wave02 = sound_object("ambient\\blowout\\blowout_wave_02_short"),
wave03 = sound_object("ambient\\blowout\\blowout_wave_03_short"),
wave04 = sound_object("ambient\\blowout\\blowout_wave_04"),
body_tear = sound_object("anomaly\\anomaly_body_tear_1"),
siren = sound_object("ambient\\blowout\\blowout_siren"),
crow0 = sound_object("ambient\\blowout\\cut\\close"),
crow1 = sound_object("ambient\\blowout\\cut\\closemedium"),
crow2 = sound_object("ambient\\blowout\\cut\\medium"),
crow3 = sound_object("ambient\\blowout\\cut\\prettyfar"),
crow4 = sound_object("ambient\\blowout\\cut\\far"),
crow5 = sound_object("ambient\\blowout\\cut\\veryfar"),
}
self.blowout_waves = {}
self.body_tears = {}
end
function CSurgeManager:initialize()
self.aspectRatio = 1
if device().width / device().height > (1024 / 768 + 0.01) then
self.aspectRatio = 0.8
end
if not self._state_loaded then -- because state_load called too early, we wait till actor_on_load to truly init and we do not want to overwrite loaded values
self.last_surge_time = game.get_game_time()
self.inited_time = game.get_game_time()
self.started = false
self.finished = true
self.game_time_factor = level.get_time_factor()
self.task_given = nil
end
if not self._delta then
local freq = ui_options.get("alife/event/emission_frequency")
self._delta = math_random(math.floor(freq / 2) * 3600, freq * 3600)
end
self.hitFactor = 0
self:init_surge_covers()
local ini = self.ini
self.condlist = ini:r_string_to_condlist("settings", "condlist")
self.survive = ini:r_string_to_condlist("settings", "survive")
self.surge_message = ""
self.surge_task_sect = ""
-- Stages of emission in real seconds, start is 0
-- Siren
self.siren_sec = 25
-- Blowout warning
self.blowout_warning_sec = 30
-- Blowout impact sound
self.blowout_impact_sec = 47
-- Start rumble
self.rumble_sec = 50
-- First earthquake
self.first_earthquake_sec = 80
-- Second warning
self.second_message_sec = 90
-- Earthquakes
self.earthquakes1_sec = 100
self.earthquakes2_sec = self.earthquakes1_sec + 2
self.earthquakes3_sec = self.earthquakes1_sec + 4
self.earthquakes4_sec = self.earthquakes1_sec + 6
-- First wave sound
self.first_wave_sound_sec = 108
-- First wave
self.first_wave_sec = 120
-- Second wave sound
self.second_wave_sound_sec = self.first_wave_sound_sec + 48
-- Second wave
self.second_wave_sec = self.first_wave_sec + 48
-- Earthquakes fade
self.earthquakes1_fade_sec = self.second_wave_sec + 32
self.earthquakes2_fade_sec = self.earthquakes1_fade_sec + 2
self.earthquakes3_fade_sec = self.earthquakes1_fade_sec + 6
self.earthquakes4_fade_sec = self.earthquakes1_fade_sec + 9
self.earthquakes5_fade_sec = self.earthquakes1_fade_sec + 14
-- End message
self.end_message_sec = self.earthquakes5_fade_sec + 5
-- After sound sec
self.after_sound_sec = self.earthquakes5_fade_sec + 3
-- Total surge time based on upper times
self.surge_time = self.end_message_sec - 3
-- Options
-- Turn to zombies right after first wave (false - vanilla behaviour - only after second)
self.zombie_at_first_wave = true
end
function CSurgeManager:start(manual)
-- manual is used to set inited_time to current in-game date
-- sets inited_time for the surge + prepare values
-- skip surge in special cases
if not self._state_loaded then
self:initialize()
end
self.game_time_factor = level.get_time_factor()
local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0
Y, M, D, h, m, s, ms = self.last_surge_time:get(Y, M, D, h, m, s, ms)
if manual or not self.inited_time then
self.inited_time = game.get_game_time()
else
self.inited_time:set(Y, M, D, h, m, s + self._delta, ms)
end
local flags = { allow = true }
SendScriptCallback("on_before_surge", flags)
local level_name = level.name()
if
level_weathers.valid_levels[level_name] ~= true
or (not ui_options.get("alife/event/emission_state"))
or flags.allow == false
then
self:skip_surge()
return
end
-- Generators is emission source and Deserted Hospital has nowhere to hide (almost all the map is inside anyway).
if
level_name == "l13_generators"
or level_name == "l11_hospital"
or has_alife_info("bar_arena_fight")
or has_alife_info("lttz_dp_active")
then
self.skip_message = true
self:skip_surge()
return
end
local diff_sec = math.ceil(game.get_game_time():diffSec(self.inited_time) / level.get_time_factor())
if diff_sec + 6 > self.surge_time then
self.skip_message = true
self:skip_surge()
else
-- heli_combat flag
save_var(db.actor, "heli_enemy_flag", nil)
self.started = true
self.finished = false
self.task_given = nil
self:finalize()
self.blowout_waves = empty_table(self.blowout_waves)
self.objects_to_kill = empty_table(self.objects_to_kill)
self.stages = empty_table(self.stages)
self.body_tears = empty_table(self.body_tears)
self.hitFactor = 0
level.set_time_factor(surge_time_factor)
self.zombie_count = 0
SendScriptCallback("actor_on_interaction", "anomalies", nil, "emission_start")
end
end
function CSurgeManager:new_surge_time(reset)
if reset then
self.last_surge_time = game.get_game_time()
end
local freq = ui_options.get("alife/event/emission_frequency")
self._delta = math_random(math.floor(freq / 2) * 3600, freq * 3600)
local g_time = game.get_game_time()
local psi_manager = psi_storm_manager and psi_storm_manager.get_psi_storm_manager()
local last_psi_storm_time = psi_manager and psi_manager.last_psi_storm_time or game.get_game_time()
local psi_storm_start = psi_manager and math.floor(psi_manager._delta - g_time:diffSec(last_psi_storm_time)) or 0
local psi_storm_end = psi_manager and math.floor(psi_manager._delta + 3600 - g_time:diffSec(last_psi_storm_time))
or 0
local surge_start = math.floor(self._delta - g_time:diffSec(self.last_surge_time))
local surge_end = math.floor(self._delta + 3600 - g_time:diffSec(self.last_surge_time))
if (surge_end > psi_storm_start) and (surge_end < psi_storm_end) then
--1h earlier
self._delta = self._delta - 3600
end
if (surge_start > psi_storm_start) and (surge_start < psi_storm_end) then
--1h later
self._delta = self._delta + 3600
end
end
function CSurgeManager:skip_surge()
if not self.inited_time then
self.inited_time = game.get_game_time()
end
if not self.last_surge_time then
self.last_surge_time = game.get_game_time()
end
local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0
Y, M, D, h, m, s, ms = self.inited_time:get(Y, M, D, h, m, s, ms)
self.last_surge_time:set(Y, M, D, h, m, s + self.surge_time, ms)
self:new_surge_time(false)
self.started = false
self.finished = true
self.surge_message = ""
self.surge_task_sect = ""
self.task_given = false
self.effector_set = false
self.second_message_given = false
self.ui_disabled = false
self.blowout_sound = false
self.wave_sound = false
prev_sec = 0
SendScriptCallback("actor_on_interaction", "anomalies", nil, "emission_end")
if self.skip_message and ui_options.get("alife/event/emission_state") then
news_manager.send_tip(db.actor, "st_surge_while_asleep", nil, "recent_surge", nil, nil)
SendScriptCallback("actor_on_interaction", "anomalies", nil, "emissions")
game_statistics.increment_statistic("emissions")
self.skip_message = nil
--release_body_manager.get_release_body_manager():clear(true)
end
-- hide indicators
self:displayIndicators(0)
level.set_time_factor(self.game_time_factor)
presurge_played = { false, false, false, false, false }
end
function CSurgeManager:end_surge(manual)
if self.started == true then
game_statistics.increment_statistic("emissions")
self:kill_all_unhided()
SendScriptCallback("actor_on_interaction", "anomalies", nil, "emissions")
SendScriptCallback("actor_on_interaction", "anomalies", nil, "emission_end")
end
self.last_surge_time = game.get_game_time()
self.started = false
self.finished = true
self:new_surge_time(false)
self.surge_message = ""
self.surge_task_sect = ""
self.task_given = nil
presurge_played = { false, false, false, false, false }
if self.blowout_sound then
xr_sound.stop_sound_looped(AC_ID, "blowout_rumble")
end
if self.wave_sound then
xr_sound.stop_sound_looped(AC_ID, "blowout_particle_wave_looped")
end
for k, wave in pairs(self.blowout_waves) do
if wave.effect:playing() then
self:kill_wave(k)
end
end
for k, snd in pairs(self.blowout_sounds) do
if snd ~= nil and snd:playing() then
snd:stop()
end
end
if self.second_message_given then
xr_sound.stop_sound_looped(AC_ID, "surge_earthquake_sound_looped")
end
if manual or (self.time_forwarded and level_weathers.get_weather_manager().weather_fx) then
level.stop_weather_fx()
level_weathers.get_weather_manager():forced_weather_change()
end
self.effector_set = false
self.second_message_given = false
self.ui_disabled = false
self.blowout_sound = false
self.wave_sound = false
prev_sec = 0
self.hitFactor = 0
level.remove_pp_effector(surge_shock_pp_eff)
level.remove_cam_effector(earthquake_cam_eff)
-- hide indicators
self:displayIndicators(0)
level.set_time_factor(self.game_time_factor)
for k, v in pairs(db.signal_light) do
v:stop_light()
v:stop()
end
end
function CSurgeManager:pos_in_cover(pos, by_name)
if not self.covers then
return
end
local sr
for name, condlist in pairs(self.covers) do
sr = db.zone_by_name[name]
if sr and sr:inside(pos) then
if by_name then
return name
end
return true
end
end
return false
end
function CSurgeManager:init_surge_covers() -- collect safe zones list, if not done yet
if self.covers then
return
end
self.covers = {}
local ini = self.ini
for i = 0, ini:line_count("list") - 1 do
temp1, id, temp2 = ini:r_line("list", i, "", "")
self.covers[id] = ini:r_string_to_condlist(id, "condlist")
or xr_logic.parse_condlist(nil, "surge_manager", "covers", "true")
end
end
function CSurgeManager:hit_power(power, hit_type)
local hit_types = { "burn", "shock", "chemical_burn", "radiation", "telepatic" }
local suit_factors = { 10, 1, 5, 33, 10 }
local drug_factors = { 1, 1, 5, 33, 10 }
local artefact_factors = { 10, 5, 10, 10, 10 }
local suit_protection = db.actor:get_current_outfit_protection(hit_type) * suit_factors[hit_type + 1]
local helmet_protection = 0
local helmet = db.actor:item_in_slot(12)
if helmet ~= nil then
helmet_protection = ini_sys:r_float_ex(helmet:section(), hit_types[hit_type + 1] .. "_protection")
* suit_factors[hit_type + 1]
end
local g_time
-- DRUG PROTECTIONS CHECK
local expiration = load_var(db.actor, "drug_psy_blockade_expiration")
if expiration then
g_time = utils_data.CTimeToSec(game.get_game_time())
if g_time < expiration then
self.drug_telepatic_protection = ini_sys:r_float_ex("drug_psy_blockade", "boost_telepat_protection")
else
self.drug_telepatic_protection = nil
end
end
expiration = load_var(db.actor, "drug_radioprotector_expiration")
if expiration then
g_time = g_time or utils_data.CTimeToSec(game.get_game_time())
if g_time < expiration then
self.drug_radiation_protection = ini_sys:r_float_ex("drug_radioprotector", "boost_radiation_protection")
else
self.drug_radiation_protection = nil
end
end
expiration = load_var(db.actor, "drug_antidot_expiration")
if expiration then
g_time = g_time or utils_data.CTimeToSec(game.get_game_time())
if g_time < expiration then
self.drug_chemical_burn_protection = ini_sys:r_float_ex("drug_antidot", "boost_chemburn_protection")
else
self.drug_chemical_burn_protection = nil
end
end
local drug_protection = 0
if hit_type == 2 and self.drug_chemical_burn_protection ~= nil then
drug_protection = self.drug_chemical_burn_protection * drug_factors[hit_type + 1]
elseif hit_type == 3 and self.drug_radiation_protection ~= nil then
drug_protection = self.drug_radiation_protection * drug_factors[hit_type + 1]
elseif hit_type == 4 and self.drug_telepatic_protection ~= nil then
drug_protection = self.drug_telepatic_protection * drug_factors[hit_type + 1]
end
protection = (suit_protection + helmet_protection + drug_protection)
if protection > 1 then
protection = 1
end
hit_power = power * (1 - protection)
return hit_power
end
function CSurgeManager:start_wave(num, inited_time)
if inited_time == nil then
inited_time = prev_game_sec
end
local actor_pos = db.actor:position()
local effect = particles_object("crommcruac\\blowout_wave_blend")
local inited_pos = vector():set(actor_pos.x, actor_pos.y / 2, actor_pos.z + 250)
self.blowout_waves[num] = { effect = effect, inited_time = inited_time, inited_pos = inited_pos }
self.blowout_waves[num].effect:play_at_pos(inited_pos)
if not self.wave_sound then
xr_sound.play_sound_looped(AC_ID, "blowout_particle_wave_looped")
xr_sound.set_volume_sound_looped(AC_ID, "blowout_particle_wave_looped", 0)
self.wave_sound = true
end
end
function CSurgeManager:finalize()
for k, v in pairs(self.blowout_waves) do
if v.effect and v.effect:playing() then
v.effect:stop()
end
end
for k, v in pairs(self.body_tears) do
if v:playing() then
v:stop()
end
end
end
function CSurgeManager:kill_wave(num)
if self.blowout_waves[num] then
self.blowout_waves[num].effect:stop_deffered()
self.blowout_waves[num] = nil
end
end
function fade(currentTime, startTime, endTime, startValue, endValue)
local totalFadeTime = endTime - startTime
local totalFadeValue = endValue - startValue
local elapsedFadeTime = currentTime - startTime
local currentValue = (totalFadeValue * elapsedFadeTime) / totalFadeTime + startValue
if totalFadeValue < 0 then
if currentValue > startValue then
currentValue = startValue
end
if currentValue < endValue then
currentValue = endValue
end
else
if currentValue > endValue then
currentValue = endValue
end
if currentValue < startValue then
currentValue = startValue
end
end
return currentValue
end
function play_sound_radius(sndobj, radius, min, max)
local rnd_pos = db.actor:position()
local rnd_angle = math.random(min, max)
rnd_pos.x = rnd_pos.x + (math.cos(rnd_angle) * radius)
rnd_pos.z = rnd_pos.z + (math.sin(rnd_angle) * radius)
if sndobj:playing() then
sndobj:stop()
end
sndobj:play_at_pos(db.actor, rnd_pos)
printf("played sound at x=%s z=%s", rnd_pos.x, rnd_pos.z)
end
-- Update
function CSurgeManager:update()
if not self._state_loaded then
print_dbg("self._state_loaded is not loaded -> initialize")
self:initialize()
return
end
if not self.first_update then
print_dbg("first update")
self.first_update = true
local flags = { allow = true }
SendScriptCallback("on_before_surge", flags)
-- end surge if level is not valid
local level_name = level.name()
if
level_name == "l13_generators"
or level_name == "l11_hospital"
or has_alife_info("bar_arena_fight")
or has_alife_info("lttz_dp_active")
or flags.allow == false
or level_weathers.valid_levels[level_name] ~= true
then
print_dbg("level is not valid")
if self.started then
self:end_surge()
end
return
end
local diff_sec = math.ceil(game.get_game_time():diffSec(self.inited_time) / level.get_time_factor())
print_dbg("diff_sec 1 = %s", diff_sec)
if diff_sec > self.surge_time then
print_dbg("diff_sec > self.surge_time")
self:skip_surge()
return
end
end
SetEvent("surge", "state", self.started)
--DEBUG
local g_time = game.get_game_time()
-- printf("until blowout: %s", self._delta - g_time:diffSec(self.last_surge_time))
--Plays sound before a blowout
if self._delta - g_time:diffSec(self.last_surge_time) < presurge_time and not presurge_played[1] then
play_sound_radius(self.blowout_sounds["crow5"], 1000, 140, 220)
play_sound_radius(self.blowout_sounds["crow4"], 1000, 140, 220)
printf("Played far crows")
presurge_played[1] = true
end
if self._delta - g_time:diffSec(self.last_surge_time) < presurge_time - 60 and not presurge_played[2] then
play_sound_radius(self.blowout_sounds["crow3"], 1000, 100, 260)
play_sound_radius(self.blowout_sounds["crow2"], 1000, 100, 260)
printf("Played medium crows")
presurge_played[2] = true
end
if self._delta - g_time:diffSec(self.last_surge_time) < presurge_time - 120 and not presurge_played[3] then
play_sound_radius(self.blowout_sounds["crow1"], 1000, 0, 360)
play_sound_radius(self.blowout_sounds["crow0"], 1000, 0, 360)
printf("Played close crows")
presurge_played[3] = true
end
if self._delta - g_time:diffSec(self.last_surge_time) < presurge_time - 180 and not presurge_played[4] then
play_sound_radius(self.blowout_sounds["crow2"], 1000, 280, 440)
play_sound_radius(self.blowout_sounds["crow3"], 1000, 280, 440)
printf("Played medium crows")
presurge_played[4] = true
end
if self._delta - g_time:diffSec(self.last_surge_time) < presurge_time - 240 and not presurge_played[5] then
play_sound_radius(self.blowout_sounds["crow4"], 1000, 320, 400)
play_sound_radius(self.blowout_sounds["crow5"], 1000, 320, 400)
printf("Played far crows")
presurge_played[5] = true
end
if not self.started then
local g_time = game.get_game_time()
if self.time_forwarded then
local diff = math.abs(self._delta - g_time:diffSec(self.last_surge_time))
if diff < 3600 then
self._delta = 3 * 3600 + g_time:diffSec(self.last_surge_time)
end
self.time_forwarded = false
print_dbg("time forwarded")
end
-- return if next surge time hasn't been reached yet
if g_time:diffSec(self.last_surge_time) < self._delta then
--print_dbg('next surge time hasnt been reached yet')
return
end
if self.condlist and xr_logic.pick_section_from_condlist(db.actor, nil, self.condlist) == "false" then
return
end
print_dbg("start")
self:start()
return
end
local diff_sec = math.ceil(game.get_game_time():diffSec(self.inited_time) / level.get_time_factor())
if diff_sec >= self.surge_time then
print_dbg("diff_sec 2 = %s", diff_sec)
if load_var(db.actor, "surge_immuned", false) ~= true then -- special surged immuned for tasks
self:end_surge()
return
else
print_dbg("start surge")
self:start(true)
end
end
local diff_game_sec = math.ceil(game.get_game_time():diffSec(self.inited_time) / 2)
print_dbg("diff_game_sec = %s", diff_game_sec)
-- update here
if prev_sec ~= diff_sec then
prev_sec = diff_sec
SetEvent("surge", "time", diff_sec)
if not ui_options.get("alife/event/emission_state") then
print_dbg("stop surge because option")
self:end_surge()
return
end
local rnd_sound = math.random(1, 4)
-- blowout begins
if diff_sec >= 0 and self.stages["beginning"] ~= true then
print_dbg("diff_sec>=%s | blowout begins", diff_sec)
if level.get_time_hours() >= 5 and level.get_time_hours() <= 20 then
level.set_weather_fx("fx_blowout_day")
else
level.set_weather_fx("fx_blowout_night")
end
if (rnd_sound % 2) ~= 0 then
self:play_blowout_sound("begin01")
else
self:play_blowout_sound("begin02")
end
self.stages["beginning"] = true
--self.debugMessages[2]:SetText("blowout launched at: "..tostring(diff_sec).." rnd: "..tostring(rnd_sound))
end
-- siren warning
local warn = ui_options.get("alife/event/emission_warning")
if (diff_sec >= self.siren_sec) and (self.stages["siren"] ~= true) and (warn == "siren" or warn == "radio_siren") then
print_dbg("diff_sec>=%s | siren warning", diff_sec)
self:play_siren_sound()
self.stages["siren"] = true
end
-- blowout warning
if diff_sec >= self.blowout_warning_sec then
print_dbg("diff_sec>=%s | blowout warning", diff_sec)
if self.task_given ~= true then
--[[if (warn=="radio" or warn =="radio_siren") then
local level_name = level.name()
if (level_name == "zaton") then
if not (utils_obj.npc_in_zone(db.actor,"zat_a2_sr_noweap")) then
xr_sound.set_sound_play(AC_ID, "zat_a2_stalker_barmen_surge_phase_1")
end
elseif (level_name == "jupiter") then
if not (utils_obj.npc_in_zone(db.actor,"jup_a6_sr_noweap")) then
xr_sound.set_sound_play(AC_ID, "jup_a6_stalker_medik_phase_1")
end
elseif (level_name == "l03_agroprom") then
xr_sound.set_sound_play(AC_ID, "kovalsky_surge_phase_1")
end
end]]
self:launch_rockets()
if load_var(db.actor, "surge_immuned", false) ~= true then -- special surged immuned for tasks
printf("opt_blowout_task", ui_options.get("alife/event/emission_task"))
if ui_options.get("alife/event/emission_task") then
self:give_surge_hide_task()
end
end
self.task_given = true
--self.debugMessages[2]:SetText("task given at: "..tostring(diff_sec))
end
end
-- blowout impact
if (diff_sec >= self.blowout_impact_sec) and (self.stages["impact"] ~= true) then
print_dbg("diff_sec>=%s | blowout impact", diff_sec)
if (rnd_sound % 2) ~= 0 then
self:play_blowout_sound("impact01")
else
self:play_blowout_sound("impact02")
end
self.stages["impact"] = true
--self.debugMessages[2]:SetText("impact launched at: "..tostring(diff_sec).." rnd: "..tostring(rnd_sound))
end
-- start rumble
if (diff_sec >= self.rumble_sec) and (self.stages["rumble"] == nil) and not self.blowout_sound then
print_dbg("diff_sec>=%s | blowout rumble", diff_sec)
xr_sound.play_sound_looped(AC_ID, "blowout_rumble")
xr_sound.set_volume_sound_looped(AC_ID, "blowout_rumble", 0.25)
self.stages["rumble"] = true
self.blowout_sound = true
--self.debugMessages[2]:SetText("rumble launched at: "..tostring(diff_sec))
end
-- rumble sound fade in
if (diff_sec >= self.rumble_sec) and (diff_sec <= self.rumble_sec + 25) and (self.stages["rumble"] ~= nil) and self.blowout_sound then
print_dbg("diff_sec>=%s | blowout rumble sound fade in", diff_sec)
xr_sound.set_volume_sound_looped(AC_ID, "blowout_rumble", fade(diff_game_sec, self.rumble_sec * 5, (self.rumble_sec + 25) * 5, 0.25, 1))
--self.debugMessages[3]:SetText("rumble vol: "..tostring(fade(diff_game_sec,50*5,75*5,0.25,1)))
end
-- 1st earthquake
if (diff_sec >= self.first_earthquake_sec) and (self.stages["quake20"] == nil) then
print_dbg("diff_sec>=%s | 1st earthquake", diff_sec)
--self.debugMessages[2]:SetText("eathquake started at: "..tostring(diff_sec))
level.add_cam_effector("camera_effects\\earthquake_20.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake20"] = true
--self.debugMessages[2]:SetText("quake20 at: "..tostring(diff_sec))
end
-- second message
if (diff_sec >= self.second_message_sec) and not self.second_message_given then
print_dbg("diff_sec>=%s | second message", diff_sec)
--[[if (warn=="radio" or warn=="radio_siren") then
local level_name = level.name()
if(level_name == "zaton" and not utils_obj.npc_in_zone(db.actor,"zat_a2_sr_noweap")) then
xr_sound.set_sound_play(AC_ID, "zat_a2_stalker_barmen_surge_phase_2")
elseif(level_name == "jupiter" and not utils_obj.npc_in_zone(db.actor,"jup_a6_sr_noweap")) then
xr_sound.set_sound_play(AC_ID, "jup_a6_stalker_medik_phase_2")
elseif (level_name == "l03_agroprom") then
xr_sound.set_sound_play(AC_ID, "kovalsky_surge_phase_2")
end
end]]
self.second_message_given = true
--self.debugMessages[2]:SetText("second message given at: "..tostring(diff_sec))
end
-- earthquakes
if (diff_sec >= self.earthquakes1_sec) and (self.stages["quake40"] == nil) then
print_dbg("diff_sec>=%s | earthquakes quake40", diff_sec)
xr_sound.play_sound_looped(AC_ID, "surge_earthquake_sound_looped")
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake_40.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake40"] = true
--self.debugMessages[2]:SetText("quake40 at: "..tostring(diff_sec))
end
if (diff_sec >= self.earthquakes2_sec) and (self.stages["quake60"] == nil) then
print_dbg("diff_sec>=%s | earthquakes quake60", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake_60.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake60"] = true
--self.debugMessages[2]:SetText("quake60 at: "..tostring(diff_sec))
end
if (diff_sec >= self.earthquakes3_sec) and (self.stages["quake80"] == nil) then
print_dbg("diff_sec>=%s | earthquakes quake80", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake_80.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake80"] = true
--self.debugMessages[2]:SetText("quake80 at: "..tostring(diff_sec))
end
if (diff_sec >= self.earthquakes4_sec) and (self.stages["quake100"] == nil) then
print_dbg("diff_sec>=%s | earthquakes quake100", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake100"] = true
--self.debugMessages[2]:SetText("quake100 at: "..tostring(diff_sec))
end
if load_var(db.actor, "surge_immuned", false) ~= true then -- special surged immuned for tasks
-- 1st wave sound
if (diff_sec >= self.first_wave_sound_sec) and (self.stages["1stwavesnd"] == nil) then
print_dbg("diff_sec>=%s | 1st wave sound", diff_sec)
if rnd_sound == 1 then
self:play_blowout_sound("wave01")
elseif rnd_sound == 2 then
self:play_blowout_sound("wave02")
elseif rnd_sound == 3 then
self:play_blowout_sound("wave03")
elseif rnd_sound == 4 then
self:play_blowout_sound("wave04")
end
self.stages["1stwavesnd"] = true
--self.debugMessages[2]:SetText("1st wave snd at: "..tostring(diff_sec).." rnd: "..tostring(rnd_sound))
end
-- 1st wave
if (diff_sec >= self.first_wave_sec) and (self.stages["1stwave"] == nil) then
print_dbg("diff_sec>=%s | 1st wave", diff_sec)
self:start_wave(1, diff_game_sec)
self.stages["1stwave"] = true
--self.debugMessages[2]:SetText("1st wave at: "..tostring(diff_sec))
end
-- 2nd wave sound
if (diff_sec >= self.second_wave_sound_sec) and (self.stages["2ndwavesnd"] == nil) then
print_dbg("diff_sec>=%s | 2nd wave sound", diff_sec)
if rnd_sound == 1 then
self:play_blowout_sound("wave01")
elseif rnd_sound == 2 then
self:play_blowout_sound("wave02")
elseif rnd_sound == 3 then
self:play_blowout_sound("wave03")
elseif rnd_sound == 4 then
self:play_blowout_sound("wave04")
end
self.stages["2ndwavesnd"] = true
--self.debugMessages[2]:SetText("2nd wave snd at: "..tostring(diff_sec))
end
-- 2nd wave
if (diff_sec >= self.second_wave_sec) and (self.stages["2ndwave"] == nil) then
print_dbg("diff_sec>=%s | 2nd wave", diff_sec)
self:start_wave(2, diff_game_sec)
self.stages["2ndwave"] = true
--self.debugMessages[2]:SetText("2nd wave at: "..tostring(diff_sec))
end
end
-- earthquakes fade
if (diff_sec >= self.earthquakes1_fade_sec) and (self.stages["quake100"] == true) then
print_dbg("diff_sec>=%s | earthquakes fade quake100", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake_80.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake100"] = false
--self.debugMessages[2]:SetText("quake80 at: "..tostring(diff_sec))
end
if (diff_sec >= self.earthquakes2_fade_sec) and (self.stages["quake80"] == true) then
print_dbg("diff_sec>=%s | earthquakes fade quake80", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake_60.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake80"] = false
--self.debugMessages[2]:SetText("quake60 at: "..tostring(diff_sec))
end
if (diff_sec >= self.earthquakes3_fade_sec) and (self.stages["quake60"] == true) then
print_dbg("diff_sec>=%s | earthquakes fade quake60", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake_40.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake60"] = false
--self.debugMessages[2]:SetText("quake40 at: "..tostring(diff_sec))
end
if (diff_sec >= self.earthquakes4_fade_sec) and (self.stages["quake40"] == true) then
print_dbg("diff_sec>=%s | earthquakes fade quake40", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
level.add_cam_effector("camera_effects\\earthquake_20.anm", earthquake_cam_eff, true, "", 0, false)
self.stages["quake40"] = false
--self.debugMessages[2]:SetText("quake20 at: "..tostring(diff_sec))
end
if (diff_sec >= self.earthquakes5_fade_sec) and (self.stages["quake20"] == true) and (self.stages["quakeended"] == nil) then
print_dbg("diff_sec>=%s | earthquakes fade quake20", diff_sec)
level.remove_cam_effector(earthquake_cam_eff)
self.stages["quakeended"] = true
--self.debugMessages[2]:SetText("eathquake ended at: "..tostring(diff_sec))
end
-- rumble and quake sound fade out
if (diff_sec >= self.earthquakes1_fade_sec) and (diff_sec <= self.earthquakes5_fade_sec) and (self.stages["rumble"] ~= nil) and self.blowout_sound then
print_dbg("diff_sec>=%s | rumble and quake sound fade out", diff_sec)
xr_sound.set_volume_sound_looped(AC_ID, "blowout_rumble", fade(diff_game_sec, self.earthquakes1_fade_sec * 5, self.earthquakes5_fade_sec * 5, 1, 0))
xr_sound.set_volume_sound_looped(
AC_ID,
"surge_earthquake_sound_looped",
fade(diff_game_sec, self.earthquakes1_fade_sec * 5, self.earthquakes5_fade_sec * 5, 1, 0)
)
--self.debugMessages[3]:SetText("rumble vol: "..tostring(fade(diff_game_sec,200*5,214*5,1,0)))
end
-- rumble and quake sound stop
if (diff_sec >= self.earthquakes5_fade_sec) and (self.stages["rumble"] == true) and self.blowout_sound then
print_dbg("diff_sec>=%s | rumble and quake sound stop", diff_sec)
xr_sound.stop_sound_looped(AC_ID, "blowout_rumble")
xr_sound.stop_sound_looped(AC_ID, "surge_earthquake_sound_looped")
--self.debugMessages[2]:SetText("sounds stopped at: "..tostring(diff_sec))
self.stages["rumble"] = false
end
-- end message
if (diff_sec >= self.end_message_sec) and (self.stages["endmessage"] == nil) then
print_dbg("diff_sec>=%s | end message", diff_sec)
--[[if (level) and (warn=="radio" or warn=="radio_siren") then
if(level.name()=="zaton" and not utils_obj.npc_in_zone(db.actor,"zat_a2_sr_noweap")) then
xr_sound.set_sound_play(AC_ID, "zat_a2_stalker_barmen_after_surge")
elseif(level.name()=="jupiter"and not utils_obj.npc_in_zone(db.actor,"jup_a6_sr_noweap")) then
xr_sound.set_sound_play(AC_ID, "jup_a6_stalker_medik_after_surge")
elseif (level.name() == "l03_agroprom") then
xr_sound.set_sound_play(AC_ID, "kovalsky_after_surge")
end
end]]
--self.debugMessages[2]:SetText("end message at: "..tostring(diff_sec))
self.stages["endmessage"] = true
end
-- after sound
if (diff_sec >= self.after_sound_sec) and (self.stages["endsnd"] == nil) then
print_dbg("diff_sec>=%s | after sound", diff_sec)
xr_sound.set_sound_play(AC_ID, "blowout_hit_3")
self.stages["endsnd"] = true
--self.debugMessages[2]:SetText("ending snd at: "..tostring(diff_sec))
end
end
-- update every 2 game sec
if prev_game_sec ~= diff_game_sec then
prev_game_sec = diff_game_sec
if diff_sec < self.surge_time then
print_dbg("diff_sec>=%s | effectors", diff_sec)
local actor_pos = db.actor:position()
-- hitFactor changes
--self.debugMessages[3]:SetText("hitFactor: "..tostring(self.hitFactor))
--self.debugMessages[4]:SetText("psy_health="..tostring(db.actor.psy_health))
if (diff_sec >= self.first_wave_sound_sec) and (diff_sec < self.earthquakes4_fade_sec) and not self.effector_set then
level.add_pp_effector("psychic.ppe", surge_shock_pp_eff, true)
level.set_pp_effector_factor(surge_shock_pp_eff, 0.001)
self.effector_set = true
end
if (diff_sec >= self.first_wave_sound_sec) and (diff_sec <= self.first_wave_sec) and self.effector_set then
self.hitFactor = fade(diff_sec, self.first_wave_sound_sec, self.first_wave_sec, 0.1, 1)
end
if (diff_sec >= self.first_wave_sec) and (diff_sec <= self.second_wave_sound_sec) and self.effector_set then
self.hitFactor = fade(diff_sec, self.first_wave_sec, self.second_wave_sound_sec, 1, 0.3)
end
if (diff_sec >= self.second_wave_sound_sec) and (diff_sec <= self.second_wave_sec) and self.effector_set then
self.hitFactor = fade(diff_sec, self.second_wave_sound_sec, self.second_wave_sec, 0.3, 1)
end
if (diff_sec >= self.second_wave_sec) and (diff_sec <= self.earthquakes1_fade_sec) and self.effector_set then
self.hitFactor = fade(diff_sec, self.second_wave_sec, self.earthquakes1_fade_sec, 1, 0.3)
end
if (diff_sec >= self.earthquakes1_fade_sec) and (diff_sec <= self.earthquakes4_fade_sec) and self.effector_set then
self.hitFactor = fade(diff_sec, self.earthquakes1_fade_sec, self.earthquakes4_fade_sec, 0.3, 0.001)
end
if (diff_sec >= self.earthquakes4_fade_sec) and self.effector_set then
self.hitFactor = 0
level.remove_pp_effector(surge_shock_pp_eff)
self.effector_set = false
end
print_dbg("sec %s, hit factor %s", diff_sec, self.hitFactor)
-- setting effector
local PPEfactor
local hitPower
if not (GetEvent("current_safe_cover")) then
PPEfactor = self.hitFactor
hitPower = self.hitFactor / 30
if PPEfactor < 0.001 then
PPEfactor = 0.001
end
--self.debugMessages[6]:SetText("outside")
else
PPEfactor = 0.002
hitPower = 0
--self.debugMessages[6]:SetText("in cover")
end
currentPPEfactor = currentPPEfactor + (PPEfactor - currentPPEfactor) * 0.1
if not level_environment.is_actor_immune() then
if self.effector_set then
level.set_pp_effector_factor(surge_shock_pp_eff, currentPPEfactor)
self:displayIndicators(currentPPEfactor)
if db.actor:alive() and character_community(db.actor) ~= "actor_monolith" then
local h = hit()
h.type = hit.telepatic
h.power = self:hit_power(hitPower, h.type)
h.impulse = 0.0
h.direction = VEC_Z
h.draftsman = db.actor
if
self.survive
and xr_logic.pick_section_from_condlist(db.actor, nil, self.survive) == "true"
then
if db.actor.health <= h.power then
if db.actor.health - 0.05 > 0.05 then
h.power = 0.05
else
h.power = 0
end
end
end
print_dbg("diff_sec>=%s | hit actor", diff_sec)
db.actor:hit(h)
end
end
end
--self.debugMessages[5]:SetText("PPE: "..tostring(currentPPEfactor))
--self.debugMessages[7]:SetText("hitPower: "..tostring(hitPower))
-- Waves
local wavevol = 0
local fate = ui_options.get("alife/event/emission_fate") or "kill_at_wave"
for k, wave in pairs(self.blowout_waves) do
if wave.effect:playing() then
local wave_pos = vector():set(
actor_pos.x,
actor_pos.y / 2,
wave.inited_pos.z - (diff_game_sec - wave.inited_time) * 3
)
wave.effect:move_to(wave_pos, VEC_ZERO)
local wavevoltemp = (250 - math.abs(actor_pos.z - wave_pos.z)) / 250
if wavevoltemp > wavevol then
wavevol = wavevoltemp
end
if wave_pos.z < actor_pos.z - 250 then
wave.effect:stop_deffered()
if k == 2 then
--kill all remaining npcs
if fate == "turn_to_zombie" or fate == "explode" then
print_dbg("diff_sec>=%s | turn_to_zombie", diff_sec)
self:kill_objects_at_pos(-10000, fate)
end
end
end
self:kill_crows_at_pos(wave_pos.z)
if fate == "kill_at_wave" then
-- if (prev_sec == prev_game_sec/5) then -- Commenting that bit
if 1 then
print_dbg("diff_sec>=%s | kill_objects_at_pos", diff_sec)
self:kill_objects_at_pos(wave_pos.z, fate)
end
end
if fate == "explode" then
print_dbg("diff_sec>=%s | explode", diff_sec)
self:kill_objects_at_pos(wave_pos.z, fate)
end
if (fate == "turn_to_zombie") and (self.zombie_at_first_wave or k == 2) then
print_dbg("diff_sec>=%s | turn_to_zombie", diff_sec)
self:kill_objects_at_pos(wave_pos.z, fate)
end
if wave_pos.z < actor_pos.z then
if
k == 1
and not GetEvent("current_safe_cover")
and (character_community(db.actor) ~= "actor_monolith")
and (character_community(db.actor) ~= "actor_zombied")
then
local h = hit()
h.type = hit.telepatic
h.power = self:hit_power(2, h.type) + 0.5
h.impulse = 0.0
h.direction = VEC_Z
h.draftsman = db.actor
if xr_logic.pick_section_from_condlist(db.actor, nil, self.survive) == "true" then
if db.actor.health <= h.power then
if db.actor.health - 0.05 > 0.05 then
h.power = 0.05
else
h.power = 0
end
end
end
if not level_environment.is_actor_immune() then
print_dbg("diff_sec>=%s | hit actor 2", diff_sec)
db.actor:hit(h)
end
self.stages["1stwavehit"] = true
--self.debugMessages[2]:SetText("first wave hit at: "..tostring(diff_sec)..' with strength: '..h.power)
end
if k == 2 and self.stages["2ndwavehit"] == nil then
if fate == "kill_at_end" then
print_dbg("diff_sec>=%s | kill_at_end", diff_sec)
self:kill_all_unhided()
else
if not (level_environment.is_actor_immune()) then
print_dbg("diff_sec>=%s | kill_actor_at_pos", diff_sec)
self:kill_actor_at_pos(wave_pos.z)
end
end
self.stages["2ndwavehit"] = true
--self.debugMessages[2]:SetText("second wave hit at: "..tostring(diff_sec))
end
end
end
end
if self.wave_sound then
--self.debugMessages[1]:SetText("wave vol: "..tostring(wavevol))
xr_sound.set_volume_sound_looped(AC_ID, "blowout_particle_wave_looped", wavevol)
end
end
end
end
----------------------------------
-- Effects
----------------------------------
function CSurgeManager:displayIndicators(power)
local hud = get_hud()
if not hud then
return
end
local indik
if power >= 0.8 then
indik = "red"
elseif power >= 0.6 then
indik = "orange"
elseif power >= 0.4 then
indik = "yellow"
elseif power >= 0.1 then
indik = "green"
else
indik = nil
end
local aspectRatio = ""
if self.aspectRatio ~= 1 then
aspectRatio = "_16"
end
local indikName = "atm_indik_psi_" .. tostring(indik) .. aspectRatio
local currentIndikName = "atm_indik_psi_" .. tostring(currentIndik) .. aspectRatio
--remove indik if 0
if not indik and currentIndik ~= nil then
local hudIndikRem = hud:GetCustomStatic(currentIndikName)
if hudIndikRem then
hud:RemoveCustomStatic(currentIndikName)
end
currentIndik = nil
return
end
-- display different indik
if currentIndik ~= indik then
-- first remove existing one
if currentIndik ~= nil then
local hudIndik = hud:GetCustomStatic(currentIndikName)
if hudIndik then
hud:RemoveCustomStatic(currentIndikName)
end
end
-- now display new one
if indik then
hud:AddCustomStatic(indikName, true)
currentIndik = indik
end
end
--self.debugMessages[8]:SetText("indik="..currentIndikName)
end
function CSurgeManager:play_blowout_sound(id)
local snd_obj = self.blowout_sounds[id]
if snd_obj ~= nil and snd_obj:playing() then
snd_obj:stop()
end
if snd_obj ~= nil then
snd_obj:play(db.actor)
snd_obj.volume = 1
--self.debugMessages[12]:SetText("playing: "..tostring(id))
end
end
function CSurgeManager:play_siren_sound()
local snd_obj = self.blowout_sounds["siren"]
if snd_obj == nil then
return
end
local snd_position
if snd_obj:playing() then
snd_obj:stop()
end
if level.name() == "zaton" then
snd_position = vector():set(115, 8, 184)
elseif level.name() == "jupiter" then
snd_position = vector():set(-50, 16, 198)
else
snd_position = vector():set(147, 15, -187)
end
snd_obj:play_at_pos(db.actor, snd_position)
snd_obj.volume = 1
end
function CSurgeManager:launch_rockets()
for k, v in pairs(db.signal_light) do
if not (v:is_flying()) then
v:launch()
end
end
end
function surge_callback()
level.add_cam_effector("camera_effects\\surge_01.anm", sleep_cam_eff, false, "surge_manager.surge_callback2")
-- level.stop_weather_fx()
-- level.change_game_time(0,0,15)
-- level_weathers.get_weather_manager():forced_weather_change()
end
function surge_callback2()
xr_effects.enable_ui(db.actor, nil)
--[[
level.enable_input()
level.show_indicators()
db.actor:restore_weapon()
]]
--
end
----------------------------------
-- NPC fate
----------------------------------
function make_dead_crow(id, powr)
local crow = id and level.object_by_id(id)
if crow and crow:alive() and powr then
crow:hit(powr)
end
return true
end
function make_dead(id)
local obj = id and db.storage[id] and db.storage[id].object --level.object_by_id(id)
if obj and obj:alive() then
obj:kill(obj)
else
local se_obj = id and alife_object(id)
if se_obj and se_obj:alive() then
se_obj:kill()
end
end
return true
end
function CSurgeManager:kill_crows_at_pos(pos, delay) -- hit all crows if their pos is bigger than "pos"
local h = hit()
h.type = hit.fire_wound
h.power = 0.9
h.impulse = 0.0
h.direction = VEC_Z
h.draftsman = db.actor
for k, id in pairs(bind_crow.crow_storage) do
if delay then
CreateTimeEvent("delay_kill_crow", id, math_random(1, 3), make_dead_crow, id, h)
else
local crow = level.object_by_id(id)
if crow and crow:alive() and pos < crow:position().z then
crow:hit(h)
end
end
end
end
function CSurgeManager:set_grace_threshold(npc)
local id = npc:id()
db.storage[id].surge_cover_grace_threshold_time = time_global()
end
function CSurgeManager:check_grace_threshold(npc)
local id = npc:id()
local gt = db.storage[id].surge_cover_grace_threshold_time or 0
return ((time_global() - gt) < 1500)
end
function CSurgeManager:kill_objects_at_pos(surge_pos, fate, delay)
-- if (level_environment.is_actor_immune()) then
-- return
-- end
local can_kill = (fate == "kill_at_wave")
or (fate == "turn_to_zombie")
or (fate == "explode")
or (fate == "kill_at_end")
if not can_kill then
print_dbg("kill_objects_at_pos | cant kill because fate is [%s]", fate)
return
end
print_dbg("kill_objects_at_pos | pos: %s - fate: %s - delay: %s", surge_pos, fate, delay)
local id, comm, npc_pos, se_npc, npc, squad, squad_id, smart, surge_smart
local board = SIMBOARD
local sim = alife()
for i = 1, #db.OnlineStalkers do
id = db.OnlineStalkers[i]
npc = db.storage[id] and db.storage[id].object or level.object_by_id(id)
if npc then
comm = npc:character_community()
npc_pos = npc:position()
if
npc_pos
and IsStalker(npc) -- must be stalker
and npc:alive() -- must be alive
and (comm ~= "monolith") -- not monolith (immune)
and (comm ~= "zombied") -- not zombied (immune)
and (not get_object_story_id(id)) -- not story npc
and ((surge_pos == false) or (surge_pos and npc_pos.z > surge_pos)) -- npc is behind emission wave
and not (id == AC_ID and level_environment.is_actor_immune()) -- npc is actor and not immune
and not surge_manager_ignore_npc.ignore_npc[npc:section()] -- not important npc (immune)
and not self:pos_in_cover(npc_pos) -- not in cover
and not self:check_grace_threshold(npc)
then
se_npc = sim:object(id)
if se_npc then
if fate == "turn_to_zombie" then
squad_id = se_npc.group_id
squad = squad_id and squad_id ~= 65535 and sim:object(squad_id)
if not self:pos_in_cover(npc_pos) then -- if stalker is not inside
print_dbg("kill_objects_at_pos | make npc zombie [%s]", se_npc:name())
self:turn_to_zombie(se_npc, squad)
else
if self:pos_in_cover(npc_pos) then
self:set_grace_threshold(npc)
print_dbg("cant make npc zombie %s, in cover", se_npc:name())
end
end
else
if not self:pos_in_cover(npc_pos) then -- if stalker is not inside
if fate == "explode" then
print_dbg("kill_objects_at_pos | explode [%s]", se_npc:name())
self:explode(se_npc)
elseif fate == "kill_at_wave" or fate == "kill_at_end" then
print_dbg("kill_objects_at_pos | kill npc [%s]", se_npc:name())
if delay then
CreateTimeEvent("delay_kill", id, math_random(1, 3), make_dead, id)
else
npc:kill(npc)
end
end
else
if self:pos_in_cover(npc_pos) then
self:set_grace_threshold(npc)
print_dbg("cant kill %s, in cover", se_npc:name())
end
print_dbg(
"kill_objects_at_pos | cant kill npc [%s] | surge_smart: %s - in_cover: %s",
se_npc:name(),
surge_smart,
self:pos_in_cover(npc_pos, true)
)
end
end
end
else
if not IsStalker(npc) then
print_dbg("cant kill %s, must be stalker", npc:name())
end
if not npc:alive() then
print_dbg("cant kill %s, must be alive", npc:name())
end
if not (comm ~= "monolith") then
print_dbg("cant kill %s, is monolith (immune)", npc:name())
end
if not (comm ~= "zombied") then
print_dbg("cant kill %s, is zombied (immune)", npc:name())
end
if not (not get_object_story_id(id)) then
print_dbg("cant kill %s, is story npc", npc:name())
end
if not ((surge_pos == false) or (surge_pos and npc_pos.z > surge_pos)) then
print_dbg(
"cant kill %s, npc is behind emission wave, npc pos %s, wave pos %s",
npc:name(),
npc_pos.z,
surge_pos
)
end
if not not (id == AC_ID and level_environment.is_actor_immune()) then
print_dbg("cant kill %s, npc is actor and immune", npc:id())
end
if not not surge_manager_ignore_npc.ignore_npc[npc:section()] then
print_dbg("cant kill %s, is important, section %s", npc:name(), npc:section())
end
if npc_pos and self:pos_in_cover(npc_pos) then
self:set_grace_threshold(npc)
print_dbg(
"cant kill npc [%s] | in_cover: %s",
npc:name(),
npc_pos and self:pos_in_cover(npc_pos, true)
)
end
end
end
end
end
function CSurgeManager:kill_actor_at_pos(pos) -- kill player at end
if level_environment.is_actor_immune() then
return
end
if db.actor and db.actor:alive() and db.actor:position().z > pos then
if not (GetEvent("current_safe_cover")) then
xr_effects.disable_ui_only(db.actor, nil)
if
xr_logic.pick_section_from_condlist(get_story_object("actor"), nil, self.survive) ~= "true"
and not level_environment.is_actor_immune()
then
local fate = ui_options.get("alife/event/emission_fate") or "kill_at_wave"
self:kill_objects_at_pos(false, fate, true)
db.actor:kill(db.actor)
self.ui_disabled = true
return
else
level.add_cam_effector(
"camera_effects\\surge_02.anm",
sleep_cam_eff,
false,
"surge_manager.surge_callback"
)
level.add_pp_effector("surge_fade.ppe", sleep_fade_pp_eff, false)
--db.actor:change_health(-0.05)
self:end_surge(true)
end
end
end
end
function CSurgeManager:kill_all_unhided() -- called only when "kill_at_end" is active, turn to zombie + explode + kill with delay, kill actor
-- if (level_environment.is_actor_immune()) then
-- return
-- end
-- delay hit for crows
self:kill_crows_at_pos(false, true)
-- delay kill for online npcs
local fate = ui_options.get("alife/event/emission_fate") or "kill_at_wave"
self:kill_objects_at_pos(false, fate, true)
if level_environment.is_actor_immune() then
return
end
-- don't kill actor if he isn't on a valid level
local sim, gg = alife(), game_graph()
local actor_level = sim:level_name(gg:vertex(sim:actor().m_game_vertex_id):level_id())
if self.indoor_levels and self.indoor_levels[actor_level] or level_weathers.valid_levels[actor_level] ~= true then
return
end
if db.actor and db.actor:alive() and not GetEvent("current_safe_cover") then
--[[
if has_alife_info("anabiotic_in_process") then
local counter_name = "actor_marked_by_zone_cnt"
local cnt_value = load_var(db.actor, counter_name, 0)
save_var(db.actor, counter_name, cnt_value + 1)
end
]]
xr_effects.disable_ui_only(db.actor, nil)
if
xr_logic.pick_section_from_condlist(get_story_object("actor"), nil, self.survive) ~= "true"
and not level_environment.is_actor_immune()
then
db.actor:kill(db.actor)
return
else
level.add_cam_effector("camera_effects\\surge_02.anm", sleep_cam_eff, false, "surge_manager.surge_callback")
level.add_pp_effector("surge_fade.ppe", sleep_fade_pp_eff, false)
--db.actor.health = db.actor.health-0.05
end
end
end
function CSurgeManager:turn_to_zombie(se_obj, squad)
if not se_obj then return end
-- if not squad then
-- se_obj:kill()
-- return
-- end
self.zombie_count = self.zombie_count and self.zombie_count + 1 or 0
-- if self.zombie_count > 12 then
-- se_obj:kill()
-- return
-- end
local zombie_type
local section_number = string.sub(se_obj:section_name(), -1)
if section_number == "4" then
zombie_type = "sim_default_zombied_4"
elseif section_number == "3" then
zombie_type = "sim_default_zombied_3"
elseif section_number == "2" or section_number == "1" then
zombie_type = "sim_default_zombied_2"
else
zombie_type = "sim_default_zombied_1"
end
local pos = se_obj.position
local lvid = se_obj.m_level_vertex_id
local gvid = se_obj.m_game_vertex_id
if squad then
squad:remove_npc(se_obj.id, true)
else
alife():release(se_obj)
end
alife_create(zombie_type, pos, lvid, gvid)
end
function CSurgeManager:explode(se_obj, squad)
-- play body tear and scary sound
self.body_tears[se_obj.id] = particles_object("anomaly2\\body_tear_0" .. math.random(1, 2))
self.body_tears[se_obj.id]:play_at_pos(se_obj.position)
local snd_obj = self.blowout_sounds["body_tear"]
if snd_obj then
if snd_obj:playing() then
snd_obj:stop()
end
snd_obj:play_at_pos(db.actor, se_obj.position)
snd_obj.volume = 1
end
-- alife():release(se_obj)
if not squad then
alife():release(se_obj)
return
end
squad:remove_npc(se_obj.id, true)
end
----------------------------------
-- Task
----------------------------------
function CSurgeManager:give_surge_hide_task()
if self.surge_message ~= "empty" then
local mess = ""
if self.surge_message == "" then
local time = 0
mess = game.translate_string("hide_from_surge_message")
else
mess = game.translate_string(self.surge_message)
end
end
if self.surge_task_sect ~= "empty" then
if self.surge_task_sect == "" then
task_manager.get_task_manager():give_task("hide_from_surge")
else
task_manager.get_task_manager():give_task(self.surge_task_sect)
end
end
end
function get_task_descr()
if GetEvent("current_safe_cover") then
return game.translate_string("hide_from_surge_descr_2_a")
end
return game.translate_string("hide_from_surge_descr_1_a")
end
function get_task_target()
if GetEvent("current_safe_cover") then
return nil
end
return GetEvent("nearest_safe_cover")
end
function set_surge_task(tsk)
get_surge_manager().surge_task_sect = tsk
end
----------------------------------
-- Data management
----------------------------------
function actor_on_save(binder, packet)
if USE_MARSHAL then
return
end
-- initialize to set default values if not loaded
local mgr = SurgeManager
if not mgr._state_loaded then
mgr:initialize()
mgr._state_loaded = true
end
set_save_marker(packet, "save", false, "SurgeHide")
utils_data.w_stpk(packet, "bool", mgr.finished, "CSurgeManager:finished")
utils_data.w_stpk(packet, "bool", mgr.started, "CSurgeManager:started")
utils_data.w_stpk(packet, "CTime", mgr.last_surge_time, "CSurgeManager:last_surge_time")
if mgr.started then
utils_data.w_stpk(packet, "CTime", mgr.inited_time, "CSurgeManager:inited_time")
utils_data.w_stpk(packet, "bool", mgr.task_given, "CSurgeManager:task_given")
utils_data.w_stpk(packet, "bool", mgr.effector_set, "CSurgeManager:effector_set")
utils_data.w_stpk(packet, "bool", mgr.second_message_given, "CSurgeManager:second_message_given")
utils_data.w_stpk(packet, "bool", mgr.ui_disabled, "CSurgeManager:ui_disabled")
utils_data.w_stpk(packet, "bool", mgr.blowout_sound, "CSurgeManager:blowout_sound")
--utils_data.w_stpk(packet,"stringZ",mgr.surge_message,"CSurgeManager:surge_message")
--utils_data.w_stpk(packet,"stringZ",mgr.surge_task_sect,"CSurgeManager:surge_task_sect")
utils_data.w_stpk(packet, "u32", mgr.game_time_factor, "CSurgeManager:game_time_factor")
end
utils_data.w_stpk(packet, "u32", mgr._delta, "CSurgeManager:_delta")
set_save_marker(packet, "save", true, "SurgeHide")
end
function actor_on_load(binder, packet)
--printf("actor on load")
local mgr = get_surge_manager()
if not mgr._state_loaded then
mgr:initialize()
mgr._state_loaded = true
end
if USE_MARSHAL then
return
end
set_save_marker(packet, "load", false, "SurgeHide")
mgr.finished = packet:r_bool()
mgr.started = packet:r_bool()
mgr.last_surge_time = utils_data.r_CTime(packet, "surge_manager") or game.get_game_time()
if mgr.started then
mgr.inited_time = utils_data.r_CTime(packet, "surge_manager") or game.get_game_time()
mgr.task_given = packet:r_bool()
mgr.effector_set = packet:r_bool()
mgr.second_message_given = packet:r_bool()
mgr.ui_disabled = packet:r_bool()
mgr.blowout_sound = packet:r_bool()
--mgr.surge_message = packet:r_stringZ()
--mgr.surge_task_sect = packet:r_stringZ()
mgr.game_time_factor = packet:r_u32()
mgr:finalize()
mgr.blowout_waves = empty_table(mgr.blowout_waves)
mgr.objects_to_kill = empty_table(mgr.objects_to_kill)
mgr.stages = empty_table(mgr.stages)
mgr.body_tears = empty_table(mgr.body_tears)
end
mgr._delta = packet:r_u32()
set_save_marker(packet, "load", true, "SurgeHide")
end
function save_state(m_data)
--utils_data.debug_write("SurgeManager:save_state BEFORE")
m_data.SurgeManager = {}
local mgr = get_surge_manager()
if not mgr._state_loaded then
mgr:initialize()
mgr._state_loaded = true
end
m_data.SurgeManager.finished = mgr.finished == nil and true or mgr.finished
m_data.SurgeManager.started = mgr.started == nil and false or mgr.started
m_data.SurgeManager.last_surge_time = mgr.last_surge_time and utils_data.CTime_to_table(mgr.last_surge_time)
or game.get_game_time()
if mgr.started then
m_data.SurgeManager.inited_time = utils_data.CTime_to_table(mgr.inited_time) or game.get_game_time()
--m_data.SurgeManager.levels_respawn = mgr.levels_respawn
m_data.SurgeManager.task_given = mgr.task_given
m_data.SurgeManager.effector_set = mgr.effector_set
m_data.SurgeManager.second_message_given = mgr.second_message_given
m_data.SurgeManager.ui_disabled = mgr.ui_disabled
m_data.SurgeManager.blowout_sound = mgr.blowout_sound
m_data.SurgeManager.game_time_factor = mgr.game_time_factor
end
m_data.SurgeManager._delta = mgr._delta
--utils_data.debug_write("SurgeManager:save_state AFTER")
end
function load_state(m_data)
if not m_data.SurgeManager then
return
end
--utils_data.debug_write("SurgeManager:load_state BEFORE")
local mgr = get_surge_manager()
mgr:initialize()
mgr.finished = m_data.SurgeManager.finished
mgr.started = m_data.SurgeManager.started
mgr.last_surge_time = m_data.SurgeManager.last_surge_time
and utils_data.CTime_from_table(m_data.SurgeManager.last_surge_time)
or game.get_game_time()
if mgr.started == true and mgr.finished == false then
mgr.inited_time = m_data.SurgeManager.inited_time
and utils_data.CTime_from_table(m_data.SurgeManager.inited_time)
or game.get_game_time()
mgr.task_given = m_data.SurgeManager.task_given or false
mgr.effector_set = m_data.SurgeManager.effector_set or false
mgr.second_message_given = m_data.SurgeManager.second_message_given or false
mgr.ui_disabled = m_data.SurgeManager.ui_disabled or false
mgr.blowout_sound = m_data.SurgeManager.blowout_sound or false
mgr.game_time_factor = m_data.SurgeManager.game_time_factor or level.get_time_factor()
mgr:finalize()
mgr.blowout_waves = empty_table(mgr.blowout_waves)
mgr.objects_to_kill = empty_table(mgr.objects_to_kill)
mgr.stages = empty_table(mgr.stages)
mgr.body_tears = empty_table(mgr.body_tears)
else
mgr.started = false
mgr.finished = true
end
mgr._delta = m_data.SurgeManager._delta
mgr._state_loaded = true
m_data.SurgeManager = nil
--utils_data.debug_write("SurgeManager:load_state BEFORE")
end
----------------------------------
-- Utilities
----------------------------------
function start_surge(p)
get_surge_manager():start(true)
end
function stop_surge()
if get_surge_manager().started then
get_surge_manager():end_surge(true)
end
end
function is_started()
return get_surge_manager().started
end
function is_finished()
return not get_surge_manager().started
end
function is_loaded()
return get_surge_manager()._state_loaded == true
end
function actor_in_cover()
return GetEvent("current_safe_cover") and true or false
end
function npc_in_cover(npc)
return get_surge_manager():pos_in_cover(npc:position())
end
function job_in_surge_cover(se_obj, job)
if not job.alife_task then
return false
end
return get_surge_manager():pos_in_cover(job.alife_task:position())
end
function set_surge_message(mess)
get_surge_manager().surge_message = mess
end
function is_killing_all()
local mgr = get_surge_manager()
if mgr.started and mgr.ui_disabled then
return true
end
return false
end
function sound_started()
return get_surge_manager().started and get_surge_manager().blowout_sound
end