Divergent/mods/Arrival/gamedata/scripts/drx_da_main.script

3711 lines
123 KiB
Plaintext
Raw Normal View History

-- Dynamic Anomaly Generator by DoctorX
-- Revisited by demonized, 2022
-- Edited by S.e.m.i.t.o.n.e. for Arrival Mod
-- Creating anomalies at the start of level after emission/psi-storm and removing anomalies after emission/psi-storm instead of just disabling them, allowing for truly dynamic generation
-- Anomalies behaviour:
-- enable/disable with randomized periods, duration and cooldowns for every single anomaly
-- behaviour if actor is near an anomaly
-- behaviour on hit
-- Spawning artefacts in new anomaly zones
--=============================================================
--
-- Dynamic Anomaly Generator (drx_da_main.script)
-- CoC 1.5b r4 - DoctorX Dynamic Anomalies 2.1
--
-- - Generates randomly placed anomalies on specified smart terrains
-- - Setting file: configs\drx\drx_da_config.ltx
--
-- Created by: DoctorX
-- Last revised: April 02, 2018
--
--=============================================================
-- Imports
local clamp = clamp
local time_global = time_global
local get_start_time = level.get_start_time
local get_game_time = game.get_game_time
local level_vertex_id = level.vertex_id
local level_vertex_position = level.vertex_position
local abs = math.abs
local ceil = math.ceil
local cos = math.cos
local floor = math.floor
local max = math.max
local min = math.min
local random = math.random
local sin = math.sin
local sqrt = math.sqrt
local CreateTimeEvent = demonized_time_events.CreateTimeEvent
local RemoveTimeEvent = demonized_time_events.RemoveTimeEvent
local process_queue = demonized_concurrent_queues.process_queue
local remove_queue = demonized_concurrent_queues.remove_queue
local add_speed = speed.add_speed
local remove_speed = speed.remove_speed
-- MCM
-- Load the defaults
local function load_defaults()
local t = {}
local op = drx_da_main_mcm.op
for i, v in ipairs(op.gr) do
if v.def ~= nil then
t[v.id] = v.def
end
end
return t
end
local settings = load_defaults()
local function load_settings()
settings = load_defaults()
if ui_mcm then
for k, v in pairs(settings) do
settings[k] = ui_mcm.get("drx_da/" .. k)
end
end
end
-- UTILS
--Recursive print of tables similar to PHP print_r function
local print_r = print_r or function(t)
local print_r_cache={}
local function sub_print_r(t,indent)
if (print_r_cache[tostring(t)]) then
printf(indent.."*"..tostring(t))
else
print_r_cache[tostring(t)]=true
if (type(t)=="table") then
for pos,val in pairs(t) do
if (type(val)=="table") then
printf(indent.."["..pos.."] => "..tostring(t).." {")
sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
printf(indent..string.rep(" ",string.len(pos)+6).."}")
else
printf(indent.."["..pos.."] => "..tostring(val))
end
end
else
printf(indent..tostring(t))
end
end
end
sub_print_r(t," ")
end
--Protected function call to prevent crashes to desktop
--Prints error in console if occured, otherwise proceed normally
--Use for test only, slower than usual
local try = try or function(func, ...)
local status, error_or_result = pcall(func, ...)
if not status then
printf(error_or_result)
return false, status, error_or_result
else
return error_or_result, status
end
end
-- Shuffle table, Fisher-Yates shuffle with preserving original table
local function shuffle(t)
local s = {}
for i = 1, #t do s[i] = t[i] end
for i = #t, 2, -1 do
local j = random(i)
s[i], s[j] = s[j], s[i]
end
return s
end
-- Get time elapsed from the start of the game in IRL seconds
local time_factor = 6
local function get_time_elapsed()
--trace(time_factor)
return floor(get_game_time():diffSec(get_start_time()) / time_factor * 10) * 0.1
end
local function round(amount)
return floor(amount + 0.5)
end
local og_printf = printf
local function printf(str, ...)
if settings.debug_mode then
og_printf("DRX DA: " .. str, ...)
end
end
--EMA smoothing for changing values, frame independent
local default_smoothing = 11.5
local smoothed_values = {}
local function ema(key, value, def, steps, delta)
local steps = steps or default_smoothing
local delta = delta or steps
local smoothing_alpha = 2.0 / (steps + 1)
smoothed_values[key] = smoothed_values[key] and smoothed_values[key] + min(smoothing_alpha * (delta / steps), 1) * (value - smoothed_values[key]) or def or value
--printf("EMA fired, key %s, target %s, current %s, going %s", key, value, smoothed_values[key], (value > smoothed_values[key] and "up" or "down"))
return smoothed_values[key]
end
local get_safe_sound_object = xr_sound.get_safe_sound_object
local function play_sound_on_actor(snd, volume, frequency, obj)
if not snd then
printf("snd is nil")
return
end
local actor = db.actor
local snd = get_safe_sound_object(snd)
if snd then
if obj and obj:id() ~= AC_ID then
snd:play_at_pos(obj, obj:position(), 0, sound_object.s3d)
else
snd:play(actor, 0, sound_object.s2d)
end
snd.volume = volume or 1
snd.frequency = frequency or 1
return snd
end
end
-- Table of current active effects, contains tables of these {
-- timer = time_elapsed + timer in seconds, how long effect will be applied
-- effect = function(), the function of the effect
-- effect_function - string, dump of effect function to store in m_data
-- effect_args - array, args to effect_function
-- on_end = function(), the function on the expiration of effect
-- on_end_function - string, dump of on_end function to store in m_data
-- on_end_args - array, args to on_end_function
-- key - string, custom key to set in timed_effects table, otherwise will be used first available one
-- not_save - boolean, do not save in mdata }
-- this is the complex function intended to have persistence between saves, moving to different maps and so on, use if needed
-- no upvalues are allowed in the functions, best to reference globals by _G. lookup
-- The precision for both cooldown and timed effects is 0.1 or 100ms, making more precise timer or timed effect is pointless
local time_elapsed = 0
timed_effects = {}
function add_timed_effect(timer, effect_function, effect_args, on_end_function, on_end_args, key, not_save)
printf("current_time %s, adding effect %s", time_elapsed, time_elapsed + (timer or 0))
local dump = string.dump
local load = loadstring
local unpack = unpack
local table_insert = table.insert
local effect_args = effect_args or {}
local on_end_args = on_end_args or {}
local effect = {
timer = time_elapsed + (timer or 0),
effect = effect_function and function()
effect_function(unpack(effect_args))
end,
effect_function = effect_function and dump(effect_function),
effect_args = effect_args,
on_end = on_end_function and function()
on_end_function(unpack(on_end_args))
end,
on_end_function = on_end_function and dump(on_end_function),
on_end_args = on_end_args,
save = not not_save
}
if key then
timed_effects[key] = effect
else
table_insert(timed_effects, effect)
end
end
-- This is the simpler version of the function above if you do not care about persistence and saving states
function add_simple_timed_effect(timer, effect_function, on_end_function, key, overwrite_mode)
-- printf("current_time %s, adding effect %s", time_elapsed, time_elapsed + (timer or 0))
if key and timed_effects[key] then
if overwrite_mode == false or overwrite_mode == 0 then
-- printf("can't add effect %s, already exists", k)
return
elseif overwrite_mode == 1 then
timed_effects[key].timer = time_elapsed + (timer or 0)
return
end
end
local dump = string.dump
local load = loadstring
local unpack = unpack
local table_insert = table.insert
local effect = {
timer = time_elapsed + (timer or 0),
effect = effect_function,
on_end = on_end_function
}
if effect.on_end then
-- printf("effect has on end function %s", effect.on_end)
end
if key then
timed_effects[key] = effect
else
table_insert(timed_effects, effect)
end
end
function remove_timed_effect(key, on_end)
if not timed_effects[key] then return end
printf("removing effect, key %s", key)
if on_end and timed_effects[key].on_end then
printf("removing effect, firing on end, key %s", key)
timed_effects[key].on_end()
end
timed_effects[key] = nil
end
-- Processing the effects
-- Whatever lowest time is set for effect, it will be processed at least once on process cycle
local function process_timed_effects()
local pairs = pairs
local printf = printf
for key, props in pairs(timed_effects) do
if props.effect then
props.effect()
end
-- printf("effect %s, timer %s, current_time %s", key, props.timer, time_elapsed)
if props.timer < time_elapsed then
printf("removing effect, effect timer %s, current_time %s", props.timer, time_elapsed)
if props.on_end then
props.on_end()
end
timed_effects[key] = nil
end
end
end
-- Callbacks
callbacks = {}
function register_callback(callback, callback_function, on_end_function, key)
if key and callbacks[key] then
UnregisterScriptCallback(callbacks[key].callback, callbacks[key].func)
end
local t = {
callback = callback,
func = callback_function,
on_end = on_end_function
}
local key = key or (#callbacks + 1)
callbacks[key] = t
printf("registering callback %s, key %s", callback, key)
RegisterScriptCallback(callbacks[key].callback, callbacks[key].func)
return key
end
function unregister_callback(key)
if not callbacks[key] then return end
printf("unregistering callback %s, %s", key, callbacks[key].callback)
UnregisterScriptCallback(callbacks[key].callback, callbacks[key].func)
if callbacks[key].on_end then
callbacks[key].on_end()
end
callbacks[key] = nil
end
function unregister_callbacks()
for i, props in pairs(callbacks) do
unregister_callback(i)
end
end
-- Get psy table to manipulate psy health values
local actor_psy_table = {}
function get_actor_psy_table()
if is_not_empty(actor_psy_table) then return end
local m_data = alife_storage_manager.get_state()
arszi_psy.save_state(m_data)
actor_psy_table = m_data.psy_table
end
function set_psy_health(amount)
if actor_psy_table.actor_psy_health then
actor_psy_table.actor_psy_health = amount <= 1 and amount or 1
end
end
function change_psy_health(amount)
if actor_psy_table.actor_psy_health then
set_psy_health(actor_psy_table.actor_psy_health + amount)
end
end
-- DRX DA
-- Location of the settings file:
local ini = ini_file("drx\\drx_da_config.ltx")
-- Table of levels that will have reduced chance to spawn anomalies
reduced_chance_levels = {
k00_marsh = true,
l03u_agr_underground = true,
l04_darkvalley = true,
l04u_labx18 = true,
l05_bar = true,
l10_radar = true,
jupiter_underground = true,
jupiter = true,
l11_pripyat = true,
pripyat = true,
zaton = true,
}
anomaly_radii = {
zone_field_radioactive = {min = 5, max = 8},
zone_field_radioactive_average = {min = 5, max = 8},
zone_field_radioactive_strong = {min = 5, max = 8},
zone_field_radioactive_weak = {min = 5, max = 8},
zone_radioactive = {min = 4, max = 6},
zone_radioactive_average = {min = 4, max = 6},
zone_radioactive_strong = {min = 4, max = 6},
zone_radioactive_weak = {min = 4, max = 6},
zone_mine_acid = {min = 2, max = 3},
zone_mine_acidic_weak = {min = 2, max = 3},
zone_mine_acidic_average = {min = 2, max = 3},
zone_mine_acidic_strong = {min = 2, max = 3},
zone_mine_blast = {min = 2, max = 3},
zone_mine_umbra = {min = 2, max = 3},
zone_mine_electra = {min = 2, max = 3},
zone_mine_electric_weak = {min = 2, max = 3},
zone_mine_electric_average = {min = 2, max = 3},
zone_mine_electric_strong = {min = 2, max = 3},
zone_mine_flash = {min = 3, max = 3},
zone_mine_ghost = {min = 2, max = 3},
zone_mine_gold = {min = 2, max = 3},
zone_mine_thorn = {min = 2, max = 3},
zone_mine_seed = {min = 3, max = 3},
zone_mine_shatterpoint = {min = 6, max = 8},
zone_mine_gravitational_weak = {min = 2, max = 3},
zone_mine_gravitational_average = {min = 3, max = 5},
zone_mine_gravitational_strong = {min = 4, max = 6},
zone_mine_sloth = {min = 3, max = 4},
zone_mine_mefistotel = {min = 3, max = 4},
zone_mine_net = {min = 2, max = 3},
zone_mine_point = {min = 2, max = 3},
zone_mine_cdf = {min = 2, max = 3},
zone_mine_sphere = {min = 4, max = 5},
zone_mine_springboard = {min = 4, max = 6},
zone_mine_thermal_weak = {min = 1, max = 2},
zone_mine_thermal_average = {min = 1, max = 2},
zone_mine_thermal_strong = {min = 1, max = 2},
zone_mine_vapour = {min = 1, max = 2},
zone_mine_vortex = {min = 3, max = 5},
zone_mine_zharka = {min = 1, max = 2},
}
updated_anomaly_levels = {}
last_surge_time = 0
function init_anomaly_table_on_level(level_name)
local level_name = level_name or level.name()
if not updated_anomaly_levels[level_name] then updated_anomaly_levels[level_name] = {} end
if not updated_anomaly_levels[level_name].cleaned_old_anomalies then updated_anomaly_levels[level_name].cleaned_old_anomalies = false end
if not updated_anomaly_levels[level_name].anomalies then updated_anomaly_levels[level_name].anomalies = {} end
if not updated_anomaly_levels[level_name].anomalies_properties then updated_anomaly_levels[level_name].anomalies_properties = {} end
if not updated_anomaly_levels[level_name].anomalies_by_smart then updated_anomaly_levels[level_name].anomalies_by_smart = {} end
if not updated_anomaly_levels[level_name].smart_by_anomalies then updated_anomaly_levels[level_name].smart_by_anomalies = {} end
if not updated_anomaly_levels[level_name].anomaly_types_by_smart then updated_anomaly_levels[level_name].anomaly_types_by_smart = {} end
if not updated_anomaly_levels[level_name].available_smarts_reduced then updated_anomaly_levels[level_name].available_smarts_reduced = {} end
if not updated_anomaly_levels[level_name].artefacts then updated_anomaly_levels[level_name].artefacts = {} end
if not updated_anomaly_levels[level_name].time then updated_anomaly_levels[level_name].time = -1 end
if not updated_anomaly_levels[level_name].disabled then updated_anomaly_levels[level_name].disabled = false end
end
function init_anomaly_table_global(current_level)
init_anomaly_table_on_level(current_level)
for k, v in pairs(updated_anomaly_levels) do
init_anomaly_table_on_level(k)
end
end
local obj_restrictions = {}
local in_restrictions = {}
local smart_restrictions = {}
function clean_restriction_tables()
empty_table(obj_restrictions)
empty_table(in_restrictions)
empty_table(smart_restrictions)
end
function get_obj_restrictions(clean)
if clean then
clean_restriction_tables()
end
if is_not_empty(obj_restrictions) then return obj_restrictions end
local alife = alife()
local alife_level_name = alife.level_name
local alife_object = alife.object
local gg = game_graph()
local gg_vertex = gg.vertex
local level_name = level.name()
local get_monster_data = utils_stpk.get_monster_data
local get_stalker_data = utils_stpk.get_stalker_data
local function get_nearest_smart_id(se_obj)
local dist
local min_dist
local nearest
local nearest_name
for name,smart in pairs( SIMBOARD.smarts_by_names ) do
local dist = smart.position:distance_to(se_obj.position)
if (not min_dist) then
min_dist = dist
nearest = smart
nearest_name = name
elseif (dist < min_dist) then
min_dist = dist
nearest = smart
nearest_name = name
end
end
if (nearest) then
if (simulation_objects.is_on_the_same_level(nearest, se_obj)) then
return nearest.id, nearest_name
end
end
end
local restrictions = {
["dynamic_out_restrictions"] = true,
["dynamic_in_restrictions"] = true,
["base_out_restrictors"] = true,
["base_in_restrictors"] = true,
}
for i = 1, 65534 do
local se_obj = alife_object(alife, i)
if se_obj then
local cls = se_obj:clsid()
if IsMonster(_, cls) then
local se_obj_level = alife_level_name(alife, gg_vertex(gg, se_obj.m_game_vertex_id):level_id())
if true or se_obj_level == level_name then
local monster_data = get_monster_data(se_obj)
if monster_data then
-- printf(".")
-- printf("monster_data for %s, %s, level %s", se_obj:section_name(), se_obj.id, se_obj_level)
for k, v in spairs(restrictions) do
-- printf("[%s] => %s", k, v)
if monster_data[k] and type(monster_data[k]) == "table" then
-- printf("..")
for k1, v1 in spairs(monster_data[k]) do
if not obj_restrictions[se_obj.id] then obj_restrictions[se_obj.id] = {} end
if not obj_restrictions[se_obj.id][k] then obj_restrictions[se_obj.id][k] = {} end
obj_restrictions[se_obj.id][k][v1] = true
in_restrictions[v1] = true
local nearest_smart_id, nearest_smart_name = get_nearest_smart_id(se_obj)
if nearest_smart_name then
-- printf("%s", nearest_smart_name)
smart_restrictions[nearest_smart_name] = v1
end
-- printf("[%s] => %s", k1, v1)
end
end
end
end
end
end
end
end
-- print_r(smart_restrictions)
-- print_r(obj_restrictions)
return obj_restrictions
end
function remove_all_restrictions(level_name)
local alife_release_id = alife_release_id
local gg = game_graph()
local gg_vertex = gg.vertex
local invert_table = invert_table
local is_not_empty = is_not_empty
local IsMonster = IsMonster
local load_var = load_var
local pairs = pairs
local printf = printf
local sim = alife()
local sim_level_name = sim.level_name
local sim_object = sim.object
local sim_release = sim.release
local sim_remove_in_restriction = sim.remove_in_restriction
local sim_remove_out_restriction = sim.remove_out_restriction
local spairs = spairs
local strformat = strformat
local type = type
local globally = not level_name
local get_monster_data = utils_stpk.get_monster_data
local get_stalker_data = utils_stpk.get_stalker_data
local restrictions = {
["dynamic_out_restrictions"] = true,
["dynamic_in_restrictions"] = true,
-- ["base_out_restrictors"] = true,
-- ["base_in_restrictors"] = true,
}
local anomalies_ids = {}
for i = 1, 65534 do
local se_obj = sim_object(sim, i)
if se_obj then
local cls = se_obj:clsid()
if IsMonster(_, cls) or IsStalker(_, cls) then
local se_obj_level = sim_level_name(sim, gg_vertex(gg, se_obj.m_game_vertex_id):level_id())
if globally or se_obj_level == level_name then
if not anomalies_ids[se_obj_level] then
anomalies_ids[se_obj_level] = updated_anomaly_levels[se_obj_level] and invert_table(updated_anomaly_levels[se_obj_level].anomalies) or {}
end
local monster_data = IsMonster(_, cls) and get_monster_data(se_obj) or get_stalker_data(se_obj)
if monster_data then
for k, v in pairs(restrictions) do
if monster_data[k] and type(monster_data[k]) == "table" then
for k1, v1 in pairs(monster_data[k]) do
if anomalies_ids[se_obj_level][v1] then
printf("removed restriction %s for level %s", se_obj:name(), se_obj_level)
sim_remove_in_restriction(sim, se_obj, v1)
sim_remove_out_restriction(sim, se_obj, v1)
end
end
end
end
end
end
end
end
end
-- for i = 1, 65534 do
-- local obj = level.object_by_id(i)
-- if obj and obj ~= 0 and IsMonster(obj) then
-- printf("removing restrictions for %s, %s", obj:section(), i)
-- obj:remove_all_restrictions()
-- end
-- end
end
function get_anomaly_obj(id, level_name)
local level_name = level_name or level.name()
local obj = alife():object(id)
if not obj or obj == 0 or obj.id == 0 then
-- printf("Error, anomaly game object not found by id %s, level %s", id, level_name)
return
end
if not IsAnomaly(_, obj:clsid()) then
-- printf("Error, object is not an anomaly, %s, %s, on level %s", obj:section_name(), obj.id, level_name)
return
end
local sim = alife()
local sim_level_name = sim.level_name
local gg = game_graph()
local gg_vertex = gg.vertex
local obj_level = sim_level_name(sim, gg_vertex(gg, obj.m_game_vertex_id):level_id())
if obj_level ~= level_name then
-- printf("Error, anomaly game object found by id %s but on different level %s, requested level %s", id, obj_level, level_name)
return
end
return obj
end
function get_anomaly_smart(id, level_name)
local obj = get_anomaly_obj(id, level_name)
if obj then
return updated_anomaly_levels[level_name].smart_by_anomalies[id]
end
end
function get_anomalies_by_smart(level_name)
local level_name = level_name or level.name()
if not updated_anomaly_levels[level_name].anomalies_by_smart or is_empty(updated_anomaly_levels[level_name].anomalies_by_smart) then
local sim = alife()
local gg = game_graph()
for _, id in pairs(updated_anomaly_levels[level_name].anomalies) do
local se_obj = alife_object(id)
if se_obj then
for smart_name, smart in pairs(SIMBOARD.smarts_by_names) do
if smart and smart.m_game_vertex_id == se_obj.m_game_vertex_id then
printf("adding anomaly %s to smart %s", se_obj:section_name(), smart_name)
if not updated_anomaly_levels[level_name].anomalies_by_smart then
updated_anomaly_levels[level_name].anomalies_by_smart = {}
end
if not updated_anomaly_levels[level_name].smart_by_anomalies then
updated_anomaly_levels[level_name].smart_by_anomalies = {}
end
if not updated_anomaly_levels[level_name].anomalies_by_smart[smart_name] then
updated_anomaly_levels[level_name].anomalies_by_smart[smart_name] = {}
end
if not updated_anomaly_levels[level_name].anomaly_types_by_smart[smart_name] then
updated_anomaly_levels[level_name].anomaly_types_by_smart[smart_name] = ""
end
updated_anomaly_levels[level_name].anomalies_by_smart[smart_name][id] = true
updated_anomaly_levels[level_name].smart_by_anomalies[id] = smart_name
end
end
end
end
end
end
function disable_anomaly_obj(id, level_name)
local obj = get_anomaly_obj(id, level_name)
if obj then
local g_obj = level.object_by_id(id)
if g_obj and g_obj ~= 0 and g_obj:id() ~= 0 then
printf("disabling anomaly %s, %s, level %s", g_obj:section(), g_obj:id(), level_name)
g_obj:disable_anomaly()
end
end
end
function disable_anomalies_on_level(level_name)
local level_name = level_name or level.name()
for _, id in pairs(updated_anomaly_levels[level_name].anomalies) do
disable_anomaly_obj(id, level_name)
end
end
function enable_anomaly_obj(id, level_name)
local obj = get_anomaly_obj(id, level_name)
if obj then
local g_obj = level.object_by_id(id)
if g_obj and g_obj ~= 0 and g_obj:id() ~= 0 then
printf("enabling anomaly %s, %s, level %s", g_obj:section(), g_obj:id(), level_name)
g_obj:enable_anomaly()
end
end
end
function enable_anomalies_on_level(level_name)
local level_name = level_name or level.name()
for _, id in pairs(updated_anomaly_levels[level_name].anomalies) do
enable_anomaly_obj(id, level_name)
end
end
function remove_anomaly_obj(id, level_name)
local obj = get_anomaly_obj(id, level_name)
if obj then
if not IsAnomaly(_, obj:clsid()) then
printf("Error, object is not an anomaly, %s, %s, on level %s", obj:section_name(), obj.id, level_name)
return
end
printf("removing anomaly object %s, %s, on level %s", obj:section_name(), obj.id, level_name)
-- obj:disable_anomaly()
local db_tables = {
db.actor_inside_zones,
db.anim_obj_by_name,
db.anomaly_by_name,
db.bridge_by_name,
db.camp_storage,
db.campfire_by_name,
db.campfire_table_by_smart_names,
db.dynamic_ltx,
db.heli,
db.heli_enemies,
db.info_restr,
db.level_doors,
db.no_weap_zones,
db.offline_objects,
db.script_ids,
db.signal_light,
db.smart_terrain_by_id,
db.spawned_vertex_by_id,
db.storage,
db.story_by_id,
db.story_object,
db.used_level_vertex_ids,
db.zone_by_name,
}
for i = 1, #db_tables do
if is_not_empty(db_tables[i]) then
db_tables[i][obj.id] = nil
db_tables[i][obj:name()] = nil
end
end
bind_anomaly_field.fields_by_names[obj:name()] = nil
local g_obj = level.object_by_id(id)
if g_obj and g_obj ~= 0 and g_obj:id() ~= 0 then
g_obj:destroy_object()
else
alife_record(obj, false)
alife():release(obj, true)
end
end
end
-- Delete old anomalies persisting from old code
function clean_old_dynamic_anomalies_on_level(level_name)
if not updated_anomaly_levels[level_name].cleaned_old_anomalies then
local load_var = load_var
local pairs = pairs
local sim = alife()
local sim_level_name = sim.level_name
local gg = game_graph()
local gg_vertex = gg.vertex
local strformat = strformat
local fully_cleaned = true
for smart_name, v in pairs(SIMBOARD.smarts_by_names) do
local smart_level = sim_level_name(sim, gg_vertex(gg, v.m_game_vertex_id):level_id())
if smart_level == level_name then
for j = 1, 1000 do
local anom_id = load_var(db.actor, strformat("drx_da_anom_id_%s_%s", smart_name, j), nil)
if anom_id then
if not in_restrictions[anom_id] then
remove_anomaly_obj(anom_id, level_name)
else
printf("Error, can't remove old anomaly %s, level %s, in restriction", anom_id, level_name)
fully_cleaned = false
end
end
end
end
end
updated_anomaly_levels[level_name].cleaned_old_anomalies = fully_cleaned
else
printf("old anomalies already cleaned on level %s", level_name)
end
end
-- Clean dynamic anomalies on level, normally after surge on level change or forcefully
function clean_dynamic_anomalies_on_level_func(level_name)
local t = updated_anomaly_levels[level_name]
if not t then
printf("Error, updated_anomaly_levels table not found for %s", level_name)
return
end
local t = t.anomalies
for i, id in pairs(t) do
if t[i] and not in_restrictions[t[i]] then
remove_anomaly_obj(t[i], level_name)
for k, v in pairs(updated_anomaly_levels[level_name].anomalies_by_smart) do
v[t[i]] = nil
updated_anomaly_levels[level_name].anomaly_types_by_smart[k] = nil
if is_empty(v) then
updated_anomaly_levels[level_name].anomalies_by_smart[k] = nil
updated_anomaly_levels[level_name].available_smarts_reduced[k] = nil
end
end
updated_anomaly_levels[level_name].smart_by_anomalies[t[i]] = nil
updated_anomaly_levels[level_name].anomalies_properties[t[i]] = nil
t[i] = nil
else
printf("Error, can't remove anomaly %s, level %s, in restriction", t[i], level_name)
end
end
clean_old_dynamic_anomalies_on_level(level_name)
printf("Anomalies cleaned on level %s", level_name)
end
function clean_dynamic_anomalies_on_level(level_name, dont_clean_restrictions)
get_anomalies_by_smart(level_name)
remove_all_restrictions(level_name)
get_obj_restrictions(not dont_clean_restrictions)
-- disable_anomalies_on_level(level_name)
-- remove_restrictions(level_name)
clean_dynamic_anomalies_on_level_func(level_name)
-- enable_anomalies_on_level(level_name)
-- clean_restriction_tables()
end
function clean_dynamic_anomalies_global()
remove_all_restrictions()
get_obj_restrictions(true)
unregister_anomalies_behaviour()
local alife_release_id = alife_release_id
local gg = game_graph()
local gg_vertex = gg.vertex
local level_name = level.name()
local load_var = load_var
local pairs = pairs
local printf = printf
local sim = alife()
local sim_level_name = sim.level_name
local sim_object = sim.object
local sim_release = sim.release
local alife_record = alife_record
local strformat = strformat
for k, v in pairs(updated_anomaly_levels) do
get_anomalies_by_smart(k)
clean_artefacts_on_level(k)
if k == level_name then
disable_anomalies_on_level(level_name)
v.disabled = true
-- clean_dynamic_anomalies_on_level_func(level_name)
else
for k1, v1 in pairs(v.anomalies) do
if not in_restrictions[v1] then
local se_obj = sim_object(sim, v1)
if se_obj then
if IsAnomaly(_, se_obj:clsid()) then
printf("Deleting anomaly %s, %s globally, level %s", se_obj:section_name(), v1, k)
alife_record(se_obj ,false)
alife():release(se_obj, true)
else
printf("Error, object is not an anomaly, %s, %s, on level %s", se_obj:section_name(), v1, k)
end
end
for k2, v2 in pairs(v.anomalies_by_smart) do
v2[v1] = nil
v.anomaly_types_by_smart[k2] = nil
if is_empty(v2) then
v.anomalies_by_smart[k2] = nil
v.available_smarts_reduced[k2] = nil
end
end
v.smart_by_anomalies[v1] = nil
v.anomalies_properties[v1] = nil
v.anomalies[k1] = nil
else
printf("can't delete anomaly %s globally, level %s, in restriction", v1, k)
end
end
-- Old anomalies
if not v.cleaned_old_anomalies then
local fully_cleaned = true
for smart_name, v in pairs(SIMBOARD.smarts_by_names) do
local smart_level = sim_level_name(sim, gg_vertex(gg, v.m_game_vertex_id):level_id())
if smart_level == k then
for j = 1, 1000 do
local anom_id = load_var(db.actor, strformat("drx_da_anom_id_%s_%s", smart_name, j), nil)
if anom_id then
if not in_restrictions[anom_id] then
local o = alife_object(anom_id)
if o then
alife_record(o ,false)
alife():release(o, true)
end
else
printf("Error, can't remove old anomaly %s, level %s, in restriction", anom_id, k)
fully_cleaned = false
end
end
end
end
end
v.cleaned_old_anomalies = fully_cleaned
else
printf("old anomalies already cleaned on level %s", k)
end
end
end
printf("Cleaned dynamic anomalies globally")
if settings.save_after_cleanup then
CreateTimeEvent("drx_da_save_after_cleanup", 0, 0.1, function()
exec_console_cmd("save " .. (user_name() or "") .. " - DAO tempsave")
return true
end)
end
end
function drx_da_spawn_anomaly_on_smart(level_file, smart_name, anomaly_type, level_name, position_data)
-- Get the smart terrain:
local smart = SIMBOARD.smarts_by_names[smart_name]
if not smart then
printf("Error: Unable to create dynamic anomaly field for %s, the specified smart location does not exist", smart_name)
return false
end
-- Select a location for the current anomaly:
local pos = drx_da_generate_position(smart_name, anomaly_type, position_data)
if pos then
-- Get the new level vertex id for the generated position:
local lvid = level_vertex_id(pos)
-- Spawn the anomaly:
local anom_id = drx_da_spawn_anomaly(anomaly_type, pos, lvid, smart.m_game_vertex_id, level_file)
-- Return the anomaly id:
if anom_id then
printf("Dynamic anomaly field %s spawned at %s, level %s", anomaly_type, smart_name, level_name)
return anom_id
end
else
printf("Error: failed to generate position")
end
end
function get_level_data(level_name)
local level_file_name = "hazardous_anomalies\\regions\\" .. level_name .. ".ltx"
local level_file = ini_file(level_file_name)
if not level_file then
printf("ltx file not found: %s", level_file_name)
return false
end
-- Get the percent chance for anomalies to spawn:
local spawn_percent = level_file:r_float_ex("spawn_properties", "spawn_percent") or 0
if not spawn_percent or spawn_percent <= 0 then
printf("Dynamic anomalies not spawned, spawn chance is 0")
return false
end
-- Determine the maximum amount of anomalies spawned in each anomaly field:
local anomaly_max_number = level_file:r_float_ex("spawn_properties", "anomaly_max_number") or 0
if not anomaly_max_number or anomaly_max_number < 1 then
printf("Dynamic anomalies not spawned, max anomaly count is 0")
return false
end
-- Determine the maximum amount of anomalies active in each anomaly field:
local anomaly_max_active = level_file:r_float_ex("spawn_properties", "anomaly_max_active") or 0
if not anomaly_max_active or anomaly_max_active < 1 then
printf("Dynamic anomalies not spawned, max active count is 0")
return false
end
return {
level_file = level_file,
spawn_percent = spawn_percent,
anomaly_max_number = anomaly_max_number,
anomaly_max_active = anomaly_max_active,
}
end
function generate_random_anomaly_properties()
return {
time_active = random(6500, 15000),
time_cooldown = random(2200, 3800),
active = true,
}
end
function drx_da_spawn_anomalies_on_level(level_name)
local level_data = get_level_data(level_name)
if not level_data then
printf("Error, unable to get data for %s", level_name)
return
end
local level_file = level_data.level_file
local spawn_percent = level_data.spawn_percent
local anomaly_max_number = level_data.anomaly_max_number * settings.anomaly_amount_modifier
local anomaly_max_active = level_data.anomaly_max_active
local pairs = pairs
local collect_section = utils_data.collect_section
local size_table = size_table
local invert_table = invert_table
local is_not_empty = is_not_empty
local is_empty = is_empty
local table_remove = table.remove
-- Build a list of available smart terrains:
local smart_list = collect_section(level_file, "available_smarts")
if is_not_empty(smart_list) then
if reduced_chance_levels[level_name] then
local t = {}
for k, v in pairs(smart_list) do
if random(100) <= 50 then
t[#t + 1] = v
end
end
smart_list = t
end
end
-- Build a list of available smart terrains with reduced amount:
local smart_list_reduced = collect_section(level_file, "available_smarts_reduced")
if is_not_empty(smart_list_reduced) then
smart_list_reduced = invert_table(smart_list_reduced)
for k, v in pairs(smart_list_reduced) do
smart_list[#smart_list + 1] = k
end
end
-- Build a list of available anomalies:
local anomaly_list = collect_section(level_file, "anomaly_types")
if settings.disable_new_anomalies then
local t = {}
for _, v in pairs(anomaly_list) do
if not drx_da_main_mcm.new_anomalies_sections[v] then
t[#t + 1] = v
else
printf("disable all new anomalies, found section %s", v)
end
end
anomaly_list = t
else
local t = {}
for k, v in pairs(anomaly_list) do
if drx_da_main_mcm.new_anomalies_sections[v] then
if drx_da_main_mcm.is_enabled_anomaly(v) then
t[#t + 1] = v
else
printf("anomaly %s is not enabled", v)
end
else
t[#t + 1] = v
end
end
anomaly_list = t
end
-- Combine radiation fields to one type
-- local rad_fields = {}
-- for i = #anomaly_list, 1, -1 do
-- if anomalies_radiation_fields[anomaly_list[i]] then
-- rad_fields[#rad_fields + 1] = anomaly_list[i]
-- table_remove(anomaly_list, i)
-- end
-- end
-- if is_not_empty(rad_fields) then
-- anomaly_list[#anomaly_list + 1] = "zone_radiation_field"
-- end
-- Build anomalies table
if is_not_empty(smart_list) then
local anomalies = updated_anomaly_levels[level_name].anomalies or {}
local anomalies_by_smart = updated_anomaly_levels[level_name].anomalies_by_smart or {}
local smart_by_anomalies = updated_anomaly_levels[level_name].smart_by_anomalies or {}
local anomaly_types_by_smart = updated_anomaly_levels[level_name].anomaly_types_by_smart or {}
local anomalies_properties = updated_anomaly_levels[level_name].anomalies_properties or {}
local available_smarts_reduced = updated_anomaly_levels[level_name].available_smarts_reduced or {}
for i, smart_name in pairs(smart_list) do
if (true or not smart_restrictions[smart_name]) and random() <= settings.anomaly_zone_spawn_chance then
-- Choose an anomaly type to spawn:
if anomaly_list and #anomaly_list >= 1 then
local anomaly_type = anomaly_list[random(#anomaly_list)]
-- if anomaly_type == "zone_radiation_field" then
-- anomaly_type = rad_fields[math.random(#rad_fields)]
-- end
printf("picked anomaly type %s", anomaly_type)
if not anomalies_by_smart[smart_name] then anomalies_by_smart[smart_name] = {} end
local j = size_table(anomalies_by_smart[smart_name])
-- Store position data of generated anomalies
local position_data = kd_tree.buildTreeVectors()
if j > 0 then
for k, v in pairs(anomalies_by_smart[smart_name]) do
local se_obj = get_anomaly_obj(k)
if se_obj then
local pos = se_obj.position
if pos then
position_data:insertAndRebuild({x = pos.x, y = pos.y, z = pos.z})
end
end
end
end
while j < (smart_list_reduced[smart_name] and random(3) or anomaly_max_number) do
if random() <= (smart_list_reduced[smart_name] and 0.5 or spawn_percent) then
local anom_id = drx_da_spawn_anomaly_on_smart(level_file, smart_name, anomaly_type, level_name, position_data)
if anom_id then
anomalies[#anomalies + 1] = anom_id
anomalies_by_smart[smart_name][anom_id] = true
smart_by_anomalies[anom_id] = smart_name
anomaly_types_by_smart[smart_name] = anomaly_type
available_smarts_reduced[smart_name] = smart_list_reduced[smart_name]
anomalies_properties[anom_id] = generate_random_anomaly_properties()
end
end
j = j + 1
end
else
printf("No dynamic anomaly types specified for level %s", level_name)
end
else
printf("no anomalies spawn on smart %s, level %s in restriction", smart_name, level_name)
end
end
return #anomalies > 0
end
end
-- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-- ////////////////////////////////////////////////////////////////////////////////////////////////
--
-- drx_da_spawn_anomaly function
--
-- ------------------------------------------------------------------------------------------------
--
-- Description:
-- - Spawns an anomaly at the specified location
--
-- Usage:
-- drx_da_spawn_anomaly( anom_type, pos, lvid, gvid )
--
-- Parameters:
-- anom_type (type: string, anomaly type name)
-- - Type of anomaly to spawn
-- pos (type: vector)
-- - Positional data for the anomaly
-- lvid (type: int, level vertex id)
-- - Level vertex id
-- gvid (type: int, game vertex id)
-- - Game vertex id
--
-- Return value (type: object id):
-- Returns the id of the spawned anomaly
-- Returns nil on failure
--
-- ------------------------------------------------------------------------------------------------
-- Created by DoctorX
-- for DoctorX Dynamic Anomalies 2.0
-- Last modified March 02, 2018
-- ------------------------------------------------------------------------------------------------
-- Spawn a single anomaly:
function drx_da_spawn_anomaly(anom_type, pos, lvid, gvid, level_file)
local function abort_creation(se_obj_id, anom_type)
CreateTimeEvent("drx_da_abort_creation" .. se_obj_id, "drx_da_abort_creation" .. se_obj_id, 0.2, function()
local obj = alife():object(se_obj_id)
if obj then
printf("Error, anomaly %s failed to spawn correctly, releasing", anom_type)
alife_record(obj ,false)
alife():release(obj, true)
end
return true
end)
end
local min_radius = (level_file:r_float_ex("radius_properties", "min_radius") or 2)
local max_radius = (level_file:r_float_ex("radius_properties", "max_radius") or 3)
-- Spawn the anomaly:
local se_obj = alife():create(anom_type, pos, lvid, gvid)
if (not se_obj) then
printf("Error: Unable to spawn dynamic anomaly")
return
end
-- Set anomaly properties:
local data = utils_stpk.get_anom_zone_data(se_obj)
if (not data) then
printf("Error: Unable to set dynamic anomaly properties")
abort_creation(se_obj.id, anom_type)
return
end
data.shapes[1] = {}
data.shapes[1].shtype = 0
data.shapes[1].offset = vector():set(0, 0, 0) -- Leave for compatibility with CoC 1.4.22, delete later
data.shapes[1].center = vector():set(0, 0, 0)
data.shapes[1].radius = anomaly_radii[anom_type] and random(anomaly_radii[anom_type].min, anomaly_radii[anom_type].max) or random(min_radius, max_radius)
utils_stpk.set_anom_zone_data(data, se_obj)
-- Return the anomaly id:
return se_obj.id, se_obj
end
-- \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-- ////////////////////////////////////////////////////////////////////////////////////////////////
--
-- drx_da_generate_position function
--
-- ------------------------------------------------------------------------------------------------
--
-- Description:
-- - Generates a random position vector on the ground within a smart terrain location
--
-- Usage:
-- drx_da_generate_position( smart_name )
--
-- Parameters:
-- smart_name (type: string, smart terrain name)
-- - Name of the smart terrain
--
-- Ini requirements:
-- drx\drx_da_config.ltx
-- [location_offset]
-- max_offset_x (type: float, meters)
-- - Magnitude of maximum offset from smart terrain center in x (north-south) direction
-- max_offset_y (type: float, meters)
-- - Magnitude of maximum offset from smart terrain center in y (up-down) direction
-- max_offset_z (type: float, meters)
-- - Magnitude of maximum offset from smart terrain center in z (east-west) direction
-- max_tries (type: int)
-- - Maximum number of iterations to try generating a spawn position before aborting
--
-- Return value (type: vector):
-- Returns the generated positional data
-- Returns nil on failure
--
-- ------------------------------------------------------------------------------------------------
-- Created by DoctorX, (modification of method suggested by Alundaio)
-- for DoctorX Dynamic Anomalies 2.0
-- Last modified January 31, 2018
-- ------------------------------------------------------------------------------------------------
-- Table of small levels to adjust spawn extra to underground levels
small_levels = {
y04_pole = true,
l11_hospital = true,
}
-- Table of anomalies to ignore distance check
anomalies_distance_check_ignore = {
zone_field_radioactive = true,
zone_field_radioactive_above_average = true,
zone_field_radioactive_average = true,
zone_field_radioactive_below_average = true,
zone_field_radioactive_lethal = true,
zone_field_radioactive_strong = true,
zone_field_radioactive_very_weak = true,
zone_field_radioactive_weak = true,
zone_radioactive = true,
zone_radioactive_above_average = true,
zone_radioactive_average = true,
zone_radioactive_below_average = true,
zone_radioactive_lethal = true,
zone_radioactive_strong = true,
zone_radioactive_very_weak = true,
zone_radioactive_weak = true,
}
-- Table of radiation fields
anomalies_radiation_fields = {
zone_field_radioactive = true,
zone_field_radioactive_above_average = true,
zone_field_radioactive_average = true,
zone_field_radioactive_below_average = true,
zone_field_radioactive_lethal = true,
zone_field_radioactive_strong = true,
zone_field_radioactive_very_weak = true,
zone_field_radioactive_weak = true,
zone_radioactive = true,
zone_radioactive_above_average = true,
zone_radioactive_average = true,
zone_radioactive_below_average = true,
zone_radioactive_lethal = true,
zone_radioactive_strong = true,
zone_radioactive_very_weak = true,
zone_radioactive_weak = true,
}
-- Generate positional data:
function drx_da_generate_position(smart_name, anomaly_type, position_data)
-- Get the smart terrain:
local smart = SIMBOARD.smarts_by_names[smart_name]
if (not smart) then
printf("Error: Unable to generate positional data, specified smart location does not exist")
return
end
-- Get maximum offset values:
local max_offset_x = settings.anomaly_zone_anomalies_distance_max or ini:r_float_ex("location_offset", "max_offset_x") or 40
local max_offset_y = ini:r_float_ex("location_offset", "max_offset_y") or 0
local max_offset_z = settings.anomaly_zone_anomalies_distance_max or ini:r_float_ex("location_offset", "max_offset_z") or 40
local num_tries = (ini:r_float_ex("location_offset", "max_tries") or 64)
-- Reduce offset by 2 times for underground
if level_weathers.bLevelUnderground or small_levels[level.name()] then
max_offset_x = floor(max_offset_x * 0.5)
max_offset_y = floor(max_offset_y * 0.5)
max_offset_z = floor(max_offset_z * 0.5)
end
-- Try to generate valid positional data on the ground:
local pos = vector():set(0, 0, 0)
local valid_lvid = false
while ((valid_lvid ~= true) and (num_tries > 0)) do
-- Randomly offset anomaly x-position from center of smart terrain:
local offset_x = max_offset_x * random()
if (random() <= 0.5) then
offset_x = -(offset_x)
end
local pos_x = (smart.position.x + offset_x)
-- Randomly offset anomaly y-position from center of smart terrain:
local offset_y = (max_offset_y * random())
if (random() <= 0.5) then
offset_y = -(offset_y)
end
local pos_y = (smart.position.y + offset_y)
-- Randomly offset anomaly z-position from center of smart terrain:
local offset_z = max_offset_z * random()
if (random() <= 0.5) then
offset_z = -(offset_z)
end
local pos_z = (smart.position.z + offset_z)
-- Set anomaly position at location vertex and check if valid:
pos = vector():set(pos_x, pos_y, pos_z)
local lvid = level_vertex_id(pos)
if (lvid < 4294967295) then
pos = level_vertex_position(lvid)
-- Don't check distance for certain anomalies
if anomaly_type and anomalies_distance_check_ignore[anomaly_type] then
valid_lvid = true
else
-- If position data exists and distance of generated position is more than anomaly radius - valid
try(function()
if anomaly_type and anomaly_radii[anomaly_type] and position_data and position_data.root then
local nearest = position_data:nearest(pos)
if nearest and nearest[1] and nearest[1][2] then
local distance = sqrt(nearest[1][2]) - anomaly_radii[anomaly_type].max * 2
if distance >= settings.anomaly_zone_anomalies_distance_min then
printf("Anomaly type %s, Position data valid, distance %s, saving %s, %s, %s", anomaly_type, distance, pos_x, pos_y, pos_z)
position_data:insertAndRebuild({x = pos_x, y = pos_y, z = pos_z})
valid_lvid = true
else
printf("Anomaly type %s, Position data invalid, too close, distance %s, %s, %s, %s", anomaly_type, distance, pos_x, pos_y, pos_z)
valid_lvid = false
end
else
printf("Anomaly type %s, Can't check position data %s, %s, %s", anomaly_type, pos_x, pos_y, pos_z)
position_data:insertAndRebuild({x = pos_x, y = pos_y, z = pos_z})
valid_lvid = true
end
else
if position_data then
printf("Anomaly type %s, Position data provided, saving %s, %s, %s", anomaly_type, pos_x, pos_y, pos_z)
position_data:insertAndRebuild({x = pos_x, y = pos_y, z = pos_z})
end
valid_lvid = true
end
end)
end
end
-- Decrement the number of tries left:
num_tries = (num_tries - 1)
if ((num_tries <= 0) and (valid_lvid ~= true)) then
printf("Error: Unable to generate valid lvid pos, aborting")
return
end
end
-- Return the position vector:
return pos
end
function clean_artefacts_on_level(level_name)
local level_name = level_name or level.name()
init_anomaly_table_on_level(level_name)
local artefacts = updated_anomaly_levels[level_name].artefacts
if is_not_empty(artefacts) then
for k, v in pairs(artefacts) do
local o = alife_object(k)
if o then safe_release_manager.release(o) end
printf("releasing artefact %s, sec %s, level_name %s", k, v, level_name)
artefacts[k] = nil
end
end
end
-- Spawn single artefact on smart
function spawn_artefact_on_smart(level_file, smart_name, picked_artefact, level_name)
local level_name = level_name or level.name()
if not picked_artefact then
printf("Error: Unable to create artefact %s, is nil", picked_artefact)
return false
end
-- Get the smart terrain:
local smart = SIMBOARD.smarts_by_names[smart_name]
if not smart then
printf("Error: Unable to create artefact for %s, the specified smart location does not exist", smart_name)
return false
end
-- Select a location for the current artefact:
local pos = drx_da_generate_position(smart_name)
if pos then
-- Correct y position so the artefact wouldnt fall OOB
pos = vector():set(pos.x, (level_weathers.bLevelUnderground or small_levels[level.name()]) and pos.y + 1 or pos.y + 7, pos.z)
-- Get the new level vertex id for the generated position:
local lvid = level_vertex_id(pos)
-- Spawn the artefact:
local artefact = alife_create(picked_artefact, pos, lvid, smart.m_game_vertex_id)
-- Return the anomaly id:
if artefact then
printf("Artefact %s, id %s spawned at %s, level %s", picked_artefact, artefact.id, smart_name, level_name)
return artefact.id
end
else
printf("Error: failed to generate position")
end
end
-- Spawn artefacts on level
function spawn_artefacts_on_level(level_name)
local level_name = level_name or level.name()
local level_data = get_level_data(level_name)
if not level_data then
printf("Error, unable to get data for %s", level_name)
return
end
local level_file = level_data.level_file
-- Build a list of available smart terrains:
local smart_list = {}
for k, v in pairs(updated_anomaly_levels[level_name].anomaly_types_by_smart) do
if not updated_anomaly_levels[level_name].available_smarts_reduced[k] then
smart_list[#smart_list + 1] = k
end
end
smart_list = invert_table(smart_list)
local pairs = pairs
local collect_section = utils_data.collect_section
local size_table = size_table
local allowed_artefacts = drx_da_main_artefacts.allowed_artefacts
local allowed_artefacts_flipped = invert_table(allowed_artefacts)
local anomaly_type_to_artefacts = drx_da_main_artefacts.anomaly_type_to_artefacts
local artefacts_map_tiers = drx_da_main_artefacts.artefacts_map_tiers[level_name] and shuffle(drx_da_main_artefacts.artefacts_map_tiers[level_name])
local artefacts_map_chances = drx_da_main_artefacts.artefacts_map_chances and drx_da_main_artefacts.artefacts_map_chances[level_name]
-- Build anomalies table
if is_not_empty(smart_list) then
printf("%s has smarts with anomalies, try to spawn artefacts", level_name)
local artefacts = updated_anomaly_levels[level_name].artefacts or {}
local anomalies_by_smart = updated_anomaly_levels[level_name].anomalies_by_smart
for smart_name, _ in pairs(anomalies_by_smart) do
printf("checking smart %s for spawning artefacts", smart_name)
if is_not_empty(anomalies_by_smart[smart_name]) and smart_list[smart_name] then
printf("try to spawn artefacts on smart %s", smart_name)
for i = 1, settings.max_artefacts_per_zone do
-- Increased chance by 2 times for underground levels
local dice_roll = random(100)
local chance = artefacts_map_chances or ceil(settings.artefacts_spawn_chance * ((level_weathers.bLevelUnderground or small_levels[level_name]) and 2 or 1))
printf("artefacts dice roll %s, chance %s, spawn %s", dice_roll, chance, dice_roll <= chance)
if dice_roll <= chance then
-- Choose an artefact to spawn:
local anomaly_type = updated_anomaly_levels[level_name].anomaly_types_by_smart[smart_name]
local picked_artefact = (function()
local res
if artefacts_map_tiers and random(100) > settings.random_artefact_spawn_chance then
local tries = 40
while tries > 0 and (not res or not allowed_artefacts_flipped[res]) do
if anomaly_type_to_artefacts[anomaly_type] then
local t = {}
for k, v in pairs(artefacts_map_tiers) do
if anomaly_type_to_artefacts[anomaly_type][v] then
t[#t + 1] = v
end
end
printf("picking artefact by level %s, anomaly zone %s has defined arty list", level_name, anomaly_type)
res = t[random(#t)]
else
printf("picking artefact by level %s", level_name)
res = artefacts_map_tiers[random(#artefacts_map_tiers)]
end
if not allowed_artefacts_flipped[res] then
printf("artefact is not allowed to spawn, repicking")
end
tries = tries - 1
end
else
printf("picking random artefacts")
res = allowed_artefacts[random(#allowed_artefacts)]
end
if not res then
printf("failed to pick artefact by level, pick random from allowed, level_name %s, anomaly_type %s, has artefacts_map_tiers %s, has anomaly_type_to_artefacts %s", level_name, anomaly_type, artefacts_map_tiers ~= nil, anomaly_type_to_artefacts[anomaly_type] ~= nil)
res = allowed_artefacts[random(#allowed_artefacts)]
end
return res
end)()
-- Artefact Variationizer compatibility
if artefact_variationizer then
local av = artefact_variationizer
picked_artefact = av.get_artefact_base(picked_artefact)
if av.valid_artys[picked_artefact] then
local variationizer_tier = av.artefact_chances[random(#av.artefact_chances)]
local arty = av.artefact_by_variationizer_tier[picked_artefact][variationizer_tier]
picked_artefact = arty[random(#arty)]
end
end
printf("picked artefact to spawn %s, anomaly_type %s, smart %s level %s", picked_artefact, anomaly_type, smart_name, level_name)
local artefact_id = spawn_artefact_on_smart(level_file, smart_name, picked_artefact, level_name)
if artefact_id then
artefacts[artefact_id] = picked_artefact
else
printf("error, unabled to spawn artefact %s, anomaly_type %s, smart %s level %s", picked_artefact, anomaly_type, smart_name, level_name)
end
end
end
end
end
return size_table(artefacts) > 0
else
printf("%s has no smarts with anomalies, dont spawn artefacts", level_name)
end
end
-- Update dynamic anomalies:
function drx_da_update_dynamic_anomalies(force)
-- Verify db.actor is available:
if not db.actor then
printf("Error: Cannot update anomalies, db.actor not available")
return false
end
-- Get surge manager:
local surgeman = surge_manager.get_surge_manager()
if not surgeman then
printf("Error: Cannot update anomalies, surge manager not available")
return false
end
local level_name = level.name()
init_anomaly_table_on_level(level_name)
if last_surge_time > updated_anomaly_levels[level_name].time or force then
clean_dynamic_anomalies_on_level(level_name)
clean_artefacts_on_level(level_name)
CreateTimeEvent("drx_da_spawn_anomalies_on_level", "drx_da_spawn_anomalies_on_level", 0.3, function()
local anomalies = drx_da_spawn_anomalies_on_level(level_name)
if not anomalies then
printf("Error, failed to spawn anomalies")
build_anomalies_pos_tree()
return true
end
printf("updated anomalies on level %s", level_name)
updated_anomaly_levels[level_name].time = last_surge_time
updated_anomaly_levels[level_name].disabled = false
build_anomalies_pos_tree()
if settings.enable_anomalies_behaviour or level_weathers.bLevelUnderground then register_anomalies_behaviour() end
spawn_artefacts_on_level(level_name)
return true
end)
else
printf("anomalies not updated on level %s, no emission happened, last_surge_time %s, level update time %s", level_name, last_surge_time, updated_anomaly_levels[level_name].time)
if settings.enable_anomalies_behaviour or level_weathers.bLevelUnderground then register_anomalies_behaviour() end
end
return true
end
-- Scripts to run when the game loads:
local tg = 0
local tg_interval = 5000
function drx_da_actor_on_update_callback()
local t = time_global()
if t < tg then return end
tg = t + tg_interval
load_settings()
get_actor_psy_table()
init_anomaly_table_global(level.name())
printf("saved level %s, current level %s", alife_storage_manager.get_state().drx_da_previous_level, level.name())
if level.name() == alife_storage_manager.get_state().drx_da_previous_level then
local level_name = level.name()
if level_name and updated_anomaly_levels[level_name] and updated_anomaly_levels[level_name].disabled then
if last_surge_time > updated_anomaly_levels[level_name].time then
clean_dynamic_anomalies_on_level(level_name)
updated_anomaly_levels[level_name].disabled = false
end
end
printf("on the same level, only register behaviour")
if settings.enable_anomalies_behaviour or level_weathers.bLevelUnderground then register_anomalies_behaviour() end
unregister_drx_da()
build_anomalies_pos_tree()
RegisterScriptCallback("actor_on_update", actor_on_update)
return
else
local level_name = alife_storage_manager.get_state().drx_da_previous_level
if level_name and updated_anomaly_levels[level_name] and updated_anomaly_levels[level_name].disabled then
if last_surge_time > updated_anomaly_levels[level_name].time then
clean_dynamic_anomalies_on_level(level_name)
updated_anomaly_levels[level_name].disabled = false
end
end
end
get_anomalies_by_smart(level.name())
-- Update dynamic anomalies:
local updated = drx_da_update_dynamic_anomalies()
unregister_drx_da()
build_anomalies_pos_tree()
RegisterScriptCallback("actor_on_update", actor_on_update)
end
function unregister_drx_da()
printf("anomalies updated, unregistering")
UnregisterScriptCallback("actor_on_update", drx_da_actor_on_update_callback)
end
function unregister_anomalies_behaviour()
remove_queue("drx_da_anomalies_behaviour")
end
-- Sections to ignore on/off switch behaviour
anomalies_do_not_register_behaviour = {
zone_mine_ghost = true
}
function register_anomalies_behaviour()
local level_name = level.name()
if is_empty(updated_anomaly_levels[level_name]) or is_empty(updated_anomaly_levels[level_name].anomalies) then
printf("anomalies behaviour, anomalies not found for level %s", level_name)
return
end
if is_empty(updated_anomaly_levels[level_name].anomalies_properties) or
size_table(updated_anomaly_levels[level_name].anomalies_properties) ~= size_table(updated_anomaly_levels[level_name].anomalies)
then
for k, v in pairs(updated_anomaly_levels[level_name].anomalies) do
updated_anomaly_levels[level_name].anomalies_properties[v] = generate_random_anomaly_properties()
end
end
printf("anomalies behaviour, turning on behaviour for level %s", level_name)
for k, v in pairs(updated_anomaly_levels[level_name].anomalies_properties) do
v.update_time = time_global()
end
local get_object = level.object_by_id
local time_global = time_global
process_queue("drx_da_anomalies_behaviour", updated_anomaly_levels[level_name].anomalies_properties, function(id, props, i)
local t = time_global()
-- if is_empty(props) then
-- printf("Error, anomaly behaviour not found for %s, level %s", id, level_name)
-- return true
-- end
local obj = get_object(id)
if not obj then return end
-- Remove from queue if its in do_not_register_behaviour table
if anomalies_do_not_register_behaviour[obj:section()] then return true end
if props.active then
if t - props.update_time > props.time_active then
-- printf("anomaly disabled, id %s, i %s, t %s, u %s", id, i, t, props.update_time)
props.update_time = t
props.active = false
obj:disable_anomaly()
end
else
if t - props.update_time > props.time_cooldown then
-- printf("anomaly enabled, id %s, i %s, t %s, u %s", id, i, t, props.update_time)
props.update_time = t
props.active = true
obj:enable_anomaly()
end
end
end, nil, 5)
end
anomalies_pos_tree = nil
detectable_anomalies_pos_tree = nil
detectable_anomalies_ids = {}
anomalies_obj_to_pos = {}
anomalies_sec_to_obj = {}
common_sec = {
zone_mine_electric = "zone_mine_electric",
zone_mine_electric_weak = "zone_mine_electric",
zone_mine_electric_average = "zone_mine_electric",
zone_mine_electric_strong = "zone_mine_electric",
zone_mine_static = "zone_mine_electric",
zone_mine_static_weak = "zone_mine_electric",
zone_mine_static_average = "zone_mine_electric",
zone_mine_static_strong = "zone_mine_electric",
zone_witches_galantine = "zone_mine_electric",
zone_witches_galantine_weak = "zone_mine_electric",
zone_witches_galantine_average = "zone_mine_electric",
zone_witches_galantine_strong = "zone_mine_electric",
}
function build_anomalies_pos_tree()
local alife_release_id = alife_release_id
local gg = game_graph()
local gg_vertex = gg.vertex
local level_name = level.name()
local load_var = load_var
local pairs = pairs
local printf = printf
local sim = alife()
local sim_level_name = sim.level_name
local sim_object = sim.object
local sim_release = sim.release
local alife_record = alife_record
local strformat = strformat
local level_name = level.name()
local objects = {}
local obj_to_pos = {}
local sec_to_obj = {}
for i = 1, 65534 do
local obj = get_anomaly_obj(i, level_name)
if obj then
table.insert(objects, obj)
obj_to_pos[i] = {
id = i,
section = obj:section_name(),
position = obj.position
}
local sec = obj:section_name()
sec = common_sec[sec] or sec
if not sec_to_obj[sec] then sec_to_obj[sec] = {} end
table.insert(sec_to_obj[sec], {
id = i,
section = obj:section_name(),
position = obj.position
})
end
end
-- try(function()
-- anomalies_pos_tree = kd_tree.buildTreeSeObjects(objects)
-- anomalies_obj_to_pos = anomalies_pos_tree and obj_to_pos
-- end)
empty_table(detectable_anomalies_ids)
local t = {}
for k, v in pairs(sec_to_obj) do
if sec_to_obj[k] then
local ids = {}
for k1, v1 in pairs(sec_to_obj[k]) do
ids[#ids + 1] = v1.id
if not anomaly_detector_ignore[v1.section] then
detectable_anomalies_ids[v1.id] = v1.position
t[#t + 1] = v1.id
end
end
anomalies_sec_to_obj[k] = kd_tree.buildTreeSeObjectIds(ids)
end
end
detectable_anomalies_pos_tree = kd_tree.buildTreeSeObjectIds(t)
end
-- Rays
local function ray_main(pos1, pos2, args)
local pos1 = vector():set(pos1.x or pos1[1], pos1.y or pos1[2], pos1.z or pos1[3])
local pos2 = vector():set(pos2.x or pos2[1], pos2.y or pos2[2], pos2.z or pos2[3])
local args = args or {}
local pick = ray_pick()
pick:set_position(pos1)
pick:set_direction(pos2:sub(pos1):normalize())
pick:set_flags(args.flags or 2)
pick:set_range(args.range or 200)
if args.ignore_object then
pick:set_ignore_object(args.ignore_object)
end
pick:query()
return pick
end
anomalies_vars = {
-- Quick lookup of sine values by degrees
sin_lut = (function()
local t = {}
for i = 0, 360 do
t[i] = sin(i * 0.0174533)
end
return t
end)(),
-- Quick lookup of cosine values by degrees
cos_lut = (function()
local t = {}
for i = 0, 360 do
t[i] = cos(i * 0.0174533)
end
return t
end)(),
-- Current anomaly factors
factors = {},
add_factor = function(self, anomaly, actor, distance_to_sqr, radius_sqr, section, factor)
self.factors[anomaly.object:name()] = {
section = section or anomaly.object:section(),
factor = factor or 1 - distance_to_sqr / radius_sqr
}
end,
remove_factor = function(self, anomaly, actor, distance_to_sqr, radius_sqr)
self.factors[anomaly.object:name()] = nil
end,
find_max_factor = function(self, anomaly, actor, distance_to_sqr, radius_sqr, condition)
if is_empty(self.factors) then
return 1 - distance_to_sqr / radius_sqr
end
local factor = 0
if condition == nil or condition == true then
condition = function() return true end
elseif condition == false then
condition = function() return false end
end
for k, v in pairs(self.factors) do
if v.factor > factor and condition(v) then
factor = v.factor
end
end
return factor
end,
find_factor_sum = function(self, anomaly, actor, distance_to_sqr, radius_sqr, condition)
if is_empty(self.factors) then
return 1 - distance_to_sqr / radius_sqr
end
local factor = 0
if condition == nil or condition == true then
condition = function() return true end
elseif condition == false then
condition = function() return false end
end
for k, v in pairs(self.factors) do
if condition(v) then
factor = factor + v.factor
end
end
return factor
end,
zone_mine_gravitational_weak_tg = 0,
zone_mine_electric_tg = 0,
zone_mine_electric_pp_effectors = {
{code = 98324, file = "electra_mine.ppe", factor = 0.5},
{code = 98325, file = "electra.ppe", factor = 1},
},
zone_mine_electric_factor_function = function(factors)
return factors.section == "zone_mine_electric"
end
}
-- Defined radius of anomalies behaviour
anomalies_near_actor_radii = {
zone_mine_umbra = anomaly_radii.zone_mine_umbra.max * 3.5,
zone_mine_gold = anomaly_radii.zone_mine_gold.max * 3,
zone_mine_thorn = anomaly_radii.zone_mine_thorn.max * 3,
zone_mine_shatterpoint = anomaly_radii.zone_mine_shatterpoint.max * 6,
zone_mine_seed = anomaly_radii.zone_mine_seed.max * 5,
zone_mine_sphere = anomaly_radii.zone_mine_sphere.max * 2,
zone_mine_sloth = anomaly_radii.zone_mine_sloth.max * 2,
zone_mine_ghost = anomaly_radii.zone_mine_ghost.max * 5,
}
-- Special anomalies behaviour if the actor is near an anomaly
anomalies_near_actor_functions = {
-- Umbral Cluster, spawns poltergeist behind the actor
zone_mine_umbra = function(anomaly, actor, distance_to_sqr, radius_sqr)
if not anomaly.spawn_time then anomaly.spawn_time = 0 end
if time_elapsed < anomaly.spawn_time then return end
local spawn_cooldown = 300
local spawn_max_amount = 2
local spawn_table = {
"m_poltergeist_normal_tele",
"m_poltergeist_normal_flame",
}
printf("trying to spawn poltergeist")
if random() * 100 <= 1.5 then
anomaly.spawn_time = time_elapsed + spawn_cooldown
for i = 1, ceil(random() ^ 2 * spawn_max_amount) do
local actor_position = actor:position()
local spawn_position = vector():set(actor_position.x - random_float(5, 7), actor_position.y, actor_position.z - random_float(5, 7))
local spawn_section = spawn_table[random(#spawn_table)]
alife_create(spawn_section, spawn_position, level_vertex_id(spawn_position), alife():actor().m_game_vertex_id)
end
end
end,
-- Seed, multiplies if standing near
zone_mine_seed = function(anomaly, actor, distance_to_sqr, radius_sqr)
if not anomaly.spawn_time then anomaly.spawn_time = 0 end
if time_elapsed < anomaly.spawn_time then return end
local spawn_cooldown = 300
local spawn_max_amount = 1
local spawn_table = {
"zone_mine_seed",
}
printf("trying to multiply seed anomaly")
if random() * 100 <= 0.5 then
anomaly.spawn_time = time_elapsed + spawn_cooldown
for i = 1, ceil(random() ^ 2 * spawn_max_amount) do
local actor_position = actor:position()
local spawn_position = vector():set(actor_position.x - random_float(5, 7), actor_position.y, actor_position.z - random_float(5, 7))
local spawn_section = spawn_table[random(#spawn_table)]
local se_obj = alife_create(spawn_section, spawn_position, level_vertex_id(spawn_position), alife():actor().m_game_vertex_id)
local data = utils_stpk.get_object_data(se_obj)
if (data) then
data.object_flags = 31
data.restrictor_type = 0
data.shapes = {}
data.shapes[1] = {}
data.shapes[1].shtype = 0
data.shapes[1].offset = VEC_ZERO
data.shapes[1].center = VEC_ZERO
data.shapes[1].radius = 3
utils_stpk.set_object_data(data,se_obj)
end
end
end
end,
-- Sloth, lower speed by 80% near it
zone_mine_sloth = function(anomaly, actor, distance_to_sqr, radius_sqr)
local key ="zone_mine_sloth_speed"
local steps_in = 70
local steps_out = 22
add_simple_timed_effect(1, function()
add_speed(key, ema(key, 0.2, 1, steps_in), false, true)
RemoveTimeEvent(key, key)
end, function()
CreateTimeEvent(key, key, 0, function()
add_speed(key, ema(key, 1, 1, steps_out, device().time_delta * 0.1), false, true)
if smoothed_values[key] >= 0.98 then
remove_speed(key)
return true
end
end)
end, key, 1)
end,
-- No Gravity, lower speed by 50% near it
zone_no_gravity = function(anomaly, actor, distance_to_sqr, radius_sqr)
local key ="zone_no_gravity"
local steps_in = 70
local steps_out = 22
add_simple_timed_effect(1, function()
add_speed(key, ema(key, 0.6, 1, steps_in), false, true)
RemoveTimeEvent(key, key)
end, function()
CreateTimeEvent(key, key, 0, function()
add_speed(key, ema(key, 1, 1, steps_out, device().time_delta * 0.1), false, true)
if smoothed_values[key] >= 0.98 then
remove_speed(key)
return true
end
end)
end, key, 1)
end,
-- Ghost, spawns phantoms when close
zone_mine_ghost = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomaly.spawn_time = anomaly.spawn_time or 0
if time_elapsed < anomaly.spawn_time then return end
local spawn_cooldown = 300
local spawn_max_amount = 2
local spawn_table = {
"m_phantom_zombi",
"m_phantom_bloodsucker",
}
printf("trying to spawn phantom")
if random() * 100 <= 15 then
anomaly.spawn_time = time_elapsed + spawn_cooldown
for i = 1, ceil(random() ^ 2 * spawn_max_amount) do
local actor_position = actor:position()
local spawn_position = vector():set(actor_position.x - random_float(5, 7), actor_position.y, actor_position.z - random_float(5, 7))
phantom_manager.spawn_phantom(spawn_position)
end
end
end,
-- Net, lower speed by 50% near it
zone_mine_net = function(anomaly, actor, distance_to_sqr, radius_sqr)
local key ="zone_mine_net_speed"
local steps_in = 70
local steps_out = 22
add_simple_timed_effect(1, function()
add_speed(key, ema(key, 0.5, 1, steps_in), false, true)
RemoveTimeEvent(key, key)
end, function()
CreateTimeEvent(key, key, 0, function()
add_speed(key, ema(key, 1, 1, steps_out, device().time_delta * 0.1), false, true)
if smoothed_values[key] >= 0.98 then
remove_speed(key)
return true
end
end)
end, key, 1)
end,
-- Cognitive Dissonance Field, negative psy aura
zone_mine_cdf = function(anomaly, actor, distance_to_sqr, radius_sqr)
local h = hit()
h.power = level_environment.is_actor_immune() and 0 or (1 - distance_to_sqr / radius_sqr) * 0.004
if h.power > 0 then
h.type = hit.telepatic
h.impulse = 0
h.direction = VEC_Z
h.draftsman = actor
actor:hit(h)
-- Artefacts protection
local hit_additional = 0
actor:iterate_belt( function(owner, obj)
local sec = obj:section()
local cond = obj:condition()
local immunities_sec = SYS_GetParam(0, sec, "hit_absorbation_sect", sec)
local prot = SYS_GetParam(2, immunities_sec, "telepatic_immunity", 0) * cond
-- Optional modifier for viability
prot = prot * 10
hit_additional = hit_additional + prot
end)
-- Final modifier
local hit_modifier = hit_additional >= 0 and 1 + hit_additional or 1 / (1 - hit_additional)
local actor_hit_power = h.power / hit_modifier * 0.375
change_psy_health(-actor_hit_power)
end
end,
-- Sphere, add 50-100% bullet damage resist
zone_mine_sphere = function(anomaly, actor, distance_to_sqr, radius_sqr)
local key = "zone_mine_sphere"
if not callbacks[key] then
register_callback("actor_on_before_hit", function(s_hit, bone_id, flags)
if s_hit.type == hit.fire_wound then
s_hit.power = s_hit.power * random_float(0, 0.5)
play_sound_on_actor("eugenium_anomaly\\sphere\\sphere_blowout", 0.9, random_float(0.95, 1.05))
local gibs = particles_object("artefact\\artefact_gravi")
if gibs and not gibs:playing() then
gibs:play_at_pos(actor:position())
end
end
end, nil, key)
end
add_simple_timed_effect(1, nil, function()
unregister_callback(key)
end, key, 1)
end,
-- Springboard, camera effect dependant on distance
zone_mine_gravitational_weak = function(anomaly, actor, distance_to_sqr, radius_sqr, power_modifier)
local key = "zone_mine_gravitational_weak"
local tg = time_global()
if tg < anomalies_vars.zone_mine_gravitational_weak_tg and timed_effects[key] then return end
anomalies_vars.zone_mine_gravitational_weak_tg = tg + 1000
local power_modifier = (power_modifier or 2) * settings.gravitational_shake_modifier
local earthquake_cam_eff = 98323
if power_modifier > 0 then
local power = (1.03 - distance_to_sqr / radius_sqr) * power_modifier
level.add_cam_effector("camera_effects\\earthquake_40.anm", earthquake_cam_eff, false, "", 0, false, power)
end
add_simple_timed_effect(1, nil, function()
level.remove_cam_effector(earthquake_cam_eff)
end, key, 1)
end,
zone_mine_gravitational_average = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr, 1)
end,
zone_mine_gravitational_strong = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr, 1)
end,
zone_mine_vortex = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr, 1)
end,
zone_mine_springboard = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr, 1)
end,
zone_mine_blast = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_gravi_zone = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mosquito_bald = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mosquito_bald_weak = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mosquito_bald_weak_noart = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mosquito_bald_average = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mosquito_bald_strong = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mosquito_bald_strong_noart = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_gravitational_weak(anomaly, actor, distance_to_sqr, radius_sqr)
end,
-- Electra, static field
zone_mine_electric = function(anomaly, actor, distance_to_sqr, radius_sqr, effector_modifier)
local hit_type_shock = HitTypeID["Shock"]
local hit_power = level_environment.is_actor_immune() and 0 or (1 - distance_to_sqr / radius_sqr) ^ 2 * settings.electric_field_modifier
local distance_to = distance_to_sqr ^ 0.5
local reduction_coeff = 1
local anom_pos = anomaly.object:position()
-- anom_pos = level_vertex_id(anom_pos)
-- anom_pos = level_vertex_position(anom_pos)
local anom_pos_adjusted = vector():set(anom_pos.x, anom_pos.y + 0.3, anom_pos.z)
local actor_pos = utils_obj.safe_bone_pos(actor, "bip01_spine")
-- local actor_pos = actor:position()
-- actor_pos = vector():set(actor_pos.x, actor_pos.y + 0.5, actor_pos.z)
local anom_to_actor_ray = ray_main(anom_pos_adjusted, actor_pos, {range = distance_to})
local actor_to_anom_ray = ray_main(actor_pos, anom_pos, {range = distance_to})
if anom_to_actor_ray and actor_to_anom_ray then
local anom_to_actor_distance = anom_to_actor_ray:get_distance()
local actor_to_anom_distance = actor_to_anom_ray:get_distance()
local obstacle_distance = 0
if anom_to_actor_distance > 0 then
obstacle_distance = distance_to - anom_to_actor_distance - actor_to_anom_distance
reduction_coeff = clamp((1 - obstacle_distance / radius_sqr ^ 0.5) ^ 3.5, 0, 1)
end
-- _G.printf("reduction coeff due to obstacle - %s", reduction_coeff)
-- _G.printf("anom_to_actor_distance - %s", anom_to_actor_distance)
-- _G.printf("actor_to_anom_distance - %s", actor_to_anom_distance)
-- _G.printf("obstacle_distance - %s", obstacle_distance)
-- _G.printf("combined distance - %s", anom_to_actor_distance + obstacle_distance + actor_to_anom_distance)
-- _G.printf("input distance - %s", distance_to)
end
hit_power = hit_power * reduction_coeff
if hit_power > 0 then
local hit_additional = 0
-- Outfit protection
local outfit = actor:item_in_slot(7)
if outfit then
local c_obj = outfit:cast_CustomOutfit()
local prot = c_obj and c_obj:GetDefHitTypeProtection(hit_type_shock) or 0
-- Optional modifier for less viability
prot = prot * 1
hit_additional = hit_additional + prot
end
-- Helmet protection
local helm = actor:item_in_slot(12)
if helm then
local c_obj = helm:cast_Helmet()
local prot = c_obj and c_obj:GetDefHitTypeProtection(hit_type_shock) or 0
-- Optional modifier for less viability
prot = prot * 1
hit_additional = hit_additional + prot
end
-- Artefacts protection
local artefacts_protection = 0
actor:iterate_belt( function(owner, obj)
local sec = obj:section()
local cond = obj:condition()
local immunities_sec = SYS_GetParam(0, sec, "hit_absorbation_sect", sec)
local prot = SYS_GetParam(2, immunities_sec, "shock_immunity", 0) * cond
-- Optional modifier for viability
prot = prot * 5
artefacts_protection = artefacts_protection + prot
hit_additional = hit_additional + prot
end)
-- Final modifier
local hit_modifier = hit_additional >= 0 and 1 + hit_additional or 1 / (1 - hit_additional)
local actor_hit_power = hit_power / hit_modifier * 0.0015
-- printf("hit %s", actor_hit_power)
actor:change_health(-actor_hit_power)
-- Affect condition of items
if outfit then
local obj = outfit
local sec = obj:section()
local cond = obj:condition()
if cond > 0.01 then
local immunities_sec = SYS_GetParam(0, sec, "immunities_sect", sec)
local shock_immunity = SYS_GetParam(2, immunities_sec, "shock_immunity", 0) * cond
shock_immunity = shock_immunity + artefacts_protection
local hit_modifier = shock_immunity >= 0 and 1 + shock_immunity or 1 / (1 - shock_immunity)
obj:set_condition(cond - hit_power / hit_modifier * 0.00015)
end
end
if helm then
local obj = helm
local sec = obj:section()
local cond = obj:condition()
if cond > 0.01 then
local immunities_sec = SYS_GetParam(0, sec, "immunities_sect", sec)
local shock_immunity = SYS_GetParam(2, immunities_sec, "shock_immunity", 0) * cond
shock_immunity = shock_immunity + artefacts_protection
local hit_modifier = shock_immunity >= 0 and 1 + shock_immunity or 1 / (1 - shock_immunity)
obj:set_condition(cond - hit_power / hit_modifier * 0.00015)
end
end
end
anomalies_vars:add_factor(anomaly, actor, distance_to_sqr, radius_sqr, "zone_mine_electric", (1 - distance_to_sqr / radius_sqr) ^ 2 * reduction_coeff)
local mine_factor = clamp(anomalies_vars:find_factor_sum(anomaly, actor, distance_to_sqr, radius_sqr, anomalies_vars.zone_mine_electric_factor_function), 0, 1)
-- PPE effector
local effector_modifier = effector_modifier or 0.66
local effector_power = (mine_factor + 0.05) * effector_modifier
local pps = anomalies_vars.zone_mine_electric_pp_effectors
for i = 1, #pps do
local key = "zone_mine_electric" .. pps[i].code
if not timed_effects[key] then
level.add_pp_effector(pps[i].file, pps[i].code, true)
end
level.set_pp_effector_factor(pps[i].code, effector_power * pps[i].factor)
add_simple_timed_effect(0.2, nil, function()
level.remove_pp_effector(pps[i].code)
end, key, 1)
end
-- PDA glitching, set value for binder, patch binder, see below
pda_glitch_value = clamp(mine_factor ^ 0.5, 0, 1)
add_simple_timed_effect(0.2, nil, function()
pda_glitch_value = nil
end, "zone_mine_electric_pda_glitch", 1)
end,
zone_mine_electric_weak = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mine_electric_average = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mine_electric_strong = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mine_static = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mine_static_weak = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mine_static_average = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_mine_static_strong = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_witches_galantine = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_witches_galantine_weak = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_witches_galantine_average = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
zone_witches_galantine_strong = function(anomaly, actor, distance_to_sqr, radius_sqr)
anomalies_near_actor_functions.zone_mine_electric(anomaly, actor, distance_to_sqr, radius_sqr)
end,
-- Ball Lightning, charges batteries
zone_mine_point = function(anomaly, actor, distance_to_sqr, radius_sqr)
for i = 8, 10 do
local item = actor:item_in_slot(i)
if item then
local cond = item:condition()
if cond > 0.01 then
item:set_condition(cond + 0.0006 * (1 - distance_to_sqr / radius_sqr) ^ 2)
end
end
end
anomalies_vars:add_factor(anomaly, actor, distance_to_sqr, radius_sqr, "zone_mine_electric", (1 - distance_to_sqr / radius_sqr) ^ 2)
local mine_factor = clamp(anomalies_vars:find_factor_sum(anomaly, actor, distance_to_sqr, radius_sqr, anomalies_vars.zone_mine_electric_factor_function), 0, 1)
-- PDA glitching, set value for binder, patch binder, see below
pda_glitch_value = clamp(mine_factor ^ 0.5, 0, 1)
add_simple_timed_effect(0.2, nil, function()
pda_glitch_value = nil
end, "zone_mine_electric_pda_glitch", 1)
end,
-- Liquid Gold, drains stamina and thirst
zone_mine_gold = function(anomaly, actor, distance_to_sqr, radius_sqr)
local change_modifier = 1 - distance_to_sqr / radius_sqr
if not anomaly.trigger_threshold then anomaly.trigger_threshold = 0 end
anomaly.trigger_threshold = anomaly.trigger_threshold + change_modifier * 0.5
if anomaly.trigger_threshold >= 1 then
thirst_sleep_changer.change_thirst_small(0.01)
anomaly.trigger_threshold = 0
end
actor:change_power(-change_modifier * 0.002)
end,
}
-- Special behaviour for anomalies after spawn
anomalies_spawn_functions = {
-- Ghost, wandering path along its start position
zone_mine_ghost = function(anomaly)
if anomaly.spawn_position then
local spawn_position = anomaly.spawn_position
local obj = anomaly.object
local obj_set_anomaly_position = obj.set_anomaly_position
local vector = vector()
local vector_set = vector.set
local key = obj:name()
local sin_lut = anomalies_vars.sin_lut
local cos_lut = anomalies_vars.cos_lut
local i = random_choice(0, 45, 90, 135, 180)
local i_step = random_choice(1, 2, 3)
local j = 0
local j_step = 1
if i_step == 1 then
j_step = random_choice(2, 3)
elseif i_step == 2 then
j_step = random_choice(1, 3)
elseif i_step == 3 then
j_step = random_choice(2, 4)
end
local full_circle = 360
local function generate_distance()
return random() * 5.5 + 1.5
end
local x_modifier = generate_distance() * random_choice(1, -1)
local z_modifier = generate_distance() * random_choice(1, -1)
local function wrap(i)
return i >= full_circle and i % full_circle or i
end
local max_offset_y = 0.5
local max_offset_y_low = spawn_position.y - max_offset_y
local max_offset_y_high = spawn_position.y + max_offset_y
local prev_pos_x = spawn_position.x
local prev_pos_y = spawn_position.y
local prev_pos_z = spawn_position.z
register_callback("actor_on_update", function()
if i >= full_circle then
i = i % full_circle
end
if j >= full_circle then
j = j % full_circle
end
local offset_x = spawn_position.x + sin_lut[i] * x_modifier
local offset_z = spawn_position.z + sin_lut[j] * z_modifier
local offset_y = spawn_position.y
-- Adjust to height by checking lvid
local lvid = level_vertex_id(vector_set(vector, offset_x, offset_y, offset_z))
if (lvid < 4294967295) then
local pos_y = level_vertex_position(lvid).y
if abs(pos_y - prev_pos_y) < max_offset_y then
offset_y = pos_y
end
-- offset_y = clamp(pos_y, max_offset_y_low, max_offset_y_high)
end
obj_set_anomaly_position(obj, vector_set(vector, offset_x, offset_y, offset_z))
prev_pos_x = offset_x
prev_pos_y = offset_y
prev_pos_z = offset_z
local delta = min(50, device().time_delta) / 22 -- Frame independent movement, less than 20 fps will be slower due to prevent sudden pops
i = floor(i + i_step * delta)
j = floor(j + j_step * delta)
end, nil, key)
end
end,
-- Sphere, wandering path along its start position
zone_mine_sphere = function(anomaly)
if anomaly.spawn_position then
local spawn_position = anomaly.spawn_position
local obj = anomaly.object
local obj_set_anomaly_position = obj.set_anomaly_position
local vector = vector()
local vector_set = vector.set
local key = obj:name()
local sin_lut = anomalies_vars.sin_lut
local cos_lut = anomalies_vars.cos_lut
local i = random_choice(0, 45, 90, 135, 180)
local i_step = random_choice(1, 2, 3)
local j = 0
local j_step = 1
if i_step == 1 then
j_step = random_choice(2, 3)
elseif i_step == 2 then
j_step = random_choice(1, 3)
elseif i_step == 3 then
j_step = random_choice(2, 4)
end
local full_circle = 360
local function generate_distance()
return random() * 0.5 + 1.0
end
local x_modifier = generate_distance() * random_choice(1, -1)
local z_modifier = generate_distance() * random_choice(1, -1)
local function wrap(i)
return i >= full_circle and i % full_circle or i
end
local max_offset_y = 0.5
local max_offset_y_low = spawn_position.y - max_offset_y
local max_offset_y_high = spawn_position.y + max_offset_y
local prev_pos_x = spawn_position.x
local prev_pos_y = spawn_position.y
local prev_pos_z = spawn_position.z
register_callback("actor_on_update", function()
if i >= full_circle then
i = i % full_circle
end
if j >= full_circle then
j = j % full_circle
end
local offset_x = spawn_position.x + sin_lut[i] * x_modifier
local offset_z = spawn_position.z + sin_lut[j] * z_modifier
local offset_y = spawn_position.y
-- Adjust to height by checking lvid
local lvid = level_vertex_id(vector_set(vector, offset_x, offset_y, offset_z))
if (lvid < 4294967295) then
local pos_y = level_vertex_position(lvid).y
if abs(pos_y - prev_pos_y) < max_offset_y then
offset_y = pos_y
end
-- offset_y = clamp(pos_y, max_offset_y_low, max_offset_y_high)
end
obj_set_anomaly_position(obj, vector_set(vector, offset_x, offset_y, offset_z))
prev_pos_x = offset_x
prev_pos_y = offset_y
prev_pos_z = offset_z
local delta = min(50, device().time_delta) / 22 -- Frame independent movement, less than 20 fps will be slower due to prevent sudden pops
i = floor(i + i_step * delta)
j = floor(j + j_step * delta)
end, nil, key)
end
end,
-- Point, wandering path along its start position
zone_mine_point = function(anomaly)
if anomaly.spawn_position then
local spawn_position = anomaly.spawn_position
local obj = anomaly.object
local obj_set_anomaly_position = obj.set_anomaly_position
local vector = vector()
local vector_set = vector.set
local key = obj:name()
local sin_lut = anomalies_vars.sin_lut
local cos_lut = anomalies_vars.cos_lut
local i = random_choice(0, 45, 90, 135, 180)
local i_step = random_choice(1, 2, 3)
local j = 0
local j_step = 1
if i_step == 1 then
j_step = random_choice(2, 3)
elseif i_step == 2 then
j_step = random_choice(1, 3)
elseif i_step == 3 then
j_step = random_choice(2, 4)
end
local full_circle = 360
local function generate_distance()
return random() * 2.5 + 4.5
end
local x_modifier = generate_distance() * random_choice(1, -1)
local z_modifier = generate_distance() * random_choice(1, -1)
local function wrap(i)
return i >= full_circle and i % full_circle or i
end
local max_offset_y = 0.5
local max_offset_y_low = spawn_position.y - max_offset_y
local max_offset_y_high = spawn_position.y + max_offset_y
local prev_pos_x = spawn_position.x
local prev_pos_y = spawn_position.y
local prev_pos_z = spawn_position.z
register_callback("actor_on_update", function()
if i >= full_circle then
i = i % full_circle
end
if j >= full_circle then
j = j % full_circle
end
local offset_x = spawn_position.x + sin_lut[i] * x_modifier
local offset_z = spawn_position.z + sin_lut[j] * z_modifier
local offset_y = spawn_position.y
-- Adjust to height by checking lvid
local lvid = level_vertex_id(vector_set(vector, offset_x, offset_y, offset_z))
if (lvid < 4294967295) then
local pos_y = level_vertex_position(lvid).y
if abs(pos_y - prev_pos_y) < max_offset_y then
offset_y = pos_y
end
-- offset_y = clamp(pos_y, max_offset_y_low, max_offset_y_high)
end
obj_set_anomaly_position(obj, vector_set(vector, offset_x, offset_y, offset_z))
prev_pos_x = offset_x
prev_pos_y = offset_y
prev_pos_z = offset_z
local delta = min(50, device().time_delta) / 22 -- Frame independent movement, less than 20 fps will be slower due to prevent sudden pops
i = floor(i + i_step * delta)
j = floor(j + j_step * delta)
end, nil, key)
end
end,
-- Umbral Cluster, wandering path along its start position
zone_mine_umbra = function(anomaly)
if anomaly.spawn_position then
local spawn_position = anomaly.spawn_position
local obj = anomaly.object
local obj_set_anomaly_position = obj.set_anomaly_position
local vector = vector()
local vector_set = vector.set
local key = obj:name()
local sin_lut = anomalies_vars.sin_lut
local cos_lut = anomalies_vars.cos_lut
local i = random_choice(0, 45, 90, 135, 180)
local i_step = random_choice(1, 2, 3)
local j = 0
local j_step = 1
if i_step == 1 then
j_step = random_choice(2, 3)
elseif i_step == 2 then
j_step = random_choice(1, 3)
elseif i_step == 3 then
j_step = random_choice(2, 4)
end
local full_circle = 360
local function generate_distance()
return random() * 0.3 + 0.5
end
local x_modifier = generate_distance() * random_choice(1, -1)
local z_modifier = generate_distance() * random_choice(1, -1)
local function wrap(i)
return i >= full_circle and i % full_circle or i
end
local max_offset_y = 0.5
local max_offset_y_low = spawn_position.y - max_offset_y
local max_offset_y_high = spawn_position.y + max_offset_y
local prev_pos_x = spawn_position.x
local prev_pos_y = spawn_position.y
local prev_pos_z = spawn_position.z
register_callback("actor_on_update", function()
if i >= full_circle then
i = i % full_circle
end
if j >= full_circle then
j = j % full_circle
end
local offset_x = spawn_position.x + sin_lut[i] * x_modifier
local offset_z = spawn_position.z + sin_lut[j] * z_modifier
local offset_y = spawn_position.y
-- Adjust to height by checking lvid
local lvid = level_vertex_id(vector_set(vector, offset_x, offset_y, offset_z))
if (lvid < 4294967295) then
local pos_y = level_vertex_position(lvid).y
if abs(pos_y - prev_pos_y) < max_offset_y then
offset_y = pos_y
end
-- offset_y = clamp(pos_y, max_offset_y_low, max_offset_y_high)
end
obj_set_anomaly_position(obj, vector_set(vector, offset_x, offset_y, offset_z))
prev_pos_x = offset_x
prev_pos_y = offset_y
prev_pos_z = offset_z
local delta = min(50, device().time_delta) / 22 -- Frame independent movement, less than 20 fps will be slower due to prevent sudden pops
i = floor(i + i_step * delta)
j = floor(j + j_step * delta)
end, nil, key)
end
end,
}
-- Special behaviour for anomalies after destroy
anomalies_destroy_functions = {
-- Ghost, destroy callback for wandering
zone_mine_ghost = function(anomaly)
local key = anomaly.object:name()
unregister_callback(key)
end,
}
anomaly_detector_ignore = {
zone_field_radioactive = true,
zone_field_radioactive_above_average = true,
zone_field_radioactive_average = true,
zone_field_radioactive_below_average = true,
zone_field_radioactive_lethal = true,
zone_field_radioactive_strong = true,
zone_field_radioactive_very_weak = true,
zone_field_radioactive_weak = true,
zone_radioactive = true,
zone_radioactive_above_average = true,
zone_radioactive_average = true,
zone_radioactive_below_average = true,
zone_radioactive_lethal = true,
zone_radioactive_strong = true,
zone_radioactive_very_weak = true,
zone_radioactive_weak = true,
zone_field_acidic = true,
zone_field_acidic_average = true,
zone_field_acidic_strong = true,
zone_field_acidic_weak = true,
zone_field_psychic = true,
zone_field_psychic_average = true,
zone_field_psychic_strong = true,
zone_field_psychic_weak = true,
zone_field_thermal = true,
zone_field_thermal_average = true,
zone_field_thermal_strong = true,
zone_field_thermal_weak = true,
zone_mine_field = true,
zone_mine_field_soc = true,
campfire = true,
campfire_base = true,
campfire_base_noshadow = true,
zone_base = true,
zone_base_noshadow = true,
zone_burning_fuzz = true,
zone_burning_fuzz1 = true,
zone_burning_fuzz_weak = true,
zone_burning_fuzz_average = true,
zone_burning_fuzz_strong = true,
zone_buzz = true,
zone_buzz_weak = true,
zone_buzz_average = true,
zone_buzz_strong = true,
zone_emi = true,
zone_liana = true,
zone_student = true,
zone_teleport = true,
zone_zhar = true,
-- MP items
mp_af_electra_flash = true,
mp_zone_witches_galantine = true,
mp_af_cta_green = true,
mp_af_cta_blue = true,
mp_medkit = true,
mp_medkit_scientic = true,
mp_medkit_army = true,
mp_energy_drink = true,
mp_bandage = true,
mp_antirad = true,
mp_drug_coagulant = true,
mp_drug_radioprotector = true,
mp_medkit_old = true,
mp_antirad_old = true,
mp_detector_advanced = true,
mp_device_torch = true,
mp_players_rukzak = true,
mp_wood_stolb_fixed = true,
mp_wood_stolb_fixed_immunities = true,
mp_explosive_fuelcan = true,
mp_explosive_tank = true,
mp_explosive_barrel = true,
}
function find_nearest_anomaly(force_enabled)
local actor = db.actor
local actor_pos = actor:position()
local actor_pos_distance = actor_pos.distance_to_sqr
local distance = math.huge
local anom_id
local anom_pos
local anom_sec
local pairs = pairs
local level_object_by_id = level.object_by_id
local level_name = level.name()
for id, _ in pairs(detectable_anomalies_ids) do
local obj = level_object_by_id(id)
if obj then
local enabled = true
if not force_enabled then
local props = updated_anomaly_levels[level_name].anomalies_properties[id]
if props and props.active ~= nil then
enabled = props.active
end
end
if enabled then
local pos = obj:position()
local d = actor_pos_distance(actor_pos, pos)
if d < distance then
distance = d
anom_id = id
anom_pos = pos
anom_sec = obj:section()
end
end
end
end
return distance, anom_id, anom_sec, anom_pos
end
detector_functions = {
-- Play sound
play_anomaly_sound = function(self, anomaly, actor, distance_to_sqr, radius_sqr)
if not anomaly.sound_threshold then anomaly.sound_threshold = 0 end
anomaly.sound_threshold = anomaly.sound_threshold + (1 - distance_to_sqr / radius_sqr) ^ 2
if anomaly.sound_threshold > 1 then
play_sound_on_actor("detectors\\da-2_beep1")
anomaly.sound_threshold = 0
end
end,
-- Store anomaly factors to find biggest one for correct display of anomaly_detector bars
detector_anomaly_factors = {},
detector_anomaly_add_factor = function(self, anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
self.detector_anomaly_factors[anomaly.object:name()] = 1 - distance_to_sqr / radius_sqr
end,
detector_anomaly_remove_factor = function(self, anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
self.detector_anomaly_factors[anomaly.object:name()] = nil
end,
detector_anomaly_find_max_factor = function(self, anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
if is_empty(self.detector_anomaly_factors) then
return 1 - distance_to_sqr / radius_sqr
end
local factor = 0
for k, v in pairs(self.detector_anomaly_factors) do
if v > factor then
factor = v
end
end
return factor
end,
detector_anomaly = function(self, anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
if not custom_callback then return end
if actor:active_detector() then
local sec = anomaly.section
self:detector_anomaly_add_factor(anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
local factor = self:detector_anomaly_find_max_factor(anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
local ui = tasks_measure.get_UI()
if ui and ui.step <= 0 then
for i = 1, ui.step_tot do
ui.m_seg[i]:InitTextureEx(i <= min(ui.step_tot, ceil(factor * 10)) and "green_seg_ano" or "green_black_ano", "hud\\p3d")
end
end
-- Play sound when detector is active
if game_difficulties.get_game_factor("notify_anomaly")
and (drx_da_main_mcm.new_anomalies_sections[sec] or drx_da_main_mcm.variations_anomalies_sections[sec])
or (not game_difficulties.get_game_factor("notify_anomaly") and actor:active_detector())
then
self:play_anomaly_sound(anomaly, actor, distance_to_sqr, radius_sqr)
end
else
local ui = tasks_measure.get_UI()
if ui and ui.step <= 0 then
for i = 1, ui.step_tot do
ui.m_seg[i]:InitTextureEx("green_black_ano", "hud\\p3d")
end
end
end
end,
detector_scientific = function(self, anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
if custom_callback then return end
local sec = anomaly.section
if drx_da_main_mcm.new_anomalies_sections[sec] or drx_da_main_mcm.variations_anomalies_sections[sec] then
self:play_anomaly_sound(anomaly, actor, distance_to_sqr, radius_sqr)
end
end
}
function notify_anomaly(anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
local detector = actor:item_in_slot(9)
local sec = detector and detector:section()
if sec and detector_functions[sec] then
detector_functions[sec](detector_functions, anomaly, actor, distance_to_sqr, radius_sqr, custom_callback)
elseif game_difficulties.get_game_factor("notify_anomaly") and (drx_da_main_mcm.new_anomalies_sections[anomaly.section] or drx_da_main_mcm.variations_anomalies_sections[anomaly.section]) and not custom_callback then
detector_functions:play_anomaly_sound(anomaly, actor, distance_to_sqr, radius_sqr)
end
end
bind_anomaly_field_spawn = bind_anomaly_field.anomaly_field_binder.net_spawn
bind_anomaly_field.anomaly_field_binder.net_spawn = function(self, se_abstract)
if not bind_anomaly_field_spawn(self, se_abstract) then
return false
end
self.spawn_position = self.object:position()
self.section = self.object:section()
if not anomaly_detector_ignore[self.section] then
detectable_anomalies_ids[self.object:id()] = self.spawn_position
end
local section = self.section
if anomalies_spawn_functions[section] then
anomalies_spawn_functions[section](self)
end
-- Update callback similar to binder update, but works even if anomaly is disabled
self.on_update_timer_default = 100
self.on_update_timer_max = 7500
self.on_update_timer = self.on_update_timer_default
self.on_update_time = time_global() + random(self.on_update_timer_default, self.on_update_timer_default * 10)
self.update_key = self.object:name() .. "_update"
local anom_zone_data = utils_stpk.get_anom_zone_data(alife_object(self.object:id()))
if anom_zone_data and anom_zone_data.shapes[1] and anom_zone_data.shapes[1].radius then
self.radius_sqr = anomalies_near_actor_radii[section] or ((anom_zone_data.shapes[1].radius + 1) * 2)
self.radius_sqr = self.radius_sqr * self.radius_sqr
-- printf("getting anom radius from net packet, %s", self.radius_sqr)
else
self.radius_sqr = get_anomaly_behaviour_radius(section)
end
self.radius = sqrt(self.radius_sqr)
self.behaviour_radius_sqr = get_anomaly_behaviour_radius(section)
local level_name = level.name()
register_callback("actor_on_update", function()
local tg = time_global()
if tg < self.on_update_time then return end
-- Get behaviour radius and check if actor inside it, then apply effect
local actor = db.actor
local radius_sqr = self.radius_sqr
local distance_to_sqr = self.object:position():distance_to_sqr(actor:position())
if distance_to_sqr <= radius_sqr then
-- Beep near anoms even if disabled for anomaly_detector
if not (
anomaly_detector_ignore[self.section]
or (
level_name
and updated_anomaly_levels[level_name]
and updated_anomaly_levels[level_name].disabled
and updated_anomaly_levels[level_name].anomalies_properties
and updated_anomaly_levels[level_name].anomalies_properties[self.object:id()]
)
) then
local og_dyn_anomalies = bind_anomaly_field.dyn_anomalies
if not og_dyn_anomalies then
notify_anomaly(self, actor, distance_to_sqr, radius_sqr, true)
else
local level_name = level.name()
if og_dyn_anomalies[level_name] and og_dyn_anomalies[level_name][self.object:id()] ~= nil then
if og_dyn_anomalies[level_name][self.object:id()] then
notify_anomaly(self, actor, distance_to_sqr, radius_sqr, true)
end
else
notify_anomaly(self, actor, distance_to_sqr, radius_sqr, true)
end
end
end
self.on_update_timer = self.on_update_timer_default
else
detector_functions.detector_anomaly_remove_factor(detector_functions, self, actor, distance_to_sqr, radius_sqr, true)
self.on_update_timer = clamp(self.on_update_timer_default * (distance_to_sqr / radius_sqr * 0.5), self.on_update_timer_default, self.on_update_timer_max)
-- _G.printf("%s, on_update_timer %s", self.object:name(), self.on_update_timer)
end
self.on_update_time = tg + self.on_update_timer
end, nil, self.update_key)
return true
end
bind_anomaly_field_destroy = bind_anomaly_field.anomaly_field_binder.net_destroy
bind_anomaly_field.anomaly_field_binder.net_destroy = function(self)
local section = self.section
detectable_anomalies_ids[self.object:id()] = nil
anomalies_vars.remove_factor(anomalies_vars, self, actor, distance_to_sqr, radius_sqr)
detector_functions.detector_anomaly_remove_factor(detector_functions, self, actor, distance_to_sqr, radius_sqr, true)
if anomalies_destroy_functions[section] then
anomalies_destroy_functions[section](self)
end
unregister_callback(self.update_key)
bind_anomaly_field_destroy(self)
end
-- Behaviour radius in priorities
-- 1. anomalies_near_actor_radii[section] -- specially defined behaviour radius
-- 2. anomaly_radii[section].max -- defined hit radius of anomaly
-- 3. max_radius -- max hit radius defined in level config
-- 4. 3 -- default radius
-- 5. p.2,3,4 or 5 is then added 1 and multiplied by 2
function get_anomaly_behaviour_radius(section)
local radius = anomalies_near_actor_radii[section] or (((anomaly_radii[section] and anomaly_radii[section].max or max_radius or 3) + 1) * 2)
local radius_sqr = radius * radius
return radius_sqr
end
-- Special behaviour is actor near an anomaly
bind_anomaly_field_update = bind_anomaly_field.anomaly_field_binder.update
bind_anomaly_field.anomaly_field_binder.update = function(self, delta)
bind_anomaly_field_update(self, delta)
if not self.object then return end
local section = self.section
if anomalies_near_actor_functions[section] or (additional_articles_to_category.encyclopedia_anomalies[section] and not opened_articles.encyclopedia_anomalies[additional_articles_to_category.encyclopedia_anomalies[section]]) then
-- Get behaviour radius and check if actor inside it, then apply effect
local actor = db.actor
local radius_sqr = self.radius_sqr
local distance_to_sqr = self.object:position():distance_to_sqr(actor:position())
if distance_to_sqr <= radius_sqr then
-- Open anomaly article
open_anomaly_article(section)
-- Beep near anoms if option enabled or have Svarog or Anomaly Detector
if not anomaly_detector_ignore[section] then
notify_anomaly(self, actor, distance_to_sqr, radius_sqr, false)
end
-- Behaviour near actor
if anomalies_near_actor_functions[section] then
anomalies_near_actor_functions[section](self, actor, distance_to_sqr, radius_sqr)
end
-- printf("actor near anomaly %s, firing effect, delta %s", section, delta)
else
anomalies_vars.remove_factor(anomalies_vars, self, actor, distance_to_sqr, radius_sqr)
end
end
if npc_on_near_anomalies_functions[section] then
if not self.iterate_nearest_func then
self.iterate_nearest_func = function(obj)
if obj
and (IsStalker(obj) or IsMonster(obj))
and (obj.alive and obj:alive())
and obj:id() ~= AC_ID
and obj:position():distance_to_sqr(self.object:position()) <= self.radius_sqr
then
npc_on_near_anomalies_functions[section](self, obj, db.actor)
end
end
end
level.iterate_nearest(self.object:position(), self.radius, self.iterate_nearest_func)
end
end
-- Apply glitches and flickers to active items near electrical anomalies
-- See above how value is set
pda_glitch_value = nil
process_glitch = item_device.device_binder.process_glitch
item_device.device_binder.process_glitch = function(self, id, section, condition)
process_glitch(self, id, section, condition)
if pda_glitch_value then
self.object:set_psy_factor(pda_glitch_value)
end
end
process_flicker = item_device.device_binder.process_flicker
item_device.device_binder.process_flicker = function(self, force)
process_flicker(self, pda_glitch_value and pda_glitch_value > 0.4 or force)
end
process_torch = item_device.device_binder.process_torch
item_device.device_binder.process_torch = function(self, id, section, condition)
process_torch(self, id, section, condition)
-- Beef's NVG integration
if z_beefs_nvgs then
if self.N_V then
z_beefs_nvgs.nvg_glitch(clamp(pda_glitch_value or 0, 0, 0.9))
else
z_beefs_nvgs.nvg_glitch(0)
end
end
end
local actor_on_update_time = 0
local actor_on_update_timer = 100
function actor_on_update()
local tg = time_global()
if tg < actor_on_update_time then return end
actor_on_update_time = tg + actor_on_update_timer
time_elapsed = get_time_elapsed()
process_timed_effects()
end
function actor_on_interaction(typ, obj, name)
-- check if emission happened globally
if typ == "anomalies" and (name == "emission_end" or name == "psi_storm_end") then
local level_name = level.name()
init_anomaly_table_on_level(level_name)
-- 50/50 chance to remove anomalies globally or just update artefacts
if random(100) <= 50 then
CreateTimeEvent("clean_dynamic_anomalies_global", "clean_dynamic_anomalies_global", 0.5, function()
last_surge_time = get_time_elapsed()
printf("surge happened globally at %s", last_surge_time)
printf("update on level %s after emission", level.name())
clean_dynamic_anomalies_global()
build_anomalies_pos_tree()
return true
end)
else
printf("surge happened globally at %s", get_time_elapsed())
printf("update artefacts on level %s after emission", level.name())
clean_artefacts_on_level(level.name())
spawn_artefacts_on_level(level.name())
build_anomalies_pos_tree()
end
end
end
function actor_on_item_take(obj)
local level_name = level.name()
local id = obj:id()
if updated_anomaly_levels[level_name] and updated_anomaly_levels[level_name].artefacts and updated_anomaly_levels[level_name].artefacts[id] then
printf("taken created artefact %s, id %s, level_name %s", updated_anomaly_levels[level_name].artefacts[id], id, level_name)
updated_anomaly_levels[level_name].artefacts[id] = nil
end
end
function npc_on_item_take(npc, obj)
actor_on_item_take(obj)
end
-- Anomalies special hit behaviour
anomalies_hit_functions = {
-- Flash special hit behaviour - Time travel
zone_mine_flash = function(s_hit, bone_id, flags, actor)
printf("change_time")
local health = actor.health
local change_hours = random(2, 5)
local change_minutes = random(1, 59)
level.change_game_time(0, change_hours, change_minutes)
level_weathers.get_weather_manager():select_weather(true)
surge_manager.get_surge_manager().time_forwarded = true
psi_storm_manager.get_psi_storm_manager().time_forwarded = true
s_hit.power = 0.001
CreateTimeEvent("zone_mine_flash", "zone_mine_flash", 0.05, function()
local new_health = actor.health
actor:set_health_ex(health)
actor:change_health(-random_float(0.01, 0.04))
-- Change thirst and sleep, params are from vanilla actor_status_sleep/thirst scripts
local sleep_params = {
step = 27,
check_after_sec = 300,
}
local thirst_params = {
step = 30,
check_after_sec = 300
}
local change_sleep_amount = (change_hours * 3600 + change_minutes * 60) / sleep_params.check_after_sec * sleep_params.step * 0.01
local change_thirst_amount = (change_hours * 3600 + change_minutes * 60) / thirst_params.check_after_sec * thirst_params.step * 0.01
thirst_sleep_changer.change_sleep(round(change_sleep_amount))
thirst_sleep_changer.change_thirst(round(change_thirst_amount))
return true
end)
end,
-- Radiation field: drain batteries of active items
-- zone_field_radioactive = function(s_hit, bone_id, flags, actor)
-- if s_hit.power <= 0 then return end
-- for i = 8, 10 do
-- local item = actor:item_in_slot(i)
-- if item then
-- local cond = item:condition()
-- if cond > 0.01 then
-- item:set_condition(cond - 0.03 * s_hit.power)
-- end
-- end
-- end
-- end,
-- zone_field_radioactive_weak = function(s_hit, bone_id, flags, actor)
-- anomalies_hit_functions.zone_field_radioactive(s_hit, bone_id, flags, actor)
-- end,
-- zone_field_radioactive_average = function(s_hit, bone_id, flags, actor)
-- anomalies_hit_functions.zone_field_radioactive(s_hit, bone_id, flags, actor)
-- end,
-- zone_field_radioactive_strong = function(s_hit, bone_id, flags, actor)
-- anomalies_hit_functions.zone_field_radioactive(s_hit, bone_id, flags, actor)
-- end,
-- zone_radioactive = function(s_hit, bone_id, flags, actor)
-- anomalies_hit_functions.zone_field_radioactive(s_hit, bone_id, flags, actor)
-- end,
-- zone_radioactive_weak = function(s_hit, bone_id, flags, actor)
-- anomalies_hit_functions.zone_field_radioactive(s_hit, bone_id, flags, actor)
-- end,
-- zone_radioactive_average = function(s_hit, bone_id, flags, actor)
-- anomalies_hit_functions.zone_field_radioactive(s_hit, bone_id, flags, actor)
-- end,
-- zone_radioactive_strong = function(s_hit, bone_id, flags, actor)
-- anomalies_hit_functions.zone_field_radioactive(s_hit, bone_id, flags, actor)
-- end,
}
-- Anomalies special hit behaviour on monster
anomalies_monster_hit_functions = {
-- Flash special hit behaviour - Time travel
zone_mine_flash = function(monster, s_hit, bone_id, flags, actor)
printf("flash got hit %s", monster:section())
s_hit.power = 0
flags.ret_value = false
safe_release_manager.release(alife_object(monster:id()))
end,
-- Reduce power for Rebounder
-- zone_mine_sphere = function(monster, s_hit, bone_id, flags, actor)
-- local sec = monster:section()
-- printf("rebounder got hit %s", sec)
-- s_hit.power = 0
-- flags.ret_value = false
-- local immunities_sec = SYS_GetParam(0, sec, "protections_sect", sec)
-- local hit_power = SYS_GetParam(2, "zone_mine_sphere", "max_start_power", 1) * SYS_GetParam(2, immunities_sec, "hit_fraction_monster", 1)
-- monster:change_health(-hit_power)
-- end,
}
-- Also used for monsters, filter for npcs or monsters specifically in functions bodies
npc_on_near_anomalies_functions = {
-- Rebounder effect for NPCs
zone_mine_sphere = function(anomaly, npc, actor)
local key = "zone_mine_sphere_" .. npc:name()
if not callbacks[key] then
register_callback(IsStalker(npc) and "npc_on_before_hit" or "monster_on_before_hit", function(npc_hit, s_hit, bone_id, flags)
if s_hit.type == hit.fire_wound and npc_hit:name() == npc:name() then
s_hit.power = s_hit.power * random_float(0, 0.5)
play_sound_on_actor("eugenium_anomaly\\sphere\\sphere_blowout", 1, random_float(0.95, 1.05), anomaly.object)
local gibs = particles_object("artefact\\artefact_gravi")
if gibs and not gibs:playing() then
gibs:play_at_pos(npc_hit:position())
end
end
end, nil, key)
end
add_simple_timed_effect(0.7, nil, function()
unregister_callback(key)
end, key, 1)
end,
}
function actor_on_before_hit(s_hit, bone_id, flags)
if not s_hit.draftsman then return end
local sec = s_hit.draftsman:section()
if anomalies_hit_functions[sec] then
anomalies_hit_functions[sec](s_hit, bone_id, flags, db.actor)
end
end
function monster_on_before_hit(monster, s_hit, bone_id, flags)
if not s_hit.draftsman then return end
local sec = s_hit.draftsman:section()
if anomalies_monster_hit_functions[sec] then
anomalies_monster_hit_functions[sec](monster, s_hit, bone_id, flags, db.actor)
end
end
function load_state(m_data)
local dump = string.dump
local load = loadstring
local unpack = unpack
local t = m_data.drx_da_effects or {}
for key, effect in pairs(t) do
if t[key].effect_function then
local effect = load(t[key].effect_function)
t[key].effect = function()
effect(unpack(t[key].effect_args))
end
end
if t[key].on_end_function then
local on_end = load(t[key].on_end_function)
t[key].on_end = function()
on_end(unpack(t[key].on_end_args))
end
end
end
timed_effects = t
last_surge_time = m_data.drx_da_last_surge_time or 0
updated_anomaly_levels = m_data.drx_da_updated_anomaly_levels or {}
if m_data.drx_da_opened_articles then opened_articles = m_data.drx_da_opened_articles end
RegisterScriptCallback("actor_on_update", drx_da_actor_on_update_callback)
end
function actor_on_first_update()
load_state(alife_storage_manager.get_state())
end
function save_anomalies(m_data)
local t = {}
for key, effect in pairs(timed_effects) do
if effect.save then
t[key] = {}
copy_table(t[key], effect)
t[key].effect = nil
t[key].on_end = nil
end
end
m_data.drx_da_effects = t
m_data.drx_da_updated_anomaly_levels = updated_anomaly_levels
m_data.drx_da_last_surge_time = last_surge_time
m_data.drx_da_opened_articles = opened_articles
end
function save_level(m_data)
m_data.drx_da_previous_level = level.name()
end
function on_before_level_changing()
printf("saving previous_level")
alife_storage_manager.get_state().drx_da_previous_level = level.name()
UnregisterScriptCallback("save_state", save_level)
end
local function on_option_change()
load_settings()
if settings.delete_dynamic_anomalies then
unregister_anomalies_behaviour()
clean_dynamic_anomalies_global()
build_anomalies_pos_tree()
if ui_mcm then
ui_mcm.set("drx_da/delete_dynamic_anomalies", false)
end
return
end
unregister_anomalies_behaviour()
if updated_anomaly_levels[level.name()] and not updated_anomaly_levels[level.name()].disabled then
if settings.enable_anomalies_behaviour or level_weathers.bLevelUnderground then
register_anomalies_behaviour()
else
enable_anomalies_on_level()
end
end
end
-- Register callback scripts:
function on_game_start()
RegisterScriptCallback("actor_on_before_hit", actor_on_before_hit)
RegisterScriptCallback("monster_on_before_hit", monster_on_before_hit)
RegisterScriptCallback("actor_on_item_take", actor_on_item_take)
RegisterScriptCallback("npc_on_item_take", npc_on_item_take)
RegisterScriptCallback("actor_on_interaction", actor_on_interaction)
RegisterScriptCallback("on_before_level_changing", on_before_level_changing)
RegisterScriptCallback("save_state", save_anomalies)
RegisterScriptCallback("save_state", save_level)
-- RegisterScriptCallback("load_state", load_state)
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
RegisterScriptCallback("on_game_load", load_settings)
RegisterScriptCallback("on_option_change", on_option_change)
end
-- Patches
-- Encyclopedia articles about new anomalies
-- Table contains category and entries in game section => article string id format
additional_articles_to_category = {
encyclopedia_anomalies = {
zone_mine_cdf = "encyclopedia_anomalies_cdf",
zone_mine_umbra = "encyclopedia_anomalies_umbra",
zone_mine_flash = "encyclopedia_anomalies_flash",
zone_mine_ghost = "encyclopedia_anomalies_ghost",
zone_mine_gold = "encyclopedia_anomalies_gold",
zone_mine_thorn = "encyclopedia_anomalies_thorn",
zone_mine_seed = "encyclopedia_anomalies_seed",
zone_mine_shatterpoint = "encyclopedia_anomalies_shatterpoint",
zone_mine_sloth = "encyclopedia_anomalies_sloth",
zone_mine_mefistotel = "encyclopedia_anomalies_mefistotel",
zone_mine_net = "encyclopedia_anomalies_net",
zone_mine_point = "encyclopedia_anomalies_point",
zone_mine_sphere = "encyclopedia_anomalies_sphere",
}
}
-- Table contains opened articles after interacting with anomalies
opened_articles = {
encyclopedia_anomalies = {}
}
-- Open anomaly article
function open_anomaly_article(section)
-- If there is no article to begin with - return
if not additional_articles_to_category.encyclopedia_anomalies[section] then
printf("article not found for %s", section)
return
end
-- If already opened - return
if opened_articles.encyclopedia_anomalies[additional_articles_to_category.encyclopedia_anomalies[section]] then
printf("article already opened for %s", section)
return
end
-- Return if the player didn't spend 3 hours in the zone
if not (has_alife_info("actor_spent_2_hrs_in_zone") or utils_obj.is_time_spent_in_zone(0,0,2)) then
printf("youre too stupid for article boy")
return
end
-- Add statistics and notify
game_statistics.increment_statistic("articles")
actor_menu.set_notification(nil, "ui_inGame2_notify_article", 20, "device\\pda\\pda_guide_2")
-- Add to already opened articles
opened_articles.encyclopedia_anomalies[additional_articles_to_category.encyclopedia_anomalies[section]] = true
-- Instance of the pda_ui object.
local pda_ui = ui_pda_encyclopedia_tab.get_ui()
pda_ui:InitCategories()
pda_ui:SelectCategory("encyclopedia_anomalies")
pda_ui:SelectArticle(additional_articles_to_category.encyclopedia_anomalies[section])
if pda_ui.article_list and pda_ui.article_list.GetSelectedIndex then
pda_ui.article_list:SetSelectedIndex(pda_ui.article_list:GetSelectedIndex() + 1)
end
if zz_Encyclopedia_messages_restored or zz_encyclopedia_messages_restored or encyclopedia_messages_restored then
-- Article and category texts.
local message = game.translate_string("pda_encyclopedia_notify")
--printf("Monkeyzz " .. categories[index_c].section)
local text_c = game.translate_string("encyclopedia_anomalies")
local text_a = game.translate_string(additional_articles_to_category.encyclopedia_anomalies[section])
-- Other information.
local header = game.translate_string("st_tip")
local texture = news_manager.tips_icons["guide_unlock"] or "ui_inGame2_Poslednie_razrabotki"
db.actor:give_game_news(header, strformat(message, text_c, text_a), texture, 0, 5000, 0)
end
end
-- Patch article list
InitArticles = ui_pda_encyclopedia_tab.pda_encyclopedia_tab.InitArticles
ui_pda_encyclopedia_tab.pda_encyclopedia_tab.InitArticles = function(self, section_c)
InitArticles(self, section_c)
-- If not existing list - return
if is_empty(opened_articles[section_c]) then return end
local n = self.article_list:GetSize()
for article, _ in pairs(opened_articles[section_c]) do
local clr = ui_pda_encyclopedia_tab.UpdateColor(article)
local item = ui_pda_encyclopedia_tab.pda_encyclopedia_entry(article, n, clr)
self.article_list:AddExistingItem(item)
n = n + 1
end
end
-- Extra Utils
function reset_timers()
last_surge_time = 0
for k, v in pairs(updated_anomaly_levels) do
v.time = -1
printf("time resetted for %s", k)
end
printf("last_surge_time is 0")
end
function print_anomalies()
return print_r(updated_anomaly_levels)
end
function debug_spawn_anomaly(sec)
local gvid = db.actor:game_vertex_id()
local r = level.get_target_dist and level.get_target_dist() or 3
local pos = vector():set(db.actor:position())
pos:add(device().cam_dir:mul(r))
pos = vector():set(pos.x,db.actor:position().y,pos.z)
local lvid = level.vertex_id(pos)
local level_name = level.name()
local level_data = get_level_data(level_name)
if not level_data then
printf("Error, unable to get data for %s", level_name)
return
end
local level_file = level_data.level_file
drx_da_spawn_anomaly(sec, pos, lvid, gvid, level_file)
build_anomalies_pos_tree()
end
-- Buggy consecutive update leading to softlocks and crashes
-- Refresh anomalies on level after surge, in concurrent fashion
function update_dynamic_anomalies_on_level_after_surge(level_name)
local level_name = level_name or level.name()
-- Clean all anomalies in one action first
clean_dynamic_anomalies_global()
local function generate_anomalies()
local level_data = get_level_data(level_name)
if not level_data then
printf("Error, unable to get data for %s", level_name)
return
end
local level_file = level_data.level_file
local spawn_percent = level_data.spawn_percent
local anomaly_max_number = level_data.anomaly_max_number
local anomaly_max_active = level_data.anomaly_max_active
-- Build a list of available smart terrains:
local smart_list = utils_data.collect_section(level_file, "available_smarts")
local pairs = pairs
local collect_section = utils_data.collect_section
local size_table = size_table
local to_generate = {}
-- Build anomalies table
if is_not_empty(smart_list) then
local anomalies = updated_anomaly_levels[level_name].anomalies or {}
local anomalies_by_smart = updated_anomaly_levels[level_name].anomalies_by_smart or {}
local smart_by_anomalies = updated_anomaly_levels[level_name].smart_by_anomalies or {}
for i, smart_name in pairs(smart_list) do
if true or not smart_restrictions[smart_name] then
-- Choose an anomaly type to spawn:
local anomaly_list = collect_section(level_file, "anomaly_types")
if anomaly_list and #anomaly_list >= 1 then
local anomaly_type = anomaly_list[random(#anomaly_list)]
printf("picked anomaly type %s", anomaly_type)
if not anomalies_by_smart[smart_name] then anomalies_by_smart[smart_name] = {} end
local j = size_table(anomalies_by_smart[smart_name])
while j < anomaly_max_number do
if random() <= spawn_percent then
to_generate[#to_generate + 1] = {
anomaly_type = anomaly_type,
level_file = level_file,
smart_name = smart_name,
level_name = level_name
}
end
j = j + 1
end
else
printf("No dynamic anomaly types specified for level %s", level_name)
end
else
printf("no anomalies spawn on smart %s, level %s in restriction", smart_name, level_name)
end
end
end
return to_generate
end
local anomalies = generate_anomalies()
if is_not_empty(anomalies) then
local tg_interval = 100
local tg = time_global() + 500
local anomalies_ids = {}
process_queue("drx_da_spawn_anomalies_on_level", anomalies, function(k, anomaly)
local t = time_global()
if t < tg then return end
tg = t + tg_interval
local anom_id = drx_da_spawn_anomaly_on_smart(anomaly.level_file, anomaly.smart_name, anomaly.anomaly_type, level_name)
if anom_id then
updated_anomaly_levels[level_name].anomalies[#updated_anomaly_levels[level_name].anomalies + 1] = anom_id
updated_anomaly_levels[level_name].anomalies_by_smart[anomaly.smart_name][anom_id] = true
updated_anomaly_levels[level_name].anomaly_types_by_smart[anomaly.smart_name] = anomaly.anomaly_type
updated_anomaly_levels[level_name].smart_by_anomalies[anom_id] = anomaly.smart_name
end
return true
end, function()
printf("anomalies updated after surge on level %s", level_name)
end)
end
end