Minetest Lua Mainmenu API Reference 0.4.10
========================================
Introduction
-------------
The main menu is defined as a formspec by Lua in builtin/mainmenu/
Description of formspec language to show your menu is in lua_api.txt
Callbacks
---------
core.buttonhandler(fields): called when a button is pressed.
^ fields = {name1 = value1, name2 = value2, ...}
core.event_handler(event)
^ event: "MenuQuit", "KeyEnter", "ExitButton" or "EditBoxEnter"
Gamedata
--------
The "gamedata" table is read when calling core.start(). It should contain:
{
playername = <name>,
password = <password>,
address = <IP/adress>,
port = <port>,
selected_world = <index>, -- 0 for client mode
singleplayer = <true/false>,
}
Functions
---------
core.start()
core.close()
Filesystem:
core.get_scriptdir()
^ returns directory of script
core.get_modpath() (possible in async calls)
^ returns path to global modpath
core.get_modstore_details(modid) (possible in async calls)
^ modid numeric id of mod in modstore
^ returns {
id = <numeric id of mod in modstore>,
title = <human readable title>,
basename = <basename for mod>,
description = <description of mod>,
author = <author of mod>,
download_url= <best match download url>,
license = <short description of license>,
rating = <float value of current rating>
}
core.get_modstore_list() (possible in async calls)
^ returns {
[1] = {
id = <numeric id of mod in modstore>,
title = <human readable title>,
basename = <basename for mod>
}
}
core.get_gamepath() (possible in async calls)
^ returns path to global gamepath
core.get_texturepath() (possible in async calls)
^ returns path to default textures
core.get_dirlist(path,onlydirs) (possible in async calls)
^ path to get subdirs from
^ onlydirs should result contain only dirs?
^ returns list of folders within path
core.create_dir(absolute_path) (possible in async calls)
^ absolute_path to directory to create (needs to be absolute)
^ returns true/false
core.delete_dir(absolute_path) (possible in async calls)
^ absolute_path to directory to delete (needs to be absolute)
^ returns true/false
core.copy_dir(source,destination,keep_soure) (possible in async calls)
^ source folder
^ destination folder
^ keep_source DEFAULT true --> if set to false source is deleted after copying
^ returns true/false
core.extract_zip(zipfile,destination) [unzip within path required]
^ zipfile to extract
^ destination folder to extract to
^ returns true/false
core.download_file(url,target) (possible in async calls)
^ url to download
^ target to store to
^ returns true/false
core.get_version() (possible in async calls)
^ returns current core version
core.sound_play(spec, looped) -> handle
^ spec = SimpleSoundSpec (see lua-api.txt)
^ looped = bool
core.sound_stop(handle)
core.get_video_drivers()
^ get list of video drivers supported by engine (not all modes are guaranteed to work)
^ returns list of available video drivers e.g. { "OpenGL", "Software" }
Formspec:
core.update_formspec(formspec)
core.get_table_index(tablename) -> index
^ can also handle textlists
core.formspec_escape(string) -> string
^ escapes characters [ ] \ , ; that can not be used in formspecs
core.explode_table_event(string) -> table
^ returns e.g. {type="CHG", row=1, column=2}
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
core.explode_textlist_event(string) -> table
^ returns e.g. {type="CHG", index=1}
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
GUI:
core.set_background(type, texturepath,[tile],[minsize])
^ type: "background", "overlay", "header" or "footer"
^ tile: tile the image instead of scaling (background only)
^ minsize: minimum tile size, images are scaled to at least this size prior
^ doing tiling (background only)
core.set_clouds(<true/false>)
core.set_topleft_text(text)
core.show_keys_menu()
core.file_open_dialog(formname,caption)
^ shows a file open dialog
^ formname is base name of dialog response returned in fields
^ -if dialog was accepted "_accepted"
^^ will be added to fieldname containing the path
^ -if dialog was canceled "_cancelled"
^ will be added to fieldname value is set to formname itself
^ returns nil or selected file/folder
core.get_screen_info()
^ returns {
density = <screen density 0.75,1.0,2.0,3.0 ... (dpi)>,
display_width = <width of display>,
display_height = <height of display>,
window_width = <current window width>,
window_height = <current window height>
}
Games:
core.get_game(index)
^ returns {
id = <id>,
path = <full path to game>,
gamemods_path = <path>,
name = <name of game>,
menuicon_path = <full path to menuicon>,
DEPRECATED:
addon_mods_paths = {[1] = <path>,},
}
core.get_games() -> table of all games in upper format (possible in async calls)
Favorites:
core.get_favorites(location) -> list of favorites (possible in async calls)
^ location: "local" or "online"
^ returns {
[1] = {
clients = <number of clients/nil>,
clients_max = <maximum number of clients/nil>,
version = <server version/nil>,
password = <true/nil>,
creative = <true/nil>,
damage = <true/nil>,
pvp = <true/nil>,
description = <server description/nil>,
name = <server name/nil>,
address = <address of server/nil>,
port = <port>
},
}
core.delete_favorite(id, location) -> success
Logging:
core.debug(line) (possible in async calls)
^ Always printed to stderr and logfile (print() is redirected here)
core.log(line) (possible in async calls)
core.log(loglevel, line) (possible in async calls)
^ loglevel one of "error", "action", "info", "verbose"
Settings:
core.setting_set(name, value)
core.setting_get(name) -> string or nil (possible in async calls)
core.setting_setbool(name, value)
core.setting_getbool(name) -> bool or nil (possible in async calls)
core.setting_save() -> nil, save all settings to config file
Worlds:
core.get_worlds() -> list of worlds (possible in async calls)
^ returns {
[1] = {
path = <full path to world>,
name = <name of world>,
gameid = <gameid of world>,
},
}
core.create_world(worldname, gameid)
core.delete_world(index)
Helpers:
core.gettext(string) -> string
^ look up the translation of a string in the gettext message catalog
fgettext(string, ...) -> string
^ call core.gettext(string), replace "$1"..."$9" with the given
^ extra arguments, call core.formspec_escape and return the result
core.parse_json(string[, nullvalue]) -> something (possible in async calls)
^ see core.parse_json (lua_api.txt)
dump(obj, dumped={})
^ Return object serialized as a string
string:split(separator)
^ eg. string:split("a,b", ",") == {"a","b"}
string:trim()
^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar"
core.is_yes(arg) (possible in async calls)
^ returns whether arg can be interpreted as yes
Async:
core.handle_async(async_job,parameters,finished)
^ execute a function asynchronously
^ async_job is a function receiving one parameter and returning one parameter
^ parameters parameter table passed to async_job
^ finished function to be called once async_job has finished
^ the result of async_job is passed to this function
Limitations of Async operations
-No access to global lua variables, don't even try
-Limited set of available functions
e.g. No access to functions modifying menu like core.start,core.close,
core.file_open_dialog
Class reference
----------------
Settings: see lua_api.txt
-- interlocking/database.lua-- saving the location of TCB's, their neighbors and their state--[[== THIS COMMENT IS PARTIALLY INCORRECT AND OUTDATED! ==The interlocking system is based on track circuits.Track circuit breaks must be manually set by the user. Signals must be assigned to track circuit breaks and to a direction(connid).To simplify the whole system, there is no overlap.== Trains ==Trains always occupy certain track circuits. These are shown red in the signalbox view (TRAIN occupation entry).== Database storage ==The things that are actually saved are the Track Circuit Breaks. Each TCB holds a list of the TCBs that are adjacent in each direction.TC occupation/state is then saved inside each (TCB,Direction) and held in sync across all TCBs adjacent to this one. If something should not be in sync,all entries are merged to perform the most restrictive setup.== Traverser function ==To determine and update the list of neighboring TCBs, we need a traverser function.It will start at one TCB in a specified direction (connid) and use get_adjacent_rail to crawl along the track. When encountering a turnout or a crossing,it needs to branch(call itself recursively) to find all required TCBs. Those found TCBs are then saved in a list as tuples (TCB,Dir)In the last step, they exchange their neighbors.== TC states ==A track circuit does not have a state as such, but has more or less a list of "reservations"type can be one of these:TRAIN See Trains oboveROUTE Route set from a signal, but no train has yet passed that signal.Not implemented (see note by reversible): OWNED - former ROUTE segments that a train has begun passing (train_id assigned) - Space behind a train up to the next signal, when a TC is set as REVERSIBLECertain TCs can be marked as "allow call-on".== Route setting: ==Routes are set from a signal (the entry signal) to another signal facing the same direction (the exit signal)Remember that signals are assigned to a TCB and a connid.Whenever this is done, the following track circuits are set "reserved" by the train by saving the entry signal's ID:- all TCs on the direct way of the route - set as ROUTERoute setting fails whenever any TC that we want to set ROUTE to is already set ROUTE or TRAIN from another signal (except call-on, see below)Apart from this, we need to set turnouts- Turnouts on the track are set held as ROUTE- Turnouts that purpose as flank protection are set held as FLANK (NOTE: left as an idea for later, because it's not clear how to do this properly without an engineer)Note: In SimSig, it is possible to set a route into an still occupied section on the victoria line sim. (at the depot exit at seven sisters), although there are still segments set ahead of the first train passing, remaining from another route. Because our system will be able to remember "requested routes" and set them automatically once ready, this is not necessary here.== Call-On/Multiple Trains ==It will be necessary to join and split trains using call-on routes. A call-on route may be set when:- there are no ROUTE reservations- there are TRAIN reservations only inside TCs that have "allow call-on" set== TC Properties ==Note: Reversible property will not be implemented, assuming everything as non-rev.This is sufficient to cover all use cases, and is done this way in reality. REVERSIBLE - Whether trains are allowed to reverse while on track circuit This property is supposed to be set for station tracks, where there is a signal at each end, and for sidings. It should in no case be set for TCs covering turnouts, or for main running lines. When a TC is not set as reversible, the OWNED status is cleared from the TC right after the train left it, to allow other trains to pass it. If it is set reversible, interlocking will keep the OWNED state behind the train up to the next signal, clearing it as soon as the train passes another signal or enters a non-reversible section.CALL_ON_ALLOWED - Whether this TC being blocked (TRAIN or ROUTE) does not prevent shunt routes being set through this TC== More notes ==- It may not be possible to switch turnouts when their TC has any state entry== Route releasing (TORR) ==A train passing through a route happens as follows:Route set from entry to exit signalTrain passes entry signal and enters first TC past the signal-> Route from signal cleared (TCs remain locked)-> ROUTE status of first TC past signal clearedTrain continues along the route.Whenever train leaves a TC-> Clearing any routes set from this TC outward recursively - see "Reversing problem"Whenever train enters a TC-> Clear route status from the just entered TCNote that this prohibits by design that the train clears the route ahead of it.== Reversing Problem ==Encountered at the Royston simulation in SimSig. It is solved there by imposing a time limit on the set route. Call-on routes can somehow be set anyway.Imagine this setup: (T=Train, R=Route, >=in_dir TCB) O-| Royston P2 |-OT->---|->RRR-|->RRR-|--Train T enters from the left, the route is set to the right signal. But train is supposed to reverse here and stops this way: O-| Royston P2 |-O------|-TTTT-|->RRR-|--The "Route" on the right is still set. Imposing a timeout here is a thing only professional engineers can determine, not an algorithm. O-| Royston P2 |-O<-T---|------|->RRR-|--The train has left again, while route on the right is still set.So, we have to clear the set route when the train has left the left TC.This does not conflict with call-on routes, because both station tracks are set as "allow call-on"Because none of the routes extends past any non-call-on sections, call-on route would be allowed here, even though the routeis locked in opposite direction at the time of routesetting.Another case of this:--TTT/--|->RRR--The / here is a non-interlocked turnout (to a non-frequently used siding). For some reason, there is no exit node there,so the route is set to the signal at the right end. The train is taking the exit to the siding and frees the TC, without everhaving touched the right TC.]]--local TRAVERSER_LIMIT =1000local ildb = {}local track_circuit_breaks = {}local track_sections = {}-- Assignment of signals to TCBslocal signal_assignments = {}-- track+direction -> signal positionlocal influence_points = {}
advtrains.interlocking.npr_rails = {}function ildb.load(data)if not data then return endif data.tcbs then
track_circuit_breaks = data.tcbs
endif data.ts then
track_sections = data.ts
endif data.signalass then
signal_assignments = data.signalass
endif data.rs_locks then
advtrains.interlocking.route.rte_locks = data.rs_locks
endif data.rs_callbacks then
advtrains.interlocking.route.rte_callbacks = data.rs_callbacks
endif data.influence_points then
influence_points = data.influence_points
endif data.npr_rails then
advtrains.interlocking.npr_rails = data.npr_rails
endendfunction ildb.save()return{
tcbs = track_circuit_breaks,
ts=track_sections,
signalass = signal_assignments,
rs_locks = advtrains.interlocking.route.rte_locks,
rs_callbacks = advtrains.interlocking.route.rte_callbacks,
influence_points = influence_points,
npr_rails = advtrains.interlocking.npr_rails,}end----[[TCB data structure{[1] = { -- Variant: with adjacent TCs. ts_id = <id> -- ID of the assigned track section signal = <pos> -- optional: when set, routes can be set from this tcb/direction and signal -- aspect will be set accordingly. routeset = <index in routes> -- Route set from this signal. This is the entry that is cleared once -- train has passed the signal. (which will set the aspect to "danger" again) route_committed = <boolean> -- When setting/requesting a route, routetar will be set accordingly, -- while the signal still displays danger and nothing is written to the TCs -- As soon as the route can actually be set, all relevant TCs and turnouts are set and this field -- is set true, clearing the signal aspect = <asp> -- The aspect the signal should show. If this is nil, should show the most restrictive aspect (red) signal_name = <string> -- The human-readable name of the signal, only for documenting purposes routes = { <route definition> } -- a collection of routes from this signal route_auto = <boolean> -- When set, we will automatically re-set the route (designated by routeset)},[2] = { -- Variant: end of track-circuited area (initial state of TC) ts_id = nil, -- this is the indication for end_of_interlocking section_free = <boolean>, --this can be set by an exit node via mesecons or atlatc, -- or from the tc formspec.}}Track section[id] = { name = "Some human-readable name" tc_breaks = { <signal specifier>,... } -- Bounding TC's (signal specifiers) -- Can be direct ends (auto-detected), conflicting routes or TCBs that are too far away from each other route = { origin = <signal>, -- route origin entry = <sigd>, -- supposed train entry point rsn = <string>, first = <bool> } route_post = { locks = {[n] = <pts>} next = <sigd> } -- Set whenever a route has been set through this TC. It saves the origin tcb id and side -- (=the origin signal). rsn is some description to be shown to the user -- first says whether to clear the routesetting status from the origin signal. -- locks contains the positions where locks are held by this ts. -- 'route' is cleared when train enters the section, while 'route_post' cleared when train leaves section. trains = {<id>, ...} -- Set whenever a train (or more) reside in this TC}Signal specifier (sigd) (a pair of TCB/Side):{p = <pos>, s = <1/2>}Signal Assignments: reverse lookup of signals assigned to TCBssignal_assignments = {[<signal pts>] = <sigd>}]]--function ildb.create_tcb(pos)local new_tcb = {[1] = {},[2] = {},}local pts = advtrains.roundfloorpts(pos)if not track_circuit_breaks[pts]then
track_circuit_breaks[pts] = new_tcb
return trueelsereturn falseendendfunction ildb.get_tcb(pos)local pts = advtrains.roundfloorpts(pos)return track_circuit_breaks[pts]endfunction ildb.get_tcbs(sigd)local tcb = ildb.get_tcb(sigd.p)if not tcb then return nil endreturn tcb[sigd.s]endfunction ildb.create_ts(sigd)local tcbs = ildb.get_tcbs(sigd)local id = advtrains.random_id()while track_sections[id]do
id = advtrains.random_id()end
track_sections[id] = {
name ="Section "..id,
tc_breaks = { sigd }}
tcbs.ts_id = id
endfunction ildb.get_ts(id)return track_sections[id]end-- various helper functions handling sigd'slocal sigd_equal = advtrains.interlocking.sigd_equal
local functioninsert_sigd_nodouble(list, sigd)for idx, cmp inpairs(list)doifsigd_equal(sigd, cmp)thenreturnendendtable.insert(list, sigd)end-- This function will actually handle the node that is in connid direction from the node at pos-- so, this needs the conns of the node at pos, since these are already calculatedlocal functiontraverser(found_tcbs, pos, conns, connid, count, brk_when_found_n)local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(pos, conns, connid, advtrains.all_tracktypes)if not adj_pos then--atdebug("Traverser found end-of-track at",pos, connid)returnend-- look whether there is a TCB hereif#next_conns ==2then--if not, don't even try!local tcb = ildb.get_tcb(adj_pos)if tcb then-- done with this branch--atdebug("Traverser found tcb at",adj_pos, adj_connid)insert_sigd_nodouble(found_tcbs, {p=adj_pos, s=adj_connid})returnendend-- recursion abort conditionif count > TRAVERSER_LIMIT then--atdebug("Traverser hit counter at",adj_pos, adj_connid)return trueend-- continue traversinglocal counter_hit =falsefor nconnid, nconn inipairs(next_conns)doif adj_connid ~= nconnid then
counter_hit = counter_hit ortraverser(found_tcbs, adj_pos, next_conns, nconnid, count +1, brk_when_found_n)if brk_when_found_n and#found_tcbs>=brk_when_found_n thenbreakendendendreturn counter_hit
end-- Merges the TS with merge_id into root_id and then deletes merge_idlocal functionmerge_ts(root_id, merge_id)local rts = ildb.get_ts(root_id)local mts = ildb.get_ts(merge_id)if not mts then return end-- This may be the case when sync_tcb_neighbors-- inserts the same id twice. do nothing.if not ildb.may_modify_ts(rts)then return false endif not ildb.may_modify_ts(mts)then return false end-- cobble together the list of TCBsfor _, msigd inipairs(mts.tc_breaks)dolocal tcbs = ildb.get_tcbs(msigd)if tcbs theninsert_sigd_nodouble(rts.tc_breaks, msigd)
tcbs.ts_id = root_id
end
advtrains.interlocking.show_tcb_marker(msigd.p)end-- done
track_sections[merge_id] =nilendlocal lntrans = {"A","B"}local functionsigd_to_string(sigd)return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]end-- Check for near TCBs and connect to their TS if they have one, and syncs their data.function ildb.sync_tcb_neighbors(pos, connid)local found_tcbs = { {p = pos, s = connid} }local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)if not node_ok thenatwarn("update_tcb_neighbors but node is NOK: "..minetest.pos_to_string(pos))returnend--atdebug("Traversing from ",pos, connid)local counter_hit =traverser(found_tcbs, pos, conns, connid,0)local ts_id
local list_eoi = {}local list_ok = {}local list_mismatch = {}local ts_to_merge = {}for idx, sigd inpairs(found_tcbs)dolocal tcbs = ildb.get_tcbs(sigd)if not tcbs.ts_id then--atdebug("Sync: put",sigd_to_string(sigd),"into list_eoi")table.insert(list_eoi, sigd)elseif not ts_id and tcbs.ts_id thenif not ildb.get_ts(tcbs.ts_id)thenatwarn("Track section database is inconsistent, there's no TS with ID=",tcbs.ts_id)
tcbs.ts_id =niltable.insert(list_eoi, sigd)else--atdebug("Sync: put",sigd_to_string(sigd),"into list_ok")
ts_id = tcbs.ts_id
table.insert(list_ok, sigd)endelseif ts_id and tcbs.ts_id and tcbs.ts_id ~= ts_id thenatwarn("Track section database is inconsistent, sections share track!")atwarn("Merging",tcbs.ts_id,"into",ts_id,".")table.insert(list_mismatch, sigd)table.insert(ts_to_merge, tcbs.ts_id)endendif ts_id thenlocal ts = ildb.get_ts(ts_id)for _, sigd inipairs(list_eoi)dolocal tcbs = ildb.get_tcbs(sigd)
tcbs.ts_id = ts_id
table.insert(ts.tc_breaks, sigd)
advtrains.interlocking.show_tcb_marker(sigd.p)endfor _, mts inipairs(ts_to_merge)domerge_ts(ts_id, mts)endendendfunction ildb.link_track_sections(merge_id, root_id)if merge_id == root_id thenreturnendmerge_ts(root_id, merge_id)endfunction ildb.remove_from_interlocking(sigd)local tcbs = ildb.get_tcbs(sigd)if not ildb.may_modify_tcbs(tcbs)then return false endif tcbs.ts_id thenlocal tsid = tcbs.ts_id
local ts = ildb.get_ts(tsid)if not ts then
tcbs.ts_id =nilreturn trueend-- remove entry from the listlocal idx =1while idx <= #ts.tc_breaks dolocal cmp = ts.tc_breaks[idx]ifsigd_equal(sigd, cmp)thentable.remove(ts.tc_breaks, idx)else
idx = idx +1endend
tcbs.ts_id =nil--ildb.sync_tcb_neighbors(sigd.p, sigd.s)if#ts.tc_breaks ==0then
track_sections[tsid] =nilendend
advtrains.interlocking.show_tcb_marker(sigd.p)if tcbs.signal thenreturn falseendreturn trueendfunction ildb.remove_tcb(pos)local pts = advtrains.roundfloorpts(pos)if not track_circuit_breaks[pts]then return endfor connid=1,2doif not ildb.remove_from_interlocking({p=pos, s=connid})thenreturn falseendend
track_circuit_breaks[pts] =nilreturn trueendfunction ildb.dissolve_ts(ts_id)local ts = ildb.get_ts(ts_id)if not ildb.may_modify_ts(ts)then return false endlocal tcbr = advtrains.merge_tables(ts.tc_breaks)for _,sigd inipairs(tcbr)do
ildb.remove_from_interlocking(sigd)end-- Note: ts gets removed in the moment of the removal of the last TCB.return trueend-- Returns true if it is allowed to modify any property of a track section, such as-- - removing TCBs-- - merging and dissolving sections-- As of now the action will be denied if a route is set or if a train is in the section.function ildb.may_modify_ts(ts)if ts.route or ts.route_post or(ts.trains and#ts.trains>0)thenreturn falseendreturn trueendfunction ildb.may_modify_tcbs(tcbs)if tcbs.ts_id thenlocal ts = ildb.get_ts(tcbs.ts_id)if ts and not ildb.may_modify_ts(ts)thenreturn falseendendreturn trueend-- Utilize the traverser to find the track section at the specified position-- Returns:-- ts_id, origin - the first found ts and the sigd of the found tcb-- nil - there were no TCBs in TRAVERSER_MAX range of the position-- false - the first found TCB stated End-Of-Interlocking, or track ends were reachedfunction ildb.get_ts_at_pos(pos)local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)if not node_ok thenerror("get_ts_at_pos but node is NOK: "..minetest.pos_to_string(pos))endlocal limit_hit =falselocal found_tcbs = {}for connid, conn inipairs(conns)do-- Note: a breadth-first-search would be better for performance
limit_hit = limit_hit ortraverser(found_tcbs, pos, conns, connid,0,1)if#found_tcbs >=1thenlocal tcbs = ildb.get_tcbs(found_tcbs[1])local ts
if tcbs.ts_id thenreturn tcbs.ts_id, found_tcbs[1]elsereturn falseendendendif limit_hit then-- there was at least one limit hitreturn nilelse-- all traverser ends were track endsreturn falseendend-- returns the sigd the signal at pos belongs to, if this is knownfunction ildb.get_sigd_for_signal(pos)local pts = advtrains.roundfloorpts(pos)local sigd = signal_assignments[pts]if sigd thenif not ildb.get_tcbs(sigd)then
signal_assignments[pts] =nilreturn nilendreturn sigd
endreturn nilendfunction ildb.set_sigd_for_signal(pos, sigd)local pts = advtrains.roundfloorpts(pos)
signal_assignments[pts] = sigd
end-- checks if there's any influence point set to this positionfunction ildb.is_ip_at(pos)local pts = advtrains.roundfloorpts(pos)if influence_points[pts]thenreturn trueendreturn falseend-- checks if a signal is influencing herefunction ildb.get_ip_signal(pts, connid)if influence_points[pts]thenreturn influence_points[pts][connid]endend-- Tries to get aspect to obey here, if there-- is a signal ip at this location-- auto-clears invalid assignmentsfunction ildb.get_ip_signal_asp(pts, connid)local p = ildb.get_ip_signal(pts, connid)if p thenlocal asp = advtrains.interlocking.signal_get_aspect(p)if not asp thenatlog("Clearing orphaned signal influence point", pts,"/", connid)
ildb.clear_ip_signal(pts, connid)return nilendreturn asp, p
endreturn nilend-- set signal assignment.function ildb.set_ip_signal(pts, connid, spos)
ildb.clear_ip_by_signalpos(spos)if not influence_points[pts]then
influence_points[pts] = {}end
influence_points[pts][connid] = spos
end-- clear signal assignment.function ildb.clear_ip_signal(pts, connid)
influence_points[pts][connid] =nilfor _,_ inpairs(influence_points[pts])doreturnend
influence_points[pts] =nilendfunction ildb.get_ip_by_signalpos(spos)for pts,tab inpairs(influence_points)dofor connid,pos inpairs(tab)doif vector.equals(pos, spos)thenreturn pts, connid
endendendend-- clear signal assignment given the signal positionfunction ildb.clear_ip_by_signalpos(spos)local pts, connid = ildb.get_ip_by_signalpos(spos)if pts then ildb.clear_ip_signal(pts, connid)endend
advtrains.interlocking.db = ildb