diff options
Diffstat (limited to 'advtrains_line_automation')
-rw-r--r-- | advtrains_line_automation/init.lua | 7 | ||||
-rw-r--r-- | advtrains_line_automation/line_editor.lua | 856 | ||||
-rw-r--r-- | advtrains_line_automation/line_functions.lua | 1377 | ||||
-rw-r--r-- | advtrains_line_automation/mod.conf | 4 | ||||
-rw-r--r-- | advtrains_line_automation/models/advtrains_tuber.obj | 150 | ||||
-rw-r--r-- | advtrains_line_automation/models/license.txt | 5 | ||||
-rw-r--r-- | advtrains_line_automation/railwaytime.lua | 15 | ||||
-rw-r--r-- | advtrains_line_automation/station_announcement.lua | 1579 | ||||
-rw-r--r-- | advtrains_line_automation/station_editor.lua | 1054 | ||||
-rw-r--r-- | advtrains_line_automation/stoprail.lua | 299 | ||||
-rw-r--r-- | advtrains_line_automation/structs.md | 190 | ||||
-rw-r--r-- | advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png | bin | 3306 -> 7559 bytes | |||
-rw-r--r-- | advtrains_line_automation/textures/advtrains_line_automation_jrad.png | bin | 0 -> 2516 bytes | |||
-rw-r--r-- | advtrains_line_automation/time_table.lua | 93 |
14 files changed, 5539 insertions, 90 deletions
diff --git a/advtrains_line_automation/init.lua b/advtrains_line_automation/init.lua index e7d0ea6..814dc27 100644 --- a/advtrains_line_automation/init.lua +++ b/advtrains_line_automation/init.lua @@ -19,10 +19,15 @@ 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.."stoprail.lua") +dofile(modpath.."station_editor.lua") +dofile(modpath.."stoprail.lua") +dofile(modpath.."station_announcement.lua") +dofile(modpath.."time_table.lua") function advtrains.lines.load(data) if data then diff --git a/advtrains_line_automation/line_editor.lua b/advtrains_line_automation/line_editor.lua new file mode 100644 index 0000000..33686d1 --- /dev/null +++ b/advtrains_line_automation/line_editor.lua @@ -0,0 +1,856 @@ +local def +local F = minetest.formspec_escape +local ifthenelse = function(cond, a, b) if cond then return a else return b end end + +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_last_passages = assert(advtrains.lines.get_last_passages) +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 show_last_passages_formspec -- forward declaration + +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 same_stops(stops1, stops2) + assert(stops1) + assert(stops2) + if #stops1 ~= #stops2 then + return false + end + for i, stop in ipairs(stops1) do + if stop.stn ~= stops2[i].stn then + return false + end + end + return true +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 + local old_linevar_def = linevars[linevar] + if old_linevar_def == nil then + return false, "Nemohu nahradit, varianta linky '"..linevar.."' dosud neexistuje!" + end + linevars[linevar] = linevar_def + local restart_count = 0 + if not same_stops(old_linevar_def.stops, linevar_def.stops) then + -- změnily se zastávky, musíme restartovat vlaky: + 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 + restart_count = restart_count + 1 + end + end + end + if restart_count > 0 then + return true, restart_count.." vlak/ů restartován/o kvůli změnám" + else + return true, nil + end +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_raw = custom_state.selection_index + local selection_index = selection_index_raw or 1 + 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_raw ~= 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 == 1 or + pinfo.player_name == custom_state.linevars[selection_index - 1].owner + + if selection_index > 1 and 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.."]".. + "field[13.5,7;3,0.75;continues;pokračování:;"..F(custom_state.continues).."]") + + if custom_state.message ~= "" then + table.insert(formspec, "label[0.5,8.25;"..F(custom_state.message).."]") + end + if selection_index > 1 then + table.insert(formspec, "button[5,15;4.5,0.75;last_passages;poslední jízdy...]".. + "tooltip[last_passages;Zobrazí přehled časů několika posledních jízd na dané variantě linky.]") + end + 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]") + 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;stání]".. + "label[3.5,0.25;kód dop.]".. + "label[6.25,0.25;režim zastávky]".. + "label[11,0.25;kolej]".. + "label[12.5,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;1.25,0.75;s01_wait;;"..F(custom_state.stops[1].wait).."]".. + "field[3,0;2.5,0.75;s01_stn;;"..F(custom_state.stops[1].stn).."]".. + "dropdown[5.75,0;4.5,0.75;s01_mode;výchozí,skrytá (výchozí);"..custom_state.stops[1].mode..";true]".. + "field[10.5,0;1.25,0.75;s01_track;;"..F(custom_state.stops[1].track).."]".. + "field[12,0;3,0.75;s01_pos;;"..F(custom_state.stops[1].pos).."]".. + "label[15.25,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..";1.25,0.75;s"..n.."_wait;;"..F(stop.wait).."]".. + "field[3,"..y..";2.5,0.75;s"..n.."_stn;;"..F(stop.stn).."]".. + "dropdown[5.75,"..y..";4.5,0.75;s"..n.. + "_mode;normální,na znamení (experimentální),skrytá (mezilehlá),vypnutá,koncová,koncová skrytá,".. + "koncová (pokračuje);"..stop.mode..";true]".. + "field[10.5,"..y..";1.25,0.75;s"..n.."_track;;"..F(stop.track).."]".. + "field[12,"..y..";3,0.75;s"..n.."_pos;;"..F(stop.pos).."]".. + "label[15.25,"..y2..";"..F(stop.label).."]") + end + + table.insert(formspec, + "scroll_container_end[]".. + "tooltip[0,0;1.5,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[1.5,0;1.5,1;Stání: očekáváná doba stání před odjezdem. Pro koncové zastávky očekávaná doba stání po příjezdu.]".. + "tooltip[3.5,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[6.25,0;4.75,1;Režim zastávky: výchozí/normální - vždy zastaví\\;\n".. + "na znamení: zastaví na znamení (zatím experimentální)\\;\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[10.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[12.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)), + wait = tostring(stop.wait or 10), + 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", ""), + wait = "10", + 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") + custom_state.continues = current_linevar.continue_line or "" + if custom_state.continues ~= "" then + custom_state.continues = custom_state.continues.."/"..(current_linevar.continue_rc or "") + end + else + custom_state.line = "" + custom_state.rc = "" + custom_state.train_name = "" + custom_state.owner = custom_state.player_name + custom_state.disable_linevar = "false" + custom_state.continues = "" + end + custom_state.owner = ch_core.prihlasovaci_na_zobrazovaci(custom_state.owner) + custom_state.compiled_linevar = nil + custom_state.evl_scroll = 0 + custom_state.message = "" +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é!" + elseif custom_state.continues:len() - custom_state.continues:gsub("/", ""):len() > 1 then + return false, "Pole 'pokračování' smí obsahovat nejvýše jedno lomítko!" + 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 + local new_wait = tonumber(stop.wait) + if new_wait ~= nil and new_wait == math.floor(new_wait) and new_wait >= 0 and new_wait <= 3600 then + new_stop.wait = new_wait + 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) + + local index_vychozi, index_cil + for i, stop in ipairs(stops) do + local mode = stop.mode or MODE_NORMAL + if mode ~= MODE_DISABLED and mode ~= MODE_HIDDEN and mode ~= MODE_FINAL_HIDDEN then + if index_vychozi == nil then + index_vychozi = i + end + index_cil = i + end + if mode == MODE_FINAL or mode == MODE_FINAL_CONTINUE or mode == MODE_FINAL_HIDDEN then + break + end + end + + custom_state.compiled_linevar = { + name = line.."/"..stops[1].stn.."/"..rc, + owner = ch_core.jmeno_na_prihlasovaci(owner), + stops = stops, + continue_line = "", + continue_rc = "", + index_vychozi = index_vychozi, + index_cil = index_cil, + } + 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 + local continues_split = custom_state.continues:find("/") + if continues_split == nil then + custom_state.compiled_linevar.continue_line = custom_state.continues + else + custom_state.compiled_linevar.continue_line = custom_state.continues:sub(1, continues_split - 1) + custom_state.compiled_linevar.continue_rc = custom_state.continues:sub(continues_split + 1, -1) + 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", "continues"}) 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", "wait", "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! + custom_state.message = color_green.."Úspěšně ověřeno. Varianta linky může být uložena." + else + custom_state.message = color_red.."Ověření selhalo: "..(errmsg or "Neznámý důvod") + end + update_formspec = true + else + -- pokusit se uložit... + custom_state.message = "" + + 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 + core.log("action", "Will add a new linevar '"..new_linevar.."'") + success, errmsg = add_linevar(new_linevar_station, new_linevar_def) + else + -- replace + core.log("action", "Will replace an existing linevar '"..new_linevar.."'") + 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 + core.log("action", "Will delete selected linevar '"..selected_linevar.."' and add new linevar '"..new_linevar.."'") + 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 + core.log("action", "Will add delete selected linevar '"..selected_linevar.."' and replace existing linevar '"..new_linevar.."'") + 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 + core.log("action", "Will replace existing linevar '"..new_linevar.."'") + 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 + custom_state.message = color_green.."Varianta linky '"..new_linevar.."' úspěšně uložena." + custom_state_refresh_linevars(custom_state, new_linevar) + else + custom_state.message = color_red.."Ukládání selhalo: "..(errmsg or "Neznámá chyba.") + end + update_formspec = true + 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 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 + custom_state.message = "Varianta linky '"..selected_linevar.."' úspěšně smazána." + custom_state_refresh_linevars(custom_state) + custom_state_set_selection_index(custom_state, 1) + else + custom_state.message = "Mazání selhalo: "..(errmsg or "Neznámá chyba.") + end + update_formspec = true + end + elseif fields.last_passages then + local selected_linevar_def = try_get_linevar_def(custom_state.linevars[(custom_state.selection_index or 1) - 1].name) + if selected_linevar_def ~= nil then + assert(selected_linevar_def.name) + show_last_passages_formspec(player, selected_linevar_def, assert(selected_linevar_def.name)) + return + end + end + + if update_formspec then + return get_formspec(custom_state) + end +end + +local function show_editor_formspec(player, linevar_to_select) + if player == nil then return false end + local custom_state = { + player_name = assert(player:get_player_name()), + evl_scroll = 0, + message = "", + continues = "", + } + if not custom_state_refresh_linevars(custom_state, linevar_to_select) then + custom_state_set_selection_index(custom_state, 1) + end + ch_core.show_formspec(player, "advtrains_line_automation:editor_linek", get_formspec(custom_state), formspec_callback, custom_state, {}) +end + +local function lp_formspec_callback(custom_state, player, formname, fields) + if fields.back then + show_editor_formspec(player, custom_state.selected_linevar) + end +end + +show_last_passages_formspec = function(player, linevar_def, selected_linevar) + local formspec = { + "formspec_version[6]".. + "size[20,10]".. + "label[0.5,0.6;Poslední jízdy na variantě linky ", + F(assert(linevar_def.name)), + "]".. + "tablecolumns[text;text;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5;text,width=5]", + "table[0.5,1.25;19,8;jizdy;KÓD,DOPRAVNA,1.j.,2.j.,3.j.,4.j.,5.j.,6.j.,7.j.,8.j.,9.j.,10.j." + } + local passages, stops = get_last_passages(linevar_def) + local max_time = {} + if passages ~= nil then + for j = 1, 10 do + max_time[j] = 0 + if passages[j] == nil then + passages[j] = {} + end + end + -- stání na výchozí zastávce: + table.insert(formspec, ",,STÁNÍ NA V.Z.:") + for j = 1, 10 do + local wait = passages[j].wait + if wait ~= nil then + table.insert(formspec, ","..wait.." s") + else + table.insert(formspec, ",-") + end + end + -- odjezd z výchozí zastávky: + table.insert(formspec, ","..F(stops[1][1])..","..F(stops[1][2]).." (odj.)") + for j = 1, 10 do + local time = passages[j][1] + if time ~= nil then + table.insert(formspec, ",("..time..")") + if max_time[j] < time then + max_time[j] = time + end + else + table.insert(formspec, ",-") + end + end + -- odjezdy z ostatních zasŧávek: + for i = 2, #stops do -- i = index zastávky + table.insert(formspec, ","..F(stops[i][1])..","..F(stops[i][2])) + for j = 1, 10 do -- j = index jízdy + local dep_vych = passages[j][1] + local time = passages[j][i] + if time ~= nil and dep_vych ~= nil then + table.insert(formspec, ","..(time - dep_vych)) + if max_time[j] < time then + max_time[j] = time + end + else + table.insert(formspec, ",-") + end + end + end + table.insert(formspec, ",,DOBA JÍZDY:") + for i = 1, 10 do + if max_time[i] ~= 0 then + table.insert(formspec, ",_"..(max_time[i] - passages[i][1]).."_") + else + table.insert(formspec, ",-") + end + end + end + table.insert(formspec, ";]".. + "button[17.75,0.3;1.75,0.75;back;zpět]".. + "tooltip[jizdy;Časové údaje jsou v sekundách železničního času.]") + formspec = table.concat(formspec) + local custom_state = { + player_name = player:get_player_name(), + selected_linevar = selected_linevar, + } + ch_core.show_formspec(player, "advtrains_line_automation:posledni_jizdy", formspec, lp_formspec_callback, custom_state, {}) +end + +def = { + -- params = "", + description = "Otevře editor variant linek", + privs = {railway_operator = true}, + func = function(player_name, param) show_editor_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..ee8f238 --- /dev/null +++ b/advtrains_line_automation/line_functions.lua @@ -0,0 +1,1377 @@ +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 current_passages = {--[[ + [train_id] = { + [1] = rwtime, + ..., + [n] = rwtime (časy *odjezdu*, kromě koncových zastávek, kde jde o čas příjezdu) + wait = int or nil (původně naplánovaná doba čekání na výchozí zastávce) + } +]]} + +local last_passages = {--[[ + [linevar] = { + [1..10] = {[1] = rwtime, ..., wait} -- jízdy seřazeny od nejstarší (1) po nejnovější (až 10) podle odjezdu z výchozí zastávky + } +]]} + +local diakritika_na_velka = { + ["á"] = "Á", ["ä"] = "Ä", ["č"] = "Č", ["ď"] = "Ď", ["é"] = "É", ["ě"] = "Ě", ["í"] = "Í", ["ĺ"] = "Ĺ", ["ľ"] = "Ľ", + ["ň"] = "Ň", ["ó"] = "Ó", ["ô"] = "Ô", ["ŕ"] = "Ŕ", ["ř"] = "Ř", ["š"] = "Š", ["ť"] = "Ť", ["ú"] = "Ú", ["ů"] = "Ů", + ["ý"] = "Ý", ["ž"] = "Ž", +} + +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 +al.get_station_name = get_station_name + +local function na_velka_pismena(s) + local l = #s + local i = 1 + local res = "" + local c + while i <= l do + c = diakritika_na_velka[s:sub(i, i + 1)] + if c then + res = res .. c + i = i + 2 + else + res = res .. s:sub(i, i) + i = i + 1 + end + end + return string.upper(res) +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 + +-- Vrací kódy výchozí a cílové stanice k zobrazení cestujícím: stn_first (string), stn_terminus (string) +-- Předané linevar_def musí obsahovat platný seznam 'stops', ale nemusí obsahovat nic jiného (nemusí to být platná definice). +-- V případě chyby vrací nil, nil. +local function get_first_last_stations(linevar_def) + local a, b = linevar_def.index_vychozi, linevar_def.index_cil + if a ~= nil and b ~= nil then + local stops = linevar_def.stops + return stops[a].stn, stops[b].stn + else + return nil, nil + 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 + 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 + ls.linevar_past = nil + train.text_outside = al.get_line_description(linevar_def, { + line_number = true, + last_stop = true, + last_stop_prefix = "", + last_stop_uppercase = true, + train_name = true, + }) + return true +end + +--[[ + Vrací: + - "true", pokud má vlak zastavit + - nil, pokud zastavit nemá + - "on_request", pokud má zastavit na znamení, ale znamení zatím nebylo dáno +]] +local function should_stop(pos, stdata, train) + if stdata == nil or stdata.stn == nil then + return nil -- 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 + return nil + 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 + return nil + 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 + return nil + end + if stop.mode ~= nil and stop.mode == MODE_REQUEST_STOP then + if ls.stop_request ~= nil then + debug_print("Vlak "..train.id.." zastaví na zastávce na znamení.") + return "true" + else + debug_print("Vlak "..train.id.." možná zastaví na zastávce na znamení.") + return "on_request" + end + end + return "true" + -- 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 + return "true" + else + return nil + end + 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 + 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í předchozí zastávka nalezena + b) nil, nil -- pokud nalezena nebyla +]] +function al.get_first_stop(linevar_def, allow_hidden_stops) + if allow_hidden_stops then + return 1, linevar_def.stops[1] + else + local result = linevar_def.index_vychozi + if result ~= nil then + return result, linevar_def.stops[result] + else + return nil, nil + end + end +end + +--[[ + Vrací: + a) index, stop_data -- pokud byla vyhovující předchozí zastávka nalezena + b) nil, nil -- pokud nalezena nebyla +]] +function al.get_last_stop(linevar_def, allow_hidden_stops) + if allow_hidden_stops then + local stops = linevar_def.stops[result] + for i = linevar_def.index_cil, #stops do + local stop = stops[i] + local mode = stop.mode + if mode == MODE_FINAL or mode == MODE_FINAL_CONTINUE or mode == MODE_FINAL_HIDDEN then + return i, stop + end + end + else + local result = linevar_def.index_cil + if result ~= nil then + return result, linevar_def.stops[result] + end + end + return nil, nil +end + +--[[ + Vrací: + a) index, stop_data -- pokud byla vyhovující předchozí zastávka nalezena + b) nil, nil -- pokud nalezena nebyla +]] +function al.get_prev_stop(linevar_def, current_index, allow_hidden_stops) + local stops = assert(linevar_def.stops) + assert(current_index) + if current_index > 1 then + for i = current_index - 1, 1, -1 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í 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) + assert(current_index) + 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) + if linevar_def.index_cil ~= nil then + return linevar_def.index_cil, linevar_def.stops[linevar_def.index_cil] + end + local stops = assert(linevar_def.stops) + local r_i, r_stop + if current_index < #stops then + for i = current_index + 1, #stops do + local mode = stops[i].mode or MODE_NORMAL + if mode ~= MODE_DISABLED and ((mode ~= MODE_HIDDEN and mode ~= MODE_FINAL_HIDDEN) or allow_hidden_stops) then + r_i, r_stop = i, stops[i] + end + if mode == MODE_FINAL or mode == MODE_FINAL_CONTINUE or mode == MODE_FINAL_HIDDEN then + break + end + end + end + return r_i, r_stop +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 => "⇒ " + last_stop_uppercase = bool or nil, -- je-li true, název cílové zastávky se před uvedením převede na velká písmena + 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) + local first_stn, last_stn = get_first_last_stations(linevar_def) + if line == nil or first_stn == nil or last_stn == 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 first_stn == last_stn and options.first_stop and (options.last_stop == nil or options.last_stop) then + s2 = get_station_name(last_stn) + if options.last_stop_uppercase then + s2 = na_velka_pismena(s2) + end + s3 = " (okružní)" + else + if options.first_stop then + s2 = get_station_name(first_stn).." " + else + s2 = "" + end + if options.last_stop == nil or options.last_stop then + s3 = get_station_name(last_stn) + if options.last_stop_uppercase then + s3 = na_velka_pismena(s3) + end + s3 = (options.last_stop_prefix or "⇒ ")..s3 + else + s3 = "" + end + 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 + 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 or + (next_stop_data ~= nil and next_stop_data.mode == MODE_FINAL_HIDDEN) + then + s2 = "Koncová zastávka" + end + ]] + end + end + if next_stop_data ~= nil then + local mode = next_stop_data.mode + 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 + else + s2 = "Koncová zastávka" + 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, rwtime) + assert(line_status) + if linevar_def == nil then + return {text = ""} + end + local stops = linevar_def.stops + local expected_departure = line_status.linevar_dep + assert(stops[line_status.linevar_index]).dep + local real_departure = line_status.linevar_last_dep + local delay = real_departure - expected_departure + + if rwtime ~= nil then + local expected_departure_next + for i = line_status.linevar_index + 1, #stops do + if stops[i] == nil then + break + else + local mode = stops[i].mode + if mode == nil or mode ~= MODE_DISABLED then + expected_departure_next = line_status.linevar_dep + stops[i].dep + break + end + end + end + if expected_departure_next ~= nil then + local delay2 = rwtime - expected_departure_next + if delay2 > delay then + delay = delay2 + end + end + end + + 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 + +-- Test na módy MODE_FINAL* +function al.is_final_mode(stop_mode) + return stop_mode ~= nil and simple_modes[stop_mode] == MODE_FINAL +end +local is_final_mode = al.is_final_mode + +-- Test na skrytou zastávku. Vrací true, pokud zadaný mód neodpovídá skryté zastávce. +function al.is_visible_mode(stop_mode) + return stop_mode == nil or (stop_mode ~= MODE_HIDDEN and stop_mode ~= MODE_FINAL_HIDDEN) +end +local is_visible_mode = al.is_visible_mode + +-- 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 + 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 + end + 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 + return linevar, result + end + end + 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 = {} + return train.line_status, nil + end + local ls = train.line_status + if ls.linevar == nil then + -- nelinkový vlak + local linevar_past = ls.linevar_past + if linevar_past ~= nil then + local rwtime = rwt.to_secs(rwt.get_time()) + if train.line ~= linevar_past.line or rwtime - linevar_past.arrival >= 86400 then + -- smazat linevar_past, pokud se změnilo označení linky nebo uplynulo 24 hodin + ls.linevar_past = nil + end + end + 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 + 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) ~= nil then + 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 + +local function record_skipped_stops(train_id, linevar_def, linevar_index, next_index) + assert(linevar_def) + if next_index > linevar_index + 1 then + local skipped_stops = {} + for i = 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 + return #skipped_stops + else + return 0 + 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 + local should_stop_result = should_stop(pos, stdata, train) + if should_stop_result == nil then + debug_print("Vlak "..train_id.." projel zastávkou "..stn) + return + elseif should_stop_result == "on_request" then + -- projetí zastávky na znamení + debug_print("Vlak "..train_id.." projel zastávkou na znamení "..stn) + if linevar_def ~= nil and next_index ~= nil then + local stop_def = assert(linevar_def.stops[next_index]) + if stop_def.mode == nil or stop_def.mode ~= MODE_REQUEST_STOP then + error("Internal error: mode "..MODE_REQUEST_STOP.." expected, but the real mode is "..tostring(stop_def.mode).."!") + end + assert(stn ~= "") + record_skipped_stops(train_id, linevar_def, ls.linevar_index, next_index) + ls.linevar_index = next_index + ls.linevar_last_dep = rwtime -- u zastávky na znamení se průjezd počítá jako odjezd + ls.linevar_last_stn = stn + local next_stop_index, next_stop_data = al.get_next_stop(linevar_def, next_index) + train.text_inside = al.get_stop_description(nil, linevar_def.stops[next_stop_index or 0]) + -- ATC command: + local atc_command = "A1 S" ..(stdata.speed or "M") + advtrains.atc.train_set_command(train, atc_command, true) + end + 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..").") + stdata.last_dep = planned_departure -- naplánovaný čas odjezdu + stdata.last_wait = wait -- naplánovaná doba čekání + ls.standing_at = pe + if linevar_def == nil or next_index == nil or (linevar_def.stops[next_index].mode or MODE_NORMAL) ~= MODE_HIDDEN then + -- zrušit stop_request, pokud jsme nezastavili na skryté zastávce: + ls.stop_request = nil + end + + local can_start_line + local had_linevar = linevar_def ~= nil + if linevar_def == nil then + -- nelinkový vlak + can_start_line = true + 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..")") + record_skipped_stops(train_id, linevar_def, ls.linevar_index, next_index) + 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 + 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 + core.log("action", "Train "..train_id.." arrived at the final station "..stop_def.stn.." of linevar "..ls.linevar.." after "..(rwtime - ls.linevar_dep).." seconds.") + 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]) + local current_passage = current_passages[train_id] + if current_passage ~= nil then + current_passage[next_index] = rwtime + current_passages[train_id] = nil + end + al.cancel_linevar(train) + -- vyplnit linevar_past: + if train.line ~= nil and train.line ~= "" then + local past_linevar = assert(linevar_def.name) + local past_line = al.linevar_decompose(past_linevar) + ls.linevar_past = { + line = assert(past_line), + linevar = past_linevar, + station = stn, + arrival = rwtime, + } + end + 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 + + -- 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 stn ~= nil and stn ~= "" 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 +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 + + if ls.standing_at == pe then + -- vlak stál v této dopravně + ls.standing_at = nil + if + linevar_def == nil or ls.linevar_index == nil or + linevar_def.stops[ls.linevar_index] == nil or + (linevar_def.stops[ls.linevar_index].mode or MODE_NORMAL) ~= MODE_HIDDEN + then + ls.stop_request = nil -- zrušit stop_request při odjezdu ze zastávky, pokud není nekoncová skrytá + end + if stn ~= "" then + 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 + 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: + debug_print("Linkový vlak "..train_id.." odjel ze zastávky s indexem "..ls.linevar_index) + if ls.linevar_index == 1 then + -- odjezd z výchozí zastávky: + local new_passage = {rwtime} + if stdata.last_wait ~= nil then + new_passage.wait = stdata.last_wait + end + current_passages[train_id] = new_passage + local passages_by_linevar = last_passages[ls.linevar] + if passages_by_linevar == nil then + passages_by_linevar = {new_passage} + last_passages[ls.linevar] = passages_by_linevar + else + while #passages_by_linevar >= 10 do + table.remove(passages_by_linevar, 1) + end + table.insert(passages_by_linevar, new_passage) + end + else + -- odjezd z nácestné zastávky + local current_passage = current_passages[train_id] + if current_passage ~= nil then + current_passage[ls.linevar_index] = rwtime + end + end + 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("Vlak "..train_id.." projel (odjezd) zastávkou "..stn..".") + else + debug_print("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 + +--[[ + Vrací: + a) pokud linevar existuje a má průjezdy: + passages, stops: + {{[1] = rwtime, ..., wait = int or nil}...}, {"kód", "název"}...} + b) jinak: + nil, nil +]] +function al.get_last_passages(linevar_def) + local lp = last_passages[linevar_def.name] + if linevar_def ~= nil and lp ~= nil and lp[1] ~= nil then + local passages, stops = {}, {} + for i, stop in ipairs(linevar_def.stops) do + stops[i] = {stop.stn, get_station_name(stop.stn)} + end + for i = 1, #lp do + passages[i] = table.copy(lp[i]) + end + return passages, stops + end +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 + +function al.get_last_pos_station_name(line_status) + local result = get_last_pos(line_status) + if result.type ~= "none" then + return get_station_name(result.last_pos.stn) + else + return nil + end +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) + 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, rwtime) + 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 + +--[[ +local function prepare_prediction_for_dump(result) + local x = {} + for i, record in ipairs(result) do + local y = table.copy(record) + y.stdata = record.stdata.name + if y.arr_linevar_def ~= nil then + y.arr_linevar_def = y.arr_linevar_def.name + end + if y.dep_linevar_def ~= nil then + y.dep_linevar_def = y.dep_linevar_def.name + end + x[i] = y + end + return x +end +]] + +local function predict_train_continue(line, stn, rc, departure, result) + if line == nil or line == "" then + return + end + local linevar_def = al.try_get_linevar_def(line.."/"..stn.."/"..rc, stn) + if linevar_def == nil then + return + end + if #result == 0 then + return + end + local stops = assert(linevar_def.stops) + local last_record = result[#result] + last_record.dep = departure + last_record.dep_linevar_def = linevar_def + last_record.dep_index = 1 + local index = 2 + while stops[index] ~= nil do + local stop = stops[index] + local stdata = advtrains.lines.stations[stop.stn] + if stop.mode == MODE_FINAL or stop.mode == MODE_FINAL_CONTINUE or stop.mode == MODE_FINAL_HIDDEN then + -- koncová zastávka + local arr = departure + stop.dep + local record = { + stn = assert(stop.stn), + track = stop.track or "", + stdata = stdata, + arr = arr, + arr_linevar_def = linevar_def, + arr_index = index, + delay = 0, + hidden = stop.mode == MODE_FINAL_HIDDEN, + } + table.insert(result, record) + return + elseif stop.mode ~= MODE_DISABLED then + -- mezilehlá zastávka + local dep = departure + stop.dep + table.insert(result, { + stn = assert(stop.stn), + track = stop.track or "", + stdata = stdata, + arr = dep - (stop.wait or 10), + arr_linevar_def = linevar_def, + arr_index = index, + dep = dep, + dep_linevar_def = linevar_def, + dep_index = index, + delay = 0, + hidden = stop.mode == MODE_HIDDEN, + }) + end + index = index + 1 + end +end + +--[[ + Zadaný vlak musí být linkový. + Parametry: + line_status = table, + linevar_def = table, + rwtime = int, + allow_continue = bool, + Vrací *pole* následujících záznamů: + { + stn = string, + track = string, + delay = int, + stdata = table or nil, + dep = int or nil, dep_linevar_def = table or nil, dep_index = int or nil, + arr = int or nil, arr_linevar_def = table or nil, arr_index = int or nil, + } +]] +function al.predict_train(line_status, linevar_def, rwtime, allow_continue) + assert(line_status) + assert(linevar_def) + local stops = linevar_def.stops + local result = {} + local index = assert(line_status.linevar_index) + if rwtime == nil then + rwtime = rwt.to_secs(rwt.get_time()) + end + local delay_desc = al.get_delay_description(line_status, linevar_def, rwtime) + local delay + if delay_desc.has_delay then + delay = delay_desc.delay + else + delay = 0 + end + local departure = line_status.linevar_dep + if line_status.standing_at ~= nil then + -- vlak stojí na zastávce + local stop = assert(stops[index]) + local stdata = advtrains.lines.stations[stop.stn] + table.insert(result, { + stn = line_status.linevar_last_stn, + track = stop.track or "", + stdata = stdata, + dep = assert(line_status.linevar_last_dep), + dep_linevar_def = assert(linevar_def), + dep_index = index, + -- arr = nil, arr_index = nil, arr_linevar_def = nil, + delay = delay, + hidden = stop.mode == MODE_HIDDEN + }) + end + index = index + 1 + while stops[index] ~= nil do + local stop = stops[index] + local stdata = advtrains.lines.stations[stop.stn] + if stop.mode == MODE_FINAL or stop.mode == MODE_FINAL_CONTINUE or stop.mode == MODE_FINAL_HIDDEN then + -- koncová zastávka + local arr = departure + stop.dep + delay + local record = { + stn = assert(stop.stn), + track = stop.track or "", + stdata = stdata, + arr = arr, + arr_linevar_def = linevar_def, + arr_index = index, + delay = delay, + hidden = stop.mode == MODE_FINAL_HIDDEN, + final = true, + } + table.insert(result, record) + if allow_continue and (linevar_def.continue_line or "") ~= "" and stop.mode == MODE_FINAL_CONTINUE then + predict_train_continue(linevar_def.continue_line, stop.stn, linevar_def.continue_rc, arr + (stop.wait or 10), result) + end + break + elseif stop.mode ~= MODE_DISABLED then + -- mezilehlá zastávka + local dep = departure + stop.dep + delay + table.insert(result, { + stn = assert(stop.stn), + track = stop.track or "", + stdata = stdata, + arr = dep - (stop.wait or 10), + arr_linevar_def = linevar_def, + arr_index = index, + dep = dep, + dep_linevar_def = assert(linevar_def), + dep_index = index, + delay = delay, + hidden = stop.mode == MODE_HIDDEN, + }) + end + index = index + 1 + end + -- print("DEBUG: "..dump2({prediction = prepare_prediction_for_dump(result)})) + return result +end + +--[[ + Parametry: + linevar_def = table, -- definice linevar, z něhož se má analýza provádět + linevar_index = int, + rwtime = int or nil, -- (aktuální žel. čas; nepovinné) + trains = {train_table...} or nil, -- je-li zadáno, bude zkoumat pouze vlaky v tomto seznamu + Vrací *pole* záznamů (stejných jako al.predict_train) vztahujících se k odjezdu + z požadované zastávky, seřazené od nejbližšího odjezdu po nejvzdálenější. +]] +function al.predict_station_departures(linevar_def, linevar_index, rwtime, trains) + assert(linevar_def) + assert(linevar_index) + local linevar = linevar_def.name + local stop = assert(linevar_def.stops[linevar_index]) + if trains == nil then + trains = al.get_trains_by_linevar()[linevar] or {} + end + if rwtime == nil then + rwtime = rwt.to_secs(rwt.get_time()) + end + local result = {} + for _, train in ipairs(trains) do + local ls, lvdef = al.get_line_status(train) + if ls.linevar == linevar and ls.linevar_index <= linevar_index then + local prediction = al.predict_train(ls, linevar_def, rwtime, true) + for _, pr in ipairs(prediction) do + if + pr.dep ~= nil and pr.dep_linevar_def ~= nil and pr.dep_index ~= nil and + pr.dep_linevar_def.name == linevar and + pr.dep_index == linevar_index and + pr.dep > rwtime + then + table.insert(result, pr) + break + end + end + end + end + table.sort(result, function(a, b) return a.dep < b.dep end) + return result +end + +--[[ + => {{ + linevar = string, + indices = {int,...}, + linevar_def = linevar_def, + }...} +]] +function al.get_linevars_by_station(stn, track, options) + if options == nil then + options = {} + end + local include_hidden_stops = options.include_hidden_stops + local ignore_first_stop = options.ignore_first_stop + local ignore_last_stop = options.ignore_last_stop + local result = {} + assert(stn) + for lvstn, stdata in pairs(advtrains.lines.stations) do + if stdata.linevars ~= nil then + for linevar, linevar_def in pairs(stdata.linevars) do + local first_stop_index = al.get_first_stop(linevar_def, include_hidden_stops) + local last_stop_index = al.get_last_stop(linevar_def, include_hidden_stops) + if not (ignore_first_stop or ignore_last_stop) or (first_stop_index ~= nil and last_stop_index ~= nil) then + local ld + for i, stop in ipairs(linevar_def.stops) do + if + stop.stn == stn and + (track == nil or tostring(stop.track) == track) and + (include_hidden_stops or is_visible_mode(stop.mode)) and + ((not ignore_first_stop) or i ~= 1) and + ((not ignore_last_stop) or (not is_final_mode(stop.mode))) + then + if ld == nil then + ld = {linevar = linevar, linevar_def = linevar_def, indices = {i}} + table.insert(result, ld) + else + table.insert(ld.indices, i) + end + end + end + end + end + end + end + if #result > 1 then + table.sort(result, function(a, b) return a.linevar < b.linevar end) + end + return result +end + +--[[ + => { + [linevar] = {train...}, -- generuje jen neprázdné seznamy + } +]] +function al.get_trains_by_linevar() + local result = {} + for train_id, train in pairs(advtrains.trains) do + local ls, linevar_def = al.get_line_status(train) + if linevar_def ~= nil then + local linevar = linevar_def.name + local list = result[linevar] + if list ~= nil then + table.insert(list, train) + else + list = {train} + result[linevar] = list + end + end + end + for linevar, list in pairs(result) do + if list[2] ~= nil then + table.sort(list, function(a, b) return a.id < b.id end) + end + end + return result +end + +local function vlaky(param, past_trains_too) + local result = {} + if param:match("/") then + return result -- parametr nesmí obsahovat '/' + end + local train_line_prefix + if param ~= "" then + train_line_prefix = param.."/" + end + local rwtime = rwt.to_secs(rwt.get_time()) + local players_per_train = {} + local results = {} + for player_name, train_id in pairs(advtrains.player_to_train_mapping) do + results[train_id] = (results[train_id] or 0) + 1 + end + for train_id, train in pairs(advtrains.trains) do + local ls, linevar_def = al.get_line_status(train) + if linevar_def ~= nil then + if train_line_prefix == nil or train_line_prefix == ls.linevar:sub(1, #train_line_prefix) 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 line = al.linevar_decompose(linevar_def.name) + local s = "("..train_id..") ["..line.."] směr „"..direction.."“, poloha: ".. + get_train_position(ls, linevar_def, rwtime) + if results[train_id] ~= nil then + s = s.." ["..results[train_id].." cestující/ch]" + end + table.insert(results, {key = linevar_def.name.."/"..ls.linevar_index, value = s}) + end + elseif past_trains_too and ls.linevar_past ~= nil and (train_line_prefix == nil or ls.linevar_past.line == param) then + local age = rwtime - ls.linevar_past.arrival + local s = "("..train_id..") ["..ls.linevar_past.line.."] služební, poloha: ".. + get_station_name(ls.linevar_past.station).." (před "..age.." sekundami)" + if results[train_id] ~= nil then + s = s.." ["..results[train_id].." cestující/ch]" + end + table.insert(results, { + key = string.format("%s/~/%s/%05d", ls.linevar_past.line, ls.linevar_past.station, age), + value = s, + }) + end + end + table.sort(results, function(a, b) return a.key < b.key end) + for i, v in ipairs(results) do + result[i] = v.value + end + return result +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 = vlaky(param, false) + 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) +def = { + params = "[linka]", + description = "Vypíše všechny linkové vlaky na zadané lince (resp. na všech linkách) a ty, které nedávno jízdu na lince ukončily", + privs = {}, + func = function(player_name, param) + local result = vlaky(param, true) + 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) + +def = { + -- params = "", + description = "(pro ladění)", + privs = {server = true}, + func = function(player_name, param) + local train = advtrains.trains[param] + if train == nil then + return false, "Vlak "..param.." nenalezen!" + end + local line_status, linevar_def = al.get_line_status(train) + if linevar_def == nil then + return false, "Vlak "..param.." není linkový!" + end + -- function al.predict_train(line_status, linevar_def, rwtime, allow_continue) + local rwtime = rwt.to_secs(rwt.get_time()) + local predictions = al.predict_train(line_status, linevar_def, rwtime, true) + local result = {"----"} + local s + for i, record in ipairs(predictions) do + local arr, dep + if record.arr ~= nil then + arr = "("..(record.arr - rwtime)..", lv="..record.arr_linevar_def.name..", i="..record.arr_index..")" + else + arr = "nil" + end + if record.dep ~= nil then + dep = "("..(record.dep - rwtime)..", lv="..record.dep_linevar_def.name..", i="..record.dep_index..")" + else + dep = "nil" + end + result[i + 1] = "- "..record.stn.." ["..record.track.."] arr="..arr.." dep="..dep.." delay="..record.delay + end + table.insert(result, "----") + s = table.concat(result, "\n") + print(s) + core.chat_send_player(player_name, s) + return true + end, +} +core.register_chatcommand("jřád", def) +core.register_chatcommand("jrad", def) diff --git a/advtrains_line_automation/mod.conf b/advtrains_line_automation/mod.conf index e9851c8..eeae5a2 100644 --- a/advtrains_line_automation/mod.conf +++ b/advtrains_line_automation/mod.conf @@ -3,5 +3,5 @@ title=Advanced Trains Line Automation description=Tools for automatic train lines author=orwell96 -depends=advtrains_interlocking -optional_depends=advtrains_train_track +depends=advtrains_interlocking,default +optional_depends=advtrains_train_track,ch_time,signs_api,unifieddyes diff --git a/advtrains_line_automation/models/advtrains_tuber.obj b/advtrains_line_automation/models/advtrains_tuber.obj new file mode 100644 index 0000000..bd17341 --- /dev/null +++ b/advtrains_line_automation/models/advtrains_tuber.obj @@ -0,0 +1,150 @@ +# Blender v2.76 (sub 0) OBJ File: 'dark_tuber.blend' +# www.blender.org +g tuber +v 0.068165 0.171006 0.601066 +v 0.068165 0.328994 0.601066 +v -0.068165 0.328994 0.601066 +v -0.068165 0.171006 0.601066 +v 0.184430 0.065570 0.081152 +v 0.184430 0.434431 0.016861 +v -0.190760 0.430332 0.072541 +v -0.184430 0.065570 0.221249 +v 0.111639 0.137188 0.264378 +v 0.108474 0.360762 0.259011 +v -0.131011 0.350268 0.238021 +v -0.114803 0.135139 0.328379 +v 0.050415 0.195602 0.451854 +v 0.050415 0.304398 0.466035 +v -0.058854 0.298933 0.440372 +v -0.050415 0.195602 0.469734 +v 0.132013 0.116200 0.277490 +v 0.132013 0.383800 0.248250 +v -0.132013 0.383800 0.277490 +v -0.132013 0.116200 0.355945 +v 0.000000 0.171006 0.601066 +v 0.000000 0.019494 0.166419 +v 0.000000 0.118180 0.299225 +v 0.000000 0.184188 0.460794 +v 0.000000 0.091796 0.320542 +v -0.000000 0.328994 0.601066 +v -0.003165 0.478457 0.029481 +v -0.012851 0.373499 0.245669 +v -0.004220 0.313080 0.453203 +v -0.000000 0.408204 0.259046 +v -0.068165 0.250000 0.601066 +v -0.233671 0.247951 0.162114 +v -0.143497 0.241679 0.281741 +v -0.064484 0.247267 0.455053 +v -0.156417 0.250000 0.320542 +v 0.068165 0.250000 0.601066 +v 0.230506 0.250000 0.033787 +v 0.130646 0.250000 0.263154 +v 0.060264 0.250000 0.458944 +v 0.156417 0.250000 0.259046 +vt 1.000000 1.000000 +vt 0.937500 1.000000 +vt 0.875000 1.000000 +vt 0.875000 0.937500 +vt 0.875000 0.875000 +vt 0.937500 0.875000 +vt 1.000000 0.875000 +vt 1.000000 0.937500 +vt 0.750000 1.000000 +vt 0.750000 0.500000 +vt 0.875000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 1.000000 +vt 0.500000 1.000000 +vt 0.500000 0.500000 +vt 0.375000 0.500000 +vt 0.375000 1.000000 +vt 0.250000 1.000000 +vt 0.250000 0.500000 +vt 0.125000 0.500000 +vt 0.125000 1.000000 +vt -0.000000 1.000000 +vt 0.000000 0.500000 +vt 0.875000 -0.000000 +vt 1.000000 0.000000 +vt 1.000000 0.500000 +vt 1.000000 0.750000 +vt 1.000000 0.812500 +vt 0.875000 0.812500 +vt 0.875000 0.750000 +vt 0.937500 0.750000 +vt 0.125000 -0.000000 +vt -0.000000 -0.000000 +vt 0.375000 -0.000000 +vt 0.250000 -0.000000 +vt 0.625000 0.000000 +vt 0.500000 -0.000000 +vt 0.750000 0.000000 +vn 0.000000 -1.000000 -0.000000 +vn 0.764800 0.518100 -0.383000 +vn 0.930800 -0.283500 0.230800 +vn -0.161500 -0.277500 0.947000 +vn -0.905200 -0.416000 -0.086500 +vn -0.072500 -0.254700 -0.964300 +vn 0.010500 0.576200 0.817300 +vn -0.165900 0.384900 -0.907900 +vn -0.905900 0.402100 0.132600 +vn 0.035600 0.997300 -0.063700 +vn -0.087800 -0.403600 -0.910700 +vn -0.956900 -0.283400 -0.062600 +vn -0.099700 -0.205600 0.973500 +vn 0.967500 -0.221400 0.122200 +vn -0.075100 0.278700 -0.957400 +vn -0.923500 0.343200 0.171400 +vn 0.889600 0.387400 -0.241800 +vn 0.065900 0.396300 0.915700 +vn -0.266100 0.364700 0.892300 +vn 0.338400 -0.356100 -0.871000 +vn -0.375700 0.505200 0.776900 +vn 0.166400 -0.230300 -0.958800 +vn 0.235500 0.298600 -0.924900 +vn 0.129200 -0.199200 0.971400 +vn 0.153800 0.447300 -0.881100 +vn 0.220500 -0.269400 0.937500 +vn 0.900900 0.426900 0.078000 +vn -0.953500 -0.254700 0.160900 +vn -0.867100 -0.365900 0.338100 +vn 0.816200 0.577700 0.008700 +vn -0.930700 0.321200 -0.174700 +vn 0.969100 -0.229000 -0.091300 +vn -0.898400 0.371600 -0.234200 +vn 0.942500 -0.300400 -0.146600 +s off +f 1/1/1 36/2/1 2/3/1 26/4/1 3/5/1 31/6/1 4/7/1 21/8/1 +f 32/3/2 7/9/2 11/10/2 33/11/2 +f 40/12/3 37/13/3 6/14/3 18/15/3 +f 30/16/4 27/17/4 7/18/4 19/19/4 +f 35/20/5 32/21/5 8/22/5 20/23/5 +f 25/11/6 21/24/6 4/25/6 20/26/6 +f 22/21/7 8/22/7 12/23/7 23/20/7 +f 27/13/8 6/14/8 10/15/8 28/12/8 +f 37/17/9 5/18/9 9/19/9 38/16/9 +f 13/27/10 24/28/10 16/7/10 34/6/10 15/5/10 29/29/10 14/30/10 39/31/10 +f 22/3/11 25/11/11 20/26/11 8/1/11 +f 31/32/12 35/20/12 20/23/12 4/33/12 +f 26/34/13 30/16/13 19/19/13 3/35/13 +f 36/36/14 40/12/14 18/15/14 2/37/14 +f 28/12/15 10/15/15 14/37/15 29/36/15 +f 38/16/16 9/19/16 13/35/16 39/34/16 +f 33/11/17 11/10/17 15/38/17 34/24/17 +f 23/20/18 12/23/18 16/33/18 24/32/18 +f 9/19/19 23/20/19 24/32/19 13/35/19 +f 5/9/20 17/10/20 25/11/20 22/3/20 +f 5/18/21 22/21/21 23/20/21 9/19/21 +f 17/10/22 1/38/22 21/24/22 25/11/22 +f 11/10/23 28/12/23 29/36/23 15/38/23 +f 2/37/24 18/15/24 30/16/24 26/34/24 +f 7/9/25 27/13/25 28/12/25 11/10/25 +f 18/15/26 6/14/26 27/17/26 30/16/26 +f 12/26/27 33/11/27 34/24/27 16/25/27 +f 3/35/28 19/19/28 35/20/28 31/32/28 +f 19/19/29 7/18/29 32/21/29 35/20/29 +f 8/1/30 32/3/30 33/11/30 12/26/30 +f 10/15/31 38/16/31 39/34/31 14/37/31 +f 1/38/32 17/10/32 40/12/32 36/36/32 +f 6/14/33 37/17/33 38/16/33 10/15/33 +f 17/10/34 5/9/34 37/13/34 40/12/34 diff --git a/advtrains_line_automation/models/license.txt b/advtrains_line_automation/models/license.txt new file mode 100644 index 0000000..c76bd96 --- /dev/null +++ b/advtrains_line_automation/models/license.txt @@ -0,0 +1,5 @@ +advtrains_tuber.obj + Author: Hume2 + Source: https://gitlab.com/h2mm/underch underch_dark_tuber.obj (Underground Challenge) + Modified (rotated, scaled) + License: CC0 diff --git a/advtrains_line_automation/railwaytime.lua b/advtrains_line_automation/railwaytime.lua index 258009e..29ef110 100644 --- a/advtrains_line_automation/railwaytime.lua +++ b/advtrains_line_automation/railwaytime.lua @@ -304,5 +304,20 @@ function rwt.last_rpt(rwtime, rpt_interval, rpt_offset) return rwt.to_table(res_s) end +function rwt.to_os_time(rwtime) + local rw_now = rwt.to_secs(rwt.get_time()) + return rwt.to_secs(rwtime) - rw_now + os.time() +end advtrains.lines.rwt = rwt + +if core.get_modpath("ch_time") then + ch_time.set_rwtime_callback(function() + local rwtime = rwt.get_time() + return { + secs = rwt.to_secs(rwtime), + string = rwt.to_string(rwtime, true), + string_extended = rwt.to_string(rwtime), + } + end) +end diff --git a/advtrains_line_automation/station_announcement.lua b/advtrains_line_automation/station_announcement.lua new file mode 100644 index 0000000..3f9956c --- /dev/null +++ b/advtrains_line_automation/station_announcement.lua @@ -0,0 +1,1579 @@ +local al = advtrains.lines +local F = core.formspec_escape +local ifthenelse = function(cond, a, b) if cond then return a else return b end end +local rwt = assert(advtrains.lines.rwt) +local def +local function CF(s) + if s ~= nil then return F(s) else return "" end +end +local has_ch_time = core.get_modpath("ch_time") +local has_signs_api = core.get_modpath("signs_api") +local has_unifieddyes = core.get_modpath("unifieddyes") +local rozhlas_node_name = "advtrains_line_automation:stanicni_rozhlas" + +local RMODE_DEP = 1 +local RMODE_ARR = 2 +local RMODE_BOTH = 3 + +local PAGE_SETUP_1 = 1 +local PAGE_SETUP_2 = 2 +local PAGE_OWNERSHIP = 3 + +local hl_texty = { + { + id = "vcpp", + sample = "Vážení cestující, prosíme pozor!", + default = "", + }, { + id = "tvl", + sample = "{TYPVLAKU}", + }, { + id = "vlk", + sample = "Vlak", + }, { + id = "lnky", + sample = "linky {LINKA}", + }, { + id = "jmvlku", + sample = "{JMVLAKU}", + }, { + id = "zesm", + sample = "ze směru {VYCHOZI}", + }, { + id = "prijnk", + sample = "přijíždí na kolej {KOLEJ}.", + }, { + id = "prisnk", + sample = "bude přistaven na kolej {KOLEJ}.", + }, { + id = "zpoz", + sample = "Vlak má {ZPOZDENI} sekund zpoždění.", + default = "", + }, { + id = "zpozz", + sample = "Vlak má -{ZPOZDENI} sekund zpoždění.", + default = "", + }, { + id = "vkonc", + sample = "Vlak zde jízdu končí.", + }, { + id = "pokrnc", + sample = "Vlak dále pokračuje směr {NASL} a {CIL}", + }, { + id = "pokrc", + sample = "Vlak pokračuje směr {CIL}", + }, { + id = "ozasek", + sample = ", odjezd za {ODJZA} sekund.", + }, { + id = "dek", + sample = "Děkujeme, že používáte naše služby.", + default = "", + } +} +local hl_texty_id_to_idx = function() + local result = {} + for i, def in ipairs(hl_texty) do + local id = assert(def.id) + if result[id] ~= nil then + error("Duplicity: id = "..id.."!") + end + def.fs_sample = F(def.sample) + def.fs_default = F(def.default or def.sample) + result[id] = i + end + return result +end +hl_texty_id_to_idx = hl_texty_id_to_idx() + +local punch_context = {--[[ + [player_name] = { + stn = string, + epos = string, + i = int, + } +]]} + +local function dist2(a, b) + local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z + return x * x + y * y + z * z +end + +local function goa(t, k) -- goa = get or add + local result = t[k] + if result == nil then + result = {} + t[k] = result + end + return result +end + +--[[ + Vrací: + - success = bool + - min = int or nil (pro success == false vždy nil) + - max = int or nil (pro success == false vždy nil) + - align = "left" or "right" or "center" +]] +local function lengths_from_string(s) + if s == "" or s == "-" then + return true, nil, nil + end + local l = #s + s = s:gsub("^ +", "") + local left = #s < l + l = #s + s = s:gsub(" +$", "") + local right = #s < l + local align + if not left then + align = "left" -- mezery vpravo, nebo žádné mezery + elseif right then + align = "center" -- mezery na obou stranách + else + align = "right" -- mezery jen vlevo + end + local n = s:match("^%d+$") + if n ~= nil then + n = assert(tonumber(n)) + if n > 256 then + n = 256 + end + return true, n, n, align + end + local min, max = s:match("^(%d*)-(%d*)$") + if min == nil then + return false, nil, nil, nil -- bad format + end + min, max = tonumber(min), tonumber(max) + if min ~= nil and min > 256 then min = 256 end + if max ~= nil and max > 256 then max = 256 end + if min ~= nil and max ~= nil and min > max then + min = max + end + return true, min, max, align +end + +--[[ (obsolete) +local function lengths_to_string(min, max) + if min == nil then + if max ~= nil then + return "-"..max + else + return "" + end + elseif max == nil then + return min.."-" + elseif min == max then + return tostring(min) + else + return min.."-"..max + end +end +]] + +local alphanum_chars_set = { + ["a"] = true, ["A"] = true, ["á"] = true, ["Á"] = true, ["ä"] = true, ["Ä"] = true, ["b"] = true, ["B"] = true, ["č"] = true, + ["Č"] = true, ["d"] = true, ["D"] = true, ["ď"] = true, ["Ď"] = true, ["e"] = true, ["E"] = true, ["é"] = true, ["É"] = true, + ["ě"] = true, ["Ě"] = true, ["f"] = true, ["F"] = true, ["g"] = true, ["G"] = true, ["h"] = true, ["H"] = true, ["i"] = true, + ["I"] = true, ["í"] = true, ["Í"] = true, ["j"] = true, ["J"] = true, ["k"] = true, ["K"] = true, ["l"] = true, ["L"] = true, + ["ĺ"] = true, ["Ĺ"] = true, ["ľ"] = true, ["Ľ"] = true, ["m"] = true, ["M"] = true, ["n"] = true, ["N"] = true, ["ň"] = true, + ["Ň"] = true, ["o"] = true, ["O"] = true, ["ó"] = true, ["Ó"] = true, ["ô"] = true, ["Ô"] = true, ["p"] = true, ["P"] = true, + ["q"] = true, ["Q"] = true, ["r"] = true, ["R"] = true, ["ŕ"] = true, ["Ŕ"] = true, ["ř"] = true, ["Ř"] = true, ["s"] = true, + ["S"] = true, ["š"] = true, ["Š"] = true, ["t"] = true, ["T"] = true, ["ť"] = true, ["Ť"] = true, ["u"] = true, ["U"] = true, + ["ú"] = true, ["Ú"] = true, ["ů"] = true, ["Ů"] = true, ["v"] = true, ["V"] = true, ["w"] = true, ["W"] = true, ["x"] = true, + ["X"] = true, ["y"] = true, ["Y"] = true, ["ý"] = true, ["Ý"] = true, ["z"] = true, ["Z"] = true, ["ž"] = true, ["Ž"] = true, + ["0"] = true, ["1"] = true, ["2"] = true, ["3"] = true, ["4"] = true, ["5"] = true, ["6"] = true, ["7"] = true, ["8"] = true, + ["9"] = true, +} + +local function dosadit(format, data, defaults) + assert(type(format) == "string") + assert(type(data) == "table") + if defaults == nil then + defaults = {} + end + local result = {} + local i = 1 + local e = 0 + local b = format:find("{", e + 1, true) + while b ~= nil do + if e < b - 1 then + table.insert(result, format:sub(e + 1, b - 1)) + end + e = format:find("}", b + 1, true) + if e == nil then + core.log("warning", "[advtrains_line_automation] dosadit(): invalid format: <"..format..">") + table.insert(result, format:sub(b, -1)) + break + end + local tag = format:sub(b + 1, e - 1) + local tag_name = tag + local tagfmt, tagalt = "" + local b2 = tag:find("[:|]") + if b2 == nil then + tag_name, tagfmt = tag, "" + else + tag_name = tag:sub(1, b2 - 1) + repeat + local e2 = tag:find("[:|]", b2 + 1) or (#tag + 1) + local c = tag:sub(b2, b2) + if c == ":" then + tagfmt = tag:sub(b2 + 1, e2 - 1) + elseif c == "|" then + tagalt = tag:sub(b2 + 1, e2 - 1) + end + b2 = e2 + until b2 >= #tag + end + local min, max, align + if tagfmt ~= "" then + local success + success, min, max, align = lengths_from_string(tagfmt) + end + tag = tag_name + if tag:len() < 4 and ch_core.utf8_length(tag) == 1 and alphanum_chars_set[tag] == nil then + -- speciální případ: zopakovat znak alespoň min-krát + if min ~= nil then + table.insert(result, string.rep(tag, min)) + else + table.insert(result, tag) + end + else + local value = data[tag] or tagalt or defaults[tag] or "ERR" + local len = ch_core.utf8_length(value) + if min ~= nil and len < min then + -- řetězec je kratší než minimum => prodloužit + local missing = min - len + if align == "left" then + table.insert(result, value) + table.insert(result, string.rep(" ", missing)) + elseif align == "right" then + table.insert(result, string.rep(" ", missing)) + table.insert(result, value) + else + table.insert(result, string.rep(" ", math.floor(missing / 2))) + table.insert(result, value) + table.insert(result, string.rep(" ", missing - math.floor(missing / 2))) + end + elseif max ~= nil and len > max then + -- řetězec je delší než maximum => oříznout + if align == "left" then + local pos = assert(ch_core.utf8_seek(value, 1, max)) + table.insert(result, value:sub(1, pos - 1)) + elseif align == "right" then + local pos = assert(ch_core.utf8_seek(value, 1, len - max)) + table.insert(result, value:sub(pos, -1)) + else + local overflow = len - max + local half = math.floor(overflow / 2) + local pos1 = assert(ch_core.utf8_seek(value, 1, half)) + local pos2 = (ch_core.utf8_seek(value, pos1, len - overflow) or 0) - 1 + table.insert(result, value:sub(pos1, pos2)) + end + else + -- řetězec odpovídá + table.insert(result, value) + end + end + b = format:find("{", e + 1, true) + end + table.insert(result, format:sub(e + 1, -1)) + return table.concat(result) +end + +-- Vrátí neuspořádaný seznam čísel řádků, které musejí být zformátovány pro daný formátovací řetězec. +local function ktere_radky(s, empty_as_string) + local set = {} + local list = {} + for match in s:gmatch("{[1-9][:|}]") do + local n = tonumber(match:sub(2,2)) + if n ~= nil and not set[n] then + set[n] = true + table.insert(list, n) + end + end + if list[1] == nil and empty_as_string then + return "" + else + return list + end +end + +local function sestavit_hlaseni(settings, settings_override, data, defaults) + local parts = {} + local function a(s) + table.insert(parts, s) + end + local function t(id) + if id == nil then + return "" + end + local key = "tx_"..id + local result = settings_override[key] or settings[key] + if result == nil then + local def = assert(hl_texty[hl_texty_id_to_idx[id]]) + result = assert(def.default or def.sample) + end + return result + end + assert(settings) + if settings_override == nil then + settings_override = {} + end + assert(data) + a(t("vcpp")) + a("{SEP}") + a(t(ifthenelse(data.TYPVLAKU ~= nil, "tvl", "vlk"))) + a("{SEP}") + if data.LINKA ~= nil then + a(t("lnky")) + a("{SEP}") + end + if data.VYCHOZI ~= nil then + a(t("zesm")) + a("{SEP}") + a(t("prijnk")) + else + a(t("prisnk")) + end + a("{SEP}") + if data.typ_zpozdeni == "+" then + a(t("zpoz")) + a("{SEP}") + elseif data.typ_zpozdeni == "-" then + a(t("zpozz")) + a("{SEP}") + end + if not data.CIL then + a(t("vkonc")) + elseif data.NASL then + a(t("pokrnc")) + else + a(t("pokrc")) + end + if data.ODJZA then + a(t("ozasek")) + else + a(".") + end + a("{SEP}") + a(t("dek")) + data = table.copy(data) + data.SEP = "{SEP}" + local s = table.concat(parts) + s = assert(dosadit(s, data, defaults)) + s = s:gsub(" *{SEP} *", "{SEP}") + while true do + t = s:gsub("{SEP}{SEP}", "{SEP}") + if t:len() == s:len() then + break + end + s = t + end + s = s:gsub("^{SEP}", "") + s = s:gsub("{SEP}$", "") + s = s:gsub("{SEP}", " ") + return s +end + +local function get_or_add_anns(stn) + local station = advtrains.lines.stations[stn] + if station == nil then + return nil -- dopravna neexistuje! + end + local anns = station.anns + if anns == nil then + anns = {} + station.anns = anns + end + return anns +end + +local cedule_default_settings = { + empty = "{-:5}", + fs = "", + pos = vector.zero(), + row = "{LINKA: 3} za {ODJZA: 2-3} s", + text = "{ZDE} - odjezdy\n{1}\n{2}", + text_rtf = {1, 2}, +} + +local function init_ann_data(stn, epos) + local anns = get_or_add_anns(stn) + if anns == nil then + return + end + local result = { + cedule = { + table.copy(cedule_default_settings), table.copy(cedule_default_settings), + table.copy(cedule_default_settings), table.copy(cedule_default_settings)}, + chat_dosah = 50, + fmt_delay = "{}", + fmt_negdelay = "-{}", + fmt_nodelay = "", + fn_firstupper = false, + fs_koleje = "", + koleje = "", + owner = "", + version = 3, + } + anns[epos] = result + return result +end + +local function get_ann_data(stn, epos, make_copy) + local anns = get_or_add_anns(stn) + if anns == nil then + core.log("error", "get_ann_data() called on non-existent stn '"..tostring(stn).."'!") + return nil + end + local ann = assert(anns[epos] or init_ann_data(stn, epos)) + if make_copy then + ann = table.copy(ann) + local cedule = {} + for i, v in ipairs(ann.cedule) do + cedule[i] = table.copy(v) + end + ann.cedule = cedule + end + return ann +end + +local function set_ann_data(stn, epos, data) + local anns = get_or_add_anns(stn) + if anns == nil then + return false + end + local ann = anns[epos] + if ann ~= nil then + for k, v in pairs(data) do + ann[k] = v + end + else + anns[epos] = table.copy(data) + end + return true +end + +local function attach_sign(stn, epos, i, sign_pos) + -- zkontrolovat rozhlas: + local rozhl_pos = advtrains.decode_pos(epos) + core.load_area(rozhl_pos) + local rozhl_node = core.get_node(rozhl_pos) + if rozhl_node.name ~= rozhlas_node_name then + return false, "Staniční rozhlas se nenachází na očekávané pozici." + end + local rozhl_meta = core.get_meta(rozhl_pos) + if rozhl_meta:get_string("stn") ~= stn then + return false, "Staniční rozhlas přísluší k jiné dopravně." -- vnitřní chyba! + end + local data = get_ann_data(stn, epos, false) + if data == nil then + return false, "Data staničního rozhlasu nebyla nalezena." + end + + -- zkontrolovat ceduli: + core.load_area(sign_pos) + local sign_node = core.get_node_or_nil(sign_pos) + if sign_node == nil or core.get_item_group(sign_node.name, "display_api") == 0 then + return false, "Toto není podporovaná cedule." + end + + -- zkontrolovat vzdálenost: + if vector.distance(rozhl_pos, sign_pos) > 1024 then + return false, "Cedule je příliš daleko. Vzdálenost může být max. 1024 metrů." + end + + -- zkontrolovat zadání: + local cedule = data.cedule[i] + if cedule == nil then + return false, "Chybný index." + end + + -- připojit: + local s = string.format("%d,%d,%d", sign_pos.x, sign_pos.y, sign_pos.z) + local fs = F(s) + cedule.fs = fs + cedule.pos = sign_pos + return true, "Cedule úspěšně připojena ke staničnímu rozhlasu." +end + +local function detach_sign(stn, epos, i) + -- zkontrolovat rozhlas: + local rozhl_pos = advtrains.decode_pos(epos) + core.load_area(rozhl_pos) + local rozhl_node = core.get_node(rozhl_pos) + if rozhl_node.name ~= rozhlas_node_name then + return false, "Staniční rozhlas se nenachází na očekávané pozici." + end + local rozhl_meta = core.get_meta(rozhl_pos) + if rozhl_meta:get_string("stn") ~= stn then + return false, "Staniční rozhlas přísluší k jiné dopravně." -- vnitřní chyba! + end + local data = get_ann_data(stn, epos, false) + if data == nil then + return false, "Data staničního rozhlasu nebyla nalezena." + end + + -- zkontrolovat zadání: + local cedule = data.cedule[i] + if cedule == nil then + return false, "Chybný index." + end + + if cedule.fs == "" then + return false, "Cedule není připojena." + end + + -- odpojit: + cedule.fs = "" + cedule.pos = vector.zero() + return true, "Cedule úspěšně odpojena." +end + + +local function init_formspec_callback(custom_state, player, formname, fields) + local player_name = player:get_player_name() + local pos = custom_state.pos + local node = core.get_node(pos) + if node.name ~= rozhlas_node_name then + core.chat_send_player(player_name, "CHYBA: staniční rozhlas nenalezen!") + return + end + if not core.check_player_privs(player, "railway_operator") then + core.chat_send_player(player_name, "*** K instalaci staničního rozhlasu je nutné právo railway_operator!") + return + end + if fields.dopravna then + local event = core.explode_textlist_event(fields.dopravna) + local new_index = tonumber(event.index) + if new_index ~= nil and custom_state.list[new_index] ~= nil then + custom_state.selection_index = new_index + end + if event.type == "DCL" then + fields.zvolit_dopravnu = "true" + end + end + if fields.zvolit_dopravnu and custom_state.selection_index ~= nil then + local stn = assert(custom_state.list[custom_state.selection_index].stn) + local stdata = advtrains.lines.stations[stn] + if stdata ~= nil then + local meta = core.get_meta(pos) + meta:set_string("infotext", "staniční rozhlas ("..(stdata.name or "???")..")") + meta:set_string("owner", player_name) + meta:set_string("stn", stn) + init_ann_data(stn, custom_state.epos) + core.chat_send_player(player_name, "*** Úspěšně nastaveno.") + end + end +end + +local function get_setup_formspec(custom_state) + local player_name = assert(custom_state.player_name) + local page = assert(custom_state.page) + local stations = assert(custom_state.stations) + local stn = custom_state.stn + local data = custom_state.data + + local formspec = { + "formspec_version[6]".. + "size[15,16.5]".. + -- "style_type[textarea;font=mono]".. + "tabheader[0,0;0.75;tab;Nastavení 1,Nastavení 2", + ifthenelse(custom_state.is_admin, ",Vlastnictví;", ";"), + custom_state.page..";false;true]".. + "button_exit[14,0.25;0.75,0.75;close;X]".. + "item_image[0.5,0.5;1,1;"..rozhlas_node_name.."]".. + "label[1.75,1;staniční rozhlas]".. + "label[9,0.75;vlastník/ice:\n", + ch_core.prihlasovaci_na_zobrazovaci(custom_state.owner), + "]", + } + local function a(x) + if type(x) == "table" then + for _, s in ipairs(x) do + table.insert(formspec, s) + end + else + table.insert(formspec, x) + end + end + if page == PAGE_SETUP_1 then + a( "container[0.5,1.5]".. + "label[0,0.5;dopravna:]".. + "dropdown[1.75,0.2;5,0.6;dopravna;") + for i, station in ipairs(stations) do + a(ifthenelse(i ~= 1, ",", "")..F(station.stn).." | "..F(station.name)) + end + a{";"..custom_state.station_index..";true]".. + "label[7,0.5;omezit jen na koleje:]".. + "field[10,0.2;4,0.6;koleje;;", + data.fs_koleje or "", + "]label[0,1.25;režim:]".. + "dropdown[1.75,1;3,0.6;rmode;odjezdy,příjezdy,odjezdy i příjezdy;", + tostring(data.rmode), + ";true]container_end[]", + -- ---- + "container[0.5,3.25]".. + "checkbox[0,0.25;fn_firstupper;první písmeno řádky odjezdu vždy velké;", + ifthenelse(ifthenelse(custom_state.fn_firstupper ~= nil, custom_state.fn_firstupper, data.fn_firstupper), "true", "false"), + "]field[0,1;3.25,0.75;fmt_nodelay;bez zpoždění;", F(data.fmt_nodelay or ""), "]".. + "field[4,1;3.25,0.75;fmt_delay;zpoždění ({} = číslo);", F(data.fmt_delay or "{}"), "]".. + "field[8,1;3.25,0.75;fmt_negdelay;záp.zpoždění ({} = číslo);", F(data.fmt_negdelay or "-{}"), "]".. + "tooltip[fmt_nodelay;Text pro značku {ZPOZDENI} v případě\\, že vlak jede bez zpoždění.]".. + "tooltip[fmt_delay;Formát pro značku {ZPOZDENI} v případě\\, že vlak má (kladné) zpoždění.\n".. + "Značka {} se nahradí počtem sekund zpoždění.]".. + "tooltip[fmt_negdelay;Formát pro značku {ZPOZDENI} v případě\\, že vlak má záporné zpoždění.\n".. + "Značka {} se nahradí absolutní hodnotou počtu sekund záporného zpoždění.]".. + "container_end[]".. + "container[0.5,10.5]".. + "box[0,0.15;14,0.05;#000000FF]"} + --[[ + -- hlášení do četu (zatím neimplementováno): + a{ "label[7,0.5;dosah hlášení v četu \\[m\\] (0=vyp):]".. + "field[11.5,0.25;2,0.5;chat_dosah;;", + tostring(data.chat_dosah or "50"), + "]".. + "tooltip[chat_dosah;Dosah je současně limitován doslechem jednotlivých hráčských postav.]".. + "label[0,0.5;texty pro hlášení příjezdu/přistavení vlaku v četu:]".. + "tablecolumns[text;text]".. + "table[0,0.75;14,2;texty;"} + for _, def in ipairs(hl_texty) do + local id = "tx_"..def.id + a(def.fs_sample) + a(",") + a(F(data[id] or def.default or def.sample)) + a(",") + end + formspec[#formspec] = ";"..(custom_state.tx_index or "").."]" + a("label[0.1,3.125;text:]".."field[0.75,2.75;10,0.75;preklad;;") + if custom_state.tx_index ~= nil then + local def = assert(hl_texty[custom_state.tx_index]) + local id = "tx_"..def.id + local s = data[id] + if s ~= nil then + a(F(s)) + else + a(def.fs_default) + end + end + a( "]".. + "button[11,2.75;3,0.75;preklad_set;nastavit]".. + "tooltip[preklad;V levém sloupci tabulky jsou vzorové texty\\, v pravém sloupci jsou texty\\,\n".. + "které budou na jejich místě použity pro tento staniční rozhlas (ty můžete měnit).]".. + "textarea[0,3.5;14,1;;;příklad: ") + local hlaseni = sestavit_hlaseni(data, custom_state, { + LINKA = "S1", + VYCHOZI = "Praha hl. n.", + typ_zpozdeni = "+", + CIL = "Bratislava hl. st.", + NASL = "Pardubice hl. n.", + ODJZA = "60", + ZPOZDENI = "13", + JMVLAKU = "Testovací expres", + KOLEJ = "A", + }) + a(F(hlaseni)) + a("]") + ]] + a("container_end[]"--[[.. + -- ---- + "container[0.5,9.25]".. + "label[0,0.25;Formáty řádků ({1}, {2} atd.):]".. + "container_end[]" ]] + ) + a("button[0.5,15;14,1;btn_save;Uložit]") + elseif page == PAGE_SETUP_2 then + a( "textarea[0.5,2;14,2;;;"..F("Pro nápovědu a použitou syntaxi viz wiki Českého hvozdu (článek „Staniční rozhlas“).").."]".. + "container[0.5,4]") + for i = 1, 4 do + local cedule = data.cedule[i] + local s = tostring(i) + a{ "container[0,", string.format("%f", 2.5 * (i - 1)), + "]label[0,0.3;cedule ", s, ":]".. + "field[0,0.9;9.75,0.75;fmt_cedule", s, "_row;formát řádky / prázdný řádek:;", + F(cedule.row), + "]field[0,1.75;9.75,0.75;fmt_cedule", s, "_empty;;", F(cedule.empty), + "]textarea[10,0.5;4,2;fmt_cedule", s, ";;"..F(cedule.text), + "]field[2,0;4.5,0.5;pos_cedule", s, ";;", cedule.fs, + "]button_exit[10,0;3,0.5;zam_cedule", s, + ";zaměřit...]button[6.75,0;3,0.5;", + ifthenelse(cedule.fs ~= "", "odp_cedule"..s..";odpojit", "pri_cedule"..s..";připojit"), + "]container_end[]", + } + end + a{ "container_end[]".. + "label[0.5,14.5;", CF(custom_state.message), "]".. + "button[0.5,15;14,1;btn_save;Uložit]", + } + elseif custom_state.is_admin and page == PAGE_OWNERSHIP then + a{ + "field[0.5,2.5;5,0.75;owner;vlastník/ice:;", + ch_core.prihlasovaci_na_zobrazovaci(custom_state.owner), + "]button[5.75,2.5;3,0.75;set_owner;nastavit]".. + "label[0.5,13;", + CF(custom_state.message), + "]button_exit[0.5,15;14,1;close2;zavřít]", + } + end + return table.concat(formspec) +end + +local function setup_formspec_callback(custom_state, player, formname, fields) + assert(player:get_player_name() == custom_state.player_name) + local node = core.get_node(custom_state.pos) + if node.name ~= rozhlas_node_name then + return + end + local is_admin = custom_state.is_admin + local page = custom_state.page + local stn = custom_state.stn + local data = custom_state.data + + if page == PAGE_SETUP_1 then + if + fields.dopravna and + fields.dopravna ~= tostring(custom_state.station_index) and + tonumber(fields.dopravna) ~= nil and + custom_state.stations[tonumber(fields.dopravna)] ~= nil + then + -- přepnout dopravnu + custom_state.station_index = tonumber(fields.dopravna) + end + if fields.rmode then + local new_rmode = tonumber(fields.rmode) + if new_rmode ~= nil and new_rmode ~= data.rmode then + data.rmode = new_rmode + end + end + + -- zaškrtávací pole: + if fields.fn_firstupper then + data.fn_firstupper = ifthenelse(fields.fn_firstupper == "true", true, false) + end + + -- koleje: + if fields.koleje and F(fields.koleje) ~= data.fs_koleje then + local parts = string.split(fields.koleje, ",", false) + local list, set = {}, {} + for _, part in ipairs(parts) do + if not set[part] then + set[part] = true + table.insert(list, part) + end + end + if #list == 0 then + -- všechny koleje + data.fs_koleje = "" + data.koleje = "" + elseif #list == 1 then + data.fs_koleje = F(list[1]) + data.koleje = list[1] + else + table.sort(list, function(a, b) return a < b end) + data.fs_koleje = F(table.concat(list, ",")) + data.koleje = set + end + end + if fields.fmt_delay then + data.fmt_delay = fields.fmt_delay + end + if fields.fmt_nodelay then + data.fmt_nodelay = fields.fmt_nodelay + end + if fields.fmt_negdelay then + data.fmt_negdelay = fields.fmt_negdelay + end + + -- texty + if fields.texty then + local event = core.explode_table_event(fields.texty) + if (event.type == "CHG" or event.type == "DCL") and event.row ~= custom_state.tx_index and hl_texty[event.row] ~= nil then + custom_state.tx_index = event.row + end + end + + if fields.preklad_set and custom_state.tx_index then + local def = assert(hl_texty[custom_state.tx_index]) + custom_state.data["tx_"..def.id] = assert(fields.preklad) + return get_setup_formspec(custom_state) + end + if fields.chat_dosah and tonumber(fields.chat_dosah) ~= nil then + local new_dosah = tonumber(fields.chat_dosah) + if new_dosah ~= nil and new_dosah == math.floor(new_dosah) and new_dosah >= 0 and new_dosah < 1024 then + custom_state.data.chat_dosah = new_dosah + end + end + + -- uložit? + if fields.btn_save then + local new_stn = custom_state.stations[custom_state.station_index].stn + if new_stn ~= stn then + -- přesunout rozhlas na jinou dopravnu + local meta = assert(core.get_meta(custom_state.pos)) + local old_anns = get_or_add_anns(stn) + local new_anns = get_or_add_anns(new_stn) + if old_anns ~= nil and new_anns ~= nil then + local epos = custom_state.epos + new_anns[epos] = old_anns[epos] + old_anns[epos] = nil + meta:set_string("stn", new_stn) + stn = new_stn + custom_state.stn = new_stn + end + end + set_ann_data(stn, custom_state.epos, data) + custom_state.data = get_ann_data(stn, custom_state.epos, true) + -- update infotext: + local meta = core.get_meta(custom_state.pos) + local station_name = al.get_station_name(meta:get_string("stn")) + local koleje = custom_state.data.fs_koleje + if koleje ~= "" then + koleje = " ["..koleje:gsub("\\", "").."]" + end + meta:set_string("infotext", "staniční rozhlas\n"..station_name..koleje) + end + elseif page == PAGE_SETUP_2 then + -- update fields: + for i = 1, 4 do + local cedule = data.cedule[i] + local s = fields["fmt_cedule"..i] + if s ~= nil then + cedule.text = s + cedule.text_rtf = ktere_radky(s, true) + end + s = fields["fmt_cedule"..i.."_row"] + if s ~= nil then + cedule.row = s + end + s = fields["fmt_cedule"..i.."_empty"] + if s ~= nil then + cedule.empty = s + end + end + if fields.btn_save then + local new_cedule = {} + for i = 1, 4 do + new_cedule[i] = table.copy(data.cedule[i]) + end + get_ann_data(stn, custom_state.epos, false).cedule = new_cedule + return get_setup_formspec(custom_state) + else + for i = 1, 4 do + if fields["pri_cedule"..i] then + -- připojit ceduli: + local pos_cedule_str = fields["pos_cedule"..i] + local x, y, z = pos_cedule_str:match("^(-?%d+),(-?%d+),(-?%d+)$") + if x == nil or y == nil or z == nil then + custom_state.message = "Chybný formát pozice: "..pos_cedule_str + return get_setup_formspec(custom_state) + end + local pos_cedule = vector.new(tonumber(x), tonumber(y), tonumber(z)) + local success, message = attach_sign(stn, custom_state.epos, i, pos_cedule) + if success then + local current_data = get_ann_data(stn, custom_state.epos, true) + if current_data ~= nil then + custom_state.data = current_data + end + custom_state.message = "Cedule úspěšně připojena." + else + custom_state.message = "CHYBA: "..message + end + return get_setup_formspec(custom_state) + end + if fields["odp_cedule"..i] then + local success, message = detach_sign(stn, custom_state.epos, i) + if success then + local current_data = get_ann_data(stn, custom_state.epos, true) + if current_data ~= nil then + custom_state.data = current_data + end + custom_state.message = "Cedule úspěšně odpojena." + else + custom_state.message = "CHYBA: "..message + end + return get_setup_formspec(custom_state) + end + if fields["zam_cedule"..i] then + local player_name = player:get_player_name() + local stack = player:get_wielded_item() + if stack:is_empty() then + player:set_wielded_item("advtrains_line_automation:sign_selector") + core.chat_send_player(player_name, "*** Levým klikem vyberte ceduli k připojení...") + else + player:get_inventory():add_item("main", "advtrains_line_automation:sign_selector") + core.chat_send_player(player_name, "*** Levým klikem výběrovým nástrojem vyberte ceduli k připojení...") + end + punch_context[player_name] = {stn = assert(custom_state.stn), epos = custom_state.epos, i = i} + return + end + end + end + elseif page == PAGE_OWNERSHIP then + if is_admin and fields.set_owner then + local new_owner = ch_core.jmeno_na_existujici_prihlasovaci(fields.owner or "") + if new_owner == nil then + custom_state.message = "Postava '"..(fields.owner or "").."' neexistuje!" + else + local ann_data = assert(get_ann_data(stn, custom_state.epos, false)) + ann_data.owner = new_owner + core.load_area(custom_state.pos) + local meta = core.get_meta(custom_state.pos) + meta:set_string("owner", new_owner) + custom_state.owner = new_owner + end + return get_setup_formspec(custom_state) + end + end + if fields.tab then + -- přepnout stránku + local new_tab = tonumber(fields.tab) + if new_tab ~= nil and new_tab ~= page and ( + new_tab == PAGE_SETUP_1 or new_tab == PAGE_SETUP_2 or (is_admin and new_tab == PAGE_OWNERSHIP) + ) then + custom_state.page = new_tab + custom_state.message = "" -- smazat zprávu + end + return get_setup_formspec(custom_state) + end + if not fields.quit then + return get_setup_formspec(custom_state) + end +end + +local function fill(line, prefix, rwtime_now, rwtime_value) + if rwtime_value == nil then + return + end + local secs = math.ceil((rwtime_value - rwtime_now) / 5) * 5 + if secs < 0 then + secs = -secs + line[prefix.."_Z"] = "-" + end + local n_s = secs % 60 + local n_m = (secs - n_s) / 60 + local s = tostring(n_s) + local m = tostring(n_m) + line[prefix] = tostring(secs) + line[prefix.."_M"] = m + line[prefix.."_S"] = s + if n_m < 10 then + line[prefix.."_MM"] = "0"..m + else + line[prefix.."_MM"] = m + end + if n_s < 10 then + line[prefix.."_SS"] = "0"..s + else + line[prefix.."_SS"] = s + end +end + +--[[ + stn -- kód dopravny, jejíž příjezdy/odjezdy se budou zapisovat na cedule + epos -- kódovaná pozice st. rozhlasu (pro načtení dat) + signs -- (volitelný) seznam až 4 pozic cedulí, které se budou aktualizovat + records -- viz níže + rwtime -- aktuální žel. čas (číselná forma) + --- + records je tabulka záznamů z al.predict_train(), doplněných o následující pole: + start = string or nil, -- název výchozí zastávky, pokud vlak odněkud přijíždí, jinak "" + destination = string, -- název cílové zastávky, pokud vlak někam pokračuje, jinak "" + prev_stop = string, -- název předchozí zastávky, pokud vlak odněkud přijíždí a pokud jde o jinou zastávku než 'start', jinak "" + next_stop = string, -- název násl. zast., pokud vlak pokračuje a pokud jde o jinou zastávku než 'destination', jinak "" + last_pos = string, -- název poslední známé polohy vlaku, nebo "" + -- + stn = string, track = string, delay = int, + stdata = table or nil, + dep = int or nil, dep_linevar_def = table or nil, dep_index = int or nil, + arr = int or nil, arr_linevar_def = table or nil, arr_index = int or nil, +]] + +local function update_ann(stn, epos, signs, records, rwtime) + local ann = get_ann_data(stn, epos) + if ann == nil then + core.log("error", "update_ann() called for "..stn.."/"..epos..", but ann is nil!") + return + end + local tracks + local any_line = { + ZDE = al.get_station_name(stn), + } + if ann.fs_koleje ~= "" then + any_line.KOLEJE = ann.fs_koleje:gsub("\\", "") + tracks = ann.koleje + if tracks ~= nil and type(tracks) ~= "table" then + if tracks == "" then + tracks = nil + else + tracks = {[tracks] = true} + end + end + end + if has_ch_time then + local cas = ch_time.aktualni_cas() + any_line.HH = string.format("%02d", cas.hodina) + any_line.MM = string.format("%02d", cas.minuta) + any_line.SS = string.format("%02d", cas.sekunda) + end + local lines = {} + for _, record in ipairs(records) do + assert(record.start ~= nil) + assert(record.destination ~= nil) + assert(record.prev_stop ~= nil) + assert(record.next_stop ~= nil) + assert(record.last_pos ~= nil) + if + (tracks == nil or tracks[record.track]) and ( + (ann.rmode == RMODE_ARR and record.arr ~= nil) or + (ann.rmode == RMODE_DEP and record.dep ~= nil) or + (ann.rmode == RMODE_BOTH and (record.arr ~= nil or record.dep ~= nil)) + ) + then + local linevar_def, index + if record.dep ~= nil then + linevar_def, index = record.dep_linevar_def, record.dep_index + else + linevar_def, index = record.arr_linevar_def, record.arr_index + end + local stops = linevar_def.stops + local line = setmetatable({}, {__index = any_line}) + line.LINKA = advtrains.lines.linevar_decompose(linevar_def.name) + assert(line.LINKA) + if record.start ~= "" then + line.VYCHOZI = record.start + end + if record.destination ~= "" then + line.CIL = record.destination + end + if record.track ~= "" then + line.KOLEJ = record.track + end + fill(line, "PRIJZA", rwtime, record.arr) + fill(line, "ODJZA", rwtime, record.dep) + local abs_delay = math.abs(record.delay) + if abs_delay < 5 then + line.ZPOZDENI = ann.fmt_nodelay or "" + else + line.ZPOZDENI = dosadit( + ifthenelse(record.delay > 0, ann.fmt_delay or "{}", ann.fmt_negdelay or "-{}"), + {[""] = tostring(5 * math.ceil(abs_delay / 5))} + ) + end + -- PREDCH + if record.prev_stop ~= "" then + line.PREDCH = record.prev_stop + end + -- NASL + if record.next_stop ~= "" then + line.NASL = record.next_stop + end + -- POLOHA + if record.last_pos ~= "" then + line.POLOHA = record.last_pos + end + -- JMVLAKU + if linevar_def.train_name ~= nil then + line.JMVLAKU = linevar_def.train_name + end + -- TYP + line.TYP = "Os" + -- TYPVLAKU + line.TYPVLAKU = "osobní vlak" + table.insert(lines, line) + end + end + if signs ~= nil then + local empty_table = {} + for i = 1, 4 do + local cedule = ann.cedule[i] + local sign_pos = signs[i] + if sign_pos ~= nil and core.compare_block_status(sign_pos, "active") then + local formatted_lines = setmetatable({}, {__index = any_line}) + if type(cedule.text_rtf) == "table" then + for _, i_row in ipairs(cedule.text_rtf) do + if lines[i_row] == nil then + -- prázdný řádek + formatted_lines[tostring(i_row)] = dosadit(cedule.empty, empty_table) + else + -- řádek odjezdu + local s = dosadit(cedule.row, lines[i_row]) + if s ~= "" and ann.fn_firstupper then + local l = ch_core.utf8_seek(s, 1, 1) + if l == nil then + s = ch_core.na_velka_pismena(s) + else + local c = s:sub(1, l - 1) + local uc = ch_core.na_velka_pismena(c) + if uc ~= c then + s = uc..s:sub(l, -1) + end + end + end + formatted_lines[tostring(i_row)] = s + end + end + end + local s = dosadit(cedule.text, formatted_lines) + if has_signs_api then + signs_api.set_display_text(sign_pos, s) + end + end + end + end +end + +local globalstep_time = -5 +local first_run = true + +local function first_globalstep() + for stn, stdata in pairs(advtrains.lines.stations) do + local anns = stdata.anns + if stdata.anns ~= nil then + for rozh_epos, ann in pairs(stdata.anns) do + if ann.version < 2 then + -- upgrade version 1 to version 2: + ann.rmode = RMODE_DEP + ann.version = 2 + end + if ann.version < 3 then + local pos = advtrains.decode_pos(rozh_epos) + core.load_area(pos) + local meta = core.get_meta(pos) + ann.owner = meta:get_string("owner") + core.log("action", "strozhlas at position "..core.pos_to_string(pos).." upgraded to metadata version 3 (owner "..ann.owner..")") + ann.version = 3 + end + end + end + end +end + +local function get_start_by_linevar_def(cache, linevar_def) + local result = cache[linevar_def.name] + if result == nil then + result = al.get_line_description(linevar_def, {first_stop = true, last_stop = false, last_stop_prefix = ""}) + if result == "???" then + result = "" + end + cache[linevar_def.name] = result + end + return result +end + +local function get_destination_by_linevar_def(cache, linevar_def) + local result = cache[linevar_def.name] + if result == nil then + result = al.get_line_description(linevar_def, {first_stop = false, last_stop = true, last_stop_prefix = ""}) + if result == "???" then + result = "" + end + cache[linevar_def.name] = result + end + return result +end + +local function get_name_by_stn(cache, stn, alt) + local result = cache[stn] + if result == nil then + result = al.get_station_name(stn) + if result == "???" then + result = "" + end + cache[stn] = result + end + if result == "" then + return alt + else + return result + end +end + +local function globalstep(dtime) + globalstep_time = globalstep_time + dtime + if globalstep_time < 0 then + return + end + globalstep_time = globalstep_time - 5 + + if first_run then + first_run = false + return first_globalstep() + end + + local rwtime = rwt.to_secs(rwt.get_time()) + local rwtime_limit = rwtime + 3600 + -- Shromáždit rozhlasy: + local subscriptions = {--[[ + [stn] = {{ + rozh_pos = vector, + rozh_epos = string, + rozh_def = table, + signs = {[1..4] = vector or nil} or nil, + }...} + ]]} + local signs = {} + for stn, stdata in pairs(advtrains.lines.stations) do + local anns = stdata.anns + if stdata.anns ~= nil then + for rozh_epos, ann in pairs(stdata.anns) do + local rozh_pos = advtrains.decode_pos(rozh_epos) + local is_admin = core.check_player_privs(assert(ann.owner), "protection_bypass") + local signs_count = 0 + for i = 1, 4 do + local cedule = assert(ann.cedule[i]) + local s = cedule.fs + if s ~= nil and s ~= "" then + local sign_pos = assert(cedule.pos) + if core.compare_block_status(sign_pos, "active") then + signs[i] = sign_pos + signs_count = signs_count + 1 + if not is_admin then + local sign_meta = core.get_meta(sign_pos) + local sign_owner = sign_meta:get_string("owner") + if sign_meta:get_int("access_level") ~= 1 and sign_owner ~= "" and ann.owner ~= sign_owner then + -- unauthorized access to the sign + core.log("warning", "Station announcement "..core.pos_to_string(rozh_pos).." owned by '".. + ann.owner.."' cannot write to sign at "..core.pos_to_string(sign_pos).." owned by '".. + sign_owner.."'! Access denied.") + signs[i] = nil + signs_count = signs_count - 1 + end + end + end + end + end + if signs_count > 0 then + -- nějaké cedule jsou aktivní + table.insert(goa(subscriptions, stn), {rozh_pos = rozh_pos, rozh_epos = rozh_epos, rozh_def = ann, signs = signs}) + signs = {} + elseif core.compare_block_status(rozh_pos, "active") then + -- cedule nejsou aktivní, ale rozhlas ano + table.insert(goa(subscriptions, stn), {rozh_pos = rozh_pos, rozh_epos = rozh_epos, rozh_def = ann}) + end + end + end + end + + -- Shromáždit vlaky: + local by_stn = {} + for stn, _ in pairs(subscriptions) do + by_stn[stn] = {} + end + local start_by_linevar = {} + local destination_by_linevar = {} + local name_by_stn = {} + + for _, train in pairs(advtrains.trains) do + local ls, linevar_def = al.get_line_status(train) + if linevar_def ~= nil then + local prediction = al.predict_train(ls, linevar_def, rwtime, true) + local last_pos = al.get_last_pos_station_name(ls) or "" + for i, record in ipairs(prediction) do + local records = by_stn[record.stn] + if + records ~= nil and + (record.dep ~= nil or record.arr ~= nil) and + (record.dep == nil or record.dep < rwtime_limit) and + (record.arr == nil or record.arr < rwtime_limit) + then + -- nutno doplnit: start, destination, prev_stop, next_stop, last_pos + record.last_pos = last_pos -- název poslední dopravny, kde byl vlak spatřen + if record.final and record.arr ~= nil and record.dep ~= nil and record.arr_linevar_def.name ~= record.dep_linevar_def.name then + -- změna linky => rozdělit na příjezd a odjezd + local record2 = table.copy(record) + record2.dep = nil + record2.dep_linevar_def = nil + record2.dep_index = nil + record2.start = get_start_by_linevar_def(start_by_linevar, record.arr_linevar_def) + local other_index, other_data = al.get_prev_stop(record.arr_linevar_def, record.arr_index, false) + if other_index ~= nil then + record2.prev_stop = get_name_by_stn(name_by_stn, other_data.stn, "") + end + record2.next_stop = "" + record2.destination = get_destination_by_linevar_def(destination_by_linevar, record.arr_linevar_def) + table.insert(records, record2) + record.arr = nil + record.arr_linevar_def = nil + record.arr_index = nil + record.start = get_start_by_linevar_def(start_by_linevar, record.dep_linevar_def) + record.prev_stop = "" + other_index, other_data = al.get_next_stop(record.dep_linevar_def, record.dep_index, false) + if other_index ~= nil then + record.next_stop = get_name_by_stn(name_by_stn, other_data.stn, "") + end + record.destination = get_destination_by_linevar_def(destination_by_linevar, record.dep_linevar_def) + table.insert(records, record) + elseif record.dep ~= nil then + -- odjezd nebo příjezd/odjezd (ale na stejné lince!) + local linevar_def = record.dep_linevar_def + local index = record.dep_index + record.start = get_start_by_linevar_def(start_by_linevar, linevar_def) + record.destination = get_destination_by_linevar_def(destination_by_linevar, linevar_def) + record.prev_stop = "" + record.next_stop = "" + local other_index, other_data + if record.arr ~= nil then + other_index, other_data = al.get_prev_stop(linevar_def, index, false) + if other_index ~= nil then + record.prev_stop = get_name_by_stn(name_by_stn, other_data.stn, "") + end + end + other_index, other_data = al.get_next_stop(linevar_def, index, false) + if other_index ~= nil then + record.next_stop = get_name_by_stn(name_by_stn, other_data.stn, "") + end + record.destination = get_destination_by_linevar_def(destination_by_linevar, linevar_def) + table.insert(records, record) + elseif record.arr ~= nil then + -- jen příjezd + local linevar_def = record.arr_linevar_def + local index = record.arr_index + record.start = get_start_by_linevar_def(start_by_linevar, linevar_def) + local other_index, other_data = al.get_prev_stop(linevar_def, index, false) + if other_index ~= nil then + record.prev_stop = get_name_by_stn(name_by_stn, other_data.stn, "") + else + record.prev_stop = "" + end + record.next_stop = "" + record.destination = get_destination_by_linevar_def(destination_by_linevar, linevar_def) + table.insert(records, record) + end + end + end + end + end + + -- Aktualizovat rozhlasy: + for stn, records in pairs(by_stn) do + local deps = records + local arrs = table.copy(records) + table.sort(deps, function(a, b) + return assert(a.dep or a.arr) < assert(b.dep or b.arr) + end) + table.sort(arrs, function(a, b) + return (a.arr or 1.0e+100) < (b.arr or 1.0e+100) + end) + for i = #arrs, 1, -1 do + if arrs[i].arr == nil then + arrs[i] = nil -- ponechat jen příjezdy v rámci nejbližší hodiny + end + end + for _, ann in ipairs(subscriptions[stn]) do + if ann.rmode ~= RMODE_ARR then + update_ann(assert(stn), assert(ann.rozh_epos), ann.signs, deps, rwtime) + else + update_ann(assert(stn), assert(ann.rozh_epos), ann.signs, arrs, rwtime) + end + end + end +end + +core.register_globalstep(globalstep) + +local function can_dig(pos, player) + if player ~= nil and core.is_player(player) then + local owner = core.get_meta(pos):get_string("owner") + local player_name = player:get_player_name() + return owner == "" or owner == player_name or core.check_player_privs(player_name, "protection_bypass") + end + return false +end + +local function on_construct(pos) + local meta = core.get_meta(pos) + meta:set_string("infotext", "staniční rozhlas") +end + +local function on_destruct(pos) + local meta = core.get_meta(pos) + local stn = meta:get_string("stn") + if stn ~= "" then + local epos = advtrains.encode_pos(pos) + local stdata = advtrains.lines.stations[stn] + if stdata ~= nil then + (stdata.anns or {})[epos] = nil -- odstranit data staničního rozhlasu + end + end +end + +local function on_dig(pos, node, digger) + -- TODO? + -- return core.node_dig(pos, node, digger) + if not can_dig(pos, digger) then + return false + end + if has_unifieddyes then + return unifieddyes.on_dig(pos, node, digger) + else + return core.node_dig(pos, node, digger) + end +end + +local function on_rightclick(pos, node, clicker, itemstack, pointed_thing) + if clicker == nil or not clicker:is_player() then + return + end + local player_name = assert(clicker:get_player_name()) + local meta = core.get_meta(pos) + local owner = meta:get_string("owner") + local stn = meta:get_string("stn") + local epos = advtrains.encode_pos(pos) + local stdata = advtrains.lines.stations[stn] + if owner == "" or stn == "" or stdata == nil or stdata.anns == nil or stdata.anns[epos] == nil then + if not core.check_player_privs(clicker, "railway_operator") then + core.chat_send_player(player_name, "*** K instalaci staničního rozhlasu je nutné právo railway_operator!") + return + end + local limit = 256 * 256 + local stations = advtrains.lines.load_stations_for_formspec() + local list = {} + for i, station_data in ipairs(stations) do + local d2 = limit + for _, stop in ipairs(station_data.tracks) do + local d2new = dist2(pos, stop.pos) + if d2new < d2 then + d2 = d2new + end + end + if d2 < limit then + table.insert(list, { + stn = assert(station_data.stn), + name = assert(station_data.name), + distance = math.floor(math.sqrt(d2)), + }) + end + end + table.sort(list, function(a, b) return a.distance < b.distance end) + local custom_state = { + pos = pos, + epos = advtrains.encode_pos(pos), + list = list, + } + local formspec = { + "formspec_version[6]".. + "size[15,8]".. + "button_exit[14,0.25;0.75,0.75;close;X]".. + "item_image[0.5,0.5;1,1;"..rozhlas_node_name.."]".. + "label[1.75,1;staniční rozhlas]".. + "label[0.5,2.25;vyberte dopravnu:]".. + "button_exit[0.5,6.75;14,1;zvolit_dopravnu;Zvolit]".. + "textlist[0.5,2.5;14,4;dopravna;", + } + if #list > 0 then + for i, candidate in ipairs(list) do + if i ~= 1 then + table.insert(formspec, ",") + end + table.insert(formspec, F(candidate.stn.." | "..candidate.name.." ("..candidate.distance.." m)")) + end + custom_state.selection_index = 1 + table.insert(formspec, ";1]") + else + table.insert(formspec, ";]") + end + ch_core.show_formspec(clicker, "advtrains_line_automation:init_rozhlas", table.concat(formspec), init_formspec_callback, custom_state, {}) + return + end + if player_name ~= owner and not core.check_player_privs(player_name, "protection_bypass") then -- train_admin? + core.chat_send_player(player_name, "Nemáte oprávnění nastavovat tento staniční rozhlas!") + return + end + local stations = advtrains.lines.load_stations_for_formspec() + local selection_index + for i, station in ipairs(stations) do + if station.stn == stn then + selection_index = i + break + end + end + if selection_index == nil then + core.log("error", "Cannot determine selection index for the station '"..stn.."' in the list of stations!") + core.chat_send_player(player_name, "CHYBA: Vnitřní chyba: dopravna nenalezena ve výpisu!") + return + end + local custom_state = { + player_name = player_name, + is_admin = ch_core.get_player_role(player_name) == "admin", + pos = pos, + epos = epos, + owner = owner, + stn = assert(stn), -- kam byl staniční rozhlas dosud přiřazen + stations = stations, -- pro dropdown[] + station_index = selection_index, -- pro dropdown[] + data = assert(get_ann_data(stn, epos, true)), + page = PAGE_SETUP_1, + } + ch_core.show_formspec(clicker, "advtrains_line_automation:rozhlas", get_setup_formspec(custom_state), + setup_formspec_callback, custom_state, {}) +end + +local function selector_on_use(itemstack, user, pointed_thing) + if pointed_thing.type ~= "node" then + return + end + if user ~= nil then + local player_name = user:get_player_name() + local pc = punch_context[player_name] + if pc ~= nil then + local success, message = attach_sign(assert(pc.stn), assert(pc.epos), assert(pc.i), assert(pointed_thing.under)) + if not success then + message = message.." Zaměřování bude zrušeno." + end + core.chat_send_player(player_name, message) + end + end + return ItemStack() +end + +local box = { + type = "fixed", + fixed = { + {-0.125, 0.125, 0.25, 0.125, 0.5, 0.5}, + {-0.25, 0.05, 0, 0.25, 0.45, 0.25}, + }, +} + +def = { + description = "staniční rozhlas", + tiles = {{name = "default_steel_block.png", backface_culling = true}}, + drawtype = "mesh", + mesh = "advtrains_tuber.obj", + paramtype = "light", + paramtype2 = "4dir", + selection_box = box, + collision_box = box, + groups = {oddly_breakable_by_hand = 3, ud_param2_colorable = 1}, + sounds = default.node_sound_metal_defaults(), + can_dig = can_dig, + on_dig = on_dig, + on_construct = on_construct, + on_destruct = on_destruct, + on_rightclick = on_rightclick, +} +if has_unifieddyes then + def.paramtype2 = "color4dir" + def.palette = "unifieddyes_palette_color4dir.png" +end +core.register_node(rozhlas_node_name, def) + +def = { + description = "levým klikem zvolte ceduli pro připojení", + groups = {not_in_creative_inventory = 1, tool = 1}, + inventory_image = "[combine:16x16:7,0=blank.png\\^[noalpha\\^[resize\\:2x16:0,7=blank.png\\^[noalpha\\^[resize\\:16x2", + wield_image = "blank.png", + on_use = selector_on_use, +} + +core.register_tool("advtrains_line_automation:sign_selector", def) + +core.register_craft({ + output = rozhlas_node_name, + recipe = { + {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}, + {"default:steel_ingot", "mesecons_noteblock:noteblock", "default:steel_ingot"}, + {"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}, + }, +}) diff --git a/advtrains_line_automation/station_editor.lua b/advtrains_line_automation/station_editor.lua new file mode 100644 index 0000000..ceeeffe --- /dev/null +++ b/advtrains_line_automation/station_editor.lua @@ -0,0 +1,1054 @@ +local def +local F = minetest.formspec_escape +local ifthenelse = function(cond, a, b) if cond then return a else return b end end +local rwt = assert(advtrains.lines.rwt) + +local function load_stations() + local result = {} + local by_stn = {} + local all_lines_set = {["*"] = true} + local lines_set = {} + for stn, data in pairs(advtrains.lines.stations) do + local new_station = { + stn = stn, + name = data.name or "", + owner = data.owner or "", + tracks = {}, + } + by_stn[stn] = new_station + lines_set[stn] = {} + table.insert(result, new_station) + end + for epos, data in pairs(advtrains.lines.stops) do + if data.stn ~= nil and data.stn ~= "" and by_stn[data.stn] ~= nil then + local st = by_stn[data.stn] + local new_track = { + epos = epos, + pos = assert(advtrains.decode_pos(epos)), + track = data.track or "", + } + table.insert(st.tracks, new_track) + local ars = data.ars + if ars ~= nil then + if ars.default then + lines_set[data.stn] = all_lines_set + else + local set = lines_set[data.stn] + for _, rule in ipairs(ars) do + if not rule.n and rule.ln ~= nil then + set[rule.ln] = true + end + end + end + end + end + end + for _, st in ipairs(result) do + local set = lines_set[st.stn] + if set["*"] then + st.lines = {"*"} + else + local lines = {} + for line, _ in pairs(set) do + table.insert(lines, line) + end + table.sort(lines) + st.lines = lines + end + end + table.sort(result, function(a, b) return a.stn < b.stn end) + return result +end + +advtrains.lines.load_stations_for_formspec = load_stations + +local function station_distance(player_pos, st) + if st.tracks[1] == nil then + return -- no tracks + end + local distance = vector.distance(player_pos, st.tracks[1].pos) + for i = 2, #st.tracks do + local d2 = vector.distance(player_pos, st.tracks[i].pos) + if d2 < distance then + distance = d2 + end + end + return distance +end + +local function station_distance_s(player_pos, st) + local result = station_distance(player_pos, st) + if result ~= nil then + return string.format("%d m", result) + else + return "" + end +end + + +local function filter_all() + return true +end + +local function filter_mine(a, player_info) + return a.owner == player_info.name +end + +local function sort_by_distance(a, b, player_info) + return (station_distance(player_info.pos, a) or 1.0e+20) < (station_distance(player_info.pos, b) or 1.0e+20) +end + +local function sort_by_stn(a, b) + return tostring(a.stn) < tostring(b.stn) +end + +local function sort_by_name(a, b) + local a_key, b_key = ch_core.utf8_radici_klic(a.name, false), ch_core.utf8_radici_klic(b.name, false) + if a_key ~= b_key then + return a_key < b_key + else + return sort_by_stn(a, b) + end +end + +local filters = { + { + description = "od nejbližší", + -- filter = filter_all, + sorter = sort_by_distance, + }, + { + description = "podle kódu A-Z", + -- filter = filter_all, + sorter = sort_by_stn, + }, + { + description = "podle názvu A-Z", + -- filter = filter_all, + sorter = sort_by_name, + }, + { + description = "moje (podle kódu A-Z)", + filter = filter_mine, + sorter = sort_by_stn, + }, + { + description = "moje (podle názvu A-Z)", + filter = filter_mine, + sorter = sort_by_name, + }, + { + description = "moje (od nejbližší)", + filter = filter_mine, + sorter = sort_by_distance, + }, +} + + +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 player_info = { + name = assert(custom_state.player_name), + pos = assert(custom_state.player_pos), + } + + local stations = custom_state.stations + if stations == nil then + local filter = filters[custom_state.active_filter] + local filter_func = filter.filter or filter_all + local sorter_func = filter.sorter + stations = {} + for _, st in ipairs(load_stations()) do + if filter_func(st, player_info) then + table.insert(stations, st) + end + end + table.sort(stations, function(a, b) return sorter_func(a, b, player_info) end) + custom_state.stations = stations + if custom_state.selection_index ~= nil and custom_state.selection_index > #stations + 1 then + custom_state.selection_index = nil + end + end + + local selection_index = custom_state.selection_index + local formspec = { + ch_core.formspec_header({formspec_version = 6, size = {20, 10}, auto_background = true}), + "label[0.5,0.6;Editor dopraven]", + } + + table.insert(formspec, "tablecolumns[image,".. + "0=ch_core_empty.png,".. + "1=basic_materials_padlock.png\\^[resize:16x16".. + ";text;text,width=25;color,span=1;text,width=7;text;text]".. + "table[0.5,1.25;19,5;dopravna;0,KÓD,NÁZEV,#ffffff,SPRAVUJE,VZDÁLENOST,INFO") + for _, st in ipairs(stations) do + local n_tracks = #st.tracks + table.insert(formspec, ",0,"..F(st.stn)..","..F(st.name)..",#ffffff,"..F(ch_core.prihlasovaci_na_zobrazovaci(st.owner))..",".. + F(station_distance_s(custom_state.player_pos, st))..","..n_tracks.." kolej") + if n_tracks < 1 or n_tracks > 4 then + table.insert(formspec, "í") + elseif n_tracks ~= 1 then + table.insert(formspec, "e") + end + if n_tracks > 0 then + table.insert(formspec, "\\, linky " ..F(table.concat(st.lines, ","))) + end + end + table.insert(formspec, ";"..(selection_index or "").."]") + + -- rozbalovací pole s volbou filtru a řazení + table.insert(formspec, "dropdown[8,0.3;7,0.6;filter;"..F(assert(filters[1].description))) + for i = 2, #filters do + table.insert(formspec, ","..F(filters[i].description)) + end + table.insert(formspec, ";"..custom_state.active_filter..";true]") + + table.insert(formspec, "button_exit[18.75,0.3;0.75,0.75;close;X]") + + local st = selection_index ~= nil and stations[selection_index - 1] + local stn, nazev, spravuje + if st then + stn = F(st.stn) + nazev = F(st.name) + spravuje = F(ch_core.prihlasovaci_na_zobrazovaci(st.owner)) + else + stn, nazev, spravuje = "", "", F(pinfo.viewname) + end + table.insert(formspec, + "field[0.5,7;2.5,0.75;stn;kód:;"..stn.."]".. + "field[3.25,7;7,0.75;name;název:;"..nazev.."]".. + ifthenelse(pinfo.role == "admin", "field[10.5,7;4,0.75;owner;spravuje:;", "label[10.5,6.75;spravuje:\n").. + spravuje.."]") + if pinfo.role ~= "new" then + table.insert(formspec, "button[0.5,8;4.5,0.75;vytvorit;vytvořit novou]".. + "button[10,8;4.5,0.75;jrad;jízdní řády...]") + if st and (pinfo.role == "admin" or st.owner == pinfo.player_name) then + table.insert(formspec, "button[5.25,8;4.5,0.75;ulozit;uložit změny]") + if st.tracks[1] == nil then + table.insert(formspec, "button[15.25,8;3,0.75;smazat;smazat]") + end + if custom_state.linevars[1] ~= nil then + table.insert(formspec, "label[0.5,9.4;přiřadit kolej]".. + "field[2.75,9.1;1,0.6;kolej;;]".. + "label[3.9,9.4;lince]".. + "dropdown[5,9.1;5,0.6;klinevar;") + for i, lvar in ipairs(custom_state.linevars) do + if i ~= 1 then + table.insert(formspec, ",") + end + table.insert(formspec, F(lvar.linevar.." | "..lvar.dep.." "..stn.." ["..lvar.track.."]")) + end + table.insert(formspec, ";"..custom_state.current_linevar..";true]".. + "button[10.25,9;4.25,0.75;priradit_kolej;přiřadit]".. + "tooltip[klinevar;") + table.insert(formspec, F("Vysvětlení formátu:\n<linka>/<kód vých.dop.>/<sm.kód> | <odjezd> <kód dop.> [<stávající kolej>]")) + table.insert(formspec, "]") + end + end + end + if st and st.tracks[1] ~= nil then + table.insert(formspec, "textarea[14.75,7;4.75,2.5;;pozice kolejí:;"..F(minetest.pos_to_string(st.tracks[1].pos))) + for i = 2, #st.tracks do + table.insert(formspec, "\n"..F(minetest.pos_to_string(st.tracks[i].pos))) + end + table.insert(formspec, "]") + end + return table.concat(formspec) +end + +local function formspec_callback(custom_state, player, formname, fields) + local update_formspec = false + if fields.vytvorit then + local new_stn, new_name, new_owner = fields.stn, fields.name or "", fields.owner + local pinfo = ch_core.normalize_player(player) + if pinfo.role ~= "admin" or new_owner == nil or new_owner == "" then + new_owner = pinfo.player_name + else + new_owner = ch_core.jmeno_na_prihlasovaci(new_owner) + end + if new_stn == nil or new_stn == "" then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: kód nesmí být prázdný!") + return + end + local als = advtrains.lines.stations + if als[new_stn] ~= nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: zastávka s kódem "..new_stn.." již existuje!") + return + end + als[new_stn] = {name = assert(new_name), owner = assert(new_owner)} + custom_state.stations = nil + update_formspec = true + ch_core.systemovy_kanal(custom_state.player_name, "Dopravna úspěšně vytvořena.") + elseif fields.ulozit then + local new_stn, new_name, new_owner = fields.stn, fields.name or "", fields.owner + local pinfo = ch_core.normalize_player(player) + local st = custom_state.stations[(custom_state.selection_index or 0) - 1] + if st == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: není vybrána žádná zastávka!") + return + end + local change_stn, change_name = st.stn ~= new_stn, st.name ~= new_name + local change_owner = pinfo.role == "admin" and fields.owner ~= nil and fields.owner ~= "" and + ch_core.jmeno_na_prihlasovaci(fields.owner) ~= st.owner + if not change_stn and not change_name and not change_owner then + ch_core.systemovy_kanal(custom_state.player_name, "Nic nezměněno.") + return + end + local t = advtrains.lines.stations[st.stn] + if t == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: zastávka k úpravě nebyla nalezena! Toto je pravděpodobně vnitřní chyba editoru.") + return + end + if change_stn then + -- zkontrolovat, že cílový kód je volný + if advtrains.lines.stations[new_stn] ~= nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: zastávka s kódem "..new_stn.." již existuje!") + return + end + end + if change_owner then + t.owner = ch_core.jmeno_na_prihlasovaci(fields.owner) + ch_core.systemovy_kanal(custom_state.player_name, "Správa zastávky změněna.") + end + if change_name then + t.name = new_name + ch_core.systemovy_kanal(custom_state.player_name, "Jmeno zastávky změněno.") + end + if change_stn then + advtrains.lines.stations[new_stn] = t + local stops = advtrains.lines.stops + local count = 0 + for _, trackinfo in ipairs(st.tracks) do + local stop = stops[trackinfo.epos] + if stop == nil then + minetest.log("error", "Expected track at position "..trackinfo.epos.." not found!") + elseif stop.stn ~= st.stn then + minetest.log("error", "Track at position "..trackinfo.epos.." has unexpected stn '"..tostring(stop.stn).."' instead of '"..st.stn.."'!") + else + stop.stn = new_stn + count = count + 1 + end + end + advtrains.lines.stations[st.stn] = nil + ch_core.systemovy_kanal(custom_state.player_name, "Kód zastávky změněn, "..count.." bloků kolejí aktualizováno.") + end + custom_state.stations = nil + update_formspec = true + elseif fields.smazat then + local pinfo = ch_core.normalize_player(player) + local st = custom_state.stations[(custom_state.selection_index or 0) - 1] + if st == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: není vybrána žádná zastávka!") + return + end + if st.tracks[1] ~= nil then + ch_core.systemovy_kanal(custom_state.player_name, "Nelze smazat zastávku, k níž jsou přiřazeny koleje!") + return + end + local t = advtrains.lines.stations[st.stn] + if t == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: zastávka k úpravě nebyla nalezena! Toto je pravděpodobně vnitřní chyba editoru.") + return + end + advtrains.lines.stations[st.stn] = nil + ch_core.systemovy_kanal(custom_state.player_name, "Zastávka úspěšně smazána.") + custom_state.stations = nil + update_formspec = true + elseif fields.jrad then + local st = custom_state.stations[(custom_state.selection_index or 0) - 1] + if st == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: není vybrána žádná zastávka!") + return + end + advtrains.lines.show_jr_formspec(player, nil, assert(st.stn)) + return + elseif fields.priradit_kolej then + local st = custom_state.stations[(custom_state.selection_index or 0) - 1] + if st == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: není vybrána žádná zastávka!") + return + end + local linevar_to_change = custom_state.linevars[custom_state.current_linevar] + if linevar_to_change == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 1!") + return + end + local linevar_def = advtrains.lines.try_get_linevar_def(linevar_to_change.linevar, linevar_to_change.stn) + if linevar_def == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 2!") + return + end + local stop = linevar_def.stops[linevar_to_change.linevar_index] + if stop == nil then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 3!") + return + end + if stop.stn ~= st.stn then + ch_core.systemovy_kanal(custom_state.player_name, "CHYBA: vnitřní chyba 4!") + return + end + stop.track = tostring(fields.kolej) + linevar_to_change.track = stop.track + ch_core.systemovy_kanal(custom_state.player_name, "Přiřazená kolej úspěšně nastavena.") + update_formspec = true + elseif fields.quit then + return + end + + if fields.dopravna then + local event = minetest.explode_table_event(fields.dopravna) + if event.type == "CHG" then + custom_state.selection_index = ifthenelse(event.row > 1, event.row, nil) + local st = custom_state.stations[(custom_state.selection_index or 0) - 1] + local new_linevars = {} + custom_state.linevars = new_linevars + custom_state.current_linevar = 1 + if st ~= nil then + for _, r in ipairs(advtrains.lines.get_linevars_by_station(st.stn)) do + local line, stn = advtrains.lines.linevar_decompose(r.linevar) + for _, i in ipairs(r.indices) do + local stop = r.linevar_def.stops[i] + table.insert(new_linevars, { + stn = stn, + linevar = r.linevar, + linevar_index = i, + dep = assert(stop.dep), + track = stop.track or "", + }) + end + end + end + update_formspec = true + end + end + + if fields.klinevar then + local n = tonumber(fields.klinevar) + if n ~= nil and custom_state.linevars[n] ~= nil then + custom_state.current_linevar = n + end + end + + -- filter (must be the last) + if fields.filter then + local new_filter = tonumber(fields.filter) + if filters[new_filter] ~= nil and new_filter ~= custom_state.active_filter then + custom_state.active_filter = new_filter + custom_state.stations = nil + update_formspec = true + end + end + if update_formspec then + return get_formspec(custom_state) + end +end + +local function show_formspec(player) + + local custom_state = { + player_name = assert(player:get_player_name()), + player_pos = vector.round(player:get_pos()), + active_filter = 1, + -- selection_index = nil, + linevars = {--[[ + {stn = string, linevar = string, linevar_index = int, track = string}... + ]]}, + current_linevar = 1, + } + -- ch_core.show_formspec(player_or_player_name, formname, formspec, formspec_callback, custom_state, options) + ch_core.show_formspec(player, "advtrains_line_automation:editor_dopraven", get_formspec(custom_state), formspec_callback, custom_state, {}) +end + +advtrains.lines.open_station_editor = show_formspec + +def = { + -- params = "", + description = "Otevře editor dopraven (stanic, zastávek a odboček)", + privs = {}, + func = function(player_name, param) show_formspec(minetest.get_player_by_name(player_name)) end, +} +core.register_chatcommand("zastavky", def) +core.register_chatcommand("zastávky", def) + + +-- Jízdní řád + +--[[ + custom_state = { + pos = vector or nil, -- node position (will use metadata to determine the owner) + player_name = string, -- player to whom the formspec is to be shown (will use this to determine privs) + stn = int, -- station selection from stns + stns = { + {stn = "", fs = "(vyberte dopravnu)", name_fs = ""}, + {stn = string, fs = string, name_fs = string}... + }, -- the list of stations to select from; the first must be "", which means no station + track = int, -- selection of track from "tracks" list + tracks = {"", string...}, -- the list of tracks to select from; the first must be "", which means all tracks + linevar = int, + linevars = {{ + stn = string, -- station from linevar + linevar = string, -- linevar + line_fs = string, -- line for formspec + target_fs = string, -- terminus name for formspec + track_fs = string, -- track info for formspec ("" if not available) + status_fs = string, -- line status for formspec (including color column) + train_name_fs = string, -- train name for formspec ("" if not available) + linevar_index = int, -- index of the selected station (stn) in linevar_def.stops of this linevar + }...}, + stop = int, + stops = {{ + stn = string, + name_fs = string, + track_fs = string, + dep = int, + wait = int, + linevar_index = int, -- index of this stop in linevar_def.stops + }...}, + message = string, -- message for label[] on the bottom; "" to disable + } +]] + +local all_stations_record = {stn = "", fs = F("(vyberte dopravnu)"), name_fs = ""} +local is_visible_mode = assert(advtrains.lines.is_visible_mode) + +-- refresh custom_state.stops according to custom_state.linevar +local function jr_refresh_stops(custom_state, stn_to_select) + if stn_to_select == "" then + stn_to_select = nil + end + local linevar_info = custom_state.linevars[custom_state.linevar] + local index_to_select = 0 + local result = {} + if linevar_info ~= nil then + local linevar_def = advtrains.lines.try_get_linevar_def(linevar_info.linevar, linevar_info.stn) + if linevar_def == nil then + -- invalid linevar => no stops + core.log("error", "Player "..custom_state.player_name.." selected invalid linevar "..linevars[linevar].linevar.."!") + else + local stops = linevar_def.stops + for linevar_index, stop_data in ipairs(linevar_def.stops) do + if is_visible_mode(stop_data.mode) then + local r = { + stn = assert(stop_data.stn), + name_fs = F(advtrains.lines.get_station_name(stop_data.stn)), + track_fs = stop_data.track or "", + dep = stop_data.dep, + wait = stop_data.wait or 10, + linevar_index = linevar_index, + } + if r.track_fs ~= "" then + r.track_fs = F("["..r.track_fs.."]") + end + table.insert(result, r) + if stn_to_select ~= nil and index_to_select == 0 and r.stn == stn_to_select then + index_to_select = #result -- stop selected + end + end + end + end + end + if index_to_select == 0 and result[1] ~= nil then + index_to_select = 1 + end + custom_state.stop = index_to_select + custom_state.stops = result + custom_state.message = "" +end + +local function get_all_linevars() + local result = {} + local empty_table = {} + local trains_by_linevar = advtrains.lines.get_trains_by_linevar() + for stn, stdata in pairs(advtrains.lines.stations) do + for linevar, linevar_def in pairs(stdata.linevars or empty_table) do + local line, stn = advtrains.lines.linevar_decompose(linevar) + local target_fs = F(advtrains.lines.get_line_description(linevar_def, {line_number = false, last_stop = true, last_stop_prefix = "", + last_stop_uppercase = false, train_name = false})) + local status_fs + if trains_by_linevar[linevar] ~= nil then + status_fs = "#00ff00,v provozu" + else + status_fs = "#999999,neznámý" + end + table.insert(result, { + stn = stn, + linevar = linevar, + line_fs = F(line), + target_fs = target_fs, + track_fs = "", + status_fs = status_fs, + train_name_fs = F(linevar_def.train_name or ""), + linevar_index = 1, + }) + end + end + table.sort(result, function(a, b) return a.linevar < b.linevar end) + return result +end + +--[[ + stn_filter = string, + track_filter = string or nil, +]] +local function get_linevars_by_filter(stn_filter, track_filter) + local result = {} + local empty_table = {} + local line_description_options = {line_number = false, last_stop = true, last_stop_prefix = "", last_stop_uppercase = false, train_name = false} + local trains_by_linevar = advtrains.lines.get_trains_by_linevar() + assert(stn_filter) + if track_filter == "" then + track_filter = nil + end + for stn, stdata in pairs(advtrains.lines.stations) do + for linevar, linevar_def in pairs(stdata.linevars or empty_table) do + local last_stop_index = advtrains.lines.get_last_stop(linevar_def, false) + if last_stop_index ~= nil then + for linevar_index = 1, last_stop_index - 1 do -- NOTE: the last visible station is intentionally ignored here! + local stop_data = linevar_def.stops[linevar_index] + local initialized = false + local line, stn, line_fs, target_fs, status_fs + if + stop_data.stn == stn_filter and + is_visible_mode(stop_data.mode) and + (track_filter == nil or track_filter == stop_data.track) + then + if not initialized then + initialized = true + line, stn = advtrains.lines.linevar_decompose(linevar) + line_fs = F(line) + target_fs = F(advtrains.lines.get_line_description(linevar_def, line_description_options)) + if trains_by_linevar[linevar] ~= nil then + status_fs = "#00ff00,v provozu" + else + status_fs = "#999999,neznámý" + end + end + local track_fs = stop_data.track + if track_fs == nil or track_fs == "" then + track_fs = "" + else + track_fs = F("["..track_fs.."]") + end + table.insert(result, { + stn = stn, + linevar = linevar, + line_fs = line_fs, + target_fs = target_fs, + track_fs = track_fs, + status_fs = status_fs, + train_name_fs = F(linevar_def.train_name or ""), + linevar_index = assert(linevar_index), + }) + end + end + end + end + end + table.sort(result, function(a, b) + if a.linevar ~= b.linevar then + return a.linevar < b.linevar + else + return a.linevar_index < b.linevar_index + end + end) + return result +end + +local function is_jr_node_name(name) + return core.get_item_group(name, "ch_jrad") ~= 0 +end + +-- refresh custom_state.linevars according to custom_state.stn and custom_state.track +local function jr_refresh_linevars(custom_state, linevar_to_select, linevar_index_to_select) + if linevar_to_select == "" then + linevar_to_select = nil + end + assert(linevar_index_to_select == nil or type(linevar_index_to_select) == "number") + local stn = assert(custom_state.stn) + local stn_info = assert(custom_state.stns[stn]) + local track = assert(custom_state.track) + local tracks = assert(custom_state.tracks) + + local new_linevars + if stn_info.stn == "" then + new_linevars = get_all_linevars() + else + new_linevars = get_linevars_by_filter(stn_info.stn, assert(tracks[track])) + end + local index_to_select = 1 + if linevar_to_select ~= nil then + for i, r in ipairs(new_linevars) do + if r.linevar == linevar_to_select and (linevar_index_to_select == nil or r.linevar_index == linevar_index_to_select) then + index_to_select = i + break + end + end + end + if new_linevars[index_to_select] == nil then + index_to_select = 0 + end + custom_state.linevars = new_linevars + custom_state.linevar = index_to_select + custom_state.message = "" + + local pos = custom_state.pos + if pos ~= nil and is_jr_node_name(core.get_node(pos).name) then + local meta = core.get_meta(pos) + meta:set_string("stn", custom_state.stns[custom_state.stn].stn) + meta:set_string("track", custom_state.tracks[custom_state.track]) + local infotext = {"jízdní řád\n"} + if stn_info.stn == "" then + table.insert(infotext, "<všechny linky>") + else + table.insert(infotext, advtrains.lines.get_station_name(stn_info.stn)) + if custom_state.tracks[custom_state.track] ~= "" then + table.insert(infotext, " ["..custom_state.tracks[custom_state.track].."]") + end + end + if new_linevars[1] ~= nil then + local prefix = "\n" + local set = {[""] = true} + for _, linevar_info in ipairs(new_linevars) do + local line = advtrains.lines.linevar_decompose(linevar_info.linevar) + if not set[line] then + set[line] = true + table.insert(infotext, prefix.."["..line.."]") + prefix = " " + end + end + end + meta:set_string("infotext", table.concat(infotext)) + end +end + +-- refresh custom_state.tracks according to custom_state.stn +-- and selects a specified track, if available +local function jr_refresh_tracks(custom_state, track_to_select) + if track_to_select == "" then + track_to_select = nil + end + local result = {""} + local index_to_select = 1 + local current_stn = custom_state.stns[custom_state.stn].stn + if current_stn ~= "" and advtrains.lines.stations[current_stn] ~= nil then + local track_set = {[""] = true} + -- search for tracks: + for epos, stdata in pairs(advtrains.lines.stops) do + if stdata.stn == current_stn and stdata.track ~= nil and not track_set[stdata.track] then + track_set[stdata.track] = true + table.insert(result, tostring(stdata.track)) + end + end + if #result > 1 then + table.sort(result) + assert(result[1] == "") + if track_to_select ~= nil then + for i, track in ipairs(result) do + if track_to_select == track then + index_to_select = i + end + end + end + end + end + custom_state.tracks = result + custom_state.track = index_to_select + custom_state.message = "" +end + +--[[ + -- will refresh custom_state.stns[] and (optionally) select a wanted station if it's on the list, + -- otherwise the default 'select station' option will be chosen + custom_state = table, + stn_to_select = string or nil, +]] +local function jr_refresh_stns(custom_state, stn_to_select) + if stn_to_select == "" then + stn_to_select = nil + end + local result = {all_stations_record} + local index_to_select = 1 + for i, st in ipairs(load_stations()) do + result[1 + i] = { + stn = assert(st.stn), + fs = F(st.stn.." | "..st.name), + name_fs = F(st.name), + } + if stn_to_select ~= nil and st.stn == stn_to_select then + index_to_select = 1 + i + end + end + custom_state.stns = result + custom_state.stn = index_to_select + custom_state.message = "" +end + +-- will refresh a departure message according to linevar + stop +local function jr_refresh_departure(custom_state) + local linevar_info = custom_state.linevars[custom_state.linevar] + local stop_info = custom_state.stops[custom_state.stop] + if linevar_info == nil or stop_info == nil then + custom_state.message = "" + return + end + local linevar_def = advtrains.lines.try_get_linevar_def(linevar_info.linevar) + if linevar_def == nil then + custom_state.message = "" + return + end + local rwtime = rwt.to_secs(rwt.get_time()) + local prediction = advtrains.lines.predict_station_departures(linevar_def, assert(stop_info.linevar_index), rwtime) + if #prediction == 0 then + custom_state.message = "v nejbližší době nenalezeny žádné odjezdy zvolené linky" + return + end + local deps = {} + for i, pred in ipairs(prediction) do + deps[i] = tostring(pred.dep - rwtime) + end + custom_state.message = "nejbližší odjezdy zvolené linky za: "..table.concat(deps, ", ").." s" +end + +local function get_jr_formspec(custom_state) + local formspec = { + ch_core.formspec_header({ + formspec_version = 6, + size = {20, 12}, + auto_background = true, + }) + } + local access_level = "player" -- player | owner | admin + local node_owner + if custom_state.pos ~= nil then + node_owner = core.get_meta(custom_state.pos):get_string("owner") + if node_owner == "" then + node_owner = nil + end + end + local stn, stn_owner + if custom_state.stn > 1 and custom_state.stns[custom_state.stn] ~= nil then + stn_owner = (advtrains.lines.stations[custom_state.stns[custom_state.stn].stn] or {}).owner -- may be nil + end + + if not custom_state.force_unprivileged then + if ch_core.get_player_role(custom_state.player_name) == "admin" then + access_level = "admin" + elseif custom_state.player_name == node_owner or custom_state.player_name == stn_owner then + access_level = "owner" + end + end + + if node_owner ~= nil then + if access_level ~= "player" then + -- admin or owner: + table.insert(formspec, "label[0.5,0.6;Jízdní řády]".. + "dropdown[5,0.3;10,0.6;dopravna;") + for i, r in ipairs(custom_state.stns) do + table.insert(formspec, ifthenelse(i == 1, r.fs, ","..r.fs)) + end + table.insert(formspec, ";"..custom_state.stn..";true]".. + "dropdown[15.25,0.3;3.5,0.6;kolej;") + for i, r in ipairs(custom_state.tracks) do + if i == 1 then + table.insert(formspec, "(všechny koleje)") + else + table.insert(formspec, ","..F(r)) + end + end + table.insert(formspec, ";"..custom_state.track..";true]") + else + -- player (including 'new' players) + local stn_info = custom_state.stns[custom_state.stn] + if stn_info.stn == "" then + table.insert(formspec, "label[0.5,0.6;Jízdní řády (všechny linky)]") + else + local track = custom_state.tracks[custom_state.track] + if track ~= "" then + track = F(" ["..track.."]") + end + table.insert(formspec, "label[0.5,0.6;"..F("Jízdní řády: ")..stn_info.name_fs..track.."]") + end + end + if access_level ~= "admin" then + -- player/owner + table.insert(formspec, "label[0.5,1.65;vlastník/ice j. řádu: ") + table.insert(formspec, ch_core.prihlasovaci_na_zobrazovaci(node_owner)) + if stn_owner ~= nil then + table.insert(formspec, " | dopravnu spravuje: ") + table.insert(formspec, ch_core.prihlasovaci_na_zobrazovaci(stn_owner)) + end + table.insert(formspec, "]") + else + -- admin only + table.insert(formspec, "label[0.5,1.65;vlastník/ice:]".. + "field[2.75,1.25;5,0.75;owner;;") + table.insert(formspec, ch_core.prihlasovaci_na_zobrazovaci(node_owner)) + table.insert(formspec, "]button[8,1.25;3,0.75;setowner;nastavit]") + if stn_owner ~= nil then + table.insert(formspec, "label[11.25,1.65;dopravnu spravuje: "..ch_core.prihlasovaci_na_zobrazovaci(stn_owner).."]") + end + end + else + table.insert(formspec, "label[0.5,0.6;Příruční jízdní řády (všechny linky)]") + end + + table.insert(formspec, "tablecolumns[text,align=right,tooltip=linka;text,width=12,tooltip=cíl;text,tooltip=kolej;color,span=1;text,tooltip=stav;color,span=1;text,tooltip=jméno vlaku]".. + "table[0.5,2.25;11,5;linka;") + for i, r in ipairs(custom_state.linevars) do + if i > 1 then + table.insert(formspec, ",") + end + table.insert(formspec, r.line_fs..","..r.target_fs..","..r.track_fs..","..r.status_fs..",#cccccc,"..r.train_name_fs) + end + table.insert(formspec, ifthenelse(custom_state.linevar > 0, ";"..custom_state.linevar.."]", ";]")) + table.insert(formspec, "tablecolumns[text,align=right;text;text]".. + "table[12.5,2.25;7,8.75;zastavka;") + if custom_state.stops[1] ~= nil then + local selected_stop_index = custom_state.stop + if custom_state.stops[selected_stop_index] == nil then + selected_stop_index = 1 + end + local base_dep + for i, r in ipairs(custom_state.stops) do + if i > 1 then + table.insert(formspec, ",") + end + if i >= selected_stop_index then + if i == selected_stop_index then + base_dep = assert(r.dep) + table.insert(formspec, "0,") + else + table.insert(formspec, (r.dep - base_dep)..",") + end + else + table.insert(formspec, " ,") + end + table.insert(formspec, r.name_fs..","..r.track_fs) + end + table.insert(formspec, ";"..selected_stop_index.."]") + else + table.insert(formspec, ";]") -- empty list + end + table.insert(formspec, "button_exit[18.75,0.3;0.75,0.75;close;X]") + if custom_state.message ~= "" then + table.insert(formspec, "label[5.25,11.35;"..F(custom_state.message).."]") + end + table.insert(formspec, "button[0.5,11;4.5,0.75;refresh;zjistit nejbližší odjezdy...]") + return table.concat(formspec) +end + +local function jr_formspec_callback(custom_state, player, formname, fields) + if fields.dopravna then + local new_stn = tonumber(fields.dopravna) + if new_stn ~= nil and new_stn ~= custom_state.stn and custom_state.stns[new_stn] ~= nil then + custom_state.stn = new_stn + local current_track = custom_state.tracks[custom_state.track] or "" + local current_linevar_info = custom_state.linevars[custom_state.linevar] + jr_refresh_tracks(custom_state, current_track) + if current_linevar_info ~= nil then + jr_refresh_linevars(custom_state, current_linevar_info.linevar, current_linevar_info.linevar_index) + else + jr_refresh_linevars(custom_state) + end + jr_refresh_stops(custom_state, custom_state.stns[new_stn].stn) + jr_refresh_departure(custom_state) + return get_jr_formspec(custom_state) + end + end + if fields.kolej then + local new_track = tonumber(fields.kolej) + if new_track ~= nil and new_track ~= custom_state.track and custom_state.tracks[new_track] ~= nil then + custom_state.track = new_track + local current_linevar_info = custom_state.linevars[custom_state.linevar] + if current_linevar_info ~= nil then + jr_refresh_linevars(custom_state, current_linevar_info.linevar, current_linevar_info.linevar_index) + else + jr_refresh_linevars(custom_state) + end + jr_refresh_stops(custom_state, custom_state.stns[custom_state.stn].stn) + jr_refresh_departure(custom_state) + return get_jr_formspec(custom_state) + end + end + if fields.setowner and custom_state.pos ~= nil and is_jr_node_name(core.get_node(custom_state.pos).name) then + local meta = core.get_meta(custom_state.pos) + local jm = ch_core.jmeno_na_existujici_prihlasovaci(fields.owner) + if jm ~= nil then + meta:set_string("owner", jm) + else + core.chat_send_player(custom_state.player_name, "*** Postava '"..fields.owner.."' neexistuje!") + end + return get_jr_formspec(custom_state) + end + if fields.linka then + local event = core.explode_table_event(fields.linka) + local new_line + if event.type == "CHG" or event.type == "DCL" then + new_line = tonumber(event.row) + end + if new_line ~= nil and new_line ~= custom_state.linevar then + local new_linevar_info = custom_state.linevars[new_line] + if new_linevar_info ~= nil then + jr_refresh_linevars(custom_state, new_linevar_info.linevar, new_linevar_info.linevar_index) + else + jr_refresh_linevars(custom_state) + end + jr_refresh_stops(custom_state, custom_state.stns[custom_state.stn].stn) + jr_refresh_departure(custom_state) + return get_jr_formspec(custom_state) + end + end + if fields.zastavka then + local event = core.explode_table_event(fields.zastavka) + if event.type == "CHG" or event.type == "DCL" then + local new_stop = tonumber(event.row) + if new_stop ~= nil and new_stop ~= custom_state.stop and custom_state.stops[new_stop] ~= nil then + custom_state.stop = new_stop + if event.type == "DCL" then + jr_refresh_departure(custom_state) + end + return get_jr_formspec(custom_state) + end + end + end + if fields.refresh then + jr_refresh_departure(custom_state) + return get_jr_formspec(custom_state) + end +end + +function advtrains.lines.show_jr_formspec(player, pos, stn, track, linevar, stop_stn, force_unprivileged) + assert(core.is_player(player)) + local custom_state = { + player_name = player:get_player_name(), + stn = 1, + stns = {all_stations_record}, + track = 1, + tracks = {""}, + linevar = 0, + linevars = {}, + stop = 0, + stops = {}, + message = "", + force_unprivileged = force_unprivileged, + } + if pos ~= nil then + custom_state.pos = pos + end + jr_refresh_stns(custom_state, stn) + jr_refresh_tracks(custom_state, track) + jr_refresh_linevars(custom_state, linevar) + if stop_stn == nil then + stop_stn = custom_state.stns[custom_state.stn].stn + end + jr_refresh_stops(custom_state, stop_stn) + jr_refresh_departure(custom_state) + + -- show formspec: + return ch_core.show_formspec(player, "advtrains_line_automation:jizdni_rad", + get_jr_formspec(custom_state), jr_formspec_callback, custom_state, {}) +end diff --git a/advtrains_line_automation/stoprail.lua b/advtrains_line_automation/stoprail.lua index aa8dfdd..8553c97 100644 --- a/advtrains_line_automation/stoprail.lua +++ b/advtrains_line_automation/stoprail.lua @@ -1,6 +1,7 @@ -- stoprail.lua -- adds "stop rail". Recognized by lzb. (part of behavior is implemented there) +local rwt = assert(advtrains.lines.rwt) -- Translation S = attrans @@ -17,16 +18,39 @@ local function updatemeta(pos) local pe = advtrains.encode_pos(pos) local stdata = advtrains.lines.stops[pe] if not stdata then - meta:set_string("infotext", "Error") + meta:set_string("infotext", attrans("Error")) end + local stn = advtrains.lines.stations[stdata.stn] + local stn_viewname = stn and stn.name or "-" - meta:set_string("infotext", "Stn. "..stdata.stn.." T. "..stdata.track) + meta:set_string("infotext", attrans("Stn. @1 (@2) T. @3", stn_viewname, stdata.stn or "", stdata.track or "")) end local door_dropdown = {L=1, R=2, C=3} ---local door_dropdown_rev = {Right="R", Left="L", Closed="C"} -- Code review : why are the value in an order different than the one in the dropdown box ? local door_dropdown_code = {"L", "R", "C"} -- switch to numerical index of selection : for conversion of the numerical index in the opening side selection dropdown box to the internal codification +local function get_stn_dropdown(stn, player_name) + local stations = advtrains.lines.load_stations_for_formspec() + local selected_index + local result = {"dropdown[0.25,2;6,0.75;stn;(nepřiřazeno)"} + local right_mark + for i, st in ipairs(stations) do + if player_name ~= nil and player_name ~= st.owner then + right_mark = "(cizí) " + else + right_mark = "" + end + table.insert(result, ","..right_mark..minetest.formspec_escape(st.stn.." | "..st.name)) + if st.stn == stn then + selected_index = i + 1 + end + end + table.insert(result, ";"..(selected_index or "1")..";true]") + return table.concat(result) +end + +local player_to_stn_override = {} + local function show_stoprailform(pos, player) local pe = advtrains.encode_pos(pos) local pname = player:get_player_name() @@ -52,23 +76,67 @@ local function show_stoprailform(pos, player) stdata.speed = "M" end - local form = "size[8,7]" - form = form.."style[stn,ars;font=mono]" - form = form.."field[0.8,0.8;2,1;stn;"..S("Station Code")..";"..minetest.formspec_escape(stdata.stn).."]" - form = form.."field[2.8,0.8;5,1;stnname;"..S("Station Name")..";"..minetest.formspec_escape(stnname).."]" - form = form.."field[0.80,2.0;1.75,1;ddelay;"..S("Door Delay")..";"..minetest.formspec_escape(stdata.ddelay).."]" - form = form.."field[2.55,2.0;1.75,1;speed;"..S("Dep. Speed")..";"..minetest.formspec_escape(stdata.speed).."]" - form = form.."field[4.30,2.0;1.75,1;track;"..S("Track")..";"..minetest.formspec_escape(stdata.track).."]" - form = form.."field[6.05,2.0;1.75,1;wait;"..S("Stop Time")..";"..stdata.wait.."]" - form = form.."label[0.5,2.6;"..S("Door Side").."]" - form = form.."dropdown[0.51,3.0;2;doors;"..S("Left")..","..S("Right")..","..S("Closed")..";"..door_dropdown[stdata.doors]..";true]" -- switch to numerical index of the selection - form = form.."checkbox[3.00,2.4;reverse;"..S("Reverse train")..";"..(stdata.reverse and "true" or "false").."]" - form = form.."checkbox[3.00,2.8;kick;"..S("Kick out passengers")..";"..(stdata.kick and "true" or "false").."]" - form = form.."checkbox[3.00,3.2;waitsig;"..S("Wait for signal to clear")..";"..(stdata.waitsig and "true" or "false").."]" - form = form.."textarea[0.8,4.2;7,2;ars;"..S("Trains stopping here (ARS rules)")..";"..advtrains.interlocking.ars_to_text(stdata.ars).."]" - form = form.."button[0.5,6;7,1;save;"..S("Save").."]" - - minetest.show_formspec(pname, "at_lines_stop_"..pe, form) + local item_name = (minetest.registered_items["advtrains_line_automation:dtrack_stop_placer"] or {}).description or "" + local pname_unless_admin + if not minetest.check_player_privs(pname, "train_admin") then + pname_unless_admin = pname + end + local formspec = "formspec_version[4]".. + "size[8,12]".. + "item_image[0.25,0.25;1,1;advtrains_line_automation:dtrack_stop_placer]".. + "textarea[1.35,0.6;5.5,0.6;;;"..minetest.formspec_escape(string.format("%s %d,%d,%d", item_name, pos.x, pos.y, pos.z)).."]".. + "button_exit[7,0.25;0.75,0.75;close;X]".. + "style[ars,line,routingcode;font=mono]".. + "label[0.25,1.75;"..attrans("Station Code").." | "..attrans("Station Name").."]".. + get_stn_dropdown(player_to_stn_override[pname] or stdata.stn, pname_unless_admin).. + "field[6.75,2;1,0.75;track;"..attrans("Track")..";"..minetest.formspec_escape(stdata.track).."]".. + "label[0.25,3.4;"..attrans("Door Side").."]".. + "dropdown[2.25,3;2,0.75;doors;"..S("Left")..","..S("Right")..","..S("Closed")..";"..door_dropdown[stdata.doors]..";true]".. -- switch to numerical index of the selection + "checkbox[4.5,3.25;reverse;"..attrans("Reverse train")..";"..(stdata.reverse and "true" or "false").."]".. + "checkbox[4.5,3.75;kick;"..attrans("Kick out passengers")..";"..(stdata.kick and "true" or "false").."]".. + "checkbox[4.5,4.25;keepopen;Nezavírat dveře na odj.;"..(stdata.keepopen and "true" or "false").."]".. + "label[0.25,4.3;"..attrans("Stop Time").."]".. + "field[0.25,4.5;1,0.75;wait;;"..stdata.wait.."]".. + "label[1.5,4.9;+]".. + "field[2,4.5;1,0.75;ddelay;;"..minetest.formspec_escape(stdata.ddelay).."]".. -- "..attrans("Door Delay").." + (advtrains.lines.open_station_editor ~= nil and "button[3.5,4.5;4,0.75;editstn;Editor dopraven]" or "").. + "field[0.25,6;2,0.75;speed;"..attrans("Dep. Speed")..";"..minetest.formspec_escape(stdata.speed).."]".. + "field[2.5,6;2,0.75;line;Linka na odj.;"..minetest.formspec_escape(stdata.line or "").."]".. + "field[4.75,6;2,0.75;routingcode;Sm.kód na odj.;"..minetest.formspec_escape(stdata.routingcode or "").."]".. + "field[0.25,7.25;2,0.75;interval;Interval \\[s\\]:;"..minetest.formspec_escape(stdata.interval or "").."]".. + "field[2.5,7.25;2,0.75;ioffset;Jeho posun:;"..minetest.formspec_escape(stdata.ioffset or "0").."]".. + "button[4.75,7;3,1.0;ioffsetnow;Nastavit posun\nna odjezd teď + uložit]".. + "textarea[0.25,8.4;7.5,1.5;ars;"..attrans("Trains stopping here (ARS rules)")..";"..advtrains.interlocking.ars_to_text(stdata.ars).."]".. + "label[0.3,10.25;Platí jen pro vlaky s]".. + "field[3,10;1,0.5;minparts;;"..minetest.formspec_escape(stdata.minparts or "0").."]".. + "label[4.15,10.25;až]".. + "field[4.6,10;1,0.5;maxparts;;"..minetest.formspec_escape(stdata.maxparts or "128").."]".. + "label[5.75,10.25;vozy.]".. + "button_exit[0.25,11;7.5,0.75;save;"..attrans("Save").."]".. + "tooltip[close;Zavře dialogové okno]".. + "tooltip[stn;Dopravna\\, ke které tato zastávka patří. Jedna dopravna může mít víc kolejí. K vytvoření a úpravám dopraven použijte Editor dopraven.]".. + "tooltip[track;Číslo koleje]".. + "tooltip[wait;Základní doba stání s otevřenými dveřmi]".. + "tooltip[ddelay;Dodatečná doba stání před odjezdem po uzavření dveří]".. + "tooltip[speed;Cílová rychlost zastavivšího vlaku na odjezdu. Platné hodnoty jsou M pro nejvyšší rychlost vlaku a čísla 0 až 20.]".. + "tooltip[line;Nová linka na odjezdu. Prázdné pole = zachovat stávající linku. Pro smazání linky zadejte znak -]".. + "tooltip[routingcode;Nový směrový kód na odjezdu. Prázdné pole = zachovat stávající směrový kód. Pro smazání kódu vlaku zadejte znak -]".. + "tooltip[ars;Seznam podmínek\\, z nichž musí vlak splnit alespoň jednu\\, aby zde zastavil:\nLN {linka}\nRC {směrovací kód}\n".. + "* = jakýkoliv vlak\n\\# značí komentář a ! na začátku řádky danou podmínku neguje (nedoporučuje se)]".. + "tooltip[minparts;Minimální počet vozů\\, které musí vlak mít\\, aby zde zastavil. Výchozí hodnota je 0.]".. + "tooltip[maxparts;Maximální počet vozů\\, které vlak může mít\\, aby zde zastavil. Výchozí (a maximální) hodnota je 128.]".. + "tooltip[editsn;Otevře v novém okně editor dopraven.\nPoužijte tento editor pro založení nové dopravny\\, jíž budete moci přiřadit koleje.]".. + "tooltip[interval;Hodnota v sekundách 1 až 3600 nebo nevyplněno. Je-li vyplněno\\, rozdělí čas na intervaly zadané délky,\n".. + "a pokud z této zastávkové koleje v rámci jednoho z nich odjede vlak\\, odjezd dalšího bude pozdržen do začátku\n".. + "následujícího intervalu. Výchozí začátky intervalů stejné délky jsou v celé železniční síti společné.\n".. + "Slouží k nastavení intervalového provozu.]".. + "tooltip[ioffset;Hodnota v sekundách 0 až (interval - 1). Posune začátek intervalů oproti výchozímu stavu\n".. + "o zadaný počet sekund vpřed. Slouží k detailnímu vyladění času odjezdů relativně vůči ostatním linkám.]".. + "tooltip[ioffsetnow;Nastaví posun intervalu tak\\, aby pro tuto kolej nový interval začínal právě teď.\n".. + "Také uloží ostatní provedené změny.]" + + + minetest.show_formspec(pname, "at_lines_stop_"..pe, formspec) end local tmp_checkboxes = {} minetest.register_on_player_receive_fields(function(player, formname, fields) @@ -91,34 +159,54 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.reverse then tmp_checkboxes[pe].reverse = (fields.reverse == "true") end + if fields.keepopen then + tmp_checkboxes[pe].keepopen = (fields.keepopen == "true") + end if fields.waitsig then tmp_checkboxes[pe].waitsig = (fields.waitsig == "true") end - if fields.save then - if fields.stn and stdata.stn ~= fields.stn and fields.stn ~= "" then - local stn = advtrains.lines.stations[fields.stn] - if stn then - if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then - stdata.stn = fields.stn - else - minetest.chat_send_player(pname, S("Station code \"@1\" already exists and is owned by @2.", fields.stn, stn.owner)) - show_stoprailform(pos,player) - return - end - else - advtrains.lines.stations[fields.stn] = {name = fields.stnname, owner = pname} - stdata.stn = fields.stn - end + + if fields.stn then + local new_index = tonumber(fields.stn) + if new_index ~= nil then + player_to_stn_override[pname] = new_index + end + end + + local set_offset + + 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 rwtime = rwt.to_secs(rwt.get_time()) + set_offset = rwtime % interval end - local stn = advtrains.lines.stations[stdata.stn] - if stn and fields.stnname and fields.stnname~="" and fields.stnname ~= stn.name then - if (stn.owner == pname or minetest.check_player_privs(pname, "train_admin")) then - stn.name = fields.stnname + end + + if fields.save or set_offset ~= nil then + local new_index = player_to_stn_override[pname] + if new_index ~= nil then + if new_index == 1 then + -- no name station + stdata.stn = "" + minetest.log("action", pname.." set track at "..minetest.pos_to_string(pos).." to no station.") else - minetest.chat_send_player(pname, S("This station is owned by @1. You are not allowed to edit its name.", stn.owner)) + local stations = advtrains.lines.load_stations_for_formspec() + local station = stations[new_index - 1] + if station ~= nil then + if station.owner == pname or minetest.check_player_privs(pname, "train_admin") then + stdata.stn = station.stn + minetest.log("action", pname.." set track at "..minetest.pos_to_string(pos).." to station '"..tostring(station.stn).."'.") + else + minetest.chat_send_player(pname, attrans("Station code '@1' does already exist and is owned by @2", station.stn, station.owner)) + show_stoprailform(pos,player) + return + end + end end + player_to_stn_override[pname] = nil end - + -- dropdowns if fields.doors then stdata.doors = door_dropdown_code[tonumber(fields.doors)] or "C" -- switch to numerical index of selection; attention : fields.doors is string typed, needed to be converted to an integer typed index in door_dropdown_code table @@ -141,6 +229,48 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if fields.speed then stdata.speed = to_int(fields.speed) or "M" end + if fields.line then + stdata.line = fields.line + end + if fields.routingcode then + stdata.routingcode = fields.routingcode + end + if fields.minparts then + local v = to_int(fields.minparts) + if v <= 0 or v > 128 then v = nil end + stdata.minparts = v + end + if fields.maxparts then + local v = to_int(fields.maxparts) + if v <= 0 or v > 128 then v = nil end + stdata.maxparts = v + end + if fields.interval then + if fields.interval == "" or fields.interval == "0" then + stdata.interval = nil + else + local n = to_int(fields.interval) + if 0 < n and n <= 3600 then + stdata.interval = n + end + end + end + if stdata.interval == nil then + stdata.ioffset = nil + elseif set_offset ~= nil then + stdata.ioffset = set_offset + elseif fields.ioffset then + if fields.ioffset == "" or fields.ioffset == "0" then + stdata.ioffset = nil + else + local n = to_int(fields.ioffset) + if n > 0 then + stdata.ioffset = n % stdata.interval + else + stdata.ioffset = nil + end + end + end for k,v in pairs(tmp_checkboxes[pe]) do --handle checkboxes stdata[k] = v or nil @@ -148,13 +278,17 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) tmp_checkboxes[pe] = nil --TODO: signal updatemeta(pos) + minetest.log("action", pname.." saved stoprail at "..minetest.pos_to_string(pos)) show_stoprailform(pos, player) + elseif fields.editstn and advtrains.lines.open_station_editor ~= nil then + minetest.close_formspec(pname, formname) + minetest.after(0.25, advtrains.lines.open_station_editor, player) + return end end end) - local adefunc = function(def, preset, suffix, rotation) return { after_place_node=function(pos) @@ -168,59 +302,42 @@ local adefunc = function(def, preset, suffix, rotation) local pe = advtrains.encode_pos(pos) advtrains.lines.stops[pe] = nil end, + on_punch = function(pos, node, puncher, pointed_thing) + updatemeta(pos) + end, on_rightclick = function(pos, node, player) + if minetest.is_player(player) then + player_to_stn_override[player:get_player_name()] = nil + end 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 stdata.ars and (stdata.ars.default or advtrains.interlocking.ars_check_rule_match(stdata.ars, train) ) then - advtrains.lzb_add_checkpoint(train, index, 2, nil) - local stn = advtrains.lines.stations[stdata.stn] - local stnname = stn and stn.name or S("Unknown Station") - train.text_inside = S("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 stdata.ars and (stdata.ars.default or advtrains.interlocking.ars_check_rule_match(stdata.ars, train) ) then - local stn = advtrains.lines.stations[stdata.stn] - local stnname = stn and stn.name or S("Unknown Station") - - -- Send ATC command and set text - advtrains.atc.train_set_command(train, "B0 W O"..stdata.doors..(stdata.kick and "K" or "") - .." D"..stdata.wait.." "..(stdata.reverse and "R" or "") - .." A1 "..(stdata.waitsig and "G" or "") - .." OC D"..(stdata.ddelay or 1) .. " S" ..(stdata.speed or "M"), true) - train.text_inside = stnname - if tonumber(stdata.wait) then - minetest.after(tonumber(stdata.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 +advtrains.station_stop_rail_additional_definition = adefunc -- HACK for tieless_tracks + +minetest.register_lbm({ + label = "Update line track metadata", + name = "advtrains_line_automation:update_metadata", + nodenames = { + "advtrains_line_automation:dtrack_stop_st", + "advtrains_line_automation:dtrack_stop_st_30", + "advtrains_line_automation:dtrack_stop_st_45", + "advtrains_line_automation:dtrack_stop_st_60", + "advtrains_line_automation:dtrack_stop_tieless_st", + "advtrains_line_automation:dtrack_stop_tieless_st_30", + "advtrains_line_automation:dtrack_stop_tieless_st_40", + "advtrains_line_automation:dtrack_stop_tieless_st_60", + }, + run_at_every_load = true, + action = updatemeta, +}) + if minetest.get_modpath("advtrains_train_track") ~= nil then advtrains.register_tracks("default", { nodename_prefix="advtrains_line_automation:dtrack_stop", @@ -232,4 +349,12 @@ if minetest.get_modpath("advtrains_train_track") ~= nil then formats={}, get_additional_definiton = adefunc, }, advtrains.trackpresets.t_30deg_straightonly) + + minetest.register_craft({ + output = "advtrains_line_automation:dtrack_stop_placer 2", + recipe = { + {"default:coal_lump", ""}, + {"advtrains:dtrack_placer", "advtrains:dtrack_placer"}, + }, + }) end diff --git a/advtrains_line_automation/structs.md b/advtrains_line_automation/structs.md new file mode 100644 index 0000000..5b2c6c0 --- /dev/null +++ b/advtrains_line_automation/structs.md @@ -0,0 +1,190 @@ +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, + + -- údaje o poslední skončené jízdě na lince, dokud se nezmění číslo linky a dokud neuplyne 24 žel. hodin (cyklů) + -- nevyplní se, pokud vlak skončí jízdu jinak než zastavením na koncové zastávce + linevar_past = { + -- označení linky (LINE z linevar) + line = string, + -- linevar poslední jízdy na lince + linevar = string, + -- kód koncové zastávky, kde vlak skončil jízdu na lince + station = string, + -- žel. čas příjezdu na koncovou zastávku + arrival = int, + } 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, + + -- 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, + + -- je-li neprázdný řetězec, udává označení linky, na kterou bude vlak pravděpodobně pokračovat + -- ze zastávky v režimu MODE_FINAL_CONTINUE + continue_line = string or nil, + + -- je-li continue_line neprázdný řetězec, udává směrový kód pro pokračování + continue_rc = string or nil, + + -- index zobrazované výchozí zastávky v poli 'stops'; nil značí, že taková zastávka nebyla nalezena + index_vychozi = int or nil, + + -- index zobrazované cílové zastávky v poli 'stops'; nil značí, že taková zastávka nebyla nalezena + index_cil = int 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, + + -- předpokládaný čas stání před časem odjezdu (používá se k zjištění času příjezdu) + -- je-li nil, počítá se 10 sekund + -- výjimka: pro koncové zastávky udává předpokládanou dobu stání po čase 'dep' + wait = int or nil, + + -- 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, + }... + } + } + }, + anns = { -- staniční rozhlasy + [encoded_pos] = { + cedule = { + -- formát prázdné řádky pro danou ceduli + empty = string, + -- pozice připojené cedule ve tvaru pro použití ve formspecu, nebo "", pokud daná cedule není připojená + fs = string, + -- pozice připojené cedule ve formě vektoru + pos = vector, + -- formát řádky s odjezdem pro danou ceduli + row = string, + -- formát pro sestavení textu cedule z řádků; může používat značky {1} až {9} a může mít víc řádek + text = "{1}{2}", + -- seznam řádků, které jsou odkazovány v poli 'text', nebo prázdný řetězec, pokud nejsou odkazovány žádné + text_rtf = {int, ...} or "", + }, + -- dosah zpráv v četu (>= 0, nil znamená 50): + chat_dosah = int or nil, + + -- formát pro kladné zpoždění + fmt_delay = string, + + -- formát pro záporné zpoždění + fmt_negdelay = string, + + -- formát pro „bez zpoždění“ + fmt_nodelay = string, + + -- udává, zda na cedulích bude první znak každého řádku s odjezdem (textu dosazeného za značku {1} až {9}) + -- převeden na velké písmeno: + fn_firstupper = bool, + + -- obsah pole "koleje" zformátovaný pro použití ve formspecu; prázdný řetězec "" znamená, že st. rozhlas platí pro všechny koleje + -- a na .koleje pak nezáleží + fs_koleje = string, + + -- pokud st. rozhlas není omezený na určité koleje, nil nebo "" + -- je-li omezen na jednu konkrétní kolej, pak jde o název této koleje + -- je-li omezen na více kolejí, pak jde o množinu indexovanou označeními kolejí + koleje = {[string] = true, ...} or string or nil, + + -- vlastník/ice staničního rozhlasu + owner = string, + + -- režim rozhlasu (RMODE_*) + rmode = int, + + -- číslo verze systému staničního rozhlasu (pro detekci zastaralých rozhlasů) + version = int, + + -- řetězce pro formátování hlášení v četu; nemusí být uvedeny všechny, nil znamená použít výchozí text + tx_* = 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, + -- původně naplánovaná doba stání vlaku vztahující se k last_dep + last_wait = int or nil, +} + +local current_passages = {--[[ + [train_id] = {[1] = rwtime, ..., [n] = rwtime (časy *odjezdu*, kromě koncových zastávek, kde jde o čas příjezdu)} +]]} + +local last_passages = {--[[ + [linevar] = { + [1..10] = {[1] = rwtime, ...} -- jízdy seřazeny od nejstarší (1) po nejnovější (až 10) podle odjezdu z výchozí zastávky + } +]]} diff --git a/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png b/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png Binary files differindex b6629cf..956b6b4 100644 --- a/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png +++ b/advtrains_line_automation/textures/advtrains_dtrack_shared_stop.png diff --git a/advtrains_line_automation/textures/advtrains_line_automation_jrad.png b/advtrains_line_automation/textures/advtrains_line_automation_jrad.png Binary files differnew file mode 100644 index 0000000..627ddf8 --- /dev/null +++ b/advtrains_line_automation/textures/advtrains_line_automation_jrad.png diff --git a/advtrains_line_automation/time_table.lua b/advtrains_line_automation/time_table.lua new file mode 100644 index 0000000..9e4885e --- /dev/null +++ b/advtrains_line_automation/time_table.lua @@ -0,0 +1,93 @@ +local visual_scale = 15/16 +local node_box = {type = "fixed", fixed = { + -16/32, -16/32, 15/32 / visual_scale, + 16/32, 16/32, 16/32 / visual_scale, +}} +local sbox = {type = "fixed", fixed = { + -16/32 * visual_scale, -16/32 * visual_scale, 15/32, + 16/32 * visual_scale, 16/32 * visual_scale, 16/32, +}} + +local def = { + description = "jízdní řád", + drawtype = "nodebox", + node_box = node_box, + selection_box = sbox, + collision_box = sbox, + tiles = { + {name = "ch_core_white_pixel.png^[multiply:#aaaaaa"}, + {name = "ch_core_white_pixel.png^[multiply:#aaaaaa"}, + {name = "ch_core_white_pixel.png^[multiply:#aaaaaa"}, + {name = "ch_core_white_pixel.png^[multiply:#aaaaaa"}, + {name = "advtrains_line_automation_jrad.png"}, + {name = "advtrains_line_automation_jrad.png"}, + }, + paramtype = "light", + paramtype2 = "4dir", + sunlight_propagates = true, + groups = {cracky = 3, ch_jrad = 1}, + sounds = default.node_sound_metal_defaults(), + visual_scale = visual_scale, + _ch_help = "Použitím (levým klikem) zobrazí jízdní řády všech linek.\nLze umístit do světa a nastavit na jízdní řády v konkrétní stanici/zastávce.\nPo umístění lze také barvit barvicí pistolí.", + _ch_help_group = "jrad", + after_place_node = function(pos, placer, itemstack, pointed_thing) + local player_name = placer and placer:get_player_name() + if player_name ~= nil then + local meta = core.get_meta(pos) + meta:set_string("infotext", "jízdní řád (spravuje: "..ch_core.prihlasovaci_na_zobrazovaci(player_name)..")") + meta:set_string("owner", player_name) + end + end, + can_dig = function(pos, player) + if player == nil then + return false + end + local player_name = player:get_player_name() + if ch_core.get_player_role(player_name) == "admin" then + return true + end + if core.is_protected(pos, player_name) then + core.record_protection_violation(pos, player_name) + return false + end + local meta = core.get_meta(pos) + local owner = meta:get_string("owner") + return owner == "" or owner == player_name + end, + on_use = function(itemstack, user, pointed_thing) + if core.is_player(user) then + advtrains.lines.show_jr_formspec(user) + end + end, + on_rightclick = function(pos, node, clicker, itemstack, pointed_thing) + if clicker ~= nil and core.is_player(clicker) then + local meta = core.get_meta(pos) + -- show_jr_formspec(player, pos, stn, track, linevar, stop_stn, force_unprivileged) + local force_unprivileged = false + if clicker:get_player_control().aux1 then + force_unprivileged = true + end + advtrains.lines.show_jr_formspec(clicker, pos, meta:get_string("stn"), meta:get_string("track"), nil, nil, force_unprivileged) + end + end, +} + +core.register_node("advtrains_line_automation:jrad", table.copy(def)) + +def.description = "jízdní řád (na tyč)" +def.tiles = table.copy(def.tiles) +def.tiles[5] = def.tiles[1] +def.node_box = { + type = "fixed", + fixed = { + -16/32, -16/32, 27/32 / visual_scale, + 16/32, 16/32, 28/32 / visual_scale, + }} +def.selection_box = { + type = "fixed", + fixed = { + -16/32 * visual_scale, -16/32 * visual_scale, 27/32, + 16/32 * visual_scale, 16/32 * visual_scale, 28/32, +}} +def.collision_box = def.selection_box +core.register_node("advtrains_line_automation:jrad_on_pole", def) |