3754 lines
124 KiB
Plaintext
3754 lines
124 KiB
Plaintext
-- Put your Lua here
|
|
-- 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 id = obj:id()
|
|
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()
|
|
local obj_l = get_object_by_id(id)
|
|
if not (obj_l and obj_l:name() == key) then
|
|
unregister_callback(key)
|
|
return
|
|
end
|
|
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 id = obj:id()
|
|
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()
|
|
local obj_l = get_object_by_id(id)
|
|
if not (obj_l and obj_l:name() == key) then
|
|
unregister_callback(key)
|
|
return
|
|
end
|
|
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 id = obj:id()
|
|
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()
|
|
local obj_l = get_object_by_id(id)
|
|
if not (obj_l and obj_l:name() == key) then
|
|
unregister_callback(key)
|
|
return
|
|
end
|
|
|
|
|
|
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 id = obj:id()
|
|
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()
|
|
local obj_l = get_object_by_id(id)
|
|
if not (obj_l and obj_l:name() == key) then
|
|
unregister_callback(key)
|
|
return
|
|
end
|
|
|
|
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,
|
|
|
|
zone_mine_sphere = function(anomaly)
|
|
local key = anomaly.object:name()
|
|
unregister_callback(key)
|
|
end,
|
|
|
|
zone_mine_point = function(anomaly)
|
|
local key = anomaly.object:name()
|
|
unregister_callback(key)
|
|
end,
|
|
|
|
zone_mine_umbra = 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
|