Various New Mods; Updated and Removed Mods

Fixed:
- PDA map colors not matching
- Aydin's Grass Tweaks mismatch
- Hax Bolt Reanimation being overridden

Updated:
- Atmospherics

Removed:
- Dialog Dynamic UI (Included in EGUI)
- Developer Mod Installations (Unnecessary bloat)
This commit is contained in:
2024-03-21 08:41:48 -04:00
parent 290378b961
commit 8abddf23c7
2362 changed files with 107187 additions and 48192 deletions
@@ -0,0 +1,322 @@
--==============================================================================================
--Yet Another Winter Mod snowfall script
--Author: Daedalus-Prime
-- Edit by demonized:
-- Refactor of script for easier adding and toggling of particles
-- Check for playing particles depending on ray collision
-- Playing particles outside of covers depending on location where actor entered the cover for muh immersion
-- Saving entered cover position and restoring it on save/load
-- edit by FabioConte for Anomqly Season's Redux (05.02.2023)
-- edit by S.e.m.i.t.o.n.e. for 'The Arrival' mod
--==============================================================================================
function on_game_start()
RegisterScriptCallback("actor_on_update", actor_on_update)
RegisterScriptCallback("save_state", save_state)
RegisterScriptCallback("load_state", load_state)
end
local ray = function(pos, dir, args)
local pos = pos and vector():set(pos.x or pos[1], pos.y or pos[2], pos.z or pos[3]) or device().cam_pos
local dir = dir and vector():set(dir.x or dir[1], dir.y or dir[2], dir.z or dir[3]) or device().cam_dir
local args = args or {}
local pick = ray_pick()
pick:set_position(pos)
pick:set_direction(dir)
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
local snow_particle_foggy = particles_object("semitone\\environmental\\seed1") --set particles to variables
local snow_particle_foggy1 = particles_object("semitone\\environmental\\seed2")
local snow_particle_foggy2 = particles_object("semitone\\environmental\\tree_leaves_stormy")
local snow_particle_foggy3 = particles_object("lanforse\\fog_mist")
-- These particles wont play inside at all
dont_play_inside_particles = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
}
weather_to_particles = {
["default"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_ccgim_fog"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_clear_foggy"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_clear1"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_clear2"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_cloudy1"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_cloudy2"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_cloudy2_dark"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_cloudy3"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_cloudy4"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_deaspir_foggy"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_foggy1"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_foggy2"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_foggy3"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_indoor_agr_underground"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_indoor_ambient"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_indoor_default"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_indoor_jupiter_underground"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_indoor_sarcofag"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
["w_partly1"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_partly2"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_partly3"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_partly4"] = {
[snow_particle_foggy] = true,
[snow_particle_foggy1] = true,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_rain1"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_rain2"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_rain3"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_storm1"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_storm2"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
["w_swtc_tuman"] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = true,
[snow_particle_foggy3] = false,
},
[0] = {
[snow_particle_foggy] = false,
[snow_particle_foggy1] = false,
[snow_particle_foggy2] = false,
[snow_particle_foggy3] = false,
},
}
function switch_particles(particle_table, pos, is_inside)
local pos = pos or device().cam_pos
for k, v in pairs(particle_table) do
-- Disable particles that are in dont_play_inside_particles table if inside
if is_inside and dont_play_inside_particles[k] then
if k:playing() then
k:stop()
end
else
if v then
if k:playing() then
k:move_to(pos, VEC_ZERO)
else
k:play_at_pos(pos)
end
else
if k:playing() then
k:stop_deffered()
end
end
end
end
end
local ray_args = {
range = 30
}
local inside_pos
function actor_on_update()
-- Check current weather
local weather = level.get_weather()
-- Detection of cover by rain collision
local cam_pos = device().cam_pos
local r1 = ray(cam_pos, VEC_Y, ray_args)
local r2 = ray(cam_pos, vector():set(VEC_Y):add(vector():set(1.5, 0, 0)), ray_args)
local r3 = ray(cam_pos, vector():set(VEC_Y):add(vector():set(-1.5, 0, 0)), ray_args)
local r4 = ray(cam_pos, vector():set(VEC_Y):add(vector():set(0, 0, 1.5)), ray_args)
local r5 = ray(cam_pos, vector():set(VEC_Y):add(vector():set(0, 0, -1.5)), ray_args)
local r = r1:get_distance() > 0
and r2:get_distance() > 0
and r3:get_distance() > 0
and r4:get_distance() > 0
and r5:get_distance() > 0
-- Set variable for whether the player is in an emission safe zone or not
local is_inside = GetEvent("current_safe_cover") or r
-- Set variable for whether the player is in an underground level or not
local is_underground = GetEvent("underground")
-- If exists saved position where actor entered cover, apply it
local pos
if is_inside then
if inside_pos then
pos = vector():set(inside_pos.x, cam_pos.y, inside_pos.z)
else
inside_pos = cam_pos
pos = inside_pos
end
else
inside_pos = nil
end
snowfall(weather, is_inside, is_underground, pos)
end
function snowfall(weather, is_inside, is_underground, inside_pos)
if is_underground then
switch_particles(weather_to_particles[0], inside_pos, is_inside)
UnregisterScriptCallback("actor_on_update", actor_on_update)
return
end
if (is_inside and not inside_pos) or not weather_to_particles[weather] then
switch_particles(weather_to_particles[0], inside_pos, is_inside)
return
end
switch_particles(weather_to_particles[weather], inside_pos, is_inside)
end
function save_state(m_data)
m_data.snowfall_inside_pos = inside_pos and utils_data.vector_to_string(inside_pos)
end
function load_state(m_data)
inside_pos = m_data.snowfall_inside_pos and utils_data.string_to_vector(m_data.snowfall_inside_pos)
end
@@ -0,0 +1,101 @@
local table_remove = table.remove
local unpack = unpack
local function table_keys(t, is_array)
local a = {}
if is_array then
for i = 1, #t do
a[i] = i
end
else
for k,v in pairs(t) do
a[#a+1] = k
end
end
return a
end
-- Queue to span processing on each game tick
-- Arguments:
-- queue_name - name of queue
-- queue_table - table to process
-- func - function for processing, must have at least first 3 arguments: key of queue_table, value by that key, the number of key in processing order
-- returning true will remove element from queue, returning false or nil will leave it in queue for next process cycle
-- on_end_func - function at the end of queue processing, you can use this for chaining queue processing one after another
-- step - amount of items in queue to process at a time
-- ... - additional arguments for "func"
-- active_queues table contains names of queues that are active.
-- Example usage, get an object by id one at a time per game update
--[[
local ids = {1, 2, 3, 4, 5, 6, 7, 8}
process_queue("my_queue", ids, function(k, id, i)
local obj = level.object_by_id(id)
if obj then
printf("object %s, section %s, time_global %s", id, obj:section(), time_global())
end
return true
end, function()
printf("queue processed, final time %s", time_global())
end
)
]]
active_queues = {}
function process_queue(queue_name, queue_table, func, on_end_func, step, ...)
-- queue_table must be a table
if not queue_table or type(queue_table) ~= "table" then
printf("%s is not table, abort", queue_table)
return
end
if active_queues[queue_name] then
printf("queue %s is already active, abort", queue_name)
return
end
-- Collect table keys and initialize the table index
local i = 1
local keys = table_keys(queue_table)
local step = clamp(step or 1, 1, #keys)
local args = {...}
local update_func
update_func = function()
for s = 1, step do
local j = keys[i]
if func(j, queue_table[j], i, unpack(args)) == true then
queue_table[j] = nil
table_remove(keys, i)
i = i - 1
step = clamp(step, 1, #keys)
end
if not keys[1] then
if on_end_func then on_end_func() end
UnregisterScriptCallback("actor_on_update", update_func)
active_queues[queue_name] = nil
return
end
i = i == #keys and 1 or i + 1
end
end
active_queues[queue_name] = {
queue_table = queue_table,
func = update_func,
on_end_func = on_end_func,
step = step,
}
RegisterScriptCallback("actor_on_update", update_func)
end
function remove_queue(queue_name, on_end)
if active_queues[queue_name] then
if on_end and active_queues[queue_name].on_end_func then
active_queues[queue_name].on_end_func()
end
UnregisterScriptCallback("actor_on_update", active_queues[queue_name].func)
active_queues[queue_name] = nil
end
end
@@ -0,0 +1,265 @@
-- Optimized time events, using sorted arrays to peek only closest event to come
-- Designed to fully reimplement existing timed events with all its quirks
-- Unlike vanilla time events, fires on actor_on_update and doesn't require any checks
-- Written by demonized
local ev_queue = {}
local computed_ev_queue = {}
local math_floor = math.floor
local math_huge = math.huge
local table_insert = table.insert
local table_remove = table.remove
local has_alife_info = has_alife_info
local time_global = time_global
local pairs = pairs
local unpack = unpack
local tg = 0
local i = 1
local function print_table(table, subs)
local sub
if subs ~= nil then
sub = subs
else
sub = ""
end
for k,v in pairs(table) do
if type(v) == "table" then
print_table(v, sub.."["..k.."]----->")
elseif type(v) == "function" then
printf(sub.."%s = function",k)
elseif type(v) == "userdata" then
if (v.x) then
printf(sub.."%s = %s",k,utils_data.vector_to_string(v))
else
printf(sub.."%s = userdata", k)
end
elseif type(v) == "boolean" then
if v == true then
if(type(k)~="userdata") then
printf(sub.."%s = true",k)
else
printf(sub.."userdata = true")
end
else
if(type(k)~="userdata") then
printf(sub.."%s = false", k)
else
printf(sub.."userdata = false")
end
end
else
if v ~= nil then
printf(sub.."%s = %s", k,v)
else
printf(sub.."%s = nil", k,v)
end
end
end
end
local function queue_timer_compare(a, b)
return a.timer < b.timer
end
-- http://lua-users.org/wiki/BinaryInsert
local function binary_insert(t, value, fcomp)
-- Initialise compare function
local fcomp = fcomp or function(a, b) return a < b end
-- print_table(value)
-- Initialise numbers
local iStart, iEnd, iMid, iState = 1, #t, 1, 0
if iEnd == 0 then
t[1] = value
-- printf("adding in beginning table empty")
return 1
end
if fcomp(value, t[1]) then
-- printf("adding in beginning %s of %s", 1, iEnd)
table_insert(t, 1, value)
return 1
end
if not fcomp(value, t[iEnd]) then
-- printf("adding in end %s of %s", iEnd + 1, iEnd)
local pos = iEnd + 1
t[pos] = value
return pos
end
-- Get insert position
while iStart <= iEnd do
-- calculate middle
iMid = math_floor((iStart + iEnd) / 2)
-- compare
if fcomp(value, t[iMid]) then
iEnd, iState = iMid - 1, 0
else
iStart, iState = iMid + 1, 1
end
end
local pos = iMid + iState
-- printf("adding in middle %s of %s", pos, iEnd)
table_insert(t, pos, value)
return pos
end
local function refresh_ev_queue()
empty_table(computed_ev_queue)
-- tg = time_global()
local t = computed_ev_queue
for ev_id,actions in pairs(ev_queue) do
for act_id,act in pairs(actions) do
if (act_id ~= "__size") then
local d = {
ev_id = ev_id,
act_id = act_id,
timer = act.timer,
f = act.f,
p = act.p
}
binary_insert(t, d, queue_timer_compare)
end
end
end
end
local function find_ev_queue(ev_id, act_id)
for i = 1, #computed_ev_queue do
local t = computed_ev_queue[i]
if t.ev_id == ev_id and t.act_id == act_id then
return i
end
end
end
local function remove_ev_queue(ev_id, act_id)
local pos = find_ev_queue(ev_id, act_id)
if pos then
if pos <= i then
i = i - 1
end
return table_remove(computed_ev_queue, pos)
end
end
function CreateTimeEvent(ev_id,act_id,timer,f,...)
if not (ev_queue[ev_id]) then
ev_queue[ev_id] = {}
ev_queue[ev_id].__size = 0
end
if not (ev_queue[ev_id][act_id]) then
local new_timer = time_global() + timer*1000
ev_queue[ev_id][act_id] = {}
ev_queue[ev_id][act_id].timer = new_timer
ev_queue[ev_id][act_id].f = f
ev_queue[ev_id][act_id].p = {...}
ev_queue[ev_id].__size = ev_queue[ev_id].__size + 1
local d = {
ev_id = ev_id,
act_id = act_id,
timer = new_timer,
f = f,
p = {...}
}
binary_insert(computed_ev_queue, d, queue_timer_compare)
end
end
function RemoveTimeEvent(ev_id,act_id)
if (ev_queue[ev_id] and ev_queue[ev_id][act_id]) then
ev_queue[ev_id][act_id] = nil
ev_queue[ev_id].__size = ev_queue[ev_id].__size - 1
remove_ev_queue(ev_id, act_id)
end
end
function ResetTimeEvent(ev_id,act_id,timer)
if (ev_queue[ev_id] and ev_queue[ev_id][act_id]) then
local new_timer = time_global() + timer*1000
ev_queue[ev_id][act_id].timer = new_timer
local el = remove_ev_queue(ev_id, act_id)
el.timer = new_timer
binary_insert(computed_ev_queue, el, queue_timer_compare)
end
end
local tg_past = 0
local to_remove = {}
function ProcessEventQueue(force)
tg = time_global()
-- if tg > tg_past then
-- printf("tg %s", tg)
-- printf("computed")
-- print_table(computed_ev_queue)
-- tg_past = tg + 100
-- end
local force_refresh
i = 1
local l = #computed_ev_queue
while i <= l do
local t = computed_ev_queue[i] or (function()
force_refresh = true
return { timer = math_huge }
end)() -- Failsafe if event does not exist, refresh queue and postpone to next tick
if tg < t.timer then
break
end
if t.f(unpack(t.p)) == true then
local t1 = ev_queue[t.ev_id]
t1[t.act_id] = nil
t1.__size = t1.__size - 1
if t1.__size == 0 then
ev_queue[t.ev_id] = nil
end
to_remove[#to_remove + 1] = {
ev_id = t.ev_id,
act_id = t.act_id
}
end
i = i + 1
end
if force_refresh then
refresh_ev_queue()
empty_table(to_remove)
elseif to_remove[1] then
for i = 1, #to_remove do
local t = to_remove[i]
remove_ev_queue(t.ev_id, t.act_id)
to_remove[i] = nil
end
empty_table(to_remove)
end
return false
end
local function process_queue()
ProcessEventQueue()
end
function on_game_start()
RegisterScriptCallback("actor_on_update", process_queue)
end
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
-- Add anomaly detector to loadouts
local loadouts = new_game_loadout_injector_mcm
loadouts.add_item({
section = "detector_anomaly",
points = 10,
faction = {
"stalker",
"dolg",
"freedom",
"csky",
-- "ecolog",
"killer",
"army",
"bandit",
"monolith",
"renegade",
"greh",
"isg",
},
})
loadouts.add_item({
section = "detector_anomaly",
points = 10,
faction = {
"ecolog",
},
add_to_inventory = true,
})
@@ -0,0 +1,209 @@
-- Table of new anomalies sections
new_anomalies_sections = {
zone_mine_cdf = true,
zone_mine_umbra = true,
zone_mine_flash = true,
zone_mine_ghost = true,
zone_mine_gold = true,
zone_mine_thorn = true,
zone_mine_seed = true,
zone_mine_shatterpoint = true,
zone_mine_sloth = true,
zone_mine_mefistotel = true,
zone_mine_net = true,
zone_mine_point = true,
zone_mine_sphere = true,
}
-- Table of variations of old anomalies
variations_anomalies_sections = {
zone_unknown = true,
zone_mine_acid = true,
zone_mine_electra = true,
zone_mine_springboard = true,
zone_mine_vortex = true,
zone_mine_blast = true,
zone_mine_zharka = true,
zone_mine_vapour = true,
}
presets = {
[0] = {
anomaly_zone_spawn_chance = 0.5,
anomaly_zone_anomalies_distance_max = 35,
anomaly_zone_anomalies_distance_min = 2,
anomaly_amount_modifier = 0.5,
max_artefacts_per_zone = 2,
artefacts_spawn_chance = 15,
random_artefact_spawn_chance = 25,
gravitational_shake_modifier = 0.5,
electric_field_modifier = 0.5,
enable_anomalies_behaviour = "true", -- Pass as string, thats important
},
[1] = {
anomaly_zone_spawn_chance = 1,
anomaly_zone_anomalies_distance_max = 25,
anomaly_zone_anomalies_distance_min = 1,
anomaly_amount_modifier = 1,
max_artefacts_per_zone = 2,
artefacts_spawn_chance = 15,
random_artefact_spawn_chance = 25,
gravitational_shake_modifier = 1,
electric_field_modifier = 1,
enable_anomalies_behaviour = "true", -- Pass as string, thats important
},
[2] = {
anomaly_zone_spawn_chance = 1,
anomaly_zone_anomalies_distance_max = 25,
anomaly_zone_anomalies_distance_min = 0.5,
anomaly_amount_modifier = 1.5,
max_artefacts_per_zone = 2,
artefacts_spawn_chance = 25,
random_artefact_spawn_chance = 25,
gravitational_shake_modifier = 1,
electric_field_modifier = 1.5,
enable_anomalies_behaviour = "true", -- Pass as string, thats important
},
}
op_id = "drx_da"
op_preset_id = "presets"
op = {
id = op_id, sh = true, gr = {
{id = "banner", type = "slide", text = "ui_mcm_drx_da_title", size = {512, 50}, spacing = 20},
{id=op_preset_id, type="list", val=2, def=1, content={
{0, "drx_da_preset_easy"},
{1, "drx_da_preset_normal"},
{2, "drx_da_preset_hard"},
}},
{id = "anomaly_zone_spawn_chance", type = "track", val = 2, min = 0, max = 1, step = 0.1, def = presets[1].anomaly_zone_spawn_chance or 1},
{id = "anomaly_zone_anomalies_distance_max", type = "track", val = 2, min = 90, max = 160, step = 1, def = presets[1].anomaly_zone_anomalies_distance_max or 130},
{id = "anomaly_zone_anomalies_distance_min", type = "track", val = 2, min = 15, max = 25, step = 0.1, def = presets[1].anomaly_zone_anomalies_distance_min or 25},
{id = "anomaly_amount_modifier", type = "track", val = 2, min = 0, max = 3, step = 0.1, def = presets[1].anomaly_amount_modifier or 1},
{id = "max_artefacts_per_zone", type = "track", val = 2, min = 0, max = 3, step = 1, def = presets[1].max_artefacts_per_zone or 2},
{id = "artefacts_spawn_chance", type = "track", val = 2, min = 0, max = 25, step = 1, def = presets[1].artefacts_spawn_chance or 15},
{id = "random_artefact_spawn_chance", type = "track", val = 2, min = 0, max = 100, step = 1, def = presets[1].random_artefact_spawn_chance or 25},
{id = "divider", type = "line"},
{id = "gravitational_shake_modifier", type = "track", val = 2, min = 0, max = 2, step = 0.1, def = presets[1].gravitational_shake_modifier or 1},
{id = "electric_field_modifier", type = "track", val = 2, min = 0, max = 3, step = 0.1, def = presets[1].electric_field_modifier or 1},
{id = "enable_anomalies_behaviour", type = "check", val = 1, def = presets[1].enable_anomalies_behaviour ~= nil and presets[1].enable_anomalies_behaviour == "true" or presets[1].enable_anomalies_behaviour == nil and true},
{id = "save_after_cleanup", type = "check", val = 1, def = false},
{id = "disable_new_anomalies", type = "check", val = 1, def = false},
{id = "divider", type = "line"},
{id = "drx_da_choose_help", type = "desc", clr = {200, 200, 255, 200}, text = "ui_mcm_drx_da_choose_help"},
{id = "drx_da_choose_divider_begin", type = "line"},
-- Here will be anomalies choose to disable (see on_mcm_load)
{id = "drx_da_choose_divider_end", type = "line"},
{id = "delete_dynamic_anomalies", type = "check", val = 1, def = false},
{id = "debug_mode", type = "check", val = 1, def = false},
}
}
function add_drx_da_choose_options()
for i = #op.gr, 1, -1 do
if op.gr[i].id == "drx_da_choose_divider_end" then
-- Build list of new anomalies from drx_da_main.script
for k, _ in spairs(new_anomalies_sections, function(t, a, b) return a > b end) do
table.insert(op.gr, i, {id = k .. "_enable", type = "check", val = 1, def = true})
table.insert(op.gr, i, {id = k .. "_banner", type = "slide", link = "banner_" .. k .. ".dds", text = k .. "_section_name", size = {512, 50}, spacing = 20})
end
break
end
end
end
function remove_drx_da_choose_options()
for i = 1, #op.gr do
if op.gr[i].id == "drx_da_choose_divider_begin" then
i = i + 1
while op.gr[i].id ~= "drx_da_choose_divider_end" do
table.remove(op.gr, i)
end
break
end
end
end
function is_enabled_anomaly(section)
if ui_mcm then
return ui_mcm.get("drx_da/" .. section .. "_enable")
end
return true
end
function set_preset(self, p)
if not presets[p] then
printf("DRX DA preset %s not found", p)
return
end
-- Pre-build available MCM values
local t = {}
for _, v in ipairs(op.gr) do
if v.def ~= nil then
t[v.id] = v.def
end
end
for k, value in pairs(presets[p]) do
if t[k] then
-- Validate option
local v = ui_mcm.get_opt_table(op_id .. "/" .. k)
if v and v.type then
-- Get proper value
if v.val == 0 then
-- Pass input value as is
elseif v.val == 1 then
value = (value == "true") and true or false
elseif v.val == 2 then
value = clamp(tonumber(value), v.min or 0, v.max or 100)
end
-- Extract path and opt
local t = ui_mcm.str_opt_explode(op_id .. "/" .. k)
local opt = t[#t]
local path = t[1]
for i=2,#t-1 do
path = ui_mcm.cc(path , t[i])
end
-- Cache changes
self:CacheValue(path, opt, value, v)
end
end
end
-- Update XML elements
self:Reset_opt(self.last_curr_tree, self.last_path)
-- Update state
self:UpdatePending()
end
if ui_mcm and ui_mcm.UIMCM and ui_mcm.UIMCM.Callback_List then
MCM_Callback_List = ui_mcm.UIMCM.Callback_List
ui_mcm.UIMCM.Callback_List = function(self, ctrl, path, opt, v)
MCM_Callback_List(self, ctrl, path, opt, v)
if path ~= op_id then return end
if opt == op_preset_id then
local value = self:GetValue(path, opt, v)
set_preset(self, value)
end
end
end
function on_mcm_load()
remove_drx_da_choose_options()
add_drx_da_choose_options()
return op
end
@@ -0,0 +1,78 @@
get_param = utils_item.SYS_GetParam
utils_item.SYS_GetParam = function(typ, sec, param, def)
if sec == "detector_anomaly" and param == "can_trade" then
return true
else
return get_param and get_param(typ, sec, param, def) or SYS_GetParam(typ, sec, param, def)
end
end
-------------------------- trader artiinject -------------------------
trade_table = {
["ecolog"] = {
[1] = {
["detector_anomaly"] = 1,
},
},
["csky"] = {
[1] = {
["detector_anomaly"] = 1,
},
},
["isg"] = {
[1] = {
["detector_anomaly"] = 1,
},
},
}
trade_table.yan_stalker_sakharov = trade_table.ecolog
trade_table.jup_b6_scientist_nuclear_physicist = trade_table.ecolog
trade_table.jup_b6_scientist_biochemist = trade_table.ecolog
trade_table.mar_base_owl_stalker_trader = trade_table.csky
trade_table.baraholka_trader_night = trade_table.csky
trade_table.jup_depo_isg_tech = trade_table.ecolog
trade_table.ds_domik_isg_leader = trade_table.ecolog
spawn_chance = 1
function trade_add(npc)
local is_trader = trader_autoinject.get_trader_type(npc) == trader_autoinject.SUPPLIER
if not is_trader then return end
local community = npc:character_community() or "stalker"
local sec = npc:section()
local trader_table = trade_table[community] or trade_table[sec]
if not trader_table then return end
local supply_level = clamp(trader_autoinject.supply_level(npc, true) or 1, 1, 5)
for i = supply_level, 1, -1 do
if trader_table[i] then
local t = dup_table(trader_table[i])
for k, v in pairs(t) do
local s = 0
for i = 1, v do
if math.random() <= spawn_chance then
s = s + 1
end
end
t[k] = s
end
trader_autoinject.spawn_items(npc, t, true)
break
end
end
end
TraderAuto = trader_autoinject.update
function trader_autoinject.update(npc)
TraderAuto(npc)
trade_add(npc)
end
@@ -0,0 +1,468 @@
ENABLE_DYNAMIC_RADIATION_ZONES = true
ENABLE_DYNAMIC_RADIATION_ZONES_NPP = true
local blacklisted_smarts = {
l01_escape = {
esc_smart_terrain_3_16 = true,
esc_smart_terrain_2_12 = true,
esc_smart_terrain_5_7 = true,
esc_smart_terrain_4_9 = true
},
y04_pole = {
},
k00_marsh = {
mar_smart_terrain_base = true,
mar_smart_terrain_5_8 = true,
mar_smart_terrain_doc_2 = true
},
k01_darkscape = {
ds2_domik_st = true
},
l02_garbage = {
gar_smart_terrain_3_5 = true,
gar_smart_terrain_6_3 = true
},
l04_darkvalley = {
val_smart_terrain_7_3 = true,
val_smart_terrain_7_4 = true,
val_smart_terrain_7_5 = true,
val_smart_terrain_8_6 = true,
val_smart_terrain_7_8 = true, --Smart Z position is too low
val_smart_terrain_8_9 = true --Smart Z position is too low
},
l03_agroprom = {
agr_smart_terrain_1_6 = true,
agr_smart_terrain_1_6_near_1 = true,
agr_smart_terrain_1_6_near_2 = true
},
l08_yantar = {
yan_smart_terrain_3_6 = true,
yan_smart_terrain_6_4 = true,
},
l06_rostok = {
},
l05_bar = {
bar_dolg_bunker = true,
bar_dolg_general = true,
bar_visitors = true,
bar_zastava = true,
bar_zastava_2 = true,
},
k02_trucks_cemetery = {
trc_sim_20 = true
},
l09_deadcity = {
cit_killers = true,
},
l07_military = {
mil_smart_terrain_7_10 = true,
mil_smart_terrain_7_8 = true,
mil_smart_terrain_7_7 = true,
mil_smart_terrain_7_12 = true
},
l10_red_forest = {
red_smart_terrain_3_2 = true
},
l10_radar = {
},
l10_limansk = {
},
jupiter = {
jup_a6 = true,
jup_b41 = true
},
l11_pripyat = {
mlr_terrain = true,
pri_monolith = true,
},
pripyat = {
pri_a18_smart_terrain = true,
kbo_terrain = true,
pri_b306 = true,
pri_a16 = true,
pri_a16_mlr_copy = true,
pri_a28_base = true,
pri_b36_smart_terrain = true
},
l11_hospital = {
},
zaton = {
zat_b40_smart_terrain = true,
zat_b18 = true,
zat_stalker_base_smart = true,
zat_sim_27 = true
},
l12_stancia = {
},
l12_stancia_2 = {
},
l13_generators = {
},
l12u_sarcofag = {
sar_monolith_general = true
}
}
local radiation_field_level_settings = {
l01_escape = {
Name = "l01_escape",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 15
},
y04_pole = {
Name = "y04_pole",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 15
},
k00_marsh = {
Name = "k00_marsh",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 15
},
k01_darkscape = {
Name = "k01_darkscape",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 15
},
l02_garbage = {
Name = "l02_garbage",
Radiation_field = "zone_radioactive_weak",
Chance_to_spawn = 30,
Radius = 16
},
l04_darkvalley = {
Name = "l04_darkvalley",
Radiation_field = "zone_radioactive_weak",
Chance_to_spawn = 30,
Radius = 16
},
l03_agroprom = {
Name = "l03_agroprom",
Radiation_field = "zone_radioactive_weak",
Chance_to_spawn = 30,
Radius = 16
},
l08_yantar = {
Name = "l08_yantar",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 16
},
l06_rostok = {
Name = "l06_rostok",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 16
},
l05_bar = {
Name = "l05_bar",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 10
},
k02_trucks_cemetery = {
Name = "k02_trucks_cemetery",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 18
},
l09_deadcity = {
Name = "l09_deadcity",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 15
},
l07_military = {
Name = "l07_military",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 18
},
l10_red_forest = {
Name = "l10_red_forest",
Radiation_field = "zone_radioactive_average",
Chance_to_spawn = 40,
Radius = 12
},
l10_radar = {
Name = "l10_radar",
Radiation_field = "zone_radioactive_average",
Chance_to_spawn = 40,
Radius = 24
},
l10_limansk = {
Name = "l10_limansk",
Radiation_field = "zone_radioactive_average",
Chance_to_spawn = 40,
Radius = 24
},
jupiter = {
Name = "jupiter",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 22
},
l11_pripyat = {
Name = "l11_pripyat",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 0,
Radius = 24
},
pripyat = {
Name = "pripyat",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 24
},
l11_hospital = {
Name = "l11_hospital",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 24
},
zaton = {
Name = "zaton",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 22
},
l12_stancia = {
Name = "l12_stancia",
Radiation_field = "zone_radioactive_strong",
Chance_to_spawn = 50,
Radius = 28
},
l12_stancia_2 = {
Name = "l12_stancia_2",
Radiation_field = "zone_radioactive_strong",
Chance_to_spawn = 50,
Radius = 28
},
l13_generators = {
Name = "l13_generators",
Radiation_field = "zone_radioactive_strong",
Chance_to_spawn = 50,
Radius = 28
}
}
--Some anomlies will affect new game start point, like scientist bunker in Yantar. They should be capped.
local blacklisted_static_radiation_anomalies = {
-- l01_escape
esc_zone_field_radioactive_weak_0000 = 80,
esc_zone_field_radioactive_weak_0011 = 75,
esc_zone_field_radioactive_weak_0012 = 75,
esc_zone_field_radioactive_weak_0013 = 75,
--k00_marsh
mar_zone_field_radioactive_weak_0007 = 80,
--pripyat
pripyat_zone_field_radioactive_weak_0001 = 5,
--l04_darkvalley
val_zone_field_radioactive_weak_baza_freedom_0016 = 3,
val_zone_field_radioactive_weak_baza_freedom_0018 = 3,
val_zone_field_radioactive_weak_baza_freedom_0019 = 3,
--l03_agroprom
agr_zone_field_radioactive_average_000 = 20,
--l07_military
mil_zone_field_radioactive_strong = 25,
mil_zone_field_radioactive_strong_3 = 50,
--zaton
zaton_zone_field_radioactive_average_0007 = 20,
zaton_zone_field_radioactive_average_0008 = 20,
zaton_zone_field_radioactive_average_0013 = 20,
--Name of level: l02_garbage
gar_zone_field_radioactive_weak_0001 = 50,
gar_zone_field_radioactive_weak_0002 = 40
}
--Underground maps should not be affected by dynamic radiations and wind behaviour
local underground_maps = {
l03u_agr_underground = true,
l08u_brainlab = true,
l10u_bunker = true,
jupiter_underground = true,
l04u_labx18 = true,
labx8 = true,
l12u_sarcofag = true,
l12u_control_monolith = true,
l13u_warlab = true
}
--Radiation zones the player is in
local radiation_zones = {}
--Table for state management
local radiation_table = {}
--Hack for a wonderful bug in the LUA interpreter itself possibly. IT IS CURSED!
environmental_radiation = 0
--Hack for icon removal
local ico_n_removed = false
local ico_p_removed = false
function spawn_radiation_fields_at_new_game()
printf("spawn_radiation_fields_at_new_game()")
for k, v in pairs (radiation_field_level_settings) do
run_script = 1
spawn_radiation_fields_for_level(v)
end
run_script = 0
end
--Creates new radiation fields for the current level
function spawn_radiation_fields_for_level(current_level_settings)
if run_script ~= 1 then
return
end
--printf("spawn_radiation_fields_for_level: "..current_level_settings.Name.." ************************************************")
--printf("get_smart_terrains_of_level: "..current_level_settings.Name)
local smarts_of_level = get_smart_terrains_of_level(current_level_settings.Name)
printf("get blacklisted smarts")
local blacklisted_smarts_for_level = blacklisted_smarts[current_level_settings.Name]
printf("Iterate through smarts")
for ksmart, smart in pairs (smarts_of_level) do
local name_of_smart = smart:name()
if (not blacklisted_smarts_for_level[name_of_smart]) then
if (math.random(1, 100) <= current_level_settings.Chance_to_spawn) then
printf("Spawn default radiation zone for smart: "..name_of_smart)
create_radiation_field(
current_level_settings.Radiation_field,
smart.position,
smart.m_game_vertex_id,
level.vertex_id(smart.position),
current_level_settings.Radius)
end
else
printf("Smart: "..name_of_smart.." is black listed, skip")
end
end
end
--Creates a new radiation field
function create_radiation_field(anomaly_type, position, game_vertex_id, level_vertex_id, radius)
printf("create_radiation_field: "..anomaly_type.." Radius: "..radius.. " *****************")
printf("spawn anomaly")
local se_obj = alife( ):create(anomaly_type, position, level_vertex_id, game_vertex_id)
if (not se_obj) then
printf("NO ANOMALY WERE SPAWNED")
return
end
local data = utils_stpk.get_anom_zone_data( se_obj )
if (not data) then
printf( "Error: Unable to set dynamic anomaly properties" )
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 = radius
utils_stpk.set_anom_zone_data(data, se_obj)
printf(se_obj.id)
printf("Radiation field was spawned succesfully")
end
--Get smart terrains if the level
function get_smart_terrains_of_level(level_name)
printf("get_smart_terrains_of_level("..level_name..")")
smarts_of_level = {}
for id,smart in pairs(db.smart_terrain_by_id) do
cvertex = smart and game_graph():vertex(smart.m_game_vertex_id)
if (cvertex and alife():level_name(cvertex:level_id()) == level_name) then
table.insert(smarts_of_level, smart)
end
end
return smarts_of_level
end
function initialize_radiation_table()
if (not radiation_table.dosimeter_volume) then
radiation_table.dosimeter_volume = 1
printf("INITIALIZE - dosimeter_volume")
end
if (not radiation_table.wind_alert_state) then
--radiation_table.wind_alert_state = WIND_ALERT_STATES.NoAlert
--printf("INITIALIZE - wind_alert_state")
end
end
function on_game_load()
--Initialize radiation_table of not initialized
initialize_radiation_table()
if (ENABLE_DYNAMIC_RADIATION_ZONES) then
if (not radiation_table.dynamic_radiation_zones_generated) then
--spawn dynamic radiation fields
spawn_radiation_fields_at_new_game()
radiation_table.dynamic_radiation_zones_generated = true
end
end
if (not radiation_table.static_radiation_fields_resized) then
--Resize both original and new radiation fields
--resize_radiation_fields()
radiation_table.static_radiation_fields_resized = true
end
if (ENABLE_DYNAMIC_RADIATION_ZONES_NPP) then
if (not radiation_table.dynamic_radiation_zones_generated_npp) then
--Spawn special radiation fields for NPP interrior
spawn_radiation_fields_for_level(special_spawn_l12u_sarcofag)
radiation_table.dynamic_radiation_zones_generated_npp = true
end
end
--Reset icon removal flags
ico_n_removed = false
ico_p_removed = false
end
function save_state(m_data)
m_data.radiation_table = radiation_table
end
function load_state(m_data)
radiation_table = m_data.radiation_table or {}
end
function on_game_start()
RegisterScriptCallback("on_game_load", on_game_load)
RegisterScriptCallback("save_state", save_state)
RegisterScriptCallback("load_state", load_state)
end
@@ -0,0 +1,571 @@
-- https://github.com/ubilabs/kd-tree-javascript
-- k-d Tree Implementation for Lua for quick search in multidimensional tables
-- k-d trees are a useful data structure for several applications, such as searches involving a multidimensional search key (e.g. range searches and nearest neighbor searches). k-d trees are a special case of binary space partitioning trees.
-- Rewritten to pure Lua and adapted for usage in Anomaly by demonized
local math_floor = math.floor
local math_log = math.log
local math_max = math.max
local math_min = math.min
local table_insert = table.insert
local table_remove = table.remove
local table_sort = table.sort
local empty_table = empty_table
local pairs = pairs
local function table_slice(t, first, last)
local res = {}
for i = first or 1, last and last - 1 or #t do
res[#res + 1] = t[i]
end
return res
end
-- http://lua-users.org/wiki/BinaryInsert
local function binary_insert(t, value, fcomp)
-- Initialise compare function
local fcomp = fcomp or function(a, b) return a < b end
-- print_table(value)
-- Initialise numbers
local iStart, iEnd, iMid, iState = 1, #t, 1, 0
if iEnd == 0 then
t[1] = value
-- printf("adding in beginning table empty")
return 1
end
if fcomp(value, t[1]) then
-- printf("adding in beginning %s of %s", 1, iEnd)
table_insert(t, 1, value)
return 1
end
if not fcomp(value, t[iEnd]) then
-- printf("adding in end %s of %s", iEnd + 1, iEnd)
local pos = iEnd + 1
t[pos] = value
return pos
end
-- Get insert position
while iStart <= iEnd do
-- calculate middle
iMid = math_floor((iStart + iEnd) / 2)
-- compare
if fcomp(value, t[iMid]) then
iEnd, iState = iMid - 1, 0
else
iStart, iState = iMid + 1, 1
end
end
local pos = iMid + iState
-- printf("adding in middle %s of %s", pos, iEnd)
table_insert(t, pos, value)
return pos
end
function Node(obj, dimension, parent)
local node = {}
node.obj = obj
node.left = nil
node.right = nil
node.parent = parent
node.dimension = dimension
return node
end
function kdTree(points, metric, dimensions)
local kd_tree = {}
kd_tree.points = points or {}
kd_tree.metric = metric
kd_tree.dimensions = dimensions
local function buildTree(new_points, depth, parent)
local dim = (depth % #dimensions) + 1
local median
local node
if not new_points then
return
end
if #new_points == 0 then
-- printf("buildTree #new_points == 0")
return
end
if #new_points == 1 then
-- printf("buildTree #new_points == 1")
return Node(new_points[1], dim, parent)
end
table_sort(new_points, function(a, b)
return a[dimensions[dim]] < b[dimensions[dim]]
end)
median = math_floor(#new_points / 2) + 1
node = Node(new_points[median], dim, parent)
node.left = buildTree(table_slice(new_points, 1, median), depth + 1, node)
node.right = buildTree(table_slice(new_points, median + 1), depth + 1, node)
return node
end
kd_tree.root = buildTree(points, 0, nil)
kd_tree.insertAndRebuild = function(self, point)
self.points[#self.points + 1] = point
self.root = buildTree(self.points, 0, nil)
return self
end
kd_tree.insert = function(self, point)
local function innerSearch(node, parent)
if node == nil then
return parent
end
local dimension = self.dimensions[node.dimension]
if point[dimension] < node.obj[dimension] then
return innerSearch(node.left, node)
else
return innerSearch(node.right, node)
end
end
local insertPosition = innerSearch(self.root, nil)
local newNode
local dimension
if insertPosition == nil then
self.points[#self.points + 1] = point
self.root = buildTree(self.points, 0, nil)
return self
end
newNode = Node(point, (insertPosition.dimension + 1) % #self.dimensions, insertPosition)
dimension = self.dimensions[insertPosition.dimension]
if point[dimension] < insertPosition.obj[dimension] then
insertPosition.left = newNode
else
insertPosition.right = newNode
end
self.points[#self.points + 1] = point
return self
end
kd_tree.remove = function(self, point)
local node
local function nodeSearch(node)
if node == nil then
return
end
if node.obj == point then
return node
end
local dimension = self.dimensions[node.dimension]
if point[dimension] < node.obj[dimension] then
return nodeSearch(node.left, node)
else
return nodeSearch(node.right, node)
end
end
local function removeNode(node)
local nextNode
local nextObj
local pDimension
local function findMin(node, dim)
local dimension
local own
local left
local right
local min
if node == nil then
return
end
dimension = self.dimensions[dim]
if node.dimension == dim then
if node.left ~= nil then
return findMin(node.left, dim)
end
return node
end
own = node.obj[dimension]
left = findMin(node.left, dim)
right = findMin(node.right, dim)
min = node
if left ~= nil and left.obj[dimension] < own then
min = left
end
if right ~= nil and right.obj[dimension] < min.obj[dimension] then
min = right
end
return min
end
if node.left == nil and node.right == nil then
if node.parent == nil then
self.root = nil
return
end
pDimension = self.dimensions[node.parent.dimension]
if node.obj[pDimension] < node.parent.obj[pDimension] then
node.parent.left = nil
else
node.parent.right = nil
end
return
end
-- If the right subtree is not empty, swap with the minimum element on the
-- node's dimension. If it is empty, we swap the left and right subtrees and
-- do the same.
if node.right ~= nil then
nextNode = findMin(node.right, node.dimension)
nextObj = nextNode.obj
removeNode(nextNode)
node.obj = nextObj
else
nextNode = findMin(node.left, node.dimension)
nextObj = nextNode.obj
removeNode(nextNode)
node.right = node.left
node.left = nil
node.obj = nextObj
end
end
node = nodeSearch(self.root)
if node == nil then
return
end
removeNode(node)
return self
end
kd_tree.clearRoot = function(self)
empty_table(self.root)
return self
end
-- Update positions of objects
-- Points input must be same structure as existing in k-d Tree
kd_tree.updatePositions = function(self, points)
self.points = points
self:clearRoot()
self.root = buildTree(points, 0, nil)
return self
end
-- get all points sorted by nearest
kd_tree.nearestAll = function(self, point)
local point = {
x = point.x or point[1],
y = point.y or point[2],
z = point.z or point[3]
}
local function comp_function(a, b)
return a[2] < b[2]
end
local res = {}
for i = 1, #self.points do
local v = self.points[i]
res[i] = {
[1] = {
x = v.x,
y = v.y,
z = v.z,
data = v.data
},
[2] = math.huge
}
res[i][2] = self.metric(res[i][1], point)
end
table_sort(res, comp_function)
return res
end
-- Query the nearest *count* neighbours to a point, with an optional
-- maximal search distance.
-- Result is an array with *count* elements.
-- Each element is an array with two components: the searched point and
-- the distance to it.
kd_tree.nearest = function(self, point, maxNodes, maxDistance)
local i
local result
local bestNodes
bestNodes = {}
local passedNodes = {}
local maxNodes = maxNodes or 1
local function comp_function(a, b)
return a[2] < b[2]
end
local function saveNode(node, distance)
binary_insert(bestNodes, {node, distance}, comp_function)
if #bestNodes > maxNodes then
table_remove(bestNodes)
end
end
local function nearestSearch(node)
if passedNodes[node] then return end
local bestChild
local dimension = self.dimensions[node.dimension]
local ownDistance = self.metric(point, node.obj)
local linearPoint = {}
local linearDistance
local otherChild
local i
for i = 1, #self.dimensions do
linearPoint[self.dimensions[i]] = i == node.dimension and point[self.dimensions[i]] or node.obj[self.dimensions[i]]
end
linearDistance = self.metric(linearPoint, node.obj)
if node.right == nil and node.left == nil then
if #bestNodes < maxNodes or ownDistance < bestNodes[#bestNodes][2] then
saveNode(node, ownDistance)
end
passedNodes[node] = true
return
end
if node.right == nil then
bestChild = node.left
elseif node.left == nil then
bestChild = node.right
else
bestChild = point[dimension] < node.obj[dimension] and node.left or node.right
end
nearestSearch(bestChild)
if #bestNodes < maxNodes or ownDistance < bestNodes[#bestNodes][2] then
saveNode(node, ownDistance)
passedNodes[node] = true
end
if #bestNodes < maxNodes or math.abs(linearDistance) < bestNodes[1][2] then
otherChild = bestChild == node.left and node.right or node.left
if (otherChild ~= nil) then
nearestSearch(otherChild)
end
end
end
if maxDistance then
for i = 1, maxNodes do
bestNodes[i] = {nil, maxDistance}
end
end
if self.root then
nearestSearch(self.root)
end
result = {}
for i = 1, math_min(maxNodes, #bestNodes) do
if bestNodes[i][1] then
result[#result + 1] = {
bestNodes[i][1].obj,
bestNodes[i][2]
}
end
end
return result
end
-- Get an approximation of how unbalanced the tree is.
-- The higher this number, the worse query performance will be.
-- It indicates how many times worse it is than the optimal tree.
-- Minimum is 1. Unreliable for small trees.
kd_tree.balanceFactor = function(self)
local function height(node)
if node == nil then
return 0
end
return math_max(height(node.left), height(node.right)) + 1
end
local function count(node)
if node == nil then
return 0
end
return count(node.left) + count(node.right) + 1
end
return height(self.root) / (math_log(count(self.root)) / math_log(2))
end
return kd_tree
end
local function distance_to(a, b)
-- printf("distance_to fired")
local dist_x = a.x - b.x
local dist_y = a.y - b.y
local dist_z = a.z - b.z
return dist_x * dist_x + dist_y * dist_y + dist_z * dist_z
end
-- Actual usage starts here
--[[
When you build position tree, you can find nearest objects in relation to other objects
Example, find nearest position to actor:
local pos_tree = kd_tree.buildTreeObjectIds({45, 65, 23, 5353, 232})
print_table(pos_tree:nearest(db.actor:position()))
will print position, distance and id of nearest object from given ids
--]]
-- Build k-d Tree by several inputs
-- Input - Array of vectors (vector():set(x, y, z) or table with x, y, z keys or 1, 2, 3 keys)
-- Data is an optional table where you can bind your data to your object, must have same amount of fields as vectors (#vectors == #data)
function buildTreeVectors(vectors, data)
local v = {}
local data = data or {}
local vectors = vectors or {}
for k, t in pairs(vectors) do
table_insert(v, {
x = t.x or t[1],
y = t.y or t[2],
z = t.z or t[3],
data = data[k]
})
end
-- printf("vectors num %s", #v)
return kdTree(v, distance_to, {"x", "y", "z"})
end
-- Input - Array of game objects
-- Vectors are binded to object ids automatically
function buildTreeObjects(objects)
local vectors = {}
local data = {}
for k, v in pairs(objects) do
table_insert(vectors, v:position())
table_insert(data, v:id())
end
return buildTreeVectors(vectors, data)
end
-- Input - Array of server objects
-- Vectors are binded to object ids automatically
function buildTreeSeObjects(objects)
local vectors = {}
local data = {}
for k, v in pairs(objects) do
table_insert(vectors, v.position)
table_insert(data, v.id)
end
return buildTreeVectors(vectors, data)
end
-- Input - Array of game object ids
-- Vectors are binded to object ids automatically
function buildTreeObjectIds(ids)
local vectors = {}
local data = {}
local level_object_by_id = level.object_by_id
for k, v in pairs(ids) do
local obj = level_object_by_id(v)
if obj and obj ~= 0 and obj:id() ~= 0 then
table_insert(vectors, obj:position())
table_insert(data, v)
end
end
return buildTreeVectors(vectors, data)
end
-- Input - Array of server object ids
-- Vectors are binded to object ids automatically
function buildTreeSeObjectIds(ids)
local vectors = {}
local data = {}
local sim = alife()
local sim_object = sim.object
for k, v in pairs(ids) do
local obj = sim_object(sim, v)
if obj and obj ~= 0 and obj.id ~= 0 then
table_insert(vectors, obj.position)
table_insert(data, v)
end
end
return buildTreeVectors(vectors, data)
end
-- If you build a tree using functions above
-- You can use this function to update positions and rebuild the tree
function updateObjPositions(kd_tree)
local points = kd_tree.points
local new_points = {}
local sim = alife()
local sim_object = sim.object
for i = 1, #points do
local obj = sim_object(sim, points[i].data)
if obj then
local pos = obj.position
table_insert(new_points, {
x = pos.x,
y = pos.y,
z = pos.z,
data = points[i].data
})
end
end
kd_tree:updatePositions(new_points)
return kd_tree
end
@@ -0,0 +1,288 @@
-- New Game Loadout Items Injector
-- Based on Lucy's Glowsticks mod code for injecting glowsticks
-- Written by demonized
-- MCM IS REQUIRED TO WORK
--[[
For usage in your mod you have to create a script file with _mcm on the end
for example, "my_best_mod_mcm.script"
Then to add an item to loadout do something like this.
Example below adds anomaly detector to loadouts
All factions except ecologists should buy detector, ecologists have them for free on all difficulties
In first or second economy difficulty, detector costs 150
In third difficulty, detector costs 200
-- Add anomaly detector to loadouts
local loadouts = new_game_loadout_injector_mcm
loadouts.add_item({
section = "detector_anomaly",
points = 150,
faction = {
"stalker",
"dolg",
"freedom",
"csky",
"killer",
"army",
"bandit",
"monolith",
"renegade",
"greh",
"isg",
},
economy = {
"st_econ_1",
"st_econ_2",
}
})
loadouts.add_item({
section = "detector_anomaly",
points = 200,
faction = {
"stalker",
"dolg",
"freedom",
"csky",
"killer",
"army",
"bandit",
"monolith",
"renegade",
"greh",
"isg",
},
economy = {
"st_econ_3"
}
})
loadouts.add_item({
section = "detector_anomaly",
points = 150,
faction = {
"ecolog",
},
add_to_inventory = true,
})
For all options see add_item function below in the script
--]]
--UTILS
local function li_printf(str, ...)
printf("Loadout Injector: " .. str, ...)
end
local function li_printerr(str, ...)
printf("!Loadout Injector: ERROR, " .. str, ...)
end
local function table_to_dict(t)
local res = {}
local t = t or {}
for k, v in pairs(t) do
if type(v) == "string" then
res[v] = true
elseif v ~= false or v ~= nil then
res[k] = true
end
end
return res
end
local function dict_keys(t)
local res = {}
local t = t or {}
for k, v in pairs(t) do
res[#res + 1] = k
end
return res
end
-- Table of loadout items to inject
loadout_items = {}
-- Table of loadout items to remove
loadout_removed_items = {}
--[[
Function to add item to loadout
Accepts table, where you define the properties of item to add
The table can have these fields (mandatory or optional):
section: string, mandatory, defines item section to add,
points: int, optional, defines price of item in loadout, default is 0
amount: int, optional, defines amount of items to add, default is 1
faction: table of string, optional, defines which factions can have this item. Empty table or nil will allow everyone to have this item. Possible values in table:
"stalker", (Loners)
"dolg", (Duty)
"freedom", (Freedom)
"csky", (Clear Sky)
"ecolog", (Ecologists)
"killer", (Mercenaries)
"army", (Military)
"bandit", (Bandits)
"monolith", (Monolith)
"renegade", (Renegades)
"greh", (Sin)
"isg", (UNISG)
economy: table of string, optional, defines which difficuly allows this item to appear. Empty table or nil will allow every economy to have this item. Possible values in table:
"st_econ_1", (Easy)
"st_econ_2", (Medium)
"st_econ_3" (Hard)
add_to_inventory: boolean, optional, defines if item has to be added in inventory directly, immediately and for free, default is false
--]]
function add_item(item_table)
if not item_table or type(item_table) ~= "table" then
li_printerr("no item table provided")
return
end
if not item_table.section or not ini_sys:section_exist(tostring(item_table.section)) then
li_printerr("no section exists by name %s", item_table.section)
return
end
item_table.points = item_table.points and tonumber(item_table.points) or 0
item_table.amount = item_table.amount and tonumber(item_table.amount) or 1
if item_table.faction and type(item_table.faction) == "string" then
local s = item_table.faction
item_table.faction = {
[s] = true
}
else
item_table.faction = table_to_dict(item_table.faction)
end
if item_table.economy and type(item_table.economy) == "string" then
local s = item_table.economy
item_table.economy = {
[s] = true
}
else
item_table.economy = table_to_dict(item_table.economy)
end
li_printf(
"adding item %s, points %s, amount %s, faction %s, economy %s, to inventory %s",
item_table.section,
item_table.points,
item_table.amount,
is_empty(item_table.faction) and "all" or table.concat(dict_keys(item_table.faction), ";"),
is_empty(item_table.economy) and "all" or table.concat(dict_keys(item_table.economy), ";"),
item_table.add_to_inventory == true
)
loadout_items[item_table.section] = loadout_items[item_table.section] or {}
table.insert(loadout_items[item_table.section], item_table)
end
-- Removes added items from loadout by section
function remove_item(section)
local section = section or ""
if not ini_sys:section_exist(tostring(section)) then
li_printerr("can't remove %s, no section exists", section)
end
loadout_items[section] = nil
end
-- Removes existing items loaded from ltx from loadout by section
function remove_existing_item(section)
local section = section or ""
if not ini_sys:section_exist(tostring(section)) then
li_printerr("can't remove %s, no section exists", section)
end
loadout_removed_items[section] = true
end
_LoadLoadout = ui_mm_faction_select.UINewGame.LoadLoadout
function ui_mm_faction_select.UINewGame.LoadLoadout(self, rand)
_LoadLoadout(self, rand)
-- Copy existing loadout
local inv_sect_list = {}
local inv_point_list = {}
local i_size = 0
for idx,ci in pairs(self.CC["inventory"].cell) do
if not loadout_removed_items[ci.section] then
i_size = i_size + 1
inv_sect_list[i_size] = ci.section
inv_point_list[i_size] = 0
end
end
-- Add items which are marked "add_to_inventory"
for k, v in pairs(loadout_items) do
for i, item_table in ipairs(v) do
if item_table.add_to_inventory
and (is_empty(item_table.faction) or item_table.faction[self.selected_faction])
and (is_empty(item_table.economy) or item_table.economy[self.selected_economy])
then
for j = 1, item_table.amount do
i_size = i_size + 1
inv_sect_list[i_size] = item_table.section
inv_point_list[i_size] = 0
end
end
end
end
-- Load the existing faction "shop"
i_size = 0
local load_sect_list = {}
local load_point_list = {}
for idx,ci in pairs(self.CC["loadout"].cell) do
if not loadout_removed_items[ci.section] then
i_size = i_size + 1
load_sect_list[i_size] = ci.section
load_point_list[i_size] = ci.flags.info or 0
end
end
-- Add items to loadout shop
for k, v in pairs(loadout_items) do
for i, item_table in ipairs(v) do
if not item_table.add_to_inventory
and (is_empty(item_table.faction) or item_table.faction[self.selected_faction])
and (is_empty(item_table.economy) or item_table.economy[self.selected_economy])
then
for j = 1, item_table.amount do
i_size = i_size + 1
load_sect_list[i_size] = item_table.section
load_point_list[i_size] = item_table.points
end
end
end
end
-- Reinit inventories to apply the changes
self.CC["inventory"]:Reinit(inv_sect_list, inv_point_list)
for idx,ci in pairs(self.CC["inventory"].cell) do
if ci:IsShown() then
local val = ci.flags.info or 0
ci.flags.value = val
ci.flags.value_str = game.translate_string("st_mm_new_game_points") .. ": " .. val
end
end
self.CC["loadout"]:Reinit(load_sect_list, load_point_list)
for idx,ci in pairs(self.CC["loadout"].cell) do
if ci:IsShown() then
local val = ci.flags.info or 0
ci.flags.value = val
ci.flags.value_str = game.translate_string("st_mm_new_game_points") .. ": " .. val
end
end
end
@@ -0,0 +1,75 @@
local speeds = {}
local sprint_modifiers = {}
local run_modifiers = {}
local not_first_update = true
local function actor_on_first_update()
speeds[0] = db.actor:get_actor_run_coef()
speeds[1] = db.actor:get_actor_runback_coef()
speeds[2] = db.actor:get_actor_sprint_koef()
not_first_update = false
end
function on_game_start()
RegisterScriptCallback("actor_on_first_update",actor_on_first_update)
end
local function update_speeds()
if not_first_update then
actor_on_first_update()
end
local run_coef = 1
for k,v in pairs(run_modifiers) do
run_coef = run_coef * v
end
local sprint_coef = 1
for k,v in pairs(sprint_modifiers) do
sprint_coef = sprint_coef * v
end
db.actor:set_actor_run_coef(clamp(speeds[0] * run_coef, 1, 10))
db.actor:set_actor_runback_coef(clamp(speeds[1] * run_coef, 1, 10))
db.actor:set_actor_sprint_koef(clamp(speeds[2] * sprint_coef, 1, 10))
end
-- Usage: Add a speed modifier.
-- Once a speed modifier is added, speed will be recalculated and set.
-- run_modifiers affects run and runback, sprint_modifiers affects sprint
-- Params:
-- speed_key - Name of speed multiplier you want to add
-- speed_mult - Speed multiplier as a number (e.g. 0.5 will halve speed)
-- is_sprint - Boolean, if true adds to sprint modifier and updates accordingly, false adds to run modifier
-- force - Boolean, will overwrite existing speed.
-- Returns true if speed added successfully, false if key already exists (and nothing happens as result)
function add_speed(speed_key, speed_mult, is_sprint, force)
if (is_sprint) then
if force or not sprint_modifiers[speed_key] then
-- printf("sprint: " .. speed_key .. " " .. speed_mult)
sprint_modifiers[speed_key] = speed_mult
update_speeds()
return true
else
return false
end
else
if force or not run_modifiers[speed_key] then
run_modifiers[speed_key] = speed_mult
update_speeds()
return true
else
return false
end
end
end
-- Usage: Drop a speed modifier. Once a speed modifier is dropped, speed will be recalculated and set.
-- Params
-- speed_key - Name of speed multiplier to drop. Will drop from both tables.
function remove_speed(speed_key)
if sprint_modifiers[speed_key] then
sprint_modifiers[speed_key] = nil
end
if run_modifiers[speed_key] then
run_modifiers[speed_key] = nil
end
update_speeds()
end
@@ -0,0 +1,130 @@
local enable_debug = false
local function trace(str, ...)
if enable_debug then
printf(str, ...)
end
end
local dbg_increase_thirst_sleep = {
sec = "dbg_increase_thirst_sleep",
section = function(self)
return self.sec
end
}
local dbg_decrease_thirst_sleep = {
sec = "dbg_decrease_thirst_sleep",
section = function(self)
return self.sec
end
}
local dbg_increase_thirst_sleep_small = {
sec = "dbg_increase_thirst_sleep_small",
section = function(self)
return self.sec
end
}
local dbg_decrease_thirst_sleep_small = {
sec = "dbg_decrease_thirst_sleep_small",
section = function(self)
return self.sec
end
}
function change_thirst(perc)
if not game_difficulties.get_game_factor("thirst") then
trace("thirst not enabled, nothing changed")
return
end
local inc = perc >= 0
local perc = math.abs(perc)
if perc <= 1 then
perc = round(perc * 100)
end
for i = 1, perc do
if inc then
actor_status_thirst.actor_on_item_use(dbg_increase_thirst_sleep)
else
actor_status_thirst.actor_on_item_use(dbg_decrease_thirst_sleep)
end
end
trace("thirst changed by %s%", perc)
end
function change_sleep(perc)
if not game_difficulties.get_game_factor("sleep") then
trace("sleep not enabled, nothing changed")
return
end
local inc = perc >= 0
local perc = math.abs(perc)
if perc <= 1 then
perc = round(perc * 100)
end
for i = 1, perc do
if inc then
actor_status_sleep.actor_on_item_use(dbg_increase_thirst_sleep)
else
actor_status_sleep.actor_on_item_use(dbg_decrease_thirst_sleep)
end
end
trace("sleep changed by %s%", perc)
end
function change_thirst_small(perc)
if not game_difficulties.get_game_factor("thirst") then
trace("thirst not enabled, nothing changed")
return
end
local inc = perc >= 0
local perc = math.abs(perc)
if perc <= 1 then
perc = round(perc * 100)
end
for i = 1, perc do
if inc then
actor_status_thirst.actor_on_item_use(dbg_increase_thirst_sleep_small)
else
actor_status_thirst.actor_on_item_use(dbg_decrease_thirst_sleep_small)
end
end
trace("thirst changed by %s%", perc * 0.1)
end
function change_sleep_small(perc)
if not game_difficulties.get_game_factor("sleep") then
trace("sleep not enabled, nothing changed")
return
end
local inc = perc >= 0
local perc = math.abs(perc)
if perc <= 1 then
perc = round(perc * 100)
end
for i = 1, perc do
if inc then
actor_status_sleep.actor_on_item_use(dbg_increase_thirst_sleep_small)
else
actor_status_sleep.actor_on_item_use(dbg_decrease_thirst_sleep_small)
end
end
trace("sleep changed by %s%", perc * 0.1)
end
@@ -0,0 +1,201 @@
--[[
Wrapper class to let you autoinject things via monkey patch to all traders, respecting the restock time.
How to use: Monkey patch the update function here in your script.
ex:
TraderAuto = trader_autoinject.update
function trader_autoinject.update(npc)
TraderAuto(npc)
add_custom_crap(npc) -- you define this function ok
end
Some functions provided below for convenience.
Note: If you want to iterate NPC inventory to check for items, fire a time event to allow the items to register on new game.
--]] --
find = string.find
local function t2c(t)
if not t then return nil end
local ct = game.CTime()
ct:set(t.Y,t.M,t.D,t.h,t.m,t.s,t.ms)
return ct
end
local function c2t(ct)
if not ct then return nil end
-- printf('%s, %s',ct,type(ct))
local Y, M, D, h, m, s, ms = 0, 0, 0, 0, 0, 0, 0
Y, M, D, h, m, s, ms = ct:get(Y, M, D, h, m, s, ms)
return { Y=Y, M=M, D=D, h=h, m=m, s=s, ms=ms }
end
TraderUpdate = trade_manager.update
function trade_manager.update(npc, force_refresh)
local id = npc:id()
if not npc:alive() then
return default
end
local reup_time = trade_manager.get_trade_profile(id, "resupply_time")
TraderUpdate(npc, force_refresh)
local restock_time = game_difficulties.get_eco_factor("restock") or 24
if force_refresh then restock_time = 0 end
if reup_time and game.get_game_time():diffSec(t2c(reup_time)) < (restock_time * 3600) then
-- print_dbg("Not time to resupply yet!")
return
end
disable_info("sleep_active")
CreateTimeEvent("custom_update"..npc:id(), "custom_resupply"..npc:id(), 0.1, timed_update, npc)
end
-- Add easier to trace callback
function timed_update(npc)
update(npc)
SendScriptCallback("trader_on_restock",npc)
return true
end
-- monkeypatch me
function update(npc)
end
-- util functions to help with monkey patching
function get_faction_goodwill(faction)
end
COMPANION = 0 -- companions got special trade logic, this is just to catch errors
MECHANIC = 1 -- mechanics/techs
BARMAN = 2 -- exclusive food suppliers like Spirit
MEDIC = 3 -- medics
SUPPLIER = 4 -- everyone else that sells crap
-- return trader type as int, or nil if error
function get_trader_type(npc)
local st = db.storage[npc:id()]
if not st then return -1 end
local trader = false
if npc:character_community() == "trader" or npc:clsid() == clsid.script_trader or npc:clsid() == clsid.trader then
trader = true
end
if find(npc:section(),"trader") then
trader = true
end
local cini = st.ini
local logic = st.section_logic
if not logic and not trader then return -1 end
local trade_logic = cini and cini:r_string_ex(logic, "trade")
if not trade_logic then return -1 end
if find(trade_logic, "companion") then
return COMPANION
elseif find(trade_logic, "trade_generic_mechanic") then
return MECHANIC
elseif find(trade_logic, "trade_generic_barman") then
return BARMAN
elseif find(trade_logic, "trade_generic_medic") then
return MEDIC
else
return SUPPLIER
end
end
-- return supply level of npc, like suppy_1, supply_2, etc
-- as_number removes the supply_ prefix and only returns as int
function supply_level(npc, as_number)
local profile = trade_manager.get_trade_profile(npc:id(), "cfg_ltx")
-- printf("Profile is %s", profile)
local config = trade_manager.get_trade_cfg(profile)
if not config then return end
local str = config:r_string_ex("trader", "buy_supplies")
if not (str) then
return -- no buy_supplies this is normal
end
local condlist = xr_logic.parse_condlist(npc, "trader", "buy_supplies", str)
str = condlist and xr_logic.pick_section_from_condlist(db.actor, npc, condlist)
if as_number then
local num = str_explode(str, "_")
return tonumber(num[2])
else
return str
end
end
-- collapse several tables into one table, the way sections work in ltx files
-- tables should be in section -> amount format
-- precedence goes up to last table, meaning whatever is in the last table will be the last changes applied
function merge_tables(tables)
local final_table = {}
if #tables > 0 then
copy_table(final_table, tables[1])
if #tables > 1 then
for i=2, #tables do
for k,v in pairs(tables[i]) do
final_table[k] = v
end
end
end
end
return final_table
end
local furniture = {
["esc_m_trader"] = true,
["red_m_lesnik"] = true
}
local blacklisted_comms = {
["trader"] = true,
["monster"] = true
}
-- used to get the real community of the NPC by checking spawn id
-- author: HarukaSai
function get_real_community(npc, default)
if furniture[npc:name()] then
return "stalker"
end
local community = character_community(npc)
if not blacklisted_comms[community] then
return community
end
local squad_community = get_object_squad(npc):get_squad_community()
if not blacklisted_comms[squad_community] then
return squad_community
else
return default
end
end
-- to_spawn should be table of sections to amount
-- if check_existing is true, only spawns up to that amount in trader inventory. else arbitrarily spawns
function spawn_items(npc, to_spawn, check_existing)
local npc_name = npc:name()
local alive_or_furniture = xr_conditions.is_alive(db.actor, npc) or furniture[npc_name]
if not alive_or_furniture then return end
local supply_table = {}
copy_table(supply_table, to_spawn)
if check_existing then
local function itr_inv(temp, item)
if supply_table[item:section()] and supply_table[item:section()] > 0 then
-- printf("Found 1 of %s", item:section())
supply_table[item:section()] = supply_table[item:section()] - 1
end
end
npc:iterate_inventory(itr_inv)
end
for k,v in pairs(supply_table) do
-- printf("Creating %s of %s", v, k)
for i=1, v do
-- printf("Created %s", k)
alife_create_item(k, npc)
end
end
end
AddScriptCallback("trader_on_restock")