From 5395dc998294640b9c04581f117dbd3fb6d6e61b Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sun, 8 Nov 2020 19:49:10 +0100 Subject: [PATCH 01/12] remember last train marker settings per player, also remember color --- train.lua | 73 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/train.lua b/train.lua index 84edb2a..d8f670c 100644 --- a/train.lua +++ b/train.lua @@ -1,6 +1,5 @@ -local last_index = 0 -local last_line = "" +local last_set_by = {} local update_formspec = function(meta) local line = meta:get_string("line") @@ -40,12 +39,56 @@ minetest.register_node("mapserver:train", { on_construct = function(pos) local meta = minetest.get_meta(pos) - last_index = last_index + 5 + local find_nearest_player = function(pos, radius) + -- adapted from https://forum.minetest.net/viewtopic.php?t=23319 + + local closest_d = radius+1 + local closest_name + + for i, obj in ipairs(minetest.get_objects_inside_radius(pos, radius)) do + -- 0.4.x compatibility: + --if obj:get_player_name() ~= "" then + + -- 5.0.0+ method: + if minetest.is_player(obj) then + local distance = vector.distance(obj:get_pos(), pos) + if distance < closest_d then + closest_d = distance + closest_name = obj:get_player_name() + end + end + end + + -- if 'closest_name' is nil, there's no player inside that radius + return closest_name + end + + -- 10 should be the max possible interaction distance + local name = find_nearest_player(pos, 10) + + local last_index = 0 + local last_line = "" + local last_color = "" + + if name ~= nil then + name = string.lower(name) + if last_set_by[name] ~= nil then + last_index = last_set_by[name].index + 5 + last_line = last_set_by[name].line + last_color = last_set_by[name].color + else + last_set_by[name] = {} + end + + last_set_by[name].index = last_index + last_set_by[name].line = last_line + last_set_by[name].color = last_color + end meta:set_string("station", "") meta:set_string("line", last_line) meta:set_int("index", last_index) - meta:set_string("color", "") + meta:set_string("color", last_color) update_formspec(meta) end, @@ -57,17 +100,27 @@ minetest.register_node("mapserver:train", { end local meta = minetest.get_meta(pos) + local name = string.lower(sender:get_player_name()) if fields.save then - last_line = fields.line - meta:set_string("color", fields.color) - meta:set_string("line", fields.line) - meta:set_string("station", fields.station) + if last_set_by[name] == nil then + last_set_by[name] = {} + end + local index = tonumber(fields.index) if index ~= nil then - last_index = index - meta:set_int("index", index) + index = index end + + meta:set_string("color", fields.color) + meta:set_string("line", fields.line) + meta:set_string("station", fields.station) + meta:set_int("index", index) + + last_set_by[name].color = fields.color + last_set_by[name].line = fields.line + last_set_by[name].station = fields.station + last_set_by[name].index = index end update_formspec(meta) From 84e5477eb909fc86da410a274db5c625636f6ceb Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Fri, 7 May 2021 08:49:41 +0200 Subject: [PATCH 02/12] fix luacheck error: pos variable overshadowing --- train.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/train.lua b/train.lua index d8f670c..5e7a326 100644 --- a/train.lua +++ b/train.lua @@ -39,19 +39,19 @@ minetest.register_node("mapserver:train", { on_construct = function(pos) local meta = minetest.get_meta(pos) - local find_nearest_player = function(pos, radius) + local find_nearest_player = function(block_pos, radius) -- adapted from https://forum.minetest.net/viewtopic.php?t=23319 local closest_d = radius+1 local closest_name - for i, obj in ipairs(minetest.get_objects_inside_radius(pos, radius)) do + for i, obj in ipairs(minetest.get_objects_inside_radius(block_pos, radius)) do -- 0.4.x compatibility: --if obj:get_player_name() ~= "" then -- 5.0.0+ method: if minetest.is_player(obj) then - local distance = vector.distance(obj:get_pos(), pos) + local distance = vector.distance(obj:get_pos(), block_pos) if distance < closest_d then closest_d = distance closest_name = obj:get_player_name() From d6b6cb8b7bcf93e384799f15c6dd9422b045655e Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Fri, 7 May 2021 09:00:24 +0200 Subject: [PATCH 03/12] refactor for after_place_node instead of on_construct, [NOT TESTED] --- train.lua | 60 ++++++++++++++++++------------------------------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/train.lua b/train.lua index 5e7a326..ec16060 100644 --- a/train.lua +++ b/train.lua @@ -34,55 +34,30 @@ minetest.register_node("mapserver:train", { groups = {cracky=3,oddly_breakable_by_hand=3}, sounds = moditems.sound_glass(), can_dig = mapserver.can_interact, - after_place_node = mapserver.after_place_node, - on_construct = function(pos) + after_place_node = function(pos, placer, itemstack, pointed_thing) local meta = minetest.get_meta(pos) - local find_nearest_player = function(block_pos, radius) - -- adapted from https://forum.minetest.net/viewtopic.php?t=23319 - - local closest_d = radius+1 - local closest_name - - for i, obj in ipairs(minetest.get_objects_inside_radius(block_pos, radius)) do - -- 0.4.x compatibility: - --if obj:get_player_name() ~= "" then - - -- 5.0.0+ method: - if minetest.is_player(obj) then - local distance = vector.distance(obj:get_pos(), block_pos) - if distance < closest_d then - closest_d = distance - closest_name = obj:get_player_name() - end - end - end - - -- if 'closest_name' is nil, there's no player inside that radius - return closest_name - end - - -- 10 should be the max possible interaction distance - local name = find_nearest_player(pos, 10) - local last_index = 0 local last_line = "" local last_color = "" - if name ~= nil then - name = string.lower(name) - if last_set_by[name] ~= nil then - last_index = last_set_by[name].index + 5 - last_line = last_set_by[name].line - last_color = last_set_by[name].color - else - last_set_by[name] = {} - end + if minetest.is_player(placer) then + local name = placer:get_player_name() + if name ~= nil then + name = string.lower(name) + if last_set_by[name] ~= nil then + last_index = last_set_by[name].index + 5 + last_line = last_set_by[name].line + last_color = last_set_by[name].color + else + last_set_by[name] = {} + end - last_set_by[name].index = last_index - last_set_by[name].line = last_line - last_set_by[name].color = last_color + last_set_by[name].index = last_index + last_set_by[name].line = last_line + last_set_by[name].color = last_color + end end meta:set_string("station", "") @@ -91,6 +66,9 @@ minetest.register_node("mapserver:train", { meta:set_string("color", last_color) update_formspec(meta) + + + return mapserver.after_place_node(pos, placer, itemstack, pointed_thing) end, on_receive_fields = function(pos, formname, fields, sender) From 545c11ffad1a86a9d0214b074864d45cd79d8c15 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sun, 16 Jan 2022 01:44:29 +0100 Subject: [PATCH 04/12] allow attaching to rails and follow them to fin the exact line --- train.lua | 409 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 403 insertions(+), 6 deletions(-) diff --git a/train.lua b/train.lua index ec16060..bb4cce9 100644 --- a/train.lua +++ b/train.lua @@ -1,13 +1,38 @@ +local advtrains_present = minetest.get_modpath("advtrains") and true or false local last_set_by = {} +local find_neighbor_blocks -- defined later +local update_neighbors --defined later +local recalculate_between -- defined later +local traverser -- defined later +local TRAVERSER_LIMIT = 1000 + local update_formspec = function(meta) local line = meta:get_string("line") local station = meta:get_string("station") local index = meta:get_string("index") local color = meta:get_string("color") or "" + local rail_pos = meta:get_string("rail_pos") or "" + + local rail_btns = "" + if advtrains_present then + if rail_pos == "" then + rail_btns = "button_exit[4,3.5;2.5,1;set_rail_pos;Set rail]" + else + rail_btns = "button_exit[4,3.5;2.5,1;set_rail_pos;" .. rail_pos .. "]" .. + "button[6.5,3.5;1.5,1;clear_rail_pos;Clear rail]" + end + end - meta:set_string("infotext", "Train: Line=" .. line .. ", Station=" .. station) + local prv = meta:get_string("prv_pos") + local path = meta:get_string("linepath_from_prv") + local nxt = meta:get_string("nxt_pos") + + meta:set_string("infotext", "Train: Line=" .. line .. ", Station=" .. station .. + (prv ~= "" and (", prv="..prv) or "") .. + (path ~= "" and " (found line)" or "") .. + (nxt ~= "" and (", nxt="..nxt) or "")) meta:set_string("formspec", "size[8,4;]" .. -- col 1 @@ -20,7 +45,7 @@ local update_formspec = function(meta) -- col 3 "field[0,3.5;4,1;color;Color;" .. color .. "]" .. - "" + rail_btns ) end @@ -64,13 +89,21 @@ minetest.register_node("mapserver:train", { meta:set_string("line", last_line) meta:set_int("index", last_index) meta:set_string("color", last_color) + meta:set_string("rail_pos", "") - update_formspec(meta) - + update_neighbors(pos, meta, minetest.is_player(placer) and placer:get_player_name() or nil) return mapserver.after_place_node(pos, placer, itemstack, pointed_thing) end, + after_dig_node = function(pos, oldnode, oldmetadata, player) + local fake_meta = minetest.get_meta(pos) + + -- TODO: why doesn't this work properly? + + update_neighbors(pos, fake_meta, player) + end, + on_receive_fields = function(pos, formname, fields, sender) if not mapserver.can_interact(pos, sender) then @@ -99,12 +132,54 @@ minetest.register_node("mapserver:train", { last_set_by[name].line = fields.line last_set_by[name].station = fields.station last_set_by[name].index = index - end - update_formspec(meta) + update_neighbors(pos, meta, name) + + elseif fields.clear_rail_pos then + meta:set_string("rail_pos", "") + update_neighbors(pos, meta, name) + + elseif fields.set_rail_pos then + minetest.chat_send_player(name, "Please punch the nearest rail this train line follows.") + if last_set_by[name] == nil then + last_set_by[name] = {} + end + last_set_by[name].waiting_for_rail = pos + end end }) +minetest.register_on_punchnode(function(pos, node, sender, pointed_thing) + local name = sender:get_player_name() + local blockpos = nil + if last_set_by[name] ~= nil and + last_set_by[name].waiting_for_rail ~= nil then + + blockpos = last_set_by[name].waiting_for_rail + else + return + end + if not mapserver.can_interact(blockpos, sender) then + return + end + + if blockpos and advtrains_present then + if vector.distance(pos, blockpos) <= 20 then + local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes) + if node_ok then + local meta = minetest.get_meta(blockpos) + meta:set_string("rail_pos", minetest.pos_to_string(pos)) + update_neighbors(blockpos, meta, name) + else + minetest.chat_send_player(name, "This is not rail! Aborted.") + end + else + minetest.chat_send_player(name, "Node is too far away. Aborted.") + end + last_set_by[name].waiting_for_rail = nil + end +end) + if mapserver.enable_crafting then minetest.register_craft({ output = 'mapserver:train', @@ -115,3 +190,325 @@ if mapserver.enable_crafting then } }) end + + +update_neighbors = function(pos, meta, name) + if meta == nil then + meta = minetest.get_meta(pos) + end + local line = meta:get_string("line") + local index = tonumber(meta:get_string("index")) + local rail_pos = meta:get_string("rail_pos") + + -- if anything critical changed (pos/line/index) virtually remove us + local prv = minetest.string_to_pos(meta:get_string("prv_pos")) + local nxt = minetest.string_to_pos(meta:get_string("nxt_pos")) + local prv_meta = prv ~= nil and minetest.get_meta(prv) or nil + local nxt_meta = nxt ~= nil and minetest.get_meta(nxt) or nil + + if prv ~= nil and prv_meta:get_string("line") ~= line and + nxt ~= nil and nxt_meta:get_string("line") ~= line then + if prv ~= nil and nxt == nil then + -- loose end + prv_meta:set_string("nxt_pos", "") + prv_meta:set_string("nxt_index", "") + prv_meta:set_string("nxt_rail_pos", "") + elseif prv == nil and nxt ~= nil then + -- loose end + nxt_meta:set_string("prv_pos", "") + nxt_meta:set_string("prv_index", "") + nxt_meta:set_string("prv_rail_pos", "") + + nxt_meta:set_string("linepath_from_prv", "") + else + -- we were in the middle + prv_meta:set_string("nxt_pos", nxt) + prv_meta:set_string("nxt_index", meta:get_string("nxt_index")) + prv_meta:set_string("nxt_rail_pos", meta:get_string("nxt_rail_pos")) + + nxt_meta:set_string("prv_pos", prv) + nxt_meta:set_string("prv_index", meta:get_string("prv_index")) + nxt_meta:set_string("prv_rail_pos", meta:get_string("prv_rail_pos")) + + recalculate_line_to(prv, nxt, prv_meta, nxt_meta) + end + + for _,m in ipairs({prv_meta, nxt_meta}) do + if m ~= nil then + update_formspec(m) + end + end + + -- remove meta from self + meta:set_string("prv_pos", "") + meta:set_string("prv_index", "") + meta:set_string("prv_rail_pos", "") + + meta:set_string("nxt_pos", "") + meta:set_string("nxt_index", "") + meta:set_string("nxt_rail_pos", "") + + meta:set_string("linepath_from_prv", "") + end + + if line == "" then + update_formspec(meta) + return + end + + -- update or add us + -- repurposing prv, prv_meta etc. vars + local neighbors = find_neighbor_blocks(pos, meta) + prv = neighbors[1] + nxt = neighbors[2] + prv_meta = prv ~= nil and minetest.get_meta(prv.pos) or nil + nxt_meta = nxt ~= nil and minetest.get_meta(nxt.pos) or nil + -- if index or rail pos changed, recalculate line path + if prv ~= nil then + local old_nxt_pos = prv_meta:get_string("nxt_pos") + local old_nxt_index = tonumber(prv_meta:get_string("nxt_index")) + local old_nxt_rail_pos = prv_meta:get_string("nxt_rail_pos") + + -- if old info on prev does not match us, set correct + if old_nxt_pos ~= (nxt == nil and "" or nxt.pos) then + if old_nxt_pos == pos then + -- phew, it's just us + elseif nxt ~= nil and old_nxt_pos == nxt.pos then + -- okay we are just freshly added + -- update the previous block + prv_meta:set_string("nxt_pos", minetest.pos_to_string(pos)) + else + -- there are more nodes we don't know about! + end + end + if old_nxt_index ~= index then + -- index changed! since our position is still unchanged + -- (otherwise removing/re-adding above would have happened instead) + -- we just need to update the info, without linepath recalculation + prv_meta:set_int("nxt_index", index) + end + if old_nxt_rail_pos ~= rail_pos then + -- rail pos changed! definitely need linepath recalculation + prv_meta:set_string("nxt_rail_pos", rail_pos) + meta:set_string("linepath_from_prv", "") + end + + meta:set_string("prv_pos", minetest.pos_to_string(prv.pos)) + meta:set_int("prv_index", prv.index) + meta:set_string("prv_rail_pos", prv.rail_pos) + end + if nxt ~= nil then + local old_prv_pos = nxt_meta:get_string("prv_pos") + local old_prv_index = tonumber(nxt_meta:get_string("prv_index")) + local old_prv_rail_pos = nxt_meta:get_string("prv_rail_pos") + + -- if old info on next does not match us, set correct + if old_prv_pos ~= (prv == nil and "" or prv.pos) then + if old_prv_pos == pos then + -- phew, it's just us + elseif prv ~= nil and old_prv_pos == prv.pos then + -- okay we are just freshly added + -- update the previous block + nxt_meta:set_string("prv_pos", minetest.pos_to_string(pos)) + nxt_meta:set_string("linepath_from_prv", "") + else + -- there are more nodes we don't know about! + end + end + if old_prv_index ~= index then + -- index changed! since our position is still unchanged + -- (otherwise removing/re-adding above would have happened instead) + -- we just need to update the info, without linepath recalculation + nxt_meta:set_int("prv_index", index) + end + if old_prv_rail_pos ~= rail_pos then + -- rail pos changed! definitely need linepath recalculation + nxt_meta:set_string("prv_rail_pos", rail_pos) + nxt_meta:set_string("linepath_from_prv", "") + end + + meta:set_string("nxt_pos", minetest.pos_to_string(nxt.pos)) + meta:set_int("nxt_index", nxt.index) + meta:set_string("nxt_rail_pos", nxt.rail_pos) + end + + if rail_pos ~= "" then + if prv ~= nil and prv.rail_pos ~= "" then + local line = recalculate_line_to(prv.pos, pos, prv_meta, meta) + if name then + if #line > 0 then + minetest.chat_send_player(name, "Found line from prv ("..tonumber(#line).."): "..table.concat(line, "->")) + else + minetest.chat_send_player(name, "Did not find line from prv.") + end + end + end + if nxt ~= nil and nxt.rail_pos ~= "" then + local line = recalculate_line_to(pos, nxt.pos, meta, nxt_meta) + if name then + if #line > 0 then + minetest.chat_send_player(name, "Found line to nxt ("..tonumber(#line).."): "..table.concat(line, "->")) + else + minetest.chat_send_player(name, "Did not find line to nxt.") + end + end + end + end + + for _,m in ipairs({prv_meta, nxt_meta}) do + if m ~= nil then + update_formspec(m) + end + end + update_formspec(meta) +end + +find_neighbor_blocks = function(pos, meta) + if meta == nil then + meta = minetest.get_meta(pos) + end + local line = meta:get_string("line") + local index = tonumber(meta:get_string("index")) + local rail_pos = meta:get_string("rail_pos") + + -- the offsets are chosen so that the resulting area is just under the maximum allowable size + local offset = vector.new(160,19, 160) + local blocks = minetest.find_nodes_in_area(vector.subtract(pos, offset), vector.add(pos, offset), "mapserver:train") + local prv = nil + local nxt = nil + local meta = nil + + for _,p in ipairs(blocks) do + meta = minetest.get_meta(p) + if meta:get_string("line") == line then + local idx = tonumber(meta:get_string("index")) + if idx < index and + (prv == nil or idx > prv.index) then + prv = { + pos = p, + index = idx, + rail_pos = meta:get_string("rail_pos") + } + end + if idx > index and + (nxt == nil or idx < nxt.index) then + nxt = { + pos = p, + index = idx, + rail_pos = meta:get_string("rail_pos") + } + end + end + end + + return {prv, nxt} +end + +local clone = nil +clone = function(tbl, n) + local out = {} + local i,v = next(tbl, nil) + while i do + if type(v) == "table" then + out[i] = clone(v, (n or 0)+1) + else + out[i] = v + end + i,v = next(tbl, i) + end + return out +end + +recalculate_line_to = function(pos_a, pos_b, meta_a, meta_b) + if meta_a == nil then + meta_a = minetest.get_meta(pos_a) + end + if meta_b == nil then + meta_b = minetest.get_meta(pos_b) + end + local line = {} + local rail_pos_a = minetest.string_to_pos(meta_a:get_string("rail_pos")) + local rail_pos_b = minetest.string_to_pos(meta_b:get_string("rail_pos")) + local node_ok_a, conns_a, rhe_a = advtrains.get_rail_info_at(rail_pos_a, advtrains.all_tracktypes) + local node_ok_b, conns_b, rhe_b = advtrains.get_rail_info_at(rail_pos_b, advtrains.all_tracktypes) + if not node_ok_a or not node_ok_b then + table.insert(line, node_ok_a and minetest.pos_to_string(rail_pos_a) or pos_a) + else + -- depth first search for rail_pos_b, + -- vector.distance(step, rail_pos_b) is score + + -- keep track of all visited positions to avoid going in circles + local visited_nodes = {} + -- heads of search positions: {pos=, score=, steps=, line=} + local progress = {} + + -- put starting rail in, for every direction + for connid, conn in ipairs(conns_a) do + table.insert(progress, { + pos = rail_pos_a, + conns = conns_a, + connid = connid, + steps = 0, + score = vector.distance(rail_pos_a, rail_pos_b), + line = {minetest.pos_to_string(rail_pos_a)} + }) + end + + while next(progress, nil) do + local min_idx = nil + local min_item = nil + -- try the node closest to the destination + for i,v in pairs(progress) do + if v.steps < TRAVERSER_LIMIT and + (min_item == nil or v.score < min_item.score) then + min_idx = i + min_item = v + end + end + + -- check the adjacent rail + local adj_pos, adj_connid, conn_idx, nextrail_y, next_conns = advtrains.get_adjacent_rail(min_item.pos, min_item.conns, min_item.connid, advtrains.all_tracktypes) + if not adj_pos then + -- there is no rail, end-of-track + progress[min_idx] = nil + elseif visited_nodes[minetest.pos_to_string(adj_pos)..adj_connid] ~= nil then + -- already been here in this direction, no use repeating same steps + progress[min_idx] = nil + elseif minetest.pos_to_string(adj_pos) == minetest.pos_to_string(rail_pos_b) then + -- found destination! + -- set line and break loop + line = min_item.line + break + else + -- remember we did this one to prevent circles + visited_nodes[minetest.pos_to_string(adj_pos)..adj_connid] = true + + if min_item.steps > TRAVERSER_LIMIT then + print("went over traverser limit! "..minetest.pos_to_string(rail_pos_a).." → "..minetest.pos_to_string(adj_pos)) + else + -- query the next conns + for nconnid, nconn in ipairs(next_conns) do + if adj_connid ~= nconnid then + local line = clone(min_item.line) + if nconn.c ~= min_item.conns[min_item.connid].c then + table.insert(line, minetest.pos_to_string(adj_pos)) + end + table.insert(progress, { + pos = adj_pos, + conns = next_conns, + connid = nconnid, + steps = min_item.steps + 1, + score = vector.distance(adj_pos, rail_pos_b), + line = line + }) + end + end + end + -- we are done with this item + progress[min_idx] = nil + end + end + end + meta_b:set_string("linepath_from_prv", table.concat(line, ";")) + return line +end From a6c5ff01e83ea756310570a716eda72a9be00efc Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sun, 16 Jan 2022 03:02:20 +0100 Subject: [PATCH 05/12] fix player name handling --- train.lua | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/train.lua b/train.lua index bb4cce9..6b6a97f 100644 --- a/train.lua +++ b/train.lua @@ -4,8 +4,7 @@ local last_set_by = {} local find_neighbor_blocks -- defined later local update_neighbors --defined later -local recalculate_between -- defined later -local traverser -- defined later +local recalculate_line_to -- defined later local TRAVERSER_LIMIT = 1000 local update_formspec = function(meta) @@ -111,11 +110,12 @@ minetest.register_node("mapserver:train", { end local meta = minetest.get_meta(pos) - local name = string.lower(sender:get_player_name()) + local name = sender:get_player_name() + local lname = string.lower(name) if fields.save then - if last_set_by[name] == nil then - last_set_by[name] = {} + if last_set_by[lname] == nil then + last_set_by[lname] = {} end local index = tonumber(fields.index) @@ -128,10 +128,10 @@ minetest.register_node("mapserver:train", { meta:set_string("station", fields.station) meta:set_int("index", index) - last_set_by[name].color = fields.color - last_set_by[name].line = fields.line - last_set_by[name].station = fields.station - last_set_by[name].index = index + last_set_by[lname].color = fields.color + last_set_by[lname].line = fields.line + last_set_by[lname].station = fields.station + last_set_by[lname].index = index update_neighbors(pos, meta, name) @@ -141,21 +141,22 @@ minetest.register_node("mapserver:train", { elseif fields.set_rail_pos then minetest.chat_send_player(name, "Please punch the nearest rail this train line follows.") - if last_set_by[name] == nil then - last_set_by[name] = {} + if last_set_by[lname] == nil then + last_set_by[lname] = {} end - last_set_by[name].waiting_for_rail = pos + last_set_by[lname].waiting_for_rail = pos end end }) minetest.register_on_punchnode(function(pos, node, sender, pointed_thing) local name = sender:get_player_name() + local lname = string.lower(name) local blockpos = nil - if last_set_by[name] ~= nil and - last_set_by[name].waiting_for_rail ~= nil then + if last_set_by[lname] ~= nil and + last_set_by[lname].waiting_for_rail ~= nil then - blockpos = last_set_by[name].waiting_for_rail + blockpos = last_set_by[lname].waiting_for_rail else return end @@ -176,7 +177,7 @@ minetest.register_on_punchnode(function(pos, node, sender, pointed_thing) else minetest.chat_send_player(name, "Node is too far away. Aborted.") end - last_set_by[name].waiting_for_rail = nil + last_set_by[lname].waiting_for_rail = nil end end) From 6ed232541793527ad157836ba945c1efb494f35c Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sun, 16 Jan 2022 04:04:07 +0100 Subject: [PATCH 06/12] don't include rail_pos_a in path, but end with rail_pos_b --- train.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/train.lua b/train.lua index 6b6a97f..c6e06f0 100644 --- a/train.lua +++ b/train.lua @@ -451,7 +451,7 @@ recalculate_line_to = function(pos_a, pos_b, meta_a, meta_b) connid = connid, steps = 0, score = vector.distance(rail_pos_a, rail_pos_b), - line = {minetest.pos_to_string(rail_pos_a)} + line = {} }) end @@ -479,6 +479,7 @@ recalculate_line_to = function(pos_a, pos_b, meta_a, meta_b) -- found destination! -- set line and break loop line = min_item.line + table.insert(line, minetest.pos_to_string(rail_pos_b)) break else -- remember we did this one to prevent circles From 22b11f83caccc819c24dd05fb6a38bb7fac8a7b8 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sun, 16 Jan 2022 15:20:10 +0100 Subject: [PATCH 07/12] only accept conns that turn 90deg at most --- train.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/train.lua b/train.lua index c6e06f0..edc65b1 100644 --- a/train.lua +++ b/train.lua @@ -488,11 +488,15 @@ recalculate_line_to = function(pos_a, pos_b, meta_a, meta_b) if min_item.steps > TRAVERSER_LIMIT then print("went over traverser limit! "..minetest.pos_to_string(rail_pos_a).." → "..minetest.pos_to_string(adj_pos)) else + local inconn = next_conns[adj_connid] -- query the next conns + local quarter = AT_CMAX/4 for nconnid, nconn in ipairs(next_conns) do - if adj_connid ~= nconnid then + local normed = (nconn.c-inconn.c)%AT_CMAX + -- only accept conns that turn 90deg at most + if normed >= quarter and normed <= quarter*3 then local line = clone(min_item.line) - if nconn.c ~= min_item.conns[min_item.connid].c then + if nconn.c ~= inconn.c then table.insert(line, minetest.pos_to_string(adj_pos)) end table.insert(progress, { From da995affbfe13fb49162c2eb788f14039b26c79e Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 17 Jan 2022 00:38:18 +0100 Subject: [PATCH 08/12] properly overblown block search area, plus debug stuff --- train.lua | 161 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 138 insertions(+), 23 deletions(-) diff --git a/train.lua b/train.lua index edc65b1..99de220 100644 --- a/train.lua +++ b/train.lua @@ -259,7 +259,7 @@ update_neighbors = function(pos, meta, name) -- update or add us -- repurposing prv, prv_meta etc. vars - local neighbors = find_neighbor_blocks(pos, meta) + local neighbors = find_neighbor_blocks(pos, meta, name) prv = neighbors[1] nxt = neighbors[2] prv_meta = prv ~= nil and minetest.get_meta(prv.pos) or nil @@ -364,7 +364,106 @@ update_neighbors = function(pos, meta, name) update_formspec(meta) end -find_neighbor_blocks = function(pos, meta) +local nroot = function(root, num) + return num^(1/root) +end + +if vector == nil then + vector = {} + vector.new = function(a, b,c) + if vector.check(a) then + return vector.copy(a) + elseif type(a) == "number" and type(b) == "number" and type(c) == "number" then + return {x=a, y=b, z=c} + end + end + vector.zero = function() + return vector.new(0,0,0) + end + vector.copy = function(v) + return vector.new(v.x, v.y, v.z) + end + vector.to_string = function(v) + if vector.check(v) then + return "("..table.concat({v.x, v.y, v.z}, ",")..")" + else + return "(invalid vector)" + end + end + + vector.add = function(p1, p2) + return vector.new(p1.x+p2.x, p1.y+p2.y, p1.z+p2.z) + end + vector.subtract = function(p1, p2) + return vector.new(p1.x-p2.x, p1.y-p2.y, p1.z-p2.z) + end + vector.multiply = function(v, s) + return vector.new(v.x*s, v.y*s, v.z*s) + end + vector.divide = function(v, s) + return vector.new(v.x/s, v.y/s, v.z/s) + end + + vector.distance = function(p1, p2) + return vector.length(vector.subtract(p2, p1)) + end + vector.length = function(v) + return nroot(3, v.x*v.x + v.y*v.y + v.z*v.z) + end + vector.normalize = function(v) + return vector.multiply(v, 1/vector.length(v)) + end + vector.offset = function(v, x,y,z) + return vector.new(v.x+x, v.y+y, v.z+z) + end + vector.check = function(v) + return type(v) == "table" and + type(v.x) == "number" and + type(v.y) == "number" and + type(v.z) == "number" + end +end + +-- searching an area for nodes is expensive. +-- minetest limits the amount to 4,096,000 nodes. +-- because there is not a good way to form one cuboid to fit all major long-distance usecases, +local max_nodes = 4096000 +local cuboid_width_for_height = function(height) + return math.floor(math.sqrt(max_nodes / height)) +end +local span_rectangle = function(pos, radius, height, v_offset, v_invert) + local v_dir = v_invert and -1 or 1 + return { vector.add(pos, vector.multiply(vector.new(-radius, v_offset, -radius), v_dir)), + vector.add(pos, vector.multiply(vector.new(radius, height+v_offset, radius), v_dir)) } +end +local halve_area = function(length) + return math.floor((length-1) / 2) +end +local twocube_length = math.floor(nroot(3, max_nodes*2)) +local flat_height = 7 +local flat_halflength = halve_area(cuboid_width_for_height(flat_height)) +local cuboid_height = math.floor(twocube_length/3) +local cuboid_length = cuboid_width_for_height(cuboid_height) +local area_from_offset = function(pos, offset) + return {vector.subtract(pos, offset), vector.add(pos, offset)} +end +local eight_corners = function(a, b) + local diff = vector.subtract(b, a) + return { a, + vector.add(a, vector.new(diff.x, 0, 0)), + vector.add(a, vector.new(0, diff.y, 0)), + vector.add(a, vector.new(0, 0, diff.z)), + vector.add(a, vector.new(diff.x, diff.y, 0)), + vector.add(a, vector.new(diff.x, 0, diff.z)), + vector.add(a, vector.new(0, diff.y, diff.z)), + b } +end +local get_volume = function(span) + local diff = vector.subtract(span[2], span[1]) + return (math.abs(diff.x)+1) * (math.abs(diff.y)+1) * (math.abs(diff.z)+1) +end + +find_neighbor_blocks = function(pos, meta, name) if meta == nil then meta = minetest.get_meta(pos) end @@ -373,31 +472,47 @@ find_neighbor_blocks = function(pos, meta) local rail_pos = meta:get_string("rail_pos") -- the offsets are chosen so that the resulting area is just under the maximum allowable size - local offset = vector.new(160,19, 160) - local blocks = minetest.find_nodes_in_area(vector.subtract(pos, offset), vector.add(pos, offset), "mapserver:train") + local areas = { + flat = area_from_offset(pos, vector.new(flat_halflength, halve_area(flat_height), flat_halflength)), + upper_half = span_rectangle(pos, halve_area(cuboid_length), cuboid_height-1, halve_area(flat_height)+1), + lower_half = span_rectangle(pos, halve_area(cuboid_length), cuboid_height-1, halve_area(flat_height)+1, true) + } + local blocks = {} + for i,span in pairs(areas) do + if get_volume(span) > max_nodes then + minetest.chat_send_player(name, "Invalid span "..i.." between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." (volume of "..tostring(get_volume(span))..")") + return {} + end + print("["..i.."] Getting nodes between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." (should be volume of "..tostring(get_volume(span))..")") + blocks[i] = minetest.find_nodes_in_area(span[1], span[2], "mapserver:train") + minetest.bulk_set_node(eight_corners(span[1], span[2]), {name=moditems.goldblock}) + minetest.chat_send_player(name, "Found "..tostring(#blocks[i]).." nodes between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." ("..i..")") + end local prv = nil local nxt = nil local meta = nil - for _,p in ipairs(blocks) do - meta = minetest.get_meta(p) - if meta:get_string("line") == line then - local idx = tonumber(meta:get_string("index")) - if idx < index and - (prv == nil or idx > prv.index) then - prv = { - pos = p, - index = idx, - rail_pos = meta:get_string("rail_pos") - } - end - if idx > index and - (nxt == nil or idx < nxt.index) then - nxt = { - pos = p, - index = idx, - rail_pos = meta:get_string("rail_pos") - } + for _,span in pairs(blocks) do + for _,p in pairs(span) do + meta = minetest.get_meta(p) + if meta:get_string("line") == line then + local idx = tonumber(meta:get_string("index")) + if idx < index and + (prv == nil or idx > prv.index) then + prv = { + pos = p, + index = idx, + rail_pos = meta:get_string("rail_pos") + } + end + if idx > index and + (nxt == nil or idx < nxt.index) then + nxt = { + pos = p, + index = idx, + rail_pos = meta:get_string("rail_pos") + } + end end end end From 7d47f7dbbc860c21d103e25aeeb66ab5b8f629cb Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 17 Jan 2022 00:54:57 +0100 Subject: [PATCH 09/12] remove debug stuff, proper comment and error reporting --- train.lua | 90 +++++++++---------------------------------------------- 1 file changed, 14 insertions(+), 76 deletions(-) diff --git a/train.lua b/train.lua index 99de220..ca59483 100644 --- a/train.lua +++ b/train.lua @@ -368,65 +368,16 @@ local nroot = function(root, num) return num^(1/root) end -if vector == nil then - vector = {} - vector.new = function(a, b,c) - if vector.check(a) then - return vector.copy(a) - elseif type(a) == "number" and type(b) == "number" and type(c) == "number" then - return {x=a, y=b, z=c} - end - end - vector.zero = function() - return vector.new(0,0,0) - end - vector.copy = function(v) - return vector.new(v.x, v.y, v.z) - end - vector.to_string = function(v) - if vector.check(v) then - return "("..table.concat({v.x, v.y, v.z}, ",")..")" - else - return "(invalid vector)" - end - end - - vector.add = function(p1, p2) - return vector.new(p1.x+p2.x, p1.y+p2.y, p1.z+p2.z) - end - vector.subtract = function(p1, p2) - return vector.new(p1.x-p2.x, p1.y-p2.y, p1.z-p2.z) - end - vector.multiply = function(v, s) - return vector.new(v.x*s, v.y*s, v.z*s) - end - vector.divide = function(v, s) - return vector.new(v.x/s, v.y/s, v.z/s) - end - - vector.distance = function(p1, p2) - return vector.length(vector.subtract(p2, p1)) - end - vector.length = function(v) - return nroot(3, v.x*v.x + v.y*v.y + v.z*v.z) - end - vector.normalize = function(v) - return vector.multiply(v, 1/vector.length(v)) - end - vector.offset = function(v, x,y,z) - return vector.new(v.x+x, v.y+y, v.z+z) - end - vector.check = function(v) - return type(v) == "table" and - type(v.x) == "number" and - type(v.y) == "number" and - type(v.z) == "number" - end -end - --- searching an area for nodes is expensive. --- minetest limits the amount to 4,096,000 nodes. --- because there is not a good way to form one cuboid to fit all major long-distance usecases, +-- Searching an area for nodes is expensive. +-- Minetest limits the amount to 4,096,000 nodes. +-- Because there is not a good way to form one cuboid to fit all major long-distance usecases +-- and this will not be frequently executed on a server (only every time a player manually +-- sets or updates a train map block) we take all we can with 3 separate ranges: +-- - One layer for most applications in long, flat stretches, allowing for 3 nodes of up/down +-- deviation, maxes out on 381 x and z deviation. +-- - One smaller, but higher cuboid on top and bottom of it each, stretching 123 in every +-- x and z direction and 67 up/down +-- This should be very luxurious and prove enough for almost everything. local max_nodes = 4096000 local cuboid_width_for_height = function(height) return math.floor(math.sqrt(max_nodes / height)) @@ -441,23 +392,12 @@ local halve_area = function(length) end local twocube_length = math.floor(nroot(3, max_nodes*2)) local flat_height = 7 -local flat_halflength = halve_area(cuboid_width_for_height(flat_height)) +local flat_length = cuboid_width_for_height(flat_height) local cuboid_height = math.floor(twocube_length/3) local cuboid_length = cuboid_width_for_height(cuboid_height) local area_from_offset = function(pos, offset) return {vector.subtract(pos, offset), vector.add(pos, offset)} end -local eight_corners = function(a, b) - local diff = vector.subtract(b, a) - return { a, - vector.add(a, vector.new(diff.x, 0, 0)), - vector.add(a, vector.new(0, diff.y, 0)), - vector.add(a, vector.new(0, 0, diff.z)), - vector.add(a, vector.new(diff.x, diff.y, 0)), - vector.add(a, vector.new(diff.x, 0, diff.z)), - vector.add(a, vector.new(0, diff.y, diff.z)), - b } -end local get_volume = function(span) local diff = vector.subtract(span[2], span[1]) return (math.abs(diff.x)+1) * (math.abs(diff.y)+1) * (math.abs(diff.z)+1) @@ -473,20 +413,18 @@ find_neighbor_blocks = function(pos, meta, name) -- the offsets are chosen so that the resulting area is just under the maximum allowable size local areas = { - flat = area_from_offset(pos, vector.new(flat_halflength, halve_area(flat_height), flat_halflength)), + flat = area_from_offset(pos, vector.new(halve_area(flat_length), halve_area(flat_height), halve_area(flat_length))), upper_half = span_rectangle(pos, halve_area(cuboid_length), cuboid_height-1, halve_area(flat_height)+1), lower_half = span_rectangle(pos, halve_area(cuboid_length), cuboid_height-1, halve_area(flat_height)+1, true) } local blocks = {} for i,span in pairs(areas) do if get_volume(span) > max_nodes then - minetest.chat_send_player(name, "Invalid span "..i.." between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." (volume of "..tostring(get_volume(span))..")") + minetest.chat_send_player(name, "Internal Error searching for nearby nodes: Invalid span "..i.." between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." (volume of "..tostring(get_volume(span))..")") + minetest.log("error", "[mapserver_mod][trainlines] Internal Error searching for nearby nodes: Invalid span "..i.." between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." (volume of "..tostring(get_volume(span))..")") return {} end - print("["..i.."] Getting nodes between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." (should be volume of "..tostring(get_volume(span))..")") blocks[i] = minetest.find_nodes_in_area(span[1], span[2], "mapserver:train") - minetest.bulk_set_node(eight_corners(span[1], span[2]), {name=moditems.goldblock}) - minetest.chat_send_player(name, "Found "..tostring(#blocks[i]).." nodes between "..minetest.pos_to_string(span[1]).." and "..minetest.pos_to_string(span[2]).." ("..i..")") end local prv = nil local nxt = nil From 4b2e54161918d14a19a394f626f1aa905ca79e24 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 17 Jan 2022 01:16:51 +0100 Subject: [PATCH 10/12] attempt to fix crash --- train.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/train.lua b/train.lua index ca59483..685e5c5 100644 --- a/train.lua +++ b/train.lua @@ -483,10 +483,15 @@ recalculate_line_to = function(pos_a, pos_b, meta_a, meta_b) local line = {} local rail_pos_a = minetest.string_to_pos(meta_a:get_string("rail_pos")) local rail_pos_b = minetest.string_to_pos(meta_b:get_string("rail_pos")) - local node_ok_a, conns_a, rhe_a = advtrains.get_rail_info_at(rail_pos_a, advtrains.all_tracktypes) - local node_ok_b, conns_b, rhe_b = advtrains.get_rail_info_at(rail_pos_b, advtrains.all_tracktypes) + local node_ok_a, conns_a, rhe_a, node_ok_b, conns_b, rhe_b + if rail_pos_a then + node_ok_a, conns_a, rhe_a = advtrains.get_rail_info_at(rail_pos_a, advtrains.all_tracktypes) + if rail_pos_b then + node_ok_b, conns_b, rhe_b = advtrains.get_rail_info_at(rail_pos_b, advtrains.all_tracktypes) + end + end if not node_ok_a or not node_ok_b then - table.insert(line, node_ok_a and minetest.pos_to_string(rail_pos_a) or pos_a) + table.insert(line, node_ok_a and minetest.pos_to_string(rail_pos_a) or minetest.pos_to_string(pos_a)) else -- depth first search for rail_pos_b, -- vector.distance(step, rail_pos_b) is score From cf568a5f66152c3820dedcf0fb6f5d809b70cb71 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 17 Jan 2022 13:55:53 +0100 Subject: [PATCH 11/12] only consider 45deg turns instead of 90deg --- train.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/train.lua b/train.lua index 685e5c5..a02a786 100644 --- a/train.lua +++ b/train.lua @@ -548,11 +548,11 @@ recalculate_line_to = function(pos_a, pos_b, meta_a, meta_b) else local inconn = next_conns[adj_connid] -- query the next conns - local quarter = AT_CMAX/4 + local deg45 = AT_CMAX/8 for nconnid, nconn in ipairs(next_conns) do local normed = (nconn.c-inconn.c)%AT_CMAX -- only accept conns that turn 90deg at most - if normed >= quarter and normed <= quarter*3 then + if normed >= deg45 and normed <= AT_CMAX-deg45 then local line = clone(min_item.line) if nconn.c ~= inconn.c then table.insert(line, minetest.pos_to_string(adj_pos)) From d6edbc02a4f3fca6f5119b7d5db0c4c14b48b573 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Mon, 17 Jan 2022 18:15:36 +0100 Subject: [PATCH 12/12] fix typo, fix feedback on delete, show "no neighbors found" --- train.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/train.lua b/train.lua index a02a786..f8cd3a1 100644 --- a/train.lua +++ b/train.lua @@ -31,7 +31,8 @@ local update_formspec = function(meta) meta:set_string("infotext", "Train: Line=" .. line .. ", Station=" .. station .. (prv ~= "" and (", prv="..prv) or "") .. (path ~= "" and " (found line)" or "") .. - (nxt ~= "" and (", nxt="..nxt) or "")) + (nxt ~= "" and (", nxt="..nxt) or "") .. + (line ~= "" and prv == "" and nxt == "" and (", no neighbors found") or "")) meta:set_string("formspec", "size[8,4;]" .. -- col 1 @@ -100,7 +101,7 @@ minetest.register_node("mapserver:train", { -- TODO: why doesn't this work properly? - update_neighbors(pos, fake_meta, player) + update_neighbors(pos, fake_meta, player:get_player_name()) end, on_receive_fields = function(pos, formname, fields, sender) @@ -172,7 +173,7 @@ minetest.register_on_punchnode(function(pos, node, sender, pointed_thing) meta:set_string("rail_pos", minetest.pos_to_string(pos)) update_neighbors(blockpos, meta, name) else - minetest.chat_send_player(name, "This is not rail! Aborted.") + minetest.chat_send_player(name, "This is not a rail! Aborted.") end else minetest.chat_send_player(name, "Node is too far away. Aborted.")