From 5c8962b39bd4f6871ec87a988ac43d7bfad04d2b Mon Sep 17 00:00:00 2001 From: "Y. Wang" Date: Mon, 11 Apr 2022 16:55:50 +0200 Subject: Implement basic route signaling with Japanese signals for demo --- advtrains/formspec.lua | 37 +++ advtrains/init.lua | 4 + advtrains_interlocking/init.lua | 3 + advtrains_interlocking/signal_api.lua | 146 ++-------- advtrains_interlocking/signal_aspect_ui.lua | 165 +++++++++++ advtrains_interlocking/signal_aspects.lua | 123 ++++++++ advtrains_signals_japan/.gitignore | 1 + advtrains_signals_japan/init.lua | 419 ++++++++++++++++++++++++++++ modpack.conf | 1 + 9 files changed, 770 insertions(+), 129 deletions(-) create mode 100644 advtrains/formspec.lua create mode 100644 advtrains_interlocking/signal_aspect_ui.lua create mode 100644 advtrains_interlocking/signal_aspects.lua create mode 100644 advtrains_signals_japan/.gitignore create mode 100644 advtrains_signals_japan/init.lua diff --git a/advtrains/formspec.lua b/advtrains/formspec.lua new file mode 100644 index 0000000..91e300d --- /dev/null +++ b/advtrains/formspec.lua @@ -0,0 +1,37 @@ +local sformat = string.format +local fsescape = minetest.formspec_escape + +local function f_button_exit(x, y, w, h, id, text) + return sformat("button_exit[%f,%f;%f,%f;%s;%s]", x, y, w, h, id, text) +end + +local function S_button_exit(x, y, w, h, id, ...) + return f_button_exit(x, y, w, h, id, attrans(...)) +end + +local function f_dropdown(x, y, w, id, entries, sel, indexed) + local t = {} + for k, v in pairs(entries) do + t[k] = fsescape(v) + end + return sformat("dropdown[%f,%f;%f;%s;%s;%d%s]", + x, y, w, id, table.concat(t, ","), + sel or 1, + indexed and ";true" or "") +end + +local function f_label(x, y, text) + return sformat("label[%f,%f;%s]", x, y, fsescape(text)) +end + +local function S_label(x, y, ...) + return f_label(x, y, attrans(...)) +end + +return { + button_exit = f_button_exit, + S_button_exit = S_button_exit, + dropdown = f_dropdown, + label = f_label, + S_label = S_label, +} diff --git a/advtrains/init.lua b/advtrains/init.lua index a7e5764..1cba255 100644 --- a/advtrains/init.lua +++ b/advtrains/init.lua @@ -24,6 +24,9 @@ minetest.log("action", "[advtrains] Loading...") -- There is no need to support 0.4.x anymore given that the compatitability with it is already broken by 1bb1d825f46af3562554c12fba35a31b9f7973ff attrans = minetest.get_translator ("advtrains") +function attrans_formspec(...) + return minetest.formspec_escape(attrans(...)) +end --advtrains advtrains = {trains={}, player_to_train_mapping={}} @@ -199,6 +202,7 @@ advtrains.meseconrules = advtrains.fpath=minetest.get_worldpath().."/advtrains" advtrains.speed = dofile(advtrains.modpath.."/speed.lua") +advtrains.formspec = dofile(advtrains.modpath.."/formspec.lua") dofile(advtrains.modpath.."/path.lua") dofile(advtrains.modpath.."/trainlogic.lua") diff --git a/advtrains_interlocking/init.lua b/advtrains_interlocking/init.lua index a2f5882..d0b75a8 100644 --- a/advtrains_interlocking/init.lua +++ b/advtrains_interlocking/init.lua @@ -12,8 +12,11 @@ end local modpath = minetest.get_modpath(minetest.get_current_modname()) .. DIR_DELIM +advtrains.interlocking.aspects = dofile(modpath.."signal_aspects.lua") + dofile(modpath.."database.lua") dofile(modpath.."signal_api.lua") +dofile(modpath.."signal_aspect_ui.lua") dofile(modpath.."demosignals.lua") dofile(modpath.."train_sections.lua") dofile(modpath.."route_prog.lua") diff --git a/advtrains_interlocking/signal_api.lua b/advtrains_interlocking/signal_api.lua index 83fae4a..0a9e6ea 100644 --- a/advtrains_interlocking/signal_api.lua +++ b/advtrains_interlocking/signal_api.lua @@ -220,8 +220,17 @@ function advtrains.interlocking.signal_set_aspect(pos, asp) local node=advtrains.ndb.get_node(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.set_aspect then + local oldasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER + local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + if suppasp.type == 2 then + asp = advtrains.interlocking.aspects.type1_to_type2main(asp, suppasp.group) + end ndef.advtrains.set_aspect(pos, node, asp) - advtrains.interlocking.signal_on_aspect_changed(pos) + local actualasp = advtrains.interlocking.signal_get_aspect(pos) or DANGER + local aspect_changed = advtrains.interlocking.aspects.not_equalp(oldasp, actualasp) + if aspect_changed then + advtrains.interlocking.signal_on_aspect_changed(pos) + end end end @@ -252,7 +261,7 @@ function advtrains.interlocking.signal_rc_handler(pos, node, player, itemstack, local function callback(pname, aspect) advtrains.interlocking.signal_set_aspect(pos, aspect) end - local isasp = ndef.advtrains.get_aspect(pos, node) + local isasp = advtrains.interlocking.signal_get_aspect(pos, node) advtrains.interlocking.show_signal_aspect_selector( pname, @@ -285,8 +294,13 @@ function advtrains.interlocking.signal_get_aspect(pos) local ndef=minetest.registered_nodes[node.name] if ndef and ndef.advtrains and ndef.advtrains.get_aspect then local asp = ndef.advtrains.get_aspect(pos, node) + local suppasp = advtrains.interlocking.signal_get_supported_aspects(pos) or {} + if suppasp.type == 2 then + asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, asp) + end if not asp then asp = DANGER end - return convert_aspect_if_necessary(asp) + asp = convert_aspect_if_necessary(asp) + return asp end return nil end @@ -411,129 +425,3 @@ minetest.register_on_punchnode(function(pos, node, player, pointed_thing) players_assign_ip[pname] = nil end end) - - ---== aspect selector ==-- - -local players_aspsel = {} - ---[[ -suppasp: "supported_aspects" table -purpose: form title string -callback: func(pname, aspect) called on form submit -isasp: aspect currently set -]] -function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp) - local suppasp = p_suppasp or { - main = {0, -1}, dst = {false}, shunt = false, info = {}, - } - local purpose = p_purpose or "" - - local form = "size[7,7]label[0.5,0.5;Select Signal Aspect:]" - form = form.."label[0.5,1;"..purpose.."]" - - form = form.."label[0.5,1.5;== Main Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.main) do - local entry - if spv == 0 then - entry = "Halt" - elseif spv == -1 then - entry = "Continue at maximum speed" - elseif not spv then - entry = "Continue\\, speed limit unchanged (no info)" - else - entry = "Continue at speed of "..spv - end - -- hack: the crappy formspec system returns the label, not the index. save the index in it. - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.main or false) then - selid = idx - end - end - form = form.."dropdown[0.5,2;6;main;"..table.concat(entries, ",")..";"..selid.."]" - - - form = form.."label[0.5,3;== Shunting ==]" - if suppasp.shunt == nil then - local st = 1 - if isasp and isasp.shunt then st=2 end - form = form.."dropdown[0.5,3.5;6;shunt_free;---,allowed;"..st.."]" - end - - form = form.."label[0.5,4.5;== Distant Signal ==]" - local selid = 1 - local entries = {} - for idx, spv in ipairs(suppasp.dst) do - local entry - if spv == 0 then - entry = "Expect to stop at the next signal" - elseif spv == -1 then - entry = "Expect to pass the next signal at maximum speed" - elseif not spv then - entry = "No info" - else - entry = string.format("Expect to pass the next signal at speed of %d", spv) - end - entries[idx] = idx.."| "..entry - if isasp and spv == (isasp.dst or false) then - selid = idx - end - end - form = form.."dropdown[0.5,5;6;dst;"..table.concat(entries, ",")..";"..selid.."]" - - form = form.."button_exit[0.5,6;5,1;save;Save signal aspect]" - - local token = advtrains.random_id() - - minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) - - minetest.after(1, function() - players_aspsel[pname] = { - suppasp = suppasp, - callback = callback, - token = token, - } - end) -end - -local function usebool(sup, val, free) - if sup == nil then - return val==free - else - return sup - end -end - --- other side of hack: extract the index -local function ddindex(val) - return tonumber(string.match(val, "^(%d+)|")) -end - --- TODO use non-hacky way to parse outputs - -minetest.register_on_player_receive_fields(function(player, formname, fields) - local pname = player:get_player_name() - local psl = players_aspsel[pname] - if psl then - if formname == "at_il_sigaspdia_"..psl.token then - if fields.save then - local maini = ddindex(fields.main) - if not maini then return end - local dsti = ddindex(fields.dst) - if not dsti then return end - local asp = { - main = psl.suppasp.main[maini], - dst = psl.suppasp.dst[dsti], - shunt = usebool(psl.suppasp.shunt, fields.shunt_free, "allowed"), - info = {} - } - psl.callback(pname, asp) - end - else - players_aspsel[pname] = nil - end - end - -end) diff --git a/advtrains_interlocking/signal_aspect_ui.lua b/advtrains_interlocking/signal_aspect_ui.lua new file mode 100644 index 0000000..edf3ab1 --- /dev/null +++ b/advtrains_interlocking/signal_aspect_ui.lua @@ -0,0 +1,165 @@ +local F = advtrains.formspec +local players_aspsel = {} + +local function make_signal_aspect_selector_t1(suppasp, purpose, isasp) + local form = {"size[7,7.5]"} + form[#form+1] = F.S_label(0.5, 0.5, "Select signal aspect") + form[#form+1] = F.label(0.5, 1, purpose) + + form[#form+1] = F.S_label(0.5, 1.5, "Main aspect") + local entries = {} + local selid = 1 + for idx, spv in ipairs(suppasp.main) do + local entry + if isasp and spv == isasp.main then + selid = idx + end + if spv == 0 then + entry = attrans("Danger (halt)") + elseif spv == -1 then + entry = attrans("Continue at maximum speed") + elseif not spv then + entry = attrans("Continue with current speed limit") + else + entry = attrans("Continue with the speed limit of @1", spv) + end + entries[idx] = entry + end + form[#form+1] = F.dropdown(0.5, 2, 6, "main", entries, selid, true) + + form[#form+1] = F.S_label(0.5, 3, "Shunt aspect") + if suppasp.shunt == nil then + local st = 1 + if isasp and isasp.shunt then st = 2 end + local entries = { + attrans("No shunting"), + attrans("Shunting allowed"), + } + form[#form+1] = F.dropdown(0.5, 3.5, 6, "shunt_free", entries, st, true) + end + + form[#form+1] = F.S_label(0.5, 4.5, "Distant aspect") + local entries = {} + local selid = 1 + for idx, spv in ipairs(suppasp.dst) do + local entry + if isasp and spv == isasp.dst then + selid = idx + end + if spv == 0 then + entry = attrans("Expect to stop at the next signal") + elseif spv == -1 then + entry = attrans("Expect to continue at maximum speed") + elseif not spv then + entry = attrans("No information on distant signal") + else + entry = attrans("Expect to continue with a speed limit of @1", spv) + end + entries[idx] = entry + end + form[#form+1] = F.dropdown(0.5, 5, 6, "dst", entries, selid, true) + + form[#form+1] = F.S_button_exit(0.5, 6, 6, 1, "save", "Save signal aspect") + return table.concat(form) +end + +local function make_signal_aspect_selector_t2(suppasp, purpose, isasp) + local form = {"size[7,4]"} + local def = advtrains.interlocking.aspects.get_type2_definition(suppasp.group) + if not def then + return nil + end + form[#form+1] = F.S_label(0.5, 0.5, "Select signal aspect") + form[#form+1] = F.label(0.5, 1, purpose) + + local entries = {} + local selid = 1 + for idx, spv in ipairs(def.main) do + if isasp and isasp.type2name == spv.name then + selid = idx + end + entries[idx] = spv.label + end + form[#form+1] = F.dropdown(0.5, 1.5, 6, "asp", entries, selid, true) + form[#form+1] = F.S_button_exit(0.5, 2.5, 6, 1, "save", "Save signal aspect") + return table.concat(form) +end + +function advtrains.interlocking.show_signal_aspect_selector(pname, p_suppasp, p_purpose, callback, isasp) + local suppasp = p_suppasp or { + main = {0, -1}, + dst = {false}, + shunt = false, + info = {}, + } + local purpose = p_purpose or "" + + local form + if suppasp.type == 2 then + form = make_signal_aspect_selector_t2(suppasp, purpose, isasp) + else + form = make_signal_aspect_selector_t1(suppasp, purpose, isasp) + end + if not form then + return + end + + local token = advtrains.random_id() + minetest.show_formspec(pname, "at_il_sigaspdia_"..token, form) + minetest.after(1, function() + players_aspsel[pname] = { + suppasp = suppasp, + callback = callback, + token = token, + } + end) +end + +local function usebool(sup, val, free) + if sup == nil then + return val == free + else + return sup + end +end + +local function get_aspect_from_formspec_t1(suppasp, fields) + local maini = tonumber(fields.main) + if not maini then return end + local dsti = tonumber(fields.dst) + if not dsti then return end + return { + main = suppasp.main[maini], + dst = suppasp.dst[dsti], + shunt = usebool(suppasp.shunt, fields.shunt_free, "2"), + info = {}, + } +end + +local function get_aspect_from_formspec_t2(suppasp, fields) + local asp = advtrains.interlocking.aspects.type2main_to_type1(suppasp.group, tonumber(fields.asp)) + return asp +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local pname = player:get_player_name() + local psl = players_aspsel[pname] + if psl then + if formname == "at_il_sigaspdia_"..psl.token then + local suppasp = psl.suppasp + if fields.save then + local asp + if suppasp.type == 2 then + asp = get_aspect_from_formspec_t2(suppasp, fields) + else + asp = get_aspect_from_formspec_t1(suppasp, fields) + end + if asp then + psl.callback(pname, asp) + end + end + else + players_aspsel[pname] = nil + end + end +end) diff --git a/advtrains_interlocking/signal_aspects.lua b/advtrains_interlocking/signal_aspects.lua new file mode 100644 index 0000000..a70d176 --- /dev/null +++ b/advtrains_interlocking/signal_aspects.lua @@ -0,0 +1,123 @@ +type2defs = {} + +local function register_type2(def) + local t = {type = 2} + local name = def.name + if type2defs[name] then + return error("Name " .. name .. " already used") + elseif type(name) ~= "string" then + return error("Name is not a string") + end + t.name = name + + local label = def.label or name + if type(label) ~= "string" then + return error("Label is not a string") + end + t.label = label + + local mainasps = {} + for idx, asp in ipairs(def.main) do + local t = {} + local name = asp.name + if type(name) ~= "string" then + return error("Aspect name is not a string") + end + t.name = name + + local label = asp.label or name + if type(label) ~= "string" then + return error("Aspect label is not a string") + end + t.label = label + + t.main = asp.main + mainasps[idx] = t + mainasps[name] = idx + end + t.main = mainasps + + type2defs[name] = t +end + +local function get_type2_definition(name) + return type2defs[name] +end + +local function type2main_to_type1(name, asp) + local def = type2defs[name] + if not def then + return nil + end + local aspidx + if type(asp) == "number" then + aspidx = asp + else + aspidx = def.main[asp] + end + local asptbl = def.main[aspidx] + if not asptbl then + return nil + end + if type(asp) == "number" then + asp = asptbl.name + end + + local t = { + main = asptbl.main, + type2name = asp, + type2group = name, + } + if aspidx > 1 and aspidx < #asptbl then + t.dst = asptbl[aspidx+1].main + end + return t +end + +local function type1_to_type2main(asp, group) + local def = type2defs[group] + if not def then + return nil + end + if group == asp.type2group and def.main[asp.type2name] then + return asp.type2name + end + local t_main = def.main + local idx + if not asp.main or asp.main == -1 then + idx = 1 + elseif asp.main == 0 then + idx = #t_main + else + idx = math.max(#t_main-1, 1) + end + return t_main[idx].name +end + +local function equalp(asp1, asp2) + if asp1 == asp2 then -- same reference + return true + elseif asp1.type2group and asp1.type2group == asp2.type2group then -- type2 with the same group + return asp1.type2name == asp2.type2name + else + for _, k in pairs {"main", "shunt", "dst"} do + if asp1[k] ~= asp2[k] then + return false + end + end + end + return true +end + +local function not_equalp(asp1, asp2) + return not equalp(asp1, asp2) +end + +return { + register_type2 = register_type2, + get_type2_definition = get_type2_definition, + type2main_to_type1 = type2main_to_type1, + type1_to_type2main = type1_to_type2main, + equalp = equalp, + not_equalp = not_equalp, +} diff --git a/advtrains_signals_japan/.gitignore b/advtrains_signals_japan/.gitignore new file mode 100644 index 0000000..0969b6b --- /dev/null +++ b/advtrains_signals_japan/.gitignore @@ -0,0 +1 @@ +models/* diff --git a/advtrains_signals_japan/init.lua b/advtrains_signals_japan/init.lua new file mode 100644 index 0000000..9ccf66b --- /dev/null +++ b/advtrains_signals_japan/init.lua @@ -0,0 +1,419 @@ +local pole_texture = "advtrains_hud_bg.png^[colorize:#858585:255" +local signal_face_texture = "advtrains_hud_bg.png^[colorize:#000000:255" +local pole_radius = 1/16 +local pole_box = {-pole_radius,-1/2,-pole_radius,pole_radius,1/2,pole_radius} +local light_radius = 1/20 +local signal_width = 6*light_radius +local signal_thickness = pole_radius*3 +local signal_height = {} +local signal_box = {} +local light_red = "advtrains_hud_bg.png^[colorize:red:255" +local light_yellow = "advtrains_hud_bg.png^[colorize:orange:255" +local light_green = "advtrains_hud_bg.png^[colorize:lime:255" +local light_purple = "advtrains_hud_bg.png^[colorize:purple:255" +local light_distant = light_purple +local light_off = signal_face_texture + +do + local model_path_prefix = table.concat({minetest.get_modpath("advtrains_signals_japan"), "models", "advtrains_signals_japan_"}, DIR_DELIM) + + local function vertex(x, y, z) + return string.format("v %f %f %f", x, y, z) + end + local function texture(u, v) + return string.format("vt %f %f", u, v) + end + local function face_element(v, vt) + if vt then + return string.format("%d/%d", v, vt) + end + return tonumber(v) + end + local function face_elements(...) + local st = {"f"} + local args = {...} + local len = #args + for i = 1, len, 2 do + st[(i+3)/2] = face_element(args[i], args[i+1]) + end + return table.concat(st, " ") + end + local function sequential_elements(v0, vt0, count) + local st = {} + for i = 1, count do + st[i] = face_element(v0+i, vt0+i) + end + return table.concat(st, " ") + end + local function mod_lower(min, a, b) + return min + (a-min)%b + end + local function connect_circular(v0, vt0, count) + return "f " .. sequential_elements(v0, vt0, count) + end + local function connect_cylindrical(v0, vt0, count) + local st = {} + for i = 0, count-1 do + local j = (i+1)%count + local v1 = v0+i+1 + local v2 = v1+count + local v3 = v0+j+1 + local v4 = v3+count + local vt1 = vt0+i+1 + local vt2 = vt1+count+1 + st[i+1] = face_elements(v1, vt1, v3, vt1+1, v4, vt2+1, v2, vt2) + end + return table.concat(st, "\n") + end + local function circular_textures(u0, v0, r, count, total, angular_offset, direction) + local st = {} + if not angular_offset then + angular_offset = 0 + end + if not total then + total = count + end + if not direction then + direction = 1 + end + for i = 0, count-1 do + local theta = angular_offset + direction*i/total*2*math.pi + local u, v = r*math.cos(theta), r*math.sin(theta) + st[i+1] = texture(u0+u, v0+v) + end + return table.concat(st, "\n") + end + local function rectangular_textures(u0, v0, u1, v1, count) + local st = {} + local width = u1-u0 + for i = 0, count do + local u = u0+i/count*width + st[i+1] = texture(u, v0) + st[i+count+2] = texture(u, v1) + end + return table.concat(st, "\n") + end + + -- generate pole model + local pole_npolygon = 32 + local pole_vertex_count = pole_npolygon*2 + local pole_uv_count = pole_npolygon*3+2 + local pole_vertices = {} + local pole_objdef = { + "g pole", + "usemtl pole", + connect_circular(0, 0, pole_npolygon), + connect_circular(pole_npolygon, 0, pole_npolygon), + connect_cylindrical(0, pole_npolygon, pole_npolygon), + } + local pole_uv = { + circular_textures(0.5, 0.5, 0.5, pole_npolygon), + rectangular_textures(0, 0, 1, 1, pole_npolygon), + } + for i = 0, pole_npolygon-1 do + local theta = i*2/pole_npolygon*math.pi + local r = pole_radius + local x, z = r*math.sin(theta), r*math.cos(theta) + local lower_index = i+1 + local upper_index = lower_index+pole_npolygon + pole_vertices[lower_index] = vertex(x, -0.5, z) + pole_vertices[upper_index] = vertex(x, 0.5, z) + end + pole_vertices = table.concat(pole_vertices, "\n") + pole_objdef = table.concat(pole_objdef, "\n") + pole_uv = table.concat(pole_uv, "\n") + minetest.safe_file_write(model_path_prefix .. "pole.obj", table.concat({pole_vertices, pole_uv, pole_objdef}, "\n")) + + -- generate signals + for lightcount = 5, 6 do + for rotname, rot in pairs {["0"] = 0, ["30"] = 26.5, ["45"] = 45, ["60"] = 63.5} do + local rot = math.rad(rot) + local lightradius = 0.05 + local lightspacing = 0.04 + local halfwidth = signal_width/2 + local halfheight = (2+lightcount)*lightradius+(lightcount-1)*lightspacing/2 + local halfthickness = signal_thickness/2 + local half_npolygon = pole_npolygon/2 + local quarter_npolygon = pole_npolygon/4 + local boxside = math.max(halfwidth, halfthickness*2) + signal_height[lightcount] = halfheight*2 + signal_box[lightcount] = {-boxside, -halfheight, -boxside, boxside, halfheight, boxside} + + local _vertex = vertex + local rv = vector.new(0, rot, 0) + local function vertex(x, y, z) + local v = vector.rotate(vector.new(x, y, z), rv) + return _vertex(v.x, v.y, v.z) + end + + -- generate signal face + local face_vertices = {} + local face_uv = { + circular_textures(0.5, 0.5+halfheight-3*lightradius, halfwidth, half_npolygon+1, pole_npolygon), + circular_textures(0.5, 0.5-halfheight+3*lightradius, halfwidth, half_npolygon+1, pole_npolygon, math.pi), + rectangular_textures(0, 0, 1, 1, 2+pole_npolygon), + } + local face_objdef = { + "g face", + "usemtl face", + connect_circular(pole_vertex_count+2+pole_npolygon, pole_uv_count, 2+pole_npolygon), + connect_circular(pole_vertex_count, pole_uv_count, 2+pole_npolygon), + connect_cylindrical(pole_vertex_count, pole_uv_count+2+pole_npolygon, 2+pole_npolygon), + } + local face_vertex_count = 4*half_npolygon+4 + local face_uv_count = 2*(half_npolygon+1) + 2*(pole_npolygon+3) + for i = 0, half_npolygon do + local theta = i/half_npolygon*math.pi + local r = halfwidth + local x, y = r*math.cos(theta), halfheight-3*lightradius+r*math.sin(theta) + face_vertices[i+1] = vertex(x, y, -halfthickness) + face_vertices[i+2+half_npolygon] = vertex(-x, -y, -halfthickness) + face_vertices[i+3+2*half_npolygon] = vertex(x, y, halfthickness) + face_vertices[i+4+3*half_npolygon] = vertex(-x, -y, halfthickness) + end + + -- generate lights + local light_vertices = {} + local light_vertex_count = 8*(half_npolygon+1)+pole_npolygon + local light_uv = {rectangular_textures(0, 0, 1, 1, half_npolygon)} + local light_uv_count = 2*(half_npolygon+1)+pole_npolygon*lightcount + local light_objdef_face = {} + local light_objdef_main = { + "g light", + "usemtl light", + } + for i = 1, lightcount do + local x0, y0 = 0, -halfheight + (2*i+1)*lightradius + (i-1)*lightspacing + local v0 = light_vertex_count*(i-1) + for j = 0, half_npolygon do + local theta = j/half_npolygon*math.pi + local xs, ys = math.cos(theta), math.sin(theta) + for k, v in pairs { + {xm = -1, ym = 1, rm = 1, z = 1}, + {xm = 1, ym = 1, rm = 0.8, z = 1}, + {xm = -1, ym = 1, rm = 1, z = 2}, + {xm = 1, ym = 1, rm = 0.8, z = 2}, + {xm = 1, ym = -1, rm = 1, z = 1}, + {xm = -1, ym = -1, rm = 0.8, z = 1}, + {xm = 1, ym = -1, rm = 1, z = 1.5}, + {xm = -1, ym = -1, rm = 0.8, z = 1.5}, + } do + local x = x0+xs*lightradius*v.xm*v.rm + local y = y0+ys*lightradius*v.ym*v.rm + light_vertices[v0+(k-1)*(half_npolygon+1)+j+1] = vertex(x, y, -halfthickness*v.z) + end + end + for j = 0, pole_npolygon-1 do + local theta = j/pole_npolygon*2*math.pi + local x, y = math.cos(theta), math.sin(theta) + light_vertices[v0+8*(half_npolygon+1)+1+j] = vertex(x0+lightradius*x, y0+lightradius*y, -halfthickness*1.05) + end + local v0 = pole_vertex_count+face_vertex_count+v0 + local vt0 = pole_uv_count + face_uv_count + local ostep = 2*half_npolygon+2 + for j = 1, half_npolygon do + local dv = 2*(half_npolygon+1) + local v0 = v0 + dv + local vn = v0 + dv + light_objdef_face[i*ostep-j+1] = face_elements(v0+j, vt0+j, v0+j+1, vt0+j+1, vn-j, vt0+half_npolygon+2+j, vn-j+1, vt0+half_npolygon+1+j) + local v0 = vn + dv + local vn = v0 + dv + light_objdef_face[i*ostep-half_npolygon-j+1] = face_elements(v0+j, vt0+j, v0+j+1, vt0+j+1, vn-j, vt0+half_npolygon+2+j, vn-j+1, vt0+half_npolygon+1+j) + end + local vt0 = vt0 + 2*(half_npolygon+1) + (i-1)*pole_npolygon + light_uv[i+1] = circular_textures(0.5, (i-1/2)/lightcount, 0.4/lightcount, pole_npolygon) + light_objdef_face[(i-1)*ostep+1] = connect_cylindrical(v0, pole_uv_count+2+pole_npolygon, 2+pole_npolygon) + light_objdef_face[(i-1)*ostep+2] = connect_cylindrical(v0+4*(half_npolygon+1), pole_uv_count+2+pole_npolygon, 2+pole_npolygon) + light_objdef_main[2+i] = connect_circular(v0+8*(half_npolygon+1), vt0, pole_npolygon) + end + + -- write file + face_vertices = table.concat(face_vertices, "\n") + face_uv = table.concat(face_uv, "\n") + face_objdef = table.concat(face_objdef, "\n") + minetest.safe_file_write(model_path_prefix .. lightcount .. "_" .. rotname .. ".obj", table.concat({ + pole_vertices, + face_vertices, + table.concat(light_vertices, "\n"), + pole_uv, + face_uv, + table.concat(light_uv, "\n"), + pole_objdef, + face_objdef, + table.concat(light_objdef_face, "\n"), + table.concat(light_objdef_main, "\n"), + }, "\n")) + end + end +end + +local S = attrans + +minetest.register_node("advtrains_signals_japan:pole_0", { + description = S("Japanese signal pole"), + drawtype = "mesh", + mesh = "advtrains_signals_japan_pole.obj", + tiles = {pole_texture}, + + paramtype = "light", + sunlight_propagates = true, + + paramtype2 = "none", + selection_box = { + type = "fixed", + fixed = {pole_box}, + }, + collision_box = { + type = "fixed", + fixed = {pole_box}, + }, + groups = { + cracky = 2, + not_blocking_trains = 1, + not_in_creative_inventory = 0, + }, + drop = "advtrains_signals_japan:pole_0", +}) + +local sigdefs = {} +local lightcolors = { + red = "red", + green = "lime", + yellow = "orange", + distant = "purple", +} +local aspnames = { + danger = "Danger (halt)", + restrictedspeed = "Restricted speed", + caution = "Caution", + reducedspeed = "Reduced speed", + clear = "Clear (proceed)", +} +local function process_signal(name, sigdata, isrpt) + local typename = "advtrains_signals_japan:" .. name + local type2def = {} + type2def.name = typename + type2def.main = {} + local def = {} + local tx = {} + def.typename = typename + def.textures = tx + def.desc = sigdata.desc + def.isdst = isrpt + local lights = sigdata.lights + local lightcount = #lights + if isrpt then + lightcount = lightcount+1 + end + def.lightcount = lightcount + for idx, asp in ipairs(sigdata.aspects) do + local aspname = asp.name + local tt = { + string.format("[combine:1x%d", lightcount), + string.format("0,0=(advtrains_hud_bg.png\\^[resize\\:1x%d\\^[colorize\\:#000)", lightcount), + } + for _, i in pairs(asp.lights) do + local color = lightcolors[lights[i]] + tt[#tt+1] = string.format("0,%d=(advtrains_hud_bg.png\\^[colorize\\:%s)", i-1, color) + end + if isrpt then + local color = lightcolors.distant + tt[#tt+1] = string.format("0,%d=(advtrains_hud_bg.png\\^[colorize\\:%s)", lightcount-1, color) + end + tx[aspname] = table.concat(tt, ":") + type2def.main[idx] = {name = asp.name, label = S(aspnames[asp.name]), main = asp.main or false} + end + local invimg = { + string.format("[combine:%dx%d", lightcount*4+1, lightcount*4+1), + string.format("%d,0=(advtrains_hud_bg.png\\^[resize\\:5x%d\\^[colorize\\:#000)", lightcount*2-2, lightcount*4+1), + } + for i, c in pairs(lights) do + local color = lightcolors[c] + invimg[i+2] = string.format("%d,%d=(advtrains_hud_bg.png\\^[resize\\:3x3\\^[colorize\\:%s)", 2*lightcount-1, 4*i-3, color) + end + if isrpt then + invimg[lightcount+2] = string.format("%d,%d=(advtrains_hud_bg.png\\^[resize\\:3x3\\^[colorize\\:%s)", 2*lightcount-1, 4*lightcount-3, lightcolors.distant) + end + def.inventory_image = table.concat(invimg, ":") + if not isrpt then + advtrains.interlocking.aspects.register_type2(type2def) + end + return def +end +for sigtype, sigdata in pairs { + ["5a"] = { + desc = "5A", + lights = {"yellow", "yellow", "red", "yellow", "green"}, + aspects = { + {name = "clear", lights = {5}, main = -1}, + {name = "reducedspeed", lights = {2, 5}}, + {name = "caution", lights = {4}}, + {name = "restrictedspeed", lights = {1, 4}}, + {name = "danger", lights = {3}, main = 0}, + } + } +} do + sigdefs["main_"..sigtype] = process_signal(sigtype, sigdata) + sigdefs["rpt_"..sigtype] = process_signal(sigtype, sigdata, true) +end + +for k in pairs(sigdefs) do + advtrains.trackplacer.register_tracktype("advtrains_signals_japan:"..k) +end + +for _, rtab in ipairs { + {rot = "0", ici = true}, + {rot = "30"}, + {rot = "45"}, + {rot = "60"}, +} do + local rot = rtab.rot + for sigtype, siginfo in pairs(sigdefs) do + local lightcount = siginfo.lightcount + for asp, texture in pairs(siginfo.textures) do + minetest.register_node("advtrains_signals_japan:"..sigtype.."_"..asp.."_"..rot, { + description = attrans(string.format("Japanese%s signal (type %s)", siginfo.isdst and " repeating" or "", siginfo.desc)), + drawtype = "mesh", + mesh = string.format("advtrains_signals_japan_%d_%s.obj", lightcount, rot), + tiles = {pole_texture, signal_face_texture, texture}, + paramtype = "light", + sunlight_propagates = true, + light_source = 4, + paramtype2 = "facedir", + selection_box = { + type = "fixed", + fixed = {pole_box, signal_box[lightcount]}, + }, + collision_box = { + type = "fixed", + fixed = {pole_box, signal_box[lightcount]}, + }, + groups = { + cracky = 2, + advtrains_signal = 2, + not_blocking_trains = 1, + save_in_at_nodedb = 1, + not_in_creative_inventory = rtab.ici and asp == "danger" and 0 or 1, + }, + inventory_image = siginfo.inventory_image, + drop = "advtrains_signals_japan:"..sigtype.."_danger_0", + advtrains = { + supported_aspects = { + type = 2, + group = siginfo.typename, + }, + get_aspect = function() + return asp + end, + set_aspect = function(pos, node, asp) + advtrains.ndb.swap_node(pos, {name = "advtrains_signals_japan:"..sigtype.."_"..asp.."_"..rot, param2 = node.param2}) + end, + }, + on_rightclick = advtrains.interlocking.signal_rc_handler, + can_dig = advtrains.interlocking.signal_can_dig, + after_dig_node = advtrains.interlocking.signal_after_dig, + }) + advtrains.trackplacer.add_worked("advtrains_signals_japan:"..sigtype, asp, "_"..rot) + end + end +end diff --git a/modpack.conf b/modpack.conf index e01f462..5185be4 100644 --- a/modpack.conf +++ b/modpack.conf @@ -2,3 +2,4 @@ name=advtrains title=Advanced Trains description=Realistic trains and various equipment for railways, with a focus on automated train operation. No trains included, please install those separately. author=orwell96 +min_minetest_version=5.4 \ No newline at end of file -- cgit v1.2.3