diff options
author | Singularis <singularis@volny.cz> | 2024-12-22 09:01:44 +0100 |
---|---|---|
committer | orwell <orwell@bleipb.de> | 2025-05-27 20:22:01 +0200 |
commit | dd258991d07b66847997441269b1ec6bb963f317 (patch) | |
tree | 0f79dd228ad683f6b5a61851f709c9972e7890bd /advtrains_line_automation | |
parent | d7c9d3149850b1dba694aaf594df2edb9753c383 (diff) | |
download | advtrains-dd258991d07b66847997441269b1ec6bb963f317.tar.gz advtrains-dd258991d07b66847997441269b1ec6bb963f317.tar.bz2 advtrains-dd258991d07b66847997441269b1ec6bb963f317.zip |
[advtrains,advtrains_line_automation] první verze systému linek
Diffstat (limited to 'advtrains_line_automation')
-rw-r--r-- | advtrains_line_automation/init.lua | 2 | ||||
-rw-r--r-- | advtrains_line_automation/line_editor.lua | 676 | ||||
-rw-r--r-- | advtrains_line_automation/line_functions.lua | 747 | ||||
-rw-r--r-- | advtrains_line_automation/stoprail.lua | 92 | ||||
-rw-r--r-- | advtrains_line_automation/structs.md | 96 |
5 files changed, 1526 insertions, 87 deletions
diff --git a/advtrains_line_automation/init.lua b/advtrains_line_automation/init.lua index 5cd890c..7e65174 100644 --- a/advtrains_line_automation/init.lua +++ b/advtrains_line_automation/init.lua @@ -19,6 +19,8 @@ advtrains.lines = { local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM dofile(modpath.."railwaytime.lua") +dofile(modpath.."line_functions.lua") +dofile(modpath.."line_editor.lua") dofile(modpath.."scheduler.lua") dofile(modpath.."station_editor.lua") dofile(modpath.."stoprail.lua") diff --git a/advtrains_line_automation/line_editor.lua b/advtrains_line_automation/line_editor.lua new file mode 100644 index 0000000..c24e2e6 --- /dev/null +++ b/advtrains_line_automation/line_editor.lua @@ -0,0 +1,676 @@ +local def +local F = minetest.formspec_escape +local ifthenelse = ch_core.ifthenelse + +local max_stations = 60 + +local MODE_NORMAL = 0 -- normální zastávka (výchozí nebo mezilehlá) +local MODE_REQUEST_STOP = 1 -- zastávka na znamení (mezilehlá) +local MODE_HIDDEN = 2 -- skrytá zastávka (výchozí nebo mezilehlá) +local MODE_DISABLED = 3 -- vypnutá zastávka (mezilehlá nebo koncová) +local MODE_FINAL = 4 -- koncová zastávka (linka zde jízdu končí) +local MODE_FINAL_HIDDEN = 5 -- koncová zastávka skrytá +local MODE_FINAL_CONTINUE = 6 -- koncová zastávka (vlak pokračuje jako jiná linka) + +local color_red = core.get_color_escape_sequence("#ff0000") +local color_green = core.get_color_escape_sequence("#00ff00") + +local cancel_linevar = assert(advtrains.lines.cancel_linevar) +local get_line_description = assert(advtrains.lines.get_line_description) +local linevar_decompose = assert(advtrains.lines.linevar_decompose) +local try_get_linevar_def = assert(advtrains.lines.try_get_linevar_def) + +local function check_rights(pinfo, owner) + if pinfo.role == "new" or pinfo.role == "none" then + return false + end + if owner == nil or pinfo.role == "admin" or pinfo.player_name == owner then + return true + end + return false +end + +local function get_stn_from_linevar(linevar) + local line, stn = linevar_decompose(linevar) + return ifthenelse(line ~= nil, stn, nil) +end + +local function add_linevar(stn, linevar_def) + local station = advtrains.lines.stations[stn] + if station == nil then + return false, "Chybná stanice!" + end + local linevars = station.linevars + if linevars == nil then + linevars = {} + station.linevars = linevars + end + if linevars[linevar_def.name] ~= nil then + return false, "Nemohu přidat, varianta linky '"..linevar_def.name.."' již existuje." + end + linevars[linevar_def.name] = linevar_def + return true, nil +end + +local function replace_linevar(stn, linevar_def) + local station = advtrains.lines.stations[stn] + if station == nil or station.linevars == nil then + return false, "Chybná stanice!" + end + local linevar = assert(linevar_def.name) + local linevars = station.linevars + if linevars[linevar] == nil then + return false, "Nemohu nahradit, varianta linky '"..linevar.."' dosud neexistuje!" + end + linevars[linevar] = linevar_def + for train_id, train in pairs(advtrains.trains) do + local ls = train.line_status + if ls ~= nil and ls.linevar == linevar then + core.log("action", "Train "..train_id.." restarted from index "..ls.linevar_index.." due to replacement of linevar '"..linevar.."'.") + ls.linevar_index = 1 + end + end + return true, nil +end + +local function delete_linevar(stn, linevar) + local station = advtrains.lines.stations[stn] + if station == nil or station.linevars == nil then + return false, "Chybná stanice!" + end + local linevars = station.linevars + if linevars[assert(linevar)] == nil then + return false, "Nemohu odstranit, varianta linky '"..linevar.."' nebyla nalezena!" + end + linevars[linevar] = nil + for train_id, train in pairs(advtrains.trains) do + local ls = train.line_status + if ls ~= nil and ls.linevar == linevar then + core.log("action", "Train "..train_id.." removed from deleted linevar '"..linevar.."'.") + cancel_linevar(train) + end + end + return true, nil +end + +local function get_formspec(custom_state) + local pinfo = ch_core.normalize_player(assert(custom_state.player_name)) + if pinfo.player == nil then + minetest.log("error", "Expected player not in game!") + return "" + end + + local selection_index = custom_state.selection_index + local formspec = { + ch_core.formspec_header({formspec_version = 6, size = {20, 16}, auto_background = true}), + "label[0.5,0.6;Editor variant linek]".. + "style[s01_pos", + } + for i = 2, max_stations do + table.insert(formspec, string.format(",s%02d_pos", i)) + end + table.insert(formspec, ";font=mono;font_size=-4]".. + "style[rc;font=mono]".. + "tablecolumns[color;text,align=right;text;text,align=center;color;text,width=7;color;text]".. + "table[0.5,1.25;19,5;linevar;#ffffff,LINKA,TRASA,SM. KÓD,#ffffff,SPRAVUJE,#ffffff,STAV") + + for _, linevar_def in ipairs(custom_state.linevars) do + local lv_line, lv_stn, lv_rc = linevar_decompose(linevar_def.name) + local color = ifthenelse(linevar_def.disabled, "#cccccc", "#ffffff") + table.insert(formspec, + ","..color..","..F(lv_line)..","..F(get_line_description(linevar_def, {first_stop = true, last_stop = true})).. + ","..F(lv_rc)..","..ifthenelse(linevar_def.owner == pinfo.player_name, "#00ff00", color)..",") + table.insert(formspec, F(ch_core.prihlasovaci_na_zobrazovaci(linevar_def.owner))) + table.insert(formspec, ","..color..",") + if linevar_def.disabled then + table.insert(formspec, "vypnutá") + end + end + if selection_index ~= nil then + table.insert(formspec, ";"..selection_index.."]") + else + table.insert(formspec, ";]") + end + if pinfo.role ~= "new" then + table.insert(formspec, "button[14.5,0.3;3.5,0.75;create;nová varianta...]") + end + local has_rights_to_open_variant = + pinfo.role == "admin" or (selection_index or 1) == 1 or + pinfo.player_name == custom_state.linevars[selection_index - 1].owner + + if has_rights_to_open_variant then + table.insert(formspec, "button[10.5,0.3;3.5,0.75;delete;smazat variantu]") + end + table.insert(formspec, "button_exit[18.75,0.3;0.75,0.75;close;X]".. + "field[0.5,7;1.25,0.75;line;linka:;"..F(custom_state.line).."]".. + "field[2,7;1.5,0.75;rc;sm.kód:;"..F(custom_state.rc).."]".. + "field[3.75,7;3,0.75;train_name;jméno vlaku:;"..F(custom_state.train_name).."]") + if pinfo.role ~= "admin" then + table.insert(formspec, "label[7,6.75;spravuje:\n") + else + table.insert(formspec, "field[7,7;4,0.75;owner;spravuje:;") + end + table.insert(formspec, F(custom_state.owner).."]".. + "checkbox[11.25,7.25;disable_linevar;vypnout;"..custom_state.disable_linevar.."]") + + if pinfo.role ~= "new" then + if has_rights_to_open_variant then + table.insert(formspec, "button[10,15;4.5,0.75;save;".. + ifthenelse(custom_state.compiled_linevar == nil, "ověřit změny\npřed uložením]", "uložit změny]")) + end + table.insert(formspec, "button[15,15.25;4,0.5;reset;vrátit změny]") + end + + table.insert(formspec, "tooltip[line;".. + "Označení linky. Musí být neprázdné. Varianta linky bude použita pouze na vlaky s tímto označením linky.]".. + "tooltip[rc;Směrový kód. Může být prázdný. Varianta linky bude použita pouze na vlaky\\,\n".. + "jejichž směrový kód se přesně shoduje se zadaným. Obvykle se toto pole nechává prázdné.]".. + "tooltip[train_name;Volitelný údaj. Je-li zadán\\, jízdní řády budou uvádět u spojů této\n".. + "varianty zadané jméno.]".. + "tooltip[disable_linevar;Zaškrtnutím variantu linky vypnete. Vypnutá varianta linky není používána\n".. + "na žádné další vlaky\\, stávající vlaky však mohou dojet do svých koncových zastávek.]") + + table.insert(formspec, "container[0,8.75]".. + "label[0.5,0.25;odjezd]".. + "label[2,0.25;kód dop.]".. + "label[4.75,0.25;režim zastávky]".. + "label[9.5,0.25;kolej]".. + "label[11,0.25;omezení pozice]".. + "scrollbaroptions[min=0;max=550;arrows=show]".. + "scrollbar[19,0.5;0.5,5.5;vertical;evl_scroll;"..custom_state.evl_scroll.."]".. + "scroll_container[0.5,0.5;18.5,5.5;evl_scroll;vertical]".. + "box[0,0;20,70;#00808040]") -- box[] = pozadí + + -- výchozí zastávka: + table.insert(formspec, + "label[0.1,0.4;0]".. + "field[1.5,0;2.5,0.75;s01_stn;;"..F(custom_state.stops[1].stn).."]".. + "dropdown[4.25,0;4.5,0.75;s01_mode;výchozí,skrytá (výchozí);"..custom_state.stops[1].mode..";true]".. + "field[9,0;1.25,0.75;s01_track;;"..F(custom_state.stops[1].track).."]".. + "field[10.5,0;3,0.75;s01_pos;;"..F(custom_state.stops[1].pos).."]".. + "label[13.75,0.4;"..F(custom_state.stops[1].label).."]") + + -- ostatní zastávky: + local y_base, y_scale = 0, 1 + for i = 2, max_stations do + local stop = custom_state.stops[i] + local n + if i < 10 then + n = "0"..i + else + n = tostring(i) + end + local y = string.format("%f", y_base + (i - 1) * y_scale) + local y2 = string.format("%f", y_base + (i - 1) * y_scale + 0.4) -- for a label + table.insert(formspec, + "field[0,"..y..";1.25,0.75;s"..n.."_dep;;"..F(stop.dep).."]".. + "field[1.5,"..y..";2.5,0.75;s"..n.."_stn;;"..F(stop.stn).."]".. + "dropdown[4.25,"..y..";4.5,0.75;s"..n.."_mode;normální,na znamení,skrytá (mezilehlá),vypnutá,koncová,koncová skrytá,".. + "koncová (pokračuje);"..stop.mode..";true]".. + "field[9,"..y..";1.25,0.75;s"..n.."_track;;"..F(stop.track).."]".. + "field[10.5,"..y..";3,0.75;s"..n.."_pos;;"..F(stop.pos).."]".. + "label[13.75,"..y2..";"..F(stop.label).."]") + end + + table.insert(formspec, + "scroll_container_end[]".. + "tooltip[0,0;2,1;Odjezd: očekávaná jízdní doba v sekundách od odjezdu z výchozí zastávky\n".. + "do odjezdu z dané zastávky. Podle ní se počítá zpoždění. Hodnota musí být jedinečná\n".. + "pro každou zastávku na lince a podle ní se zastávky seřadí.\n".. + "Pro úplné smazání dopravny z linky nechte pole prázdné.]".. + "tooltip[2,0;2.75,1;Kód dopravny: kód dopravny\\, kde má vlak zastavit. Vlak bude ignorovat\n".. + "ARS pravidla a zastaví na první zastávkové koleji v dopravně pro odpovídající počet vagonů.\n".. + "Kód dopravny se na lince může opakovat.]".. + "tooltip[4.75,0;4.75,1;Režim zastávky: výchozí/normální - vždy zastaví\\;\n".. + "na znamení: zastaví na znamení (zatím neimplementováno)\\;\n".. + "skrytá – vždy zastaví\\, ale nezobrazí se v jízdních řádech\\;\n".. + "vypnutá – nezastaví (použijte při výlukách nebo při zrušení zastávky)\\;\n".. + "koncová – vždy zastaví a tím ukončí spoj\\, vlak se stane nelinkovým\\;\n".. + "koncová (pokračuje) – jako koncová\\, ale vlak se může na odjezdu opět stát linkovým.]".. + "tooltip[9.5,0;1.5,1;Kolej: nepovinný\\, orientační údaj do jízdních řádů – na které koleji\n".. + "vlaky obvykle zastavují. Nepovinný údaj.]".. + "tooltip[10.5,0;3.5,1;Omezení pozice: Zadávejte jen v případě potřeby.\n".. + "Je-li zadáno\\, vlak v dané dopravně nezastaví na žádné jiné zastávkové koleji\n".. + "než na té\\, která leží přesně na zadané pozici. Příklad platné hodnoty:\n123,7,-13]".. + "container_end[]") + + -- if pinfo.role ~= "new" then + return table.concat(formspec) +end + +local mode_from_formspec_map = {MODE_NORMAL, MODE_REQUEST_STOP, MODE_HIDDEN, MODE_DISABLED, MODE_FINAL, MODE_FINAL_HIDDEN, MODE_FINAL_CONTINUE} +local mode_to_formspec_map = table.key_value_swap(mode_from_formspec_map) + +local function mode_to_formspec(i, raw_mode) + if i == 1 then + return ifthenelse(raw_mode ~= nil and raw_mode == MODE_HIDDEN, 2, 1) + elseif raw_mode == nil then + return 1 + else + return mode_to_formspec_map[raw_mode] or 1 + end +end + +local function mode_from_formspec(i, fs_mode) + if i == 1 then + return ifthenelse(fs_mode == 2, MODE_HIDDEN, nil) + else + local result = mode_from_formspec_map[fs_mode] + return ifthenelse(result ~= nil and result ~= MODE_NORMAL, result, nil) + end +end + +local function custom_state_set_selection_index(custom_state, new_selection_index) + -- this will also refresh stops and resets the changes + assert(custom_state.player_name) + assert(custom_state.linevars) + assert(new_selection_index) + local current_linevar = custom_state.linevars[new_selection_index - 1] + custom_state.selection_index = new_selection_index or 1 + local stops = custom_state.stops + if stops == nil then + stops = {} + custom_state.stops = stops + end + local linevar_stops + if current_linevar ~= nil then + linevar_stops = current_linevar.stops + else + linevar_stops = {} + end + + for i = 1, max_stations do + local stop = linevar_stops[i] + if stop ~= nil then + stops[i] = { + dep = tostring(assert(stop.dep)), + stn = assert(stop.stn), + mode = mode_to_formspec(i, stop.mode), + track = stop.track or "", + pos = stop.pos or "", + label = "", + } + else + stops[i] = { + dep = ifthenelse(i == 1, "0", ""), + stn = "", + mode = 1, + track = "", + pos = "", + label = "", + } + end + end + if current_linevar ~= nil then + local lv_line, lv_stn, lv_rc = linevar_decompose(current_linevar.name) + custom_state.line = lv_line or "" + custom_state.rc = lv_rc or "" + custom_state.train_name = current_linevar.train_name or "" + custom_state.owner = assert(current_linevar.owner) + custom_state.disable_linevar = ifthenelse(current_linevar.disabled, "true", "false") + else + custom_state.line = "" + custom_state.rc = "" + custom_state.train_name = "" + custom_state.owner = custom_state.player_name + custom_state.disable_linevar = "false" + end + custom_state.compiled_linevar = nil + custom_state.evl_scroll = 0 +end + +local function num_transform(s) + local prefix = s:match("^([0-9]+)/") + if prefix == nil then + return s + end + return string.format(" %020d%s", tonumber(prefix) or 0, s:sub(#prefix, -1)) +end + +local function linevars_sorter(a, b) + return num_transform(a.name) < num_transform(b.name) +end + +local function custom_state_refresh_linevars(custom_state, linevar_to_select) + assert(custom_state.player_name) + local linevars = {} + for _, stdata in pairs(advtrains.lines.stations) do + if stdata.linevars ~= nil then + for _, linevar_def in pairs(stdata.linevars) do + table.insert(linevars, linevar_def) + end + end + end + table.sort(linevars, linevars_sorter) + custom_state.selection_index = nil + custom_state.linevars = linevars + custom_state.compiled_linevar = nil + if linevar_to_select ~= nil then + for i, linevar_def in ipairs(linevars) do + if linevar_def.name == linevar_to_select then + custom_state_set_selection_index(custom_state, i + 1) + return true + end + end + return false + end +end + +local function custom_state_compile_linevar(custom_state) + local stations = advtrains.lines.stations + local line = assert(custom_state.line) + local stn = assert(custom_state.stops[1].stn) + local rc = assert(custom_state.rc) + local train_name = assert(custom_state.train_name) + local owner = assert(custom_state.owner) + local stops = {} + if line == "" then + return false, "Označení linky nesmí být prázdné!" + elseif line:find("[/|\\]") then + return false, "Označení linky nesmí obsahovat znaky '/', '|' a '\\'!" + elseif line:len() > 256 then + return false, "Označení linky je příliš dlouhé!" + elseif stn == "" then + return false, "Výchozí zastávka musí být vyplněná!" + elseif rc:find("[/|\\]") then + return false, "Směrový kód nesmí obsahovat znaky '/', '|' a '\\'!" + elseif owner == "" then + return false, "Správa linky musí být vyplněná!" + elseif train_name:len() > 256 then + return false, "Jméno vlaku je příliš dlouhé!" + end + -- Zkontrolovat zastávky: + local errcount = 0 + local finalcount = 0 + local dep_to_index = {} + for i, stop in ipairs(assert(custom_state.stops)) do + local good_label + stop.label = "" + if stop.dep == "" then + -- přeskočit + elseif not stop.dep:match("^[0-9][0-9]*$") then + errcount = errcount + 1 + stop.label = color_red.."Chybný formát času odjezdu!" + elseif tonumber(stop.dep) < 0 or tonumber(stop.dep) > 3600 then + errcount = errcount + 1 + stop.label = color_red.."Čas odjezdu musí být v rozsahu 0 až 3600!" + elseif dep_to_index[tonumber(stop.dep)] ~= nil then + errcount = errcount + 1 + stop.label = color_red.."Duplicitní čas odjezdu!" + else + dep_to_index[tonumber(stop.dep)] = i + if stop.stn == "" or stations[stop.stn] == nil or stations[stop.stn].name == nil then + errcount = errcount + 1 + stop.label = color_red.."Neznámý kód dopravny!" + elseif stop.stn:find("[/|\\]") then + errcount = errcount + 1 + stop.label = color_red.."Kód dopravny nesmí obsahovat znaky '/', '|' a '\\'!" + elseif stop.track:len() > 16 then + errcount = errcount + 1 + stop.label = color_red.."Označení koleje je příliš dlouhé!" + elseif stop.pos ~= "" and not stop.pos:match("^[-0-9][0-9]*,[-0-9][0-9]*,[-0-9][0-9]*$") then + errcount = errcount + 1 + stop.label = color_red.."Neplatný formát pozice!" + elseif stop.pos:len() > 22 then + errcount = errcount + 1 + stop.label = color_red.."Specifikace pozice je příliš dlouhá!" + else + -- v pořádku: + local new_stop = { + stn = stop.stn, + dep = tonumber(stop.dep), + } + local new_mode = mode_from_formspec(i, stop.mode) + if new_mode ~= nil then + new_stop.mode = new_mode + if i > 1 and (new_mode == MODE_FINAL or new_mode == MODE_FINAL_CONTINUE or new_mode == MODE_FINAL_HIDDEN) then + finalcount = finalcount + 1 + end + end + if stop.pos ~= "" then + new_stop.pos = stop.pos + end + if stop.track ~= "" then + new_stop.track = stop.track + end + table.insert(stops, new_stop) + if stop.stn ~= "" then + stop.label = color_green.."= "..assert(stations[stop.stn].name) + end + end + end + end + if errcount > 0 then + return false, errcount.." chyb v seznamu zastávek!" + end + if finalcount == 0 then + return false, "Varianta linky musí obsahovat alespoň jednu koncovou zastávku!" + end + table.sort(stops, function(a, b) return a.dep < b.dep end) + custom_state.compiled_linevar = { + name = line.."/"..stops[1].stn.."/"..rc, + line = line, + owner = owner, + stops = stops, + } + if train_name ~= "" then + custom_state.compiled_linevar.train_name = train_name + end + if custom_state.disable_linevar == "true" then + custom_state.compiled_linevar.disabled = true + end + return true, nil +end + +local function formspec_callback(custom_state, player, formname, fields) + local reload_stations, update_formspec = false, false + -- print("DEBUG: "..dump2({custom_state = custom_state, formname = formname, fields = fields})) + + if fields.quit then + return + end + -- scrollbar: + if fields.evl_scroll then + local event = core.explode_scrollbar_event(fields.evl_scroll) + if event.type == "CHG" then + custom_state.evl_scroll = event.value + end + end + -- checkbox: + if fields.disable_linevar then + custom_state.disable_linevar = fields.disable_linevar + end + -- dropdowns: + for i = 1, max_stations do + local id = string.format("s%02d_mode", i) + local n = tonumber(fields[id]) + if n ~= nil then + custom_state.stops[i].mode = n + end + end + -- fields: + for _, key in ipairs({"line", "rc", "train_name", "owner"}) do + if fields[key] then + custom_state[key] = fields[key] + end + end + for i, stop in ipairs(custom_state.stops) do + local prefix = string.format("s%02d_", i) + for _, key in ipairs({"dep", "stn", "track", "pos"}) do + local value = fields[prefix..key] + if value then + stop[key] = value + end + end + end + -- selection: + if fields.linevar then + local event = core.explode_table_event(fields.linevar) + if event.type == "CHG" or event.type == "DCL" then + custom_state_set_selection_index(custom_state, assert(tonumber(event.row))) + update_formspec = true + end + end + + -- buttons: + if fields.create then + custom_state_set_selection_index(custom_state, 1) + update_formspec = true + elseif fields.reset then + custom_state_set_selection_index(custom_state, custom_state.selection_index or 1) + update_formspec = true + elseif fields.save then + local pinfo = ch_core.normalize_player(player) + if pinfo.role == "new" or pinfo.role == "none" then + core.log("error", "Access violation in line editor caused by '"..pinfo.player_name.."'!") + return -- access violation! + end + if custom_state.compiled_linevar == nil then + -- zkontrolovat a skompilovat + local success, errmsg = custom_state_compile_linevar(custom_state) + if success then + -- TODO: zkontrolovat práva a možnost přepsání i zde! + ch_core.systemovy_kanal(pinfo.player_name, "Úspěšně ověřeno. Varianta linky může být uložena.") + else + ch_core.systemovy_kanal(pinfo.player_name, "Ověření selhalo: "..(errmsg or "Neznámý důvod")) + end + update_formspec = true + else + -- pokusit se uložit... + local selection_index = custom_state.selection_index or 1 + local selected_linevar, selected_linevar_def, selected_linevar_station + local to_linevar, to_linevar_def, to_linevar_station + local new_linevar, new_linevar_def, new_linevar_station + + -- NEW: + new_linevar_def = custom_state.compiled_linevar + new_linevar = new_linevar_def.name + new_linevar_station = get_stn_from_linevar(new_linevar) + + -- SELECTED: + if custom_state.selection_index > 1 and custom_state.linevars[selection_index - 1] ~= nil then + selected_linevar_def, selected_linevar_station = try_get_linevar_def(custom_state.linevars[selection_index - 1].name) + if selected_linevar_def ~= nil then + selected_linevar = selected_linevar_def.name + end + end + + -- TO OVERWRITE: + to_linevar_def, to_linevar_station = try_get_linevar_def(new_linevar) + if to_linevar_def ~= nil then + to_linevar = to_linevar_def.name + end + + local success, errmsg + if selected_linevar == nil then + if to_linevar == nil then + -- zcela nová varianta + success, errmsg = add_linevar(new_linevar_station, new_linevar_def) + else + -- replace + success = check_rights(pinfo, to_linevar_def.owner) + if success then + success, errmsg = replace_linevar(new_linevar_station, new_linevar_def) + else + errmsg = "Nedostatečná práva k variantě linky '"..to_linevar.."'." + end + end + elseif to_linevar == nil then + -- delete and add + success = check_rights(pinfo, selected_linevar_def.owner) + if success then + success, errmsg = delete_linevar(selected_linevar_station, selected_linevar) + if success then + success, errmsg = add_linevar(new_linevar_station, new_linevar_def) + end + else + errmsg = "Nedostatečná práva k variantě linky '"..selected_linevar.."'." + end + elseif selected_linevar ~= to_linevar then + -- delete and replace + success = check_rights(pinfo, to_linevar_def.owner) + if success then + success = check_rights(pinfo, selected_linevar_def.owner) + if success then + success, errmsg = delete_linevar(selected_linevar_station, selected_linevar) + if success then + success, errmsg = replace_linevar(new_linevar_station, new_linevar_def) + end + else + errmsg = "Nedostatečná práva k variantě linky '"..selected_linevar.."'." + end + else + errmsg = "Nedostatečná práva k variantě linky '"..to_linevar.."'." + end + else + -- replace + success = check_rights(pinfo, to_linevar_def.owner) + if success then + success, errmsg = replace_linevar(new_linevar_station, new_linevar_def) + else + errmsg = "Nedostatečná práva k variantě linky '"..to_linevar.."'." + end + end + + if success then + ch_core.systemovy_kanal(pinfo.player_name, "Varianta linky '"..new_linevar.."' úspěšně uložena.") + custom_state_refresh_linevars(custom_state, new_linevar) + update_formspec = true + else + ch_core.systemovy_kanal(pinfo.player_name, "Ukládání selhalo: "..(errmsg or "Neznámá chyba.")) + end + end + + elseif fields.delete then + local pinfo = ch_core.normalize_player(player) + if pinfo.role == "new" or pinfo.role == "none" then + core.log("error", "Access violation in line editor caused by '"..pinfo.player_name.."'!") + return -- access violation! + end + local selection_index = custom_state.selection_index or 1 + local selected_linevar, selected_linevar_def, selected_linevar_station + if custom_state.selection_index > 1 and custom_state.linevars[selection_index - 1] ~= nil then + selected_linevar_def, selected_linevar_station = try_get_linevar_def(custom_state.linevars[selection_index - 1].name) + if selected_linevar_def ~= nil then + selected_linevar = selected_linevar_def.name + end + local success, errmsg + success = check_rights(pinfo, selected_linevar_def.owner) + if success then + success, errmsg = delete_linevar(selected_linevar_station, selected_linevar) + else + errmsg = "Nedostatečná práva k variantě linky '"..selected_linevar.."'." + end + if success then + ch_core.systemovy_kanal(pinfo.player_name, "Varianta linky '"..selected_linevar.."' úspěšně smazána.") + custom_state_refresh_linevars(custom_state) + update_formspec = true + else + ch_core.systemovy_kanal(pinfo.player_name, "Mazání selhalo: "..(errmsg or "Neznámá chyba.")) + end + end + end + + if update_formspec then + return get_formspec(custom_state) + end +end + +local function show_formspec(player) + if player == nil then return false end + local custom_state = { + player_name = assert(player:get_player_name()), + evl_scroll = 0, + } + custom_state_refresh_linevars(custom_state) + custom_state_set_selection_index(custom_state, 1) + ch_core.show_formspec(player, "advtrains_line_automation:editor_linek", get_formspec(custom_state), formspec_callback, custom_state, {}) +end + +def = { + -- params = "", + description = "Otevře editor variant linek", + privs = {ch_registered_player = true}, + func = function(player_name, param) show_formspec(minetest.get_player_by_name(player_name)) end, +} +core.register_chatcommand("linky", def) diff --git a/advtrains_line_automation/line_functions.lua b/advtrains_line_automation/line_functions.lua new file mode 100644 index 0000000..7e050be --- /dev/null +++ b/advtrains_line_automation/line_functions.lua @@ -0,0 +1,747 @@ +local al = advtrains.lines +local rwt = assert(advtrains.lines.rwt) + +local MODE_NORMAL = 0 -- normální zastávka (výchozí nebo mezilehlá) +local MODE_REQUEST_STOP = 1 -- zastávka na znamení (mezilehlá) +local MODE_HIDDEN = 2 -- skrytá zastávka (výchozí nebo mezilehlá) +local MODE_DISABLED = 3 -- vypnutá zastávka (mezilehlá nebo koncová) +local MODE_FINAL = 4 -- koncová zastávka (linka zde jízdu končí) +local MODE_FINAL_HIDDEN = 5 -- koncová zastávka skrytá +local MODE_FINAL_CONTINUE = 6 -- koncová zastávka (vlak pokračuje jako jiná linka) + +local simple_modes = { + [MODE_NORMAL] = MODE_NORMAL, + [MODE_REQUEST_STOP] = MODE_NORMAL, + [MODE_HIDDEN] = MODE_NORMAL, + [MODE_DISABLED] = MODE_DISABLED, + [MODE_FINAL] = MODE_FINAL, + [MODE_FINAL_HIDDEN] = MODE_FINAL, + [MODE_FINAL_CONTINUE] = MODE_FINAL, +} + +local debug_print_i = 0 + +-- LOCAL funkce: +-- ========================================================================= +local function debug_print(s) + debug_print_i = debug_print_i + 1 + core.chat_send_all("["..debug_print_i.."] "..tostring(s)) + return s +end + +-- Je-li stn kód stanice, vrací její jméno, jinak vrací "???". stn může být i nil. +local function get_station_name(stn) + local station = advtrains.lines.stations[stn or ""] + if station ~= nil and station.name ~= nil then + return station.name + else + return "???" + end +end + +--[[ + -- Vrací index následujícího výskytu 'stn' v seznamu zastávek podle linevar_def. + -- Vrací i skryté zastávky, ale ne vypnuté. + -- Je-li linevar_def == nil nebo stn == nil nebo stn == "", vrací nil. + - line_status = table + - linevar_def = table or nil + - stn = string + return: int or nil +]] +local function find_next_index(line_status, linevar_def, stn) + assert(line_status) + if linevar_def ~= nil and stn ~= nil and stn ~= "" then + local stops = linevar_def.stops + if line_status.linevar_index < #stops then + for i = line_status.linevar_index + 1, #stops do + local stop = stops[i] + if stop.stn == stn and (stop.mode or MODE_NORMAL) ~= MODE_DISABLED then + return i + end + end + end + end +end + +--[[ + Používá se na *nelinkový* vlak stojící na *neanonymní* zastávce. + Zahájí jízdu na lince nalezené podle kombinace line/stn/rc, nebo vrátí false. +]] +local function line_start(train, stn, departure_rwtime) + assert(train) + assert(stn and stn ~= "") + assert(departure_rwtime) + local ls = al.get_line_status(train) + if ls.linevar ~= nil then + error("line_start() used on a train that is already on a line: "..dump2({train = train})) + end + local linevar, linevar_def = al.try_get_linevar(train.line, stn, train.routingcode) + if linevar == nil or linevar_def.disabled then + -- print("DEBUG: line_start() failed for "..(train.line or "").."/"..stn.."/"..(train.routingcode or "")) + return false + end + ls.linevar = linevar + ls.linevar_station = stn + ls.linevar_index = 1 + ls.linevar_dep = departure_rwtime + ls.linevar_last_dep = departure_rwtime + ls.linevar_last_stn = stn + train.text_outside = al.get_line_description(linevar_def, {line_number = true, last_stop = true, last_stop_prefix = "", train_name = true}) + -- print("DEBUG: line_start(): "..dump2({train_id = train.id, line_status = ls})) + return true +end + +local function should_stop(pos, stdata, train) + if stdata == nil or stdata.stn == nil then + -- print("DEBUG: should_stop() == false, because stdata is invalid!") + return false -- neplatná data + end + local n_trainparts = #assert(train.trainparts) + -- vyhovuje počet vagonů? + if not ((stdata.minparts or 0) <= n_trainparts and n_trainparts <= (stdata.maxparts or 128)) then + -- print("DEBUG: should_stop("..stdata.stn..") == false, because n_trainparts is not in interval") + return false + end + local stn = assert(stdata.stn) -- zastávka stále může být anonymní + local ls, linevar_def = al.get_line_status(train) + local next_index + if stn ~= "" then + next_index = find_next_index(ls, linevar_def, stn) + end + -- jde o linkový vlak? + if linevar_def ~= nil then + if next_index == nil then + -- print("DEBUG: should_stop("..stn..") == false, because linevar=='"..ls.linevar.."' and next_index == nil") + return false + end + local stop = assert(linevar_def.stops[next_index]) + if stop.pos ~= nil and stop.pos ~= string.format("%d,%d,%d", pos.x, pos.y, pos.z) then + -- print("DEBUG: should_stop("..stn..") == false, because the stop is limited to position "..stop.pos) + return false + end + if stop.mode ~= nil and stop.mode == MODE_REQUEST_STOP then + -- TODO... + ---debug_print("Vlak "..train.id.." by měl zastavit na zastávce na znamení.") + end + -- print("DEBUG: should_stop("..stn..") == true for "..linevar_def.name) + return true + -- local result = next_index ~= nil -- zastávka má index => zastavit + --[[ + if result then + print("DEBUG: should_stop() == true, because linevar=='"..ls.linevar.."' and get_line_status() retuned next_index == "..next_index) + else + print("DEBUG: should_stop() == false, because linevar=='"..ls.linevar.."' and get_line_status() retuned next_index == nil") + end + ]] + -- return result + -- TODO: zastávky na znamení + else + local ars = stdata.ars + -- vyhovuje vlak ARS pravidlům? + local result = ars and (ars.default or advtrains.interlocking.ars_check_rule_match(ars, train)) + --[[ + if result then + print("DEBUG: should_stop("..stn..") == true, because linevar==nil and ARS rules match") + else + print("DEBUG: should_stop("..stn..") == false, because linevar==nil and ARS rules don't match") + end ]] + return result + end +end + +local function update_value(train, key, setting) + if setting == "-" then + train[key] = nil + elseif setting ~= "" then + train[key] = setting + end + return train[key] +end + +--[[ + Pokud vlak jede na lince, odebere ho z této linky a vrátí true; jinak vrátí false. +]] +function al.cancel_linevar(train) + local ls = train.line_status + if ls == nil or ls.linevar == nil then return false end + -- print("DEBUG: line_end(train_id = "..train.id..", linevar was: "..ls.linevar.."): "..dump2({line_status_old = ls})) + ls.linevar = nil + ls.linevar_station = nil + ls.linevar_index = nil + ls.linevar_dep = nil + ls.linevar_last_dep = nil + ls.linevar_last_stn = nil + train.text_outside = "" + return true +end + +-- Vrací seznam názvů neskrytých zastávek, počínaje od uvedeného indexu. +-- Pokud by vrátila prázdný seznam, vrátí místo něj {"???"}. +function al.get_stop_names(linevar_def, start_index) + local stations = advtrains.lines.stations + local result = {} + if linevar_def ~= nil then + local stops = linevar_def.stops + if start_index == nil then + start_index = 1 + end + for i = start_index, #stops do + local stop = stops[i] + if stop.mode == nil or (stop.mode ~= MODE_HIDDEN and stop.mode ~= MODE_FINAL_HIDDEN and stop.mode ~= MODE_DISABLED) then + local station = stations[stop.stn] + if station ~= nil and station.name ~= nil and station.name ~= "" then + table.insert(result, station.name) + else + table.insert(result, "???") + end + end + end + end + if result[1] == nil then + result[1] = "???" + end + return result +end + +--[[ + Vrací: + a) index, stop_data -- pokud byla vyhovující další zastávka nalezena + b) nil, nil -- pokud nalezena nebyla +]] +function al.get_next_stop(linevar_def, current_index, allow_hidden_stops) + local stops = assert(linevar_def.stops) + if current_index < #stops then + for i = current_index + 1, #stops do + local mode = stops[i].mode + if mode == nil or (mode ~= MODE_DISABLED and ((mode ~= MODE_HIDDEN and mode ~= MODE_FINAL_HIDDEN) or allow_hidden_stops)) then + return i, stops[i] + end + end + end + return nil, nil +end + +--[[ + Vrací: + a) index, stop_data -- pokud byla vyhovující koncová zastávka nalezena + b) nil, nil -- pokud nalezena nebyla +]] +function al.get_terminus(linevar_def, current_index, allow_hidden_stops) + local stops = assert(linevar_def.stops) + if current_index < #stops then + for i = current_index + 1, #stops do + local mode = stops[i].mode + if mode ~= nil and (mode == MODE_FINAL or mode == MODE_FINAL_CONTINUE or (mode == MODE_FINAL_HIDDEN and allow_hidden_stops)) then + return i, stops[i] + end + end + end + return nil, nil +end + +--[[ + options = { + line_number = bool or nil, -- zahrnout do popisu číslo linky? nil => false + first_stop = bool or nil, -- zahrnout do popisu název výchozí zastávky? nil => false + last_stop = bool or nil, -- zahrnout do popisu název cílové zastávky? nil => true + last_stop_prefix = string or nil, -- text před název cílové zastávky; nil => "⇒ " + train_name = bool or nil, -- zahrnout do popisu jméno vlaku, je-li k dispozici; nil => false + train_name_prefix = string or nil, -- text před jméno vlaku; nil => "\n" + } +]] +function al.get_line_description(linevar_def, options) + local line, stn = al.linevar_decompose(linevar_def.name) + if line == nil then + return "???" + end + if options == nil then options = {} end + local s1, s2, s3, s4 + if options.line_number then + s1 = "["..line.."] " + else + s1 = "" + end + if options.first_stop then + s2 = get_station_name(stn).." " + else + s2 = "" + end + if options.last_stop == nil or options.last_stop then + s3 = "???" + local terminus_index, terminus_data = al.get_terminus(linevar_def, 1, false) + if terminus_index ~= nil then + s3 = get_station_name(terminus_data.stn) + end + s3 = (options.last_stop_prefix or "⇒ ")..s3 + else + s3 = "" + end + if options.train_name and linevar_def.train_name ~= nil then + s4 = (options.train_name_prefix or "\n")..linevar_def.train_name + else + s4 = "" + end + return s1..s2..s3..s4 +end + +function al.get_stop_description(stop_data, next_stop_data) + local s1, s2 = "", "" + if stop_data ~= nil then + local mode = stop_data.mode or MODE_NORMAL + if mode ~= MODE_DISABLED and mode ~= MODE_HIDDEN and mode ~= MODE_FINAL_HIDDEN then + s1 = get_station_name(stop_data.stn) + if mode == MODE_FINAL or mode == MODE_FINAL_CONTINUE then + s2 = "Koncová zastávka" + end + end + end + if next_stop_data ~= nil then + local mode = next_stop_data.mode or MODE_NORMAL + if mode ~= MODE_DISABLED and mode ~= MODE_HIDDEN and mode ~= MODE_FINAL_HIDDEN then + s2 = "Příští zastávka/stanice: "..get_station_name(next_stop_data.stn) + if mode == MODE_REQUEST_STOP then + s2 = s2.." (na znamení)" + end + end + end + if s1 ~= "" and s2 ~= "" then + return s1.."\n"..s2 + else + return s1..s2 + end +end + +--[[ + Není-li zpoždění k dispozici, vrací {text = ""}, jinak vrací: + { + has_delay = bool, + delay = int, + text = string, + } +]] +function al.get_delay_description(line_status, linevar_def) + assert(line_status) + if linevar_def == nil then + return {text = ""} + end + local expected_departure = line_status.linevar_dep + assert(linevar_def.stops[line_status.linevar_index]).dep + local real_departure = line_status.linevar_last_dep + local delay = real_departure - expected_departure + if delay < -1 or delay > 10 then + return { + has_delay = true, + delay = delay, + text = "zpoždění "..delay.." sekund", + } + else + return { + has_delay = false, + delay = delay, + text = "bez zpoždění", + } + end +end + +-- Pokud zadaná varianta linky existuje, vrátí: +-- linevar_def, linevar_station +-- Jinak vrací: +-- nil, nil +-- Je-li linevar_station == nil, doplní se z linevar. Je-li linevar == nil, vrátí nil, nil. +function al.try_get_linevar_def(linevar, linevar_station) + if linevar == nil then + -- print("DEBUG: try_get_linevar_def() => nil, because linevar == nil") + return nil, nil + end + if linevar_station == nil then + local line + line, linevar_station = al.linevar_decompose(linevar) + if line == nil then + return nil, nil + end + end + local t = advtrains.lines.stations[linevar_station] + if t ~= nil then + t = t.linevars + if t ~= nil then + return t[linevar], linevar_station + -- else + -- print("DEBUG: no linevars for linevar_station '"..linevar_station.."'") + end + -- else + -- print("DEBUG: no data for linevar_station '"..linevar_station.."'") + end + return nil, nil +end + +--[[ + Vrací: + a) nil, nil, pokud daná kombinace line/stn/rc nemá definovanou variantu linky + b) linevar, linevar_def, pokud má +]] +function al.try_get_linevar(line, stn, rc) + if line ~= nil and line ~= "" and stn ~= nil and stn ~= "" then + local linevar = line.."/"..stn.."/"..(rc or "") + local result = al.try_get_linevar_def(linevar, stn) + if result ~= nil then + -- print("DEBUG: linevar combination found: "..dump2({linevar = linevar, line = line, stn = stn, rc = rc or "", linevar_def = result})) + return linevar, result + end + end + -- print("DEBUG: linevar combination not found for "..(line or "").."/"..(stn or "").."/"..(rc or "")) + return nil, nil +end + +--[[ + Vrací 2 hodnoty: + - line_status = table -- tabulka train.line_status (může být prázdná) + - linevar_def = table or nil -- jde-li o linkový vlak, vrací definici linevar, jinak nil + Parametry: + - train = table (train) +]] +function al.get_line_status(train) + assert(train) + if train.line_status == nil then + train.line_status = {} + -- print("DEBUG: new empty line_status created for train "..train.id) + return train.line_status, nil + end + local ls = train.line_status + if ls.linevar == nil then + -- nelinkový vlak + return ls, nil + end + local rwtime = rwt.to_secs(rwt.get_time()) + if rwtime - ls.linevar_dep >= 86400 then + core.log("warning", "Train "..train.id.." put out of linevar '"..ls.linevar.."', because it was riding more then 24 hours.") + al.cancel_linevar(train) + return ls, nil + end + local linevar_def = al.try_get_linevar_def(ls.linevar, ls.linevar_station) + if linevar_def == nil then + core.log("warning", "Train "..train.id.." was riding a non-existent (undefined) line '"..tostring(ls.linevar).."'!") + al.cancel_linevar(train) + elseif linevar_def.stops[ls.linevar_index] == nil then + core.log("warning", "Train "..train.id.." put out of linevar '"..ls.linevar.."', because its index "..ls.linevar_index.." became invalid.") + al.cancel_linevar(train) + linevar_def = nil + else + local train_line_prefix = (train.line or "").."/" + if train_line_prefix ~= ls.linevar:sub(1, train_line_prefix:len()) then + core.log("warning", "Train "..train.id.." put out of linevar '"..ls.linevar.."', because its line changed to '"..tostring(train.line).."'.") + al.cancel_linevar(train) + linevar_def = nil + else + -- [DEBUG:] TEMPORARY: + if linevar_def.line == nil then + linevar_def.line = assert(train.line) + end + end + end + return ls, linevar_def +end + +function al.on_train_approach(pos, train_id, train, index, has_entered) + if has_entered then return end -- do not stop again! + if train.path_cn[index] ~= 1 then return end -- špatný směr + local pe = advtrains.encode_pos(pos) + local stdata = advtrains.lines.stops[pe] + if should_stop(pos, stdata, train) then + -- print("DEBUG: on_train_approach(): will stop at station '"..stdata.stn.."'") + advtrains.lzb_add_checkpoint(train, index, 2, nil) + if train.line_status.linevar == nil then + -- nelinkový vlak: + local stn = advtrains.lines.stations[stdata.stn] + local stnname = stn and stn.name or attrans("Unknown Station") + train.text_inside = attrans("Next Stop:") .. "\n"..stnname + end + advtrains.interlocking.ars_set_disable(train, true) + end +end + +function al.on_train_enter(pos, train_id, train, index) + if train.path_cn[index] ~= 1 then return end -- špatný směr + local pe = advtrains.encode_pos(pos) + local stdata = advtrains.lines.stops[pe] + local stn = stdata.stn or "" + local rwtime = assert(rwt.to_secs(rwt.get_time())) + local ls, linevar_def = al.get_line_status(train) + local next_index + if stn ~= "" then + if linevar_def ~= nil then + next_index = find_next_index(ls, linevar_def, stn) + end + ls.last_enter = {stn = stn, encpos = pe, rwtime = rwtime} + debug_print("Vlak "..train_id.." zaznamenán: "..stn.." "..core.pos_to_string(pos).." @ "..rwtime) + end + if not should_stop(pos, stdata, train) then + -- průjezd + debug_print("Vlak "..train_id.." projel zastávkou "..stn) + return + end + + -- naplánovat čas odjezdu + local wait = tonumber(stdata.wait) or 0 + local interval = stdata.interval + local last_dep = stdata.last_dep -- posl. odjezd z této zastávkové koleje + + if interval ~= nil and last_dep ~= nil then + if last_dep > rwtime then + last_dep = rwtime + end + local ioffset = stdata.ioffset or 0 + local normal_dep = rwtime + wait + local next_dep = last_dep + (interval - (last_dep + (interval - ioffset)) % interval) + if normal_dep < next_dep then + core.log("action", "[INFO] The train "..train_id.." will wait for "..(next_dep - normal_dep).." additional seconds due to interval at "..core.pos_to_string(pos)..".") + wait = wait + (next_dep - normal_dep) + -- else -- will wait normal time + end + end + local planned_departure = rwtime + wait + debug_print("Vlak "..train_id.." zastavil na "..stn.." a odjede za "..wait.." sekund ("..planned_departure..").") + -- print("DEBUG: planned departure: "..planned_departure.." = "..rwtime.." + "..wait) + stdata.last_dep = planned_departure -- naplánovaný čas odjezdu + ls.standing_at = pe + ls.stop_request = nil + -- print("DEBUG: standing ls = "..dump2(ls)) + + local can_start_line + local had_linevar = linevar_def ~= nil + if linevar_def == nil then + -- nelinkový vlak + can_start_line = true + -- print("DEBUG: train "..train_id.." is non-line train, can start a new line") + elseif next_index ~= nil then + -- linkový vlak zastavil na své řádné zastávce + assert(stn ~= "") -- dopravna musí mít kód + local stop_def = assert(linevar_def.stops[next_index]) + debug_print("Vlak "..train_id.." je linkový vlak ("..ls.linevar..") a zastavil na své pravidelné zastávce "..stn.." (index = "..next_index..")") + -- print("DEBUG: train "..train_id.." stopped at regular stop '"..stn.."': "..dump2({stop_def = stop_def})) + + -- zaznamenat přeskočené zastávky: + if next_index ~= ls.linevar_index + 1 then + local skipped_stops = {} + for i = ls.linevar_index + 1, next_index - 1 do + local mode = linevar_def.stops[i].mode or MODE_NORMAL + if mode ~= MODE_DISABLED then + table.insert(skipped_stops, linevar_def.stops[i].stn) + end + end + if #skipped_stops > 0 then + core.log("warning", "Train "..train_id.." of line '"..linevar_def.name.."' skipped "..#skipped_stops.." stops: "..table.concat(skipped_stops, ", ")) + end + end + + local stop_smode = simple_modes[stop_def.mode or 0] + if stop_smode == MODE_NORMAL then + -- mezilehlá zastávka + can_start_line = false + ls.linevar_index = next_index + ls.linevar_last_dep = planned_departure + ls.linevar_last_stn = stn + -- print("DEBUG: "..dump2({line_status = ls})) + debug_print("Jde o mezilehlou zastávku.") + local next_stop_index, next_stop_data = al.get_next_stop(linevar_def, next_index) + train.text_inside = al.get_stop_description(linevar_def.stops[next_index], linevar_def.stops[next_stop_index or 0]) + else + -- koncová zastávka + can_start_line = stop_def.mode == MODE_FINAL_CONTINUE + debug_print("Vlak "..train_id.." skončil jízdu na lince "..ls.linevar..", může pokračovat na jinou linku: "..(can_start_line and "ihned" or "na příští zastávce")) + train.text_inside = al.get_stop_description(linevar_def.stops[next_index]) + al.cancel_linevar(train) + end + else + -- linkový vlak zastavil na neznámé zastávce (nemělo by nastat) + core.log("warning", "Train "..train_id.." of linevar '"..ls.linevar.."' stopped at unknown station '"..stn.."' at "..core.pos_to_string(pos).."!") + debug_print("Vlak "..train_id.." je linkový vlak ("..ls.linevar.."), ale zastavil na sobě neznámé zastávce "..stn..", což by se nemělo stát.") + can_start_line = false + end + -- print("DEBUG: "..dump2({can_start_line = can_start_line})) + + -- ATC příkaz + local atc_command = "B0 W O"..stdata.doors..(stdata.kick and "K" or "").." D"..wait.. + (stdata.keepopen and " " or " OC ")..(stdata.reverse and "R" or "").." D"..(stdata.ddelay or 1).. + " A1 S" ..(stdata.speed or "M") + advtrains.atc.train_set_command(train, atc_command, true) + + -- provést změny vlaku + local new_line = stdata.line or "" + local new_routingcode = stdata.routingcode or "" + update_value(train, "line", new_line) + update_value(train, "routingcode", new_routingcode) + + -- začít novou linku? + if can_start_line and line_start(train, stn, planned_departure) then + debug_print("Vlak "..train_id.." zahájil jízdu na nové lince ("..ls.linevar..") ze stanice "..stn..".") + core.log("action", "Train "..train_id.." started a route with linevar '"..ls.linevar.."' at station '"..stn.."'.") + train.text_inside = get_station_name(stn) + assert(ls.linevar) + linevar_def = assert(al.try_get_linevar_def(ls.linevar)) + local next_stop_index, next_stop_data = al.get_next_stop(linevar_def, 1) + if next_stop_index ~= nil then + train.text_inside = train.text_inside.."\nPříští zastávka/stanice: "..get_station_name(next_stop_data.stn) + if next_stop_data.mode ~= nil and next_stop_data.mode == MODE_REQUEST_STOP then + train.text_inside = train.text_inside.." (na znamení)" + end + end + elseif not had_linevar and ls.linevar == nil then + -- vlak, který nebyl a stále není linkový: + train.text_inside = get_station_name(stn) + -- core.after(wait, function() train.text_inside = "" end) + end + -- print("DEBUG: the train will wait for "..wait.." seconds") +end + +function al.on_train_leave(pos, train_id, train, index) + local pe = advtrains.encode_pos(pos) + local stdata = advtrains.lines.stops[pe] + if stdata == nil then + return -- neplatná zastávka + end + local stn = stdata.stn or "" + local ls, linevar_def = al.get_line_status(train) + local rwtime = assert(rwt.to_secs(rwt.get_time())) + + if stn ~= "" then + ls.last_leave = {stn = stn, encpos = pe, rwtime = rwtime} + debug_print("Vlak "..train_id.." zaznamenán na odjezdu: "..stn.." "..core.pos_to_string(pos).." @ "..rwtime) + end + + -- print("DEBUG: *on_train_leave("..train_id..") from "..(stdata and stdata.stn or "nil")) + if ls.standing_at == pe then + -- vlak stál v této dopravně + ls.standing_at = nil + ls.stop_request = nil + if stn ~= "" then + -- print("DEBUG: on_train_leave from non-anonymous stop") + debug_print("Vlak "..train_id.." odjel ze zastávky "..stn) + if ls.linevar_last_dep ~= nil and ls.linevar_last_dep > rwtime then + debug_print("Vlak "..train_id.." předčasně odjel z dopravny "..stn.." (zbývalo "..(ls.linevar_last_dep - rwtime).." sekund)") + ls.linevar_last_dep = rwtime -- předčasný odjezd + if ls.linevar_index == 1 then + ls.linevar_dep = rwtime + end + end + else + -- print("DEBUG: on_train_leave from anonymous stop") + debug_print("Vlak "..train_id.." odjel z anonymní zastávky "..core.pos_to_string(pos)..".") + end + train.text_inside = "" + if linevar_def ~= nil then + -- linkový vlak: + local next_stop_index, next_stop_data = al.get_next_stop(linevar_def, ls.linevar_index) + if next_stop_index ~= nil then + train.text_inside = al.get_stop_description(nil, next_stop_data) + end + end + -- else + --[[ průjezd? + local stn = (stdata and stdata.stn) or "" + if stn ~= "" then + debug_print("DEBUG: Vlak "..train_id.." projel (odjezd) zastávkou "..stn..".") + else + debug_print("DEBUG: Vlak "..train_id.." projel (odjezd) anonymní zastávkou "..core.pos_to_string(pos)..".") + end + ]] + end +end + +function al.linevar_decompose(linevar) + if linevar == nil then + return nil, nil, nil + end + local parts = string.split(linevar, "/", true, 3) + return parts[1], parts[2] or "", parts[3] or "" +end + +--[[ DEBUG: +local debug_print = {} +function debug_print.print() + print(".") + core.after(1, debug_print.print) +end +debug_print.print() +]] + +--[[ +function al.rwt_to_cas() + return +end +]] + +local function get_last_pos(line_status) + assert(line_status) + local last_enter, last_leave, standing_at = line_status.last_enter, line_status.last_leave, line_status.standing_at + local last_pos, last_pos_type + if last_enter ~= nil then + last_pos, last_pos_type = last_enter, "enter" + if standing_at ~= nil and standing_at == last_enter.encpos then + last_pos_type = "standing" + elseif last_leave ~= nil and last_leave.rwtime > last_enter.rwtime then + last_pos, last_pos_type = last_leave, "leave" + end + elseif last_leave ~= nil then + last_pos, last_pos_type = last_leave, "leave" + else + result.type = "none" + return {type = "none"} + end + local result = {type = last_pos_type, last_pos = last_pos} + if last_enter ~= nil then + result.last_enter = last_enter + end + if last_leave ~= nil then + result.last_leave = last_leave + end + return result +end + +local function get_train_position(line_status, linevar_def, rwtime) + if line_status ~= nil then + local last_pos_info = get_last_pos(line_status) + print("DEBUG: last_pos_info = "..dump2({last_pos_info})) + local last_pos = last_pos_info.last_pos + if last_pos ~= nil then + local result = "„"..get_station_name(last_pos.stn).."“" + local delay_info = al.get_delay_description(line_status, linevar_def) + if last_pos_info.type ~= "standing" then + result = result.." (před "..(rwtime - last_pos.rwtime).." sekundami)" + end + if delay_info.has_delay ~= nil then + result = result.." ("..delay_info.text..")" + end + return result + end + end + return "???" +end + +-- příkaz /vlaky +local def = { + params = "[linka]", + description = "Vypíše všechny linkové vlaky na zadané lince (resp. na všech linkách)", + privs = {}, + func = function(player_name, param) + local result = {} + if not param:match("/") then + local rwtime = rwt.to_secs(rwt.get_time()) + for train_id, train in pairs(advtrains.trains) do + local ls, linevar_def = al.get_line_status(train) + if linevar_def ~= nil and (param == "" or ls.linevar:sub(1, #param + 1) == param.."/") then + local direction_index, direction_stop = al.get_terminus(linevar_def, ls.linevar_index, false) + local direction = "???" + if direction_index ~= nil then + direction = get_station_name(direction_stop.stn) + end + local s = "("..train_id..") ["..linevar_def.line.."] směr „"..direction.."“, poloha: "..get_train_position(ls, linevar_def, rwtime) + table.insert(result, s) + end + end + end + if #result == 0 then + return false, "Nenalezen žádný odpovídající vlak." + end + return true, "Nalezeno "..#result.." vlaků:\n- "..table.concat(result, "\n- ") + end, +} +core.register_chatcommand("vlaky", def) diff --git a/advtrains_line_automation/stoprail.lua b/advtrains_line_automation/stoprail.lua index abc3488..c63e5a9 100644 --- a/advtrains_line_automation/stoprail.lua +++ b/advtrains_line_automation/stoprail.lua @@ -1,6 +1,8 @@ -- stoprail.lua -- adds "stop rail". Recognized by lzb. (part of behavior is implemented there) +local rwt = assert(advtrains.lines.rwt) + local function to_int(n) --- Disallow floating-point numbers local k = tonumber(n) @@ -171,7 +173,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.ioffsetnow and fields.interval ~= "" and fields.interval ~= "0" then local interval = to_int(fields.interval) if 0 < interval and interval <= 3600 then - local rwt = assert(advtrains.lines.rwt) local rwtime = rwt.to_secs(rwt.get_time()) set_offset = rwtime % interval end @@ -283,13 +284,6 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end) -local function should_stop(stdata, train) - local n_trainparts = #assert(train.trainparts) - local ars = stdata.ars - return ars and (stdata.minparts or 0) <= n_trainparts and n_trainparts <= (stdata.maxparts or 128) and - (ars.default or advtrains.interlocking.ars_check_rule_match(ars, train)) -end - local adefunc = function(def, preset, suffix, rotation) return { after_place_node=function(pos) @@ -313,85 +307,9 @@ local adefunc = function(def, preset, suffix, rotation) show_stoprailform(pos, player) end, advtrains = { - on_train_approach = function(pos,train_id, train, index, has_entered) - if has_entered then return end -- do not stop again! - if train.path_cn[index] == 1 then - local pe = advtrains.encode_pos(pos) - local stdata = advtrains.lines.stops[pe] - if stdata and stdata.stn then - --[[TODO REMOVE AFTER SOME TIME (only migration) - if not stdata.ars then - stdata.ars = {default=true} - end - ]] - if should_stop(stdata, train) then - advtrains.lzb_add_checkpoint(train, index, 2, nil) - local stn = advtrains.lines.stations[stdata.stn] - local stnname = stn and stn.name or attrans("Unknown Station") - train.text_inside = attrans("Next Stop:") .. "\n"..stnname - advtrains.interlocking.ars_set_disable(train, true) - end - end - end - end, - on_train_enter = function(pos, train_id, train, index) - if train.path_cn[index] == 1 then - local pe = advtrains.encode_pos(pos) - local stdata = advtrains.lines.stops[pe] - if not stdata then - return - end - - if should_stop(stdata, train) then - local stn = advtrains.lines.stations[stdata.stn] - local stnname = stn and stn.name or attrans("Unknown Station") - local line = stdata.line or "" - local routingcode = stdata.routingcode or "" - local wait = tonumber(stdata.wait) or 0 - local interval = stdata.interval - local ioffset = stdata.ioffset or 0 - local lastdep = stdata.lastdep - local rwt = advtrains.lines.rwt - local rwtime = assert(tonumber(rwt.to_secs(rwt.get_time()))) - - -- Interval - if lastdep ~= nil and interval ~= nil then - if lastdep > rwtime then - lastdep = rwtime - end - local normaldep = rwtime + wait - local nextdep = lastdep + (interval - (lastdep + (interval - ioffset)) % interval) - if normaldep < nextdep then - minetest.log("action", "[INFO] The train "..train_id.." will wait for "..(nextdep - normaldep).." additional seconds due to interval at "..core.pos_to_string(pos)..".") - wait = wait + (nextdep - normaldep) - -- else -- will wait normal time - end - end - stdata.lastdep = rwtime + wait - - -- Send ATC command and set text - local command = "B0 W O"..stdata.doors..(stdata.kick and "K" or "").." D"..wait.. - (stdata.keepopen and " " or " OC ")..(stdata.reverse and "R" or "").." D"..(stdata.ddelay or 1).. - " A1 S" ..(stdata.speed or "M") - -- print("DEBUG: setting command for train "..train_id.." at rwtime "..rwtime..": "..command) - advtrains.atc.train_set_command(train, command, true) - train.text_inside = stnname - if line == "-" then - train.line = nil - elseif line ~= "" then - train.line = line - end - if routingcode == "-" then - train.routingcode = nil - elseif routingcode ~= "" then - train.routingcode = routingcode - end - if tonumber(wait) then - minetest.after(tonumber(wait), function() train.text_inside = "" end) - end - end - end - end + on_train_approach = advtrains.lines.on_train_approach, + on_train_enter = advtrains.lines.on_train_enter, + on_train_leave = advtrains.lines.on_train_leave, }, } end diff --git a/advtrains_line_automation/structs.md b/advtrains_line_automation/structs.md new file mode 100644 index 0000000..a18d8cf --- /dev/null +++ b/advtrains_line_automation/structs.md @@ -0,0 +1,96 @@ +Datové struktury: + +train = { + line_status = { + -- záznam o posledním zastavení/průjezdu vlaku neanonymní zastávkovou kolejí + -- (používá se jako údaj o poloze vlaku) + last_enter = { + stn = string, -- kód dopravny + encpos = string, -- zakódovaná pozice koleje, kde došlo ke kontaktu + rwtime = int, -- železniční čas + } or nil, + + -- záznam o posledním odjezdu/průjezdu vlaku neanonymní zastávkovou kolejí + -- (používá se jako údaj o poloze vlaku) + last_leave = { + stn = string, -- kód dopravny + encpos = string, -- zakódovaná pozice koleje, kde došlo ke kontaktu + rwtime = int, -- železniční čas + } or nil, + + -- pokud vlak právě stojí na zastávkové koleji, obsahuje její zakódovanou pozici; + -- při odjezdu se vynuluje + standing_at = string or nil, + + -- nastaví se na 1 v případě, že "bylo dáno znamení", aby vlak zastavil + stop_request = 1 or nil, + + -- Následující pole jsou vyplněna jen u linkových vlaků: + -- =========================== + -- varianta linky LINE/STCODE/RC + linevar = string, + + -- prostřední díl z 'linevar' (kód stanice, kde jsou uložena data varianty linky) + linevar_station = string, + + -- skutečný železniční čas odjezdu z *výchozí* zastávky spoje + linevar_dep = int, + + -- index zastávky spoje (do pole 'stops'), kde vlak naposledy zastavil + linevar_index = int, + + -- skutečný železniční čas odjezdu z poslední zastávky spoje, kde vlak zastavil + linevar_last_dep = int, + + -- kód zastávky spoje, kde vlak naposledy zastavil + linevar_last_stn = string, + } +} + +station = { + linevars = { + [linevar] = { + -- linevar (LINE/STCODE/RC) + name = string, + + -- LINE (první část názvu) + line = string, + + -- přihlašovací jméno postavy, která linku spravuje + owner = string, + + -- jméno vlaku pro zobrazení (volitelné) + train_name = string or nil, + + -- je-li true, nové vlaky nemohou dostat tuto variantu přidělenu + disabled = bool or nil, + + -- seznam zastávek na lince, seřazený podle 'dep': + stops = { + { + -- kód dopravny, kde má vlak zastavit + stn = string, + + -- plánovaný čas odjezdu, relativně vůči odjezdu z výchozí zastávky (v sekundách) + dep = int, + + -- režim zastávky (podle konstant ve zdrojovém kódu) + -- nil odpovídá 0 (normální zastavení) + mode = int or nil, + + -- je-li vyplněna, vlak zastaví jen na koleji na uvedené pozici + pos = "X,Y,Z" or nil, + + -- orientační údaj pro cestující, na které koleji má vlak zastavit + track = string or nil, + }... + } + } + } +} + +stop = { + -- žel. čas posledního odjezdu jakéhokoliv zastavivšího vlaku z této zastávkové koleje; + -- používá se v kombinaci s intervalem + last_dep = int or nil, +} |