aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorY. Wang <yw05@forksworld.de>2022-04-11 16:55:50 +0200
committerY. Wang <yw05@forksworld.de>2023-03-23 20:06:02 +0100
commit5c8962b39bd4f6871ec87a988ac43d7bfad04d2b (patch)
treeb30a30fdfa905ef5eabce1528829fa41a8c127a3
parent0b4cdbb455b5ba43b3c8d3fd7af5112021144eaa (diff)
downloadadvtrains-5c8962b39bd4f6871ec87a988ac43d7bfad04d2b.tar.gz
advtrains-5c8962b39bd4f6871ec87a988ac43d7bfad04d2b.tar.bz2
advtrains-5c8962b39bd4f6871ec87a988ac43d7bfad04d2b.zip
Implement basic route signaling with Japanese signals for demo
-rw-r--r--advtrains/formspec.lua37
-rw-r--r--advtrains/init.lua4
-rw-r--r--advtrains_interlocking/init.lua3
-rw-r--r--advtrains_interlocking/signal_api.lua146
-rw-r--r--advtrains_interlocking/signal_aspect_ui.lua165
-rw-r--r--advtrains_interlocking/signal_aspects.lua123
-rw-r--r--advtrains_signals_japan/.gitignore1
-rw-r--r--advtrains_signals_japan/init.lua419
-rw-r--r--modpack.conf1
9 files changed, 770 insertions, 129 deletions
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