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:
@@ -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")
|
||||
Reference in New Issue
Block a user