Divergent/mods/Dynamic Zone Transitions/gamedata/scripts/dynamic_zone_routes.script

483 lines
15 KiB
Plaintext
Raw Permalink Normal View History

--[[
DYNAMIC ZONE
Original Author(s)
Singustromo <singustromo at disroot.org>
Edited by
License
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
(https://creativecommons.org/licenses/by-nc-sa/4.0)
This file provides the rough structure for saving transitions and
their pairs - called routes in this context.
Additional info:
tables, functions, userdata and threads are passed around by reference,
while numbers, booleans, and nil are passed by value
Despite this we reference entries by their index because both tables
are saved in m_data. Can't reference them.
--]]
parent = _G["dynamic_zone"]
if not (parent and parent.VERSION and parent.VERSION >= 20241224) then return end
--------------------------
-- Dependencies --
--------------------------
-- verified in main script
local utils = dynamic_zone_utils
local debug = dynamic_zone_debug
CONST_LOGGING_PREFIX = "Routes"
local log = debug.log_register("info", CONST_LOGGING_PREFIX)
local log_error = debug.log_register("error", CONST_LOGGING_PREFIX)
---------------------
-- Structure --
---------------------
registered_routes = {}
registered_transitions = {
--[[
[size] = <int>,
[transition_name] = {
route = route_id, -- index in registeres_routes
master = true, -- exception for 1 -> n routes (e.g. truck cemetary)
spawned_anomalies = {}
},
...
--]]
}
function route_count()
return ((utils.is_table_empty(registered_routes))
and 0 or size_table(registered_routes))
end
function transition_count()
return (registered_transitions.size or 0)
end
function clear()
registered_routes = {}
registered_transitions = {}
end
-- first initialization of a route
--- @returns id of route (index)
function route_create()
local id = #registered_routes +1
registered_routes[id] = {
id = id, -- Needed in rare cases
members = { }, -- transition names
connects = { }, -- level names (game_levels.ltx)
blacklisted = false,
unlocked = true,
blocked = false,
block_discovered = false,
recently_discovered = false, -- set during emission (controls blip)
anomaly_theme = 0,
}
return id
end
function transition_register(transition_name, route_id)
if not utils.valid_type{ caller = "transition_register", "str", transition_name,
"tbl", registered_routes[route_id] } then return end
local id = get_story_object_id(transition_name)
if (utils.assert_failed(id, "%s is not a Story-ID. Not registered.")) then return end
local route_members = registered_routes[route_id].members
if utils.table_has(route_members, transition_name) then return end
registered_transitions[transition_name] = {
route = route_id,
master = false,
spawned_anomalies = {}, -- holds the object ids
}
table.insert(route_members, transition_name)
local index = (registered_transitions.size or 0) +1
registered_transitions.size = index
return index
end
-------------------------------------------------------------------------------
function transition_table_register(tbl, route_id)
local registered = {}
if not utils.valid_type{ caller = "transition_table_register",
"tbl", tbl, "int", route_id } then return registered end
for _, transition_name in pairs(tbl) do
if not transition_register(transition_name, route_id) then
log_error("Transition '%s' has already been registered!", transition_name)
else
registered[#registered +1] = transition_name
end
end
return registered
end
-- @returns mutable reference
function get_route(route_id)
return registered_routes[route_id]
end
-- @returns mutable reference
function get_transition(transition_name)
return registered_transitions[transition_name]
end
function route_exists(route_id)
return (nil ~= registered_routes[route_id])
end
function transition_exists(transition_name)
return (nil ~= registered_transitions[transition_name])
end
-------------------------------------------------------------------------------
-- Wacky implementation
-- TODO: Improve condition declaration and parsing
-- Conditions are table entries (route flags) which are or'ed
-- attributes are negated with an '!' or can be joined via an and '*'
-- e.g. `get_routes{"blocked*!discovered", "!blocked*discovered"}`
-- Should only be used with route flags (true, false)
-- @returns <table> (route_ids)
function get_routes(conditions)
if not utils.valid_type{ caller = "get_routes", "tbl", conditions } then return end
if utils.is_table_empty(conditions) then return end
local matches = {}
local func_body = ""
for _, condition in pairs(conditions) do
local parsed = condition
parsed = parsed:gsub('*!', ' and not ref.')
parsed = parsed:gsub('*', ' and ref.')
parsed = parsed:gsub('!', 'not ref.')
parsed = (string.find(parsed, "^not")) and parsed or "ref." .. parsed
func_body = func_body .. "(" .. parsed .. ") or "
end
func_body = func_body:sub(1, -5) -- remove last ' or '
local to_eval = "return function(ref) return (" .. func_body .. ") end"
local func, err = loadstring(to_eval)
if (err) then
log_error("Unable to evaluate '%s'\n! %s", to_eval, err)
return
end
-- loadstring encapsulates input into another function
local check_func = func()
for route_id, route in iterate_routes(true) do
local status, retval = pcall(check_func, route)
if (status and retval) then
matches[#matches +1] = route_id
end
end
return matches
end
-------------------------------------------------------------------------------
-- Filters locked and blacklisted routes by default
-- @returns name, modifiable route reference
function iterate_routes(include_inactive)
local index = 0
return function()
index = index + 1
if not (include_inactive) then
while (registered_routes[index] and route_inactive(index)) do
index = index + 1
end
end
if (index <= #registered_routes) then
return index, registered_routes[index]
end
end
end
-- @returns name, mutable-reference
function iterate_transitions(route_id)
local route = get_route(route_id)
local members = route and route.members
if (not members) then return end
local index = 0
return function()
index = index + 1
if (index > #members) then return end
return members[index], get_transition(members[index])
end
end
-------------------------------------------------------------------------------
function set_transition_property(name, property, value)
local transition = get_transition(name)
if not utils.valid_type{ caller = "set_transition_property",
"tbl", transition, "str", property} then return end
if (type(value) == 'nil') then return end
if (type(transition[property]) == "nil") then return end
transition[property] = value
return true
end
-- @returns immutable reference
function get_transition_property(name, property)
local transition = get_transition(name)
if (not transition) then return end
return transition[property]
end
-------------------------------------------------------------------------------
function set_route_property(route_id, property, value)
local route = get_route(route_id)
if not utils.valid_type{ caller = "set_route_property",
"tbl", route, "str", property} then return end
if (type(value) == 'nil') then return end
if (type(route[property]) == "nil") then return end
route[property] = value
return true
end
-- @returns immutable reference
function get_route_property(route_id, property)
local route = get_route(route_id)
if (not route) then return end
return route[property]
end
function get_route_id(transition_name)
local transition = get_transition(transition_name)
return (transition and transition.route)
end
-- used when we only have the transition name
-- @returns modifyable route reference
function get_route_by_transition(gizmo)
local transition_name = (type(gizmo) == "number")
and get_object_story_id(gizmo) or gizmo
if (utils.assert_failed(transition_name)) then return end
local transition = get_transition(transition_name)
return (transition and get_route(transition.route))
end
has_route = get_route_by_transition -- alias for readability's sake
-- @returns immutable reference
function get_route_members(route_id)
local route = get_route(route_id)
return (route and route.members)
end
-------------------------------------------------------------------------------
function route_inactive(route_id)
local route = get_route(route_id)
if (not route) then return true end
return (route.blacklisted or (not route.unlocked))
end
-- only really need this, if we do not replace accessible zone
function route_unlock(route_id)
local route = get_route(route_id)
if (not route) then return end
route.unlocked = true
return true
end
function route_unlocked(route_id)
local route = get_route(route_id)
if (not route) then return end
return (route.unlocked)
end
function route_locked(route_id)
return (not route_unlocked(route_id))
end
-------------------------------------------------------------------------------
function route_block(route_id)
local route = get_route(route_id)
if not (route and route.unlocked) then return end
route.blocked = true
return true
end
function route_unblock(route_id)
local route = get_route(route_id)
if not (route and route.unlocked and route.blocked) then return end
route.blocked = false
return true
end
function route_blocked(route_id)
local route = get_route(route_id)
if (not route) then return end
return (route.blocked)
end
-------------------------------------------------------------------------------
-- We also pass an additional functor to be executed here
function route_discover(route_id, functor, ...)
local vararg = {...}
local route = get_route(route_id)
if not (route and route.blocked) then return end
route.block_discovered = true
if (functor and type(functor) == "function") then
functor(unpack(vararg))
end
return true
end
function route_discovered(route_id)
local route = get_route(route_id)
return (route and route.block_discovered)
end
function route_known_state_differs(route_id)
local route = get_route(route_id)
return route and (route.blocked ~= route.block_discovered)
end
-------------------------------------------------------------------------------
function transition_tostring(transition_name, indentation, indent_str)
indentation = (indentation) or 0
indent_str = (indent_str) or " "
local transition = get_transition(transition_name)
local string = string.rep(indent_str, indentation)
.. "[" .. transition_name .. "] = {\n"
for key, value in pairs(transition) do
if (key == "route") then
goto next_attribute
end
string = string .. string.rep(indent_str, indentation +1)
.. key .. " = "
if (type(value) == "table") then
string = string .. "{" .. table.concat(value, ",") .. "}"
else
string = string .. tostring(value)
end
string = string .. "\n"
:: next_attribute ::
end
return string .. string.rep(indent_str, indentation) .. "}\n"
end
-- crude and specific string generation of attribute tables
function route_tostring(route_id, include_member_attributes)
local route = get_route(route_id)
if (not route) then
log_error("Printinfo No route with ID #%s !", route_id)
return
end
local indentation = " "
local string = string.format("[%03d]\n", route_id)
for key, value in pairs(route) do
if (key == 'id') then goto next_attribute end
string = string .. indentation .. key .. " = "
if (type(value) ~= "table") then
string = string .. tostring(value)
goto continue
end
if ((not include_member_attributes) or key ~= "members") then
string = string .. "{" .. table.concat(value, ", ") .. "}"
goto continue
end
-- lazy way to serialize member attributes
string = string .. "{\n"
for _, section in pairs(value) do
string = string .. transition_tostring(section, 2, indentation)
end
string = string .. indentation .. "}"
:: continue ::
string = string .. "\n"
:: next_attribute ::
end
return string:sub(1, -2) -- remove last new line
end
-- Prints all routes with the specified attributes
-- @returns amount of all blocked routes
function print_routes(conditions)
local output_string = string.format("Route Attributes%s: ",
(conditions) and " (" .. table.concat(conditions, ", ") .. ")" or "")
local routes = get_routes(conditions)
if (not routes or utils.is_table_empty(routes)) then return end
for _, route_id in pairs(routes) do
output_string = output_string .. "\n" .. route_tostring(route_id, true)
end
log("%s", output_string)
end
-- Prints all the information agnostic to what type the parameter is
function printinfo(gizmo)
local output_string = "Attributes of"
if route_exists(gizmo) then
output_string = string.format("%s route #%s:\n%s",
output_string, gizmo, route_tostring(gizmo, true))
elseif transition_exists(gizmo) then
output_string = string.format("%s transition '%s':\n%s",
output_string, gizmo, transition_tostring(gizmo))
else return end
log("%s", output_string)
end