-- 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