aboutsummaryrefslogtreecommitdiff
path: root/advtrains
diff options
context:
space:
mode:
Diffstat (limited to 'advtrains')
-rw-r--r--advtrains/api_doc.txt14
-rw-r--r--advtrains/atc.lua29
-rw-r--r--advtrains/couple.lua556
-rw-r--r--advtrains/doc/advtrains_speed_lessp.3advtrains.md15
-rw-r--r--advtrains/doc/advtrains_speed_set_restriction.3advtrains.md18
-rw-r--r--advtrains/doc/signal_aspect.7advtrains.md24
-rw-r--r--advtrains/formspec.lua111
-rw-r--r--advtrains/init.lua26
-rw-r--r--advtrains/locale/advtrains.de.tr5
-rw-r--r--advtrains/lzb.lua13
-rw-r--r--advtrains/nodedb.lua2
-rw-r--r--advtrains/path.lua39
-rw-r--r--advtrains/settingtypes.txt5
-rw-r--r--advtrains/signals.lua4
-rw-r--r--advtrains/spec/speed_spec.lua70
-rw-r--r--advtrains/speed.lua88
-rw-r--r--advtrains/trainhud.lua2
-rw-r--r--advtrains/trainlogic.lua463
-rw-r--r--advtrains/wagons.lua170
19 files changed, 1155 insertions, 499 deletions
diff --git a/advtrains/api_doc.txt b/advtrains/api_doc.txt
index 8ac4986..5668ba3 100644
--- a/advtrains/api_doc.txt
+++ b/advtrains/api_doc.txt
@@ -75,9 +75,23 @@ advtrains.register_wagon(name, prototype, description, inventory_image)
^- Getting on by walking in then takes effect.
^- Positive values mean front, negative ones back. Resulting position is automatically shifted to the right side.
+ coupler_types_front = {scharfenberg=true},
+ coupler_types_back = {chain=true},
+ ^- Defines the available coupler types on this wagon on the front and back side. Wagon will only couple to wagons that have a matching coupler. (this property does not have any visual impact)
+ ^- Default: not given (nil) - causes the wagon to couple to any other wagon regardless of coupler type.
+ ^- Empty table ({}): This wagon does not couple to any other wagon (e.g. for Linetrack vehicles)
+ ^- Register coupler types using ''advtrains.register_coupler_type(type, name)''. advtrains defines the default types "chain" (Buffer and Chain) and "scharfenberg" (Scharfenberg coupler).
+
wagon_span=2,
^- How far this wagon extends from its base position. Is the half of the wagon length.
^- Used to determine in which distance the other wagons have to be positioned. Will require tweaking.
+ wheel_positions = {1.5, -1.5},
+ ^- Optional: if defined, the wagon will be placed so that these 2 wheel positions are on the track
+ ^- This parameter is recommended for long wagons (wagon_span >= 2).
+ ^- The position is a distance relative to the center of the wagon.
+ ^- Must have exactly 2 entries, corresponding to the front (1) and rear (2) wheel of the wagon object. 1st must be greater than 2nd.
+ ^- If not provided, the simple 1-position positioning logic will be used (wagon is positioned with the center on the track)
+
extent_h = 1,
^- Determines the collision box extent in x/z direction. Defaults to 1 (=3x3)
^- The actual bounding box size is (extent_h*2)+1, so 0 means 1x1, 1 means 3x3 and 2 means 5x5
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index 64cdcec..c1ff218 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -93,6 +93,7 @@ function atc.train_reset_command(train, keep_tarvel)
train.atc_delay=nil
train.atc_brake_target=nil
train.atc_wait_finish=nil
+ train.atc_wait_autocouple=nil
train.atc_arrow=nil
if not keep_tarvel then
train.tarvelocity=nil
@@ -199,10 +200,16 @@ local matchptn={
return #match+1
end,
["B([0-9]+)"]=function(id, train, match)
- if train.velocity>tonumber(match) then
- train.atc_brake_target=tonumber(match)
- if not train.tarvelocity or train.tarvelocity>train.atc_brake_target then
- train.tarvelocity=train.atc_brake_target
+ local btar = tonumber(match)
+ if train.velocity>btar then
+ train.atc_brake_target=btar
+ if not train.tarvelocity or train.tarvelocity>btar then
+ train.tarvelocity=btar
+ end
+ else
+ -- independent of brake target, must make sure that tarvelocity is not greater than it
+ if train.tarvelocity and train.tarvelocity>btar then
+ train.tarvelocity=btar
end
end
return #match+1
@@ -267,6 +274,10 @@ local matchptn={
advtrains.interlocking.ars_set_disable(train, match=="0")
return 2
end,
+ ["Cpl"]=function(id, train)
+ train.atc_wait_autocouple=true
+ return 3
+ end,
}
eval_conditional = function(command, arrow, speed)
@@ -358,11 +369,13 @@ function atc.execute_atc_command(id, train)
local match=string.match(command, "^"..pattern)
if match then
local patlen=func(id, train, match)
-
- atprint("Executing: "..string.sub(command, 1, patlen))
-
+ --atdebug("Executing: "..string.sub(command, 1, patlen))
+ --atdebug("Train ATC State: tvel=",train.tarvelocity,"brktar=",train.atc_brake_target,"delay=",train.atc_delay,"wfinish=",train.atc_wait_finish,"wacpl=",train.atc_wait_autocouple)
+
train.atc_command=string.sub(command, patlen+1)
- if train.atc_delay<=0 and not train.atc_wait_finish then
+ if train.atc_delay<=0
+ and not train.atc_wait_finish
+ and not train.atc_wait_autocouple then
--continue (recursive, cmds shouldn't get too long, and it's a end-recursion.)
atc.execute_atc_command(id, train)
end
diff --git a/advtrains/couple.lua b/advtrains/couple.lua
index 3dc336f..3e6c432 100644
--- a/advtrains/couple.lua
+++ b/advtrains/couple.lua
@@ -1,14 +1,398 @@
--couple.lua
---defines couple entities.
+--Handles coupling and discoupling of trains, and defines the coupling entities
+--Rework June 2021 - some functions from trainlogic.lua have been moved here
---advtrains:discouple
---set into existing trains to split them when punched.
---they are attached to the wagons.
---[[fields
-wagon
+-- COUPLING --
+-- During coupling rework, the behavior of coupling was changed to make automation easier. It is now as follows:
+-- Coupling is only ever initiated when a train is standing somewhere (not moving) and another train drives onto one of its ends
+-- with a speed greater than 0
+-- "stationary" train is the one standing there - in old code called "train2"
+-- "initiating" train is the one that approached it and bumped into it - typically an engine - in old code called "train1"
+-- When the initiating train has autocouple set, trains are immediately coupled
+-- When not, a couple entity is spawned and coupling commences on click
+-- Coupling MUST preserve the train ID of the initiating train, so it is done like this:
+ -- index of initiating train is set so that it matches the front pos of stationary train
+ -- remove stationary train
+ -- wagons of stationary train are inserted at the beginning of initiating train (considers direction of stat_train and inserts reverse if required)
-wagons keep their couple entity minetest-internal id inside the field discouple_id. if it refers to nowhere, they will spawn a new one if player is near
-]]
+-- train.couple_* contain references to ObjectRefs of couple objects, which contain all relevant information
+-- These objectRefs will delete themselves once the couples no longer match (see below)
+
+advtrains.coupler_types = {}
+
+function advtrains.register_coupler_type(code, name)
+ advtrains.coupler_types[code] = name
+end
+
+-- Register some default couplers
+advtrains.register_coupler_type("chain", attrans("Buffer and Chain Coupler"))
+advtrains.register_coupler_type("scharfenberg", attrans("Scharfenberg Coupler"))
+
+
+local function create_couple_entity(pos, train1, t1_is_front, train2, t2_is_front)
+ local id1 = train1.id
+ local id2 = train2.id
+
+ -- delete previous couple entities
+ if t1_is_front then
+ if train1.cpl_front then train1.cpl_front:remove() end
+ else
+ if train1.cpl_back then train1.cpl_back:remove() end
+ end
+ if t2_is_front then
+ if train2.cpl_front then train2.cpl_front:remove() end
+ else
+ if train2.cpl_back then train2.cpl_back:remove() end
+ end
+
+ local obj=minetest.add_entity(pos, "advtrains:couple")
+ if not obj then error("Failed creating couple object!") return end
+ local le=obj:get_luaentity()
+ le.train_id_1=id1
+ le.t1_is_front=t1_is_front
+ le.train_id_2=id2
+ le.t2_is_front=t2_is_front
+ --atdebug("created couple between",train1.id,train2.id,t2_is_front)
+
+ if t1_is_front then
+ train1.cpl_front = obj
+ else
+ train2.cpl_back = obj
+ end
+ if t2_is_front then
+ train2.cpl_front = obj
+ else
+ train2.cpl_back = obj
+ end
+end
+
+-- Old static couple checking. Never used for autocouple, only used for standing trains if train did not approach
+local CPL_CHK_DST = -1
+local CPL_ZONE = 2
+function advtrains.train_check_couples(train)
+ --atdebug("rechecking couples")
+ if train.cpl_front then
+ if not train.cpl_front:get_yaw() then
+ -- objectref is no longer valid. reset.
+ train.cpl_front = nil
+ end
+ end
+ if not train.cpl_front then
+ -- recheck front couple
+ local front_trains, pos = advtrains.occ.get_occupations(train, atround(train.index) + CPL_CHK_DST)
+ if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ for tid, idx in pairs(front_trains) do
+ local other_train = advtrains.trains[tid]
+ if not advtrains.train_ensure_init(tid, other_train) then
+ atwarn("Train",tid,"is not initialized! Couldn't check couples!")
+ return
+ end
+ --atdebug(train.id,"front: ",idx,"on",tid,atround(other_train.index),atround(other_train.end_index))
+ if other_train.velocity == 0 then
+ if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
+ create_couple_entity(pos, train, true, other_train, true)
+ break
+ end
+ if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
+ create_couple_entity(pos, train, true, other_train, false)
+ break
+ end
+ end
+ end
+ end
+ end
+ if train.cpl_back then
+ if not train.cpl_back:get_yaw() then
+ -- objectref is no longer valid. reset.
+ train.cpl_back = nil
+ end
+ end
+ if not train.cpl_back then
+ -- recheck back couple
+ local back_trains, pos = advtrains.occ.get_occupations(train, atround(train.end_index) - CPL_CHK_DST)
+ if advtrains.is_node_loaded(pos) then -- if the position is loaded...
+ for tid, idx in pairs(back_trains) do
+ local other_train = advtrains.trains[tid]
+ if not advtrains.train_ensure_init(tid, other_train) then
+ atwarn("Train",tid,"is not initialized! Couldn't check couples!")
+ return
+ end
+ --atdebug(train.id,"back: ",idx,"on",tid,atround(other_train.index),atround(other_train.end_index))
+ if other_train.velocity == 0 then
+ if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
+ create_couple_entity(pos, train, false, other_train, true)
+ break
+ end
+ if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
+ create_couple_entity(pos, train, false, other_train, false)
+ break
+ end
+ end
+ end
+ end
+ end
+end
+
+-- Deletes couple entities from the train
+function advtrains.couple_invalidate(train)
+ if train.cpl_back then
+ train.cpl_back:remove()
+ train.cpl_back = nil
+ end
+ if train.cpl_front then
+ train.cpl_front:remove()
+ train.cpl_front = nil
+ end
+ train.couples_up_to_date = nil
+end
+
+-- Called from train_step_b() when the current train (init_train) just stopped at one of the end indices of another train (stat_train)
+-- Depending on autocouple, either couples immediately or spawns a couple entity
+function advtrains.couple_initiate_with(init_train, stat_train, stat_is_front)
+ --atdebug("Couple init autocouple=",init_train.autocouple,"atc_w_acpl=",init_train.atc_wait_autocouple)
+ if init_train.autocouple or init_train.atc_wait_autocouple then
+ local cplmatch, msg = advtrains.check_matching_coupler_types(init_train, true, stat_train, stat_is_front)
+ if cplmatch then
+ advtrains.couple_trains(init_train, false, stat_train, stat_is_front)
+ -- clear atc couple waiting blocker
+ init_train.atc_wait_autocouple = nil
+ return
+ end
+ end
+ -- get here if either autocouple is not on or couples dont match
+ local pos = advtrains.path_get_interpolated(init_train, init_train.index)
+ create_couple_entity(pos, init_train, true, stat_train, stat_is_front)
+ -- clear ATC command on collision
+ advtrains.atc.train_reset_command(init_train)
+
+end
+
+-- check if the player has permission for the first/last wagon of the train
+local function check_twagon_owner(train, b_first, pname)
+ local wtp = b_first and 1 or #train.trainparts
+ local wid = train.trainparts[wtp]
+ local wdata = advtrains.wagons[wid]
+ if wdata then
+ return advtrains.check_driving_couple_protection(pname, wdata.owner, wdata.whitelist)
+ end
+ return false
+end
+
+-- Perform coupling, but check if the player is authorized to couple
+function advtrains.safe_couple_trains(train1, t1_is_front, train2, t2_is_front, pname)
+
+ if pname and not minetest.check_player_privs(pname, "train_operator") then
+ minetest.chat_send_player(pname, "Missing train_operator privilege")
+ return false
+ end
+
+ local wck_t1, wck_t2
+ if pname then
+ wck_t1 = check_twagon_owner(train1, t1_is_front, pname)
+ wck_t2 = check_twagon_owner(train2, t2_is_front, pname)
+ end
+ if (wck_t1 or wck_t2) or not pname then
+
+ local cplmatch, msg = advtrains.check_matching_coupler_types(train1, t1_is_front, train2, t2_is_front)
+ if cplmatch then
+ advtrains.couple_trains(train1, not t1_is_front, train2, t2_is_front)
+ else
+ minetest.chat_send_player(pname, msg)
+ end
+ end
+end
+
+-- Actually performs the train coupling. Always retains train ID of train1
+function advtrains.couple_trains(init_train, invert_init_train, stat_train, stat_train_opposite)
+ --atdebug("Couple trains init=",init_train.id,"initinv=",invert_init_train,"stat=",stat_train.id,"statreverse=",stat_train_opposite)
+
+ if not advtrains.train_ensure_init(init_train.id, init_train) then
+ atwarn("Coupling: initiating train",init_train.id,"is not initialized! Operation aborted!")
+ return
+ end
+ if not advtrains.train_ensure_init(stat_train.id, stat_train) then
+ atwarn("Coupling: stationary train",stat_train.id,"is not initialized! Operation aborted!")
+ return
+ end
+
+ -- only used with the couple entity
+ if invert_init_train then
+ advtrains.invert_train(init_train.id)
+ end
+
+ local itp = init_train.trainparts
+ local init_wagoncnt = #itp
+ local stp = stat_train.trainparts
+ local stat_wagoncnt = #stp
+ local stat_trainlen = stat_train.trainlen -- save the train length of stat train, to be added to index
+
+ if stat_train_opposite then
+ -- insert wagons in inverse order and set their wagon_flipped state
+ for i=1,stat_wagoncnt do
+ table.insert(itp, 1, stp[i])
+ local wdata = advtrains.wagons[stp[i]]
+ if wdata then
+ wdata.wagon_flipped = not wdata.wagon_flipped
+ else
+ atwarn("While coupling, wagon",stp[i],"of stationary train",stat_train.id,"not found!")
+ end
+ end
+ else
+ --insert wagons in normal order
+ for i=stat_wagoncnt,1,-1 do
+ table.insert(itp, 1, stp[i])
+ end
+ end
+
+ -- TODO: migrate some of the properties from stat_train to init_train?
+
+ advtrains.remove_train(stat_train.id)
+
+ -- Set train index forward
+ init_train.index = advtrains.path_get_index_by_offset(init_train, init_train.index, stat_trainlen)
+
+ advtrains.update_trainpart_properties(init_train.id)
+ advtrains.update_train_start_and_end(init_train)
+
+ advtrains.couple_invalidate(init_train)
+ return true
+end
+
+-- Couple types matching check
+-- returns: true, nil if OK
+-- false, errmsg if there is an error
+function advtrains.check_matching_coupler_types(t1, t1_front, t2, t2_front)
+ -- 1. get wagons
+ local t1_wid
+ if t1_front then
+ t1_wid = t1.trainparts[1]
+ else
+ t1_wid = t1.trainparts[#t1.trainparts]
+ end
+ local t2_wid
+ if t2_front then
+ t2_wid = t2.trainparts[1]
+ else
+ t2_wid = t2.trainparts[#t2.trainparts]
+ end
+
+ --atdebug("CMCT: t1_wid",t1_wid,"t2_wid",t2_wid,"")
+
+ if not t1_wid or not t2_wid then
+ return false, "Unable to retrieve wagons from train"--note: no translation needed, case should not occur
+ end
+
+ local t1_wagon = advtrains.wagons[t1_wid]
+ local t2_wagon = advtrains.wagons[t2_wid]
+
+ if not t1_wagon or not t2_wagon then
+ return false, "At least one of wagons "..t1_wagon.." or "..t2_wagon.." does not exist"--note: no translation needed, case should not occur
+ end
+
+ -- these calls do not fail, they may return placeholder - doesn't matter
+ local _,t1_wpro = advtrains.get_wagon_prototype(t1_wagon)
+ local _,t2_wpro = advtrains.get_wagon_prototype(t2_wagon)
+
+ -- get correct couplers table (front/back)
+ local t1_cplt
+ if not t1_front == not t1_wagon.wagon_flipped then --fancy XOR
+ t1_cplt = t1_wpro.coupler_types_back
+ else
+ t1_cplt = t1_wpro.coupler_types_front
+ end
+ local t2_cplt
+ if not t2_front == not t2_wagon.wagon_flipped then --fancy XOR
+ t2_cplt = t2_wpro.coupler_types_back
+ else
+ t2_cplt = t2_wpro.coupler_types_front
+ end
+
+ --atdebug("CMCT: t1",t1_cplt,"t2",t2_cplt,"")
+
+ -- if at least one of the trains has no couplers table, it always couples (fallback behavior and mode for universal shunters)
+ if not t1_cplt or not t2_cplt then
+ return true
+ end
+
+ -- have common coupler?
+ for typ,_ in pairs(t1_cplt) do
+ if t2_cplt[typ] then
+ --atdebug("CMCT: Matching type",typ)
+ return true
+ end
+ end
+ --no match, give user an info
+ local t1_cplhr, t2_cplhr = {},{}
+ for typ,_ in pairs(t1_cplt) do
+ table.insert(t1_cplhr, advtrains.coupler_types[typ] or typ)
+ end
+ if #t1_cplhr==0 then t1_cplhr[1]=attrans("<none>") end
+ for typ,_ in pairs(t2_cplt) do
+ table.insert(t2_cplhr, advtrains.coupler_types[typ] or typ)
+ end
+ if #t2_cplhr==0 then t2_cplhr[1]=attrans("<none>") end
+ return false, attrans("Can not couple: The couplers of the trains do not match (@1 and @2).", table.concat(t1_cplhr, ","), table.concat(t2_cplhr, ","))
+end
+
+-- DECOUPLING --
+function advtrains.split_train_at_fc(train, count_empty, length_limit)
+ -- splits train at first different current FC by convention,
+ -- locomotives have empty FC so are ignored
+ -- count_empty is used to split off locomotives
+ -- length_limit limits the length of the first train to length_limit wagons
+ local train_id = train.id
+ local fc = false
+ local ind = 0
+ for i = 1, #train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if length_limit and i > length_limit then
+ ind = i
+ break
+ end
+ if data then
+ local wfc = advtrains.get_cur_fc(data)
+ if wfc ~= "" or count_empty then
+ if fc then
+ if fc ~= wfc then
+ ind = i
+ break
+ end
+ else
+ fc = wfc
+ end
+ end
+ end
+ end
+ if ind > 0 then
+ return advtrains.split_train_at_index(train, ind), fc
+ end
+ if fc then
+ return nil, fc
+ end
+end
+
+function advtrains.train_step_fc(train)
+ for i=1,#train.trainparts do
+ local w_id = train.trainparts[i]
+ local data = advtrains.wagons[w_id]
+ if data then
+ advtrains.step_fc(data)
+ end
+ end
+end
+
+
+-- split_train_at_index() is in trainlogic.lua because it needs access to two local functions
+
+function advtrains.split_train_at_wagon(wagon_id)
+ --get train
+ local data = advtrains.wagons[wagon_id]
+ advtrains.split_train_at_index(advtrains.trains[data.train_id], data.pos_in_trainparts)
+end
+
+
+-- COUPLE ENTITIES --
local couple_max_dist=3
@@ -36,8 +420,6 @@ minetest.register_entity("advtrains:discouple", {
if pname and pname~="" and self.wagon then
if advtrains.safe_decouple_wagon(self.wagon.id, pname) then
self.object:remove()
- else
- minetest.add_entity(self.object:getpos(), "advtrains:lockmarker")
end
end
end,
@@ -60,10 +442,6 @@ minetest.register_entity("advtrains:discouple", {
-- advtrains:couple
-- Couple entity
-local function lockmarker(obj)
- minetest.add_entity(obj:get_pos(), "advtrains:lockmarker")
- obj:remove()
-end
minetest.register_entity("advtrains:couple", {
visual="sprite",
@@ -75,107 +453,71 @@ minetest.register_entity("advtrains:couple", {
is_couple=true,
static_save = false,
on_activate=function(self, staticdata)
- if staticdata=="COUPLE" then
- --couple entities have no right to exist further...
- atprint("Couple loaded from staticdata, destroying")
- self.object:remove()
- return
- end
- self.object:set_armor_groups({immmortal=1})
+ if staticdata=="COUPLE" then
+ --couple entities have no right to exist further...
+ --atdebug("Couple loaded from staticdata, destroying")
+ self.object:remove()
+ return
+ end
+ self.object:set_armor_groups({immmortal=1})
end,
get_staticdata=function(self) return "COUPLE" end,
on_rightclick=function(self, clicker)
- if not self.train_id_1 or not self.train_id_2 then return end
-
- local pname=clicker
- if type(clicker)~="string" then pname=clicker:get_player_name() end
-
- if advtrains.safe_couple_trains(self.train_id_1, self.train_id_2, self.t1_is_front, self.t2_is_front, pname) then
- self.object:remove()
- else
- lockmarker(self.object)
- end
+ if not self.train_id_1 or not self.train_id_2 then return end
+
+ local pname=clicker
+ if type(clicker)~="string" then pname=clicker:get_player_name() end
+
+ local train1=advtrains.trains[self.train_id_1]
+ local train2=advtrains.trains[self.train_id_2]
+
+ advtrains.safe_couple_trains(train1, self.t1_is_front, train2, self.t2_is_front, pname)
+ self.object:remove()
end,
on_step=function(self, dtime)
- if advtrains.wagon_outside_range(self.object:getpos()) then
- self.object:remove()
- return
- end
+ if advtrains.wagon_outside_range(self.object:getpos()) then
+ --atdebug("Couple Removing outside range")
+ self.object:remove()
+ return
+ end
- if not self.train_id_1 or not self.train_id_2 then atprint("Couple: train ids not set!") self.object:remove() return end
- local train1=advtrains.trains[self.train_id_1]
- local train2=advtrains.trains[self.train_id_2]
- if not train1 or not train2 then
- atprint("Couple: trains missing, destroying")
- self.object:remove()
- return
- end
-
- --shh, silence here, this is an on-step callback!
- if not advtrains.train_ensure_init(self.train_id_1, train1) then
- --atwarn("Train",self.train_id_1,"is not initialized! Operation aborted!")
- return
- end
- if not advtrains.train_ensure_init(self.train_id_2, train2) then
- --atwarn("Train",self.train_id_2,"is not initialized! Operation aborted!")
- return
- end
-
- if train1.velocity>0 or train2.velocity>0 then
- if not self.position_set then --ensures that train stands a single time before check fires. Using flag below
- return
- end
- atprint("Couple: train is moving, destroying")
- self.object:remove()
- return
+ if not self.train_id_1 or not self.train_id_2 then
+ --atdebug("Couple Removing ids missing")
+ self.object:remove()
+ return
+ end
+ local train1=advtrains.trains[self.train_id_1]
+ local train2=advtrains.trains[self.train_id_2]
+ if not train1 or not train2 then
+ --atdebug("Couple Removing trains missing")
+ self.object:remove()
+ return
+ end
+
+ if self.position_set and train1.velocity>0 or train2.velocity>0 then
+ --atdebug("Couple: train is moving, destroying")
+ self.object:remove()
+ return
+ end
+
+ if not self.position_set then
+ local tp1
+ if self.t1_is_front then
+ tp1=advtrains.path_get_interpolated(train1, train1.index)
+ else
+ tp1=advtrains.path_get_interpolated(train1, train1.end_index)
end
-
- if not self.position_set then
- local tp1
- if self.t1_is_front then
- tp1=advtrains.path_get_interpolated(train1, train1.index)
- else
- tp1=advtrains.path_get_interpolated(train1, train1.end_index)
- end
- local tp2
- if self.t2_is_front then
- tp2=advtrains.path_get_interpolated(train2, train2.index)
- else
- tp2=advtrains.path_get_interpolated(train2, train2.end_index)
- end
- local pos_median=advtrains.pos_median(tp1, tp2)
- if not vector.equals(pos_median, self.object:getpos()) then
- self.object:set_pos(pos_median)
- end
- self.position_set=true
+ local tp2
+ if self.t2_is_front then
+ tp2=advtrains.path_get_interpolated(train2, train2.index)
+ else
+ tp2=advtrains.path_get_interpolated(train2, train2.end_index)
end
- advtrains.atprint_context_tid=nil
- end,
-})
-minetest.register_entity("advtrains:lockmarker", {
- visual="sprite",
- textures = {"advtrains_cpl_lock.png"},
- collisionbox = {-0.3,-0.3,-0.3, 0.3,0.3,0.3},
- visual_size = {x=0.7, y=0.7},
- initial_sprite_basepos = {x=0, y=0},
-
- is_lockmarker=true,
- static_save = false,
- on_activate=function(self, staticdata)
- if staticdata=="COUPLE" then
- --couple entities have no right to exist further...
- atprint("Couple loaded from staticdata, destroying")
- self.object:remove()
- return
+ local pos_median=advtrains.pos_median(tp1, tp2)
+ if not vector.equals(pos_median, self.object:getpos()) then
+ self.object:set_pos(pos_median)
end
- self.object:set_armor_groups({immmortal=1})
- self.life=5
- end,
- get_staticdata=function(self) return "COUPLE" end,
- on_step=function(self, dtime)
- self.life=(self.life or 5)-dtime
- if self.life<0 then
- self.object:remove()
+ self.position_set=true
end
end,
-})
+})
diff --git a/advtrains/doc/advtrains_speed_lessp.3advtrains.md b/advtrains/doc/advtrains_speed_lessp.3advtrains.md
new file mode 100644
index 0000000..663aa42
--- /dev/null
+++ b/advtrains/doc/advtrains_speed_lessp.3advtrains.md
@@ -0,0 +1,15 @@
+% advtrains_speed_lessp(3advtrains) | Advtrains Developer's Manual
+
+# NAME
+`advtrains.speed.lessp`, `advtrains.speed.greaterp`, `advtrains.speed.not_lessp`, `advtrains.speed_not_greaterp`, `advtrains.speed.equalp`, `advtrains.speed.not_equalp`, `advtrains.speed.max`, `advtrains.speed.min` - speed restriction comparison functions
+
+# SYNOPSIS
+Each function takes two arguments and returns a boolean or (for `advtrains.speed.max` and `advtrains.speed.min`) a valid speed limit
+
+# DESCRIPTION
+
+The functions above correspond to the arithmetic `<`, `>`, `>=`, `<=`, `==`, `~=` operators and the `math.max` and `math.min` functions, respectively. The constants `nil` and `false` are treated as -1.
+
+# NOTES
+
+These functions are trivial to implement and the implementation can be easily embedded into existing code. They are simply provided for convenience.
diff --git a/advtrains/doc/advtrains_speed_set_restriction.3advtrains.md b/advtrains/doc/advtrains_speed_set_restriction.3advtrains.md
new file mode 100644
index 0000000..b3183c6
--- /dev/null
+++ b/advtrains/doc/advtrains_speed_set_restriction.3advtrains.md
@@ -0,0 +1,18 @@
+% advtrains_speed_set_restriction(3advtrains) | Advtrains Developer's Manual
+
+# NAME
+`advtrains.speed.set_restriction`, `advtrains.speed.merge_aspect` - modify speed restriction
+
+# SYNOPSIS
+* `advtrains.speed.set_restriction(train, rtype, rval)`
+* `advtrains.speed.merge_aspect(train, asp)`
+
+# DESCRIPTION
+
+The `advtrains.speed.set_restriction` function sets the speed restriction of type `rtype` of `train` to `rval` and updates the speed restriction value to the strictest speed restriction in the table, or `nil` if all speed restrictions are `nil` or `-1`. If the speed restriction table does not exist, it is created with the `"main"` speed restriction being the speed restriction value of `train`.
+
+The `advtrains.speed.merge_aspect` function merges the main aspect of `asp` into the speed restriction table with the same procedure described above. If the signal aspect table does not provide the type of speed restriction, the restriction type `"main"` is assumed.
+
+# SIDE EFFECTS
+
+Both functions modify `train.speed_restriction` and `train.speed_restrictions_t`.
diff --git a/advtrains/doc/signal_aspect.7advtrains.md b/advtrains/doc/signal_aspect.7advtrains.md
new file mode 100644
index 0000000..827760d
--- /dev/null
+++ b/advtrains/doc/signal_aspect.7advtrains.md
@@ -0,0 +1,24 @@
+% signal_aspect(7advtrains) | Advtrains Developer's Manual
+
+# DESCRIPTION
+
+The signal aspect table used by advtrains has the following fields:
+
+* `main`: The main speed restriction
+* `dst`: The `main` aspect of the distant signal (not implemented)
+* `type`: The type of speed restriction given by the signal
+* `shunt`: Whether shunting is allowed
+* `proceed_as_main`: Whether to proceed without shunting
+
+The `main` and `dst` fields may contain the following values:
+* `-1`: No speed restriction
+* `nil`: No information is available
+
+The `type` field can be any valid table index, but it should usually be one of the following values:
+* "main": The main signal aspect used before the introduction of speed restriction types. This is the default value if the `type` field is absent.
+* "line": The speed limit for the physical line.
+* "temp": The speed limit that is temporarily introduced.
+
+# NOTES
+
+A signal with the `main` aspect of zero should not provide distant signal aspect.
diff --git a/advtrains/formspec.lua b/advtrains/formspec.lua
new file mode 100644
index 0000000..8894354
--- /dev/null
+++ b/advtrains/formspec.lua
@@ -0,0 +1,111 @@
+local sformat = string.format
+local fsescape = minetest.formspec_escape
+
+local function make_list(entries)
+ local t = {}
+ for k, v in ipairs(entries) do
+ t[k] = fsescape(v)
+ end
+ return table.concat(t, ",")
+end
+
+local function S_wrapper(f, i0)
+ return function(...)
+ local args = {...}
+ args[i0] = attrans(unpack(args,i0))
+ return f(unpack(args,1,i0))
+ end
+end
+
+local function f_button(x, y, w, id, text)
+ return sformat("button[%f,%f;%f,0.75;%s;%s]", x, y, w, id, text)
+end
+
+local function f_checkbox(x, y, name, selected, label)
+ return sformat("checkbox[%f,%f;%s;%s;%s]", x, y+0.25, name, label, selected and "true" or "false")
+end
+
+local function f_button_exit(x, y, w, id, text)
+ return sformat("button_exit[%f,%f;%f,0.75;%s;%s]", x, y, w, id, text)
+end
+
+local function f_dropdown(x, y, w, id, entries, sel, indexed)
+ return sformat("dropdown[%f,%f;%f,0.75;%s;%s;%d%s]",
+ x, y, w, id, make_list(entries),
+ sel or 1,
+ indexed and ";true" or "")
+end
+
+local function f_image_button(x, y, w, h, texture, id, label, noclip, drawborder, pressed)
+ local st = {string.format("%f,%f;%f,%f;%s;%s;%s", x, y, w, h, fsescape(texture), fsescape(id), fsescape(label))}
+ if pressed then
+ st[#st+1] = tostring(noclip or false)
+ st[#st+1] = tostring(drawborder or false)
+ st[#st+1] = fsescape(pressed)
+ end
+ return sformat("image_button[%s]", table.concat(st, ";"))
+end
+
+local function f_image_button_exit(x, y, w, h, texture, id, label)
+ local st = {string.format("%f,%f;%f,%f;%s;%s;%s", x, y, w, h, fsescape(texture), fsescape(id), fsescape(label))}
+ return sformat("image_button_exit[%s]", table.concat(st, ";"))
+end
+
+local function f_label(x, y, text)
+ return sformat("label[%f,%f;%s]", x, y+0.25, fsescape(text))
+end
+
+local function f_field_aux(x, y, w, id, default)
+ return sformat("field[%f,%f;%f,0.75;%s;;%s]", x, y, w, id, default)
+end
+
+local function f_field(x, y, w, id, label, default)
+ return f_label(x, y-0.5, label) .. f_field_aux(x, y, w, id, default)
+end
+
+local function f_tabheader(x, y, w, h, id, entries, sel, transparent, border)
+ local st = {string.format("%f,%f",x, y)}
+ if h then
+ if w then
+ st[#st+1] = string.format("%f,%f", w, h)
+ else
+ st[#st+1] = tostring(h)
+ end
+ end
+ st[#st+1] = tostring(id)
+ st[#st+1] = make_list(entries)
+ st[#st+1] = tostring(sel)
+ if transparent ~= nil then
+ st[#st+1] = tostring(transparent)
+ if border ~= nil then
+ st[#st+1] = tostring(border)
+ end
+ end
+ return string.format("tabheader[%s]", table.concat(st, ";"))
+end
+
+local function f_textlist(x, y, w, h, id, entries, sel, transparent)
+ local st = {string.format("%f,%f;%f,%f;%s;%s", x, y, w, h, id, make_list(entries))}
+ if sel then
+ st[#st+1] = tostring(sel)
+ st[#st+1] = tostring(transparent or false)
+ end
+ return string.format("textlist[%s]", table.concat(st, ";"))
+end
+
+return {
+ button = f_button,
+ S_button = S_wrapper(f_button, 5),
+ checkbox = f_checkbox,
+ S_checkbox = S_wrapper(f_checkbox, 5),
+ button_exit = f_button_exit,
+ S_button_exit = S_wrapper(f_button_exit, 5),
+ dropdown = f_dropdown,
+ field = f_field,
+ image_button = f_image_button,
+ image_button_exit = f_image_button_exit,
+ label = f_label,
+ S_label = S_wrapper(f_label, 3),
+ tabheader = f_tabheader,
+ textlist = f_textlist,
+}
diff --git a/advtrains/init.lua b/advtrains/init.lua
index 96352df..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={}}
@@ -198,6 +201,9 @@ 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")
dofile(advtrains.modpath.."/trainhud.lua")
@@ -467,8 +473,8 @@ advtrains.avt_save = function(remove_players_from_wagons)
"trainparts", "recently_collided_with_env",
"atc_brake_target", "atc_wait_finish", "atc_command", "atc_delay", "door_open",
"text_outside", "text_inside", "line", "routingcode",
- "il_sections", "speed_restriction", "is_shunt",
- "points_split", "autocouple", "ars_disable",
+ "il_sections", "speed_restriction", "speed_restrictions_t", "is_shunt",
+ "points_split", "autocouple", "atc_wait_autocouple", "ars_disable",
})
--then save it
tmp_trains[id]=v
@@ -569,11 +575,13 @@ advtrains.mainloop_runcnt=0
advtrains.global_slowdown = 1
local t = 0
+local within_mainstep = false
minetest.register_globalstep(function(dtime_mt)
if no_action then
-- the advtrains globalstep is skipped by command. Return immediately
return
end
+ within_mainstep = true
advtrains.mainloop_runcnt=advtrains.mainloop_runcnt+1
--atprint("Running the main loop, runcnt",advtrains.mainloop_runcnt)
@@ -586,6 +594,7 @@ minetest.register_globalstep(function(dtime_mt)
if GENERATE_ATRICIFIAL_LAG then
dtime = HOW_MANY_LAG
if os.clock()<t then
+ within_mainstep = false
return
end
@@ -616,7 +625,7 @@ minetest.register_globalstep(function(dtime_mt)
if advtrains.lines then
advtrains.lines.step(dtime)
end
-
+
--trigger a save when necessary
save_timer=save_timer-dtime
if save_timer<=0 then
@@ -626,6 +635,9 @@ minetest.register_globalstep(function(dtime_mt)
save_timer = advtrains.SAVE_INTERVAL
atprintbm("saving", t)
end
+
+ within_mainstep = false
+
end)
--if something goes wrong in these functions, there is no help. no pcall here.
@@ -678,7 +690,13 @@ function advtrains.save(remove_players_from_wagons)
--TODO very simple yet hacky workaround for the "green signals" bug
advtrains.invalidate_all_paths()
end
-minetest.register_on_shutdown(advtrains.save)
+minetest.register_on_shutdown(function()
+ if within_mainstep then
+ atwarn("Crash during advtrains main step - skipping the shutdown save operation to not save inconsistent data!")
+ else
+ advtrains.save()
+ end
+end)
-- This chat command provides a solution to the problem known on the LinuxWorks server
-- There are many players that joined a single time, got on a train and then left forever
diff --git a/advtrains/locale/advtrains.de.tr b/advtrains/locale/advtrains.de.tr
index cd43eed..6abbc12 100644
--- a/advtrains/locale/advtrains.de.tr
+++ b/advtrains/locale/advtrains.de.tr
@@ -70,3 +70,8 @@ This track can not be removed!=Diese Schiene kann nicht entfernt werden!
Position is occupied by a train.=Ein Zug steht an dieser Position.
There's a Track Circuit Break here.=Hier ist eine Gleisabschnittsgrenze (TCB).
There's a Signal Influence Point here.=Hier ist ein Signal-Beeinflussungspunkt.
+Buffer and Chain Coupler=Schraubenkupplung
+Scharfenberg Coupler=Scharfenbergkupplung
+Japanese Train Inter-Wagon Connection=Waggonzwischenverbindung Japanischer Personenzug
+Can not couple: The couplers of the trains do not match (@1 and @2).=Kann nicht ankuppeln: Die Kupplungen der Züge passen nicht zueinander (@1 und @2)
+<none>=<keine>
diff --git a/advtrains/lzb.lua b/advtrains/lzb.lua
index cbdc422..64e4553 100644
--- a/advtrains/lzb.lua
+++ b/advtrains/lzb.lua
@@ -90,7 +90,7 @@ local function look_ahead(id, train)
--local brake_i = advtrains.path_get_index_by_offset(train, train.index, brakedst + params.BRAKE_SPACE)
-- worst case (don't use index_by_offset)
local brake_i = atfloor(train.index + brakedst + params.BRAKE_SPACE)
- atprint("LZB: looking ahead up to ", brake_i)
+ --atprint("LZB: looking ahead up to ", brake_i)
--local aware_i = advtrains.path_get_index_by_offset(train, brake_i, AWARE_ZONE)
@@ -134,7 +134,7 @@ local function call_runover_callbacks(id, train)
local ckp = train.lzb.checkpoints
while ckp[i] do
if ckp[i].index <= idx then
- atprint("LZB: checkpoint run over: i=",ckp[i].index,"s=",ckp[i].speed)
+ --atprint("LZB: checkpoint run over: i=",ckp[i].index,"s=",ckp[i].speed,"p=",ckp[i].pos)
-- call callback
local it = ckp[i]
if it.callback then
@@ -153,7 +153,7 @@ local function apply_checkpoint_to_path(train, checkpoint)
if not checkpoint.speed then
return
end
- atprint("LZB: applying checkpoint: i=",checkpoint.index,"s=",checkpoint.speed)
+ --atprint("LZB: applying checkpoint: i=",checkpoint.index,"s=",checkpoint.speed,"p=",checkpoint.pos)
if checkpoint.speed == 0 then
train.lzb.zero_checkpoint = true
@@ -196,6 +196,9 @@ s = v0 * ------- + - * | ------- | = -----------
-- Removes all LZB checkpoints and restarts the traverser at the current train index
function advtrains.lzb_invalidate(train)
+ --advtrains.atprint_context_tid = train.id
+ --atprint("LZB: invalidate")
+ --advtrains.atprint_context_tid = nil
train.lzb = {
trav_index = atfloor(train.index) + 1,
checkpoints = {},
@@ -205,8 +208,11 @@ end
-- LZB part of path_invalidate_ahead. Clears all checkpoints that are ahead of start_idx
-- in contrast to path_inv_ahead, doesn't complain if start_idx is behind train.index, clears everything then
function advtrains.lzb_invalidate_ahead(train, start_idx)
+ --advtrains.atprint_context_tid = train.id
+ --atprint("LZB: invalidate ahead i=",start_idx)
if train.lzb then
local idx = atfloor(start_idx)
+ --atprint("LZB: invalidate ahead p=",train.path[start_idx])
local i = 1
while train.lzb.checkpoints[i] do
if train.lzb.checkpoints[i].index >= idx then
@@ -225,6 +231,7 @@ function advtrains.lzb_invalidate_ahead(train, start_idx)
apply_checkpoint_to_path(train, ckp)
end
end
+ --advtrains.atprint_context_tid = nil
end
-- Add LZB control point
diff --git a/advtrains/nodedb.lua b/advtrains/nodedb.lua
index 36b5dea..41ac089 100644
--- a/advtrains/nodedb.lua
+++ b/advtrains/nodedb.lua
@@ -302,7 +302,7 @@ ndb.run_lbm = function(pos, node)
minetest.swap_node(pos, newnode)
local ndef=minetest.registered_nodes[nodeid]
if ndef and ndef.advtrains and ndef.advtrains.on_updated_from_nodedb then
- ndef.advtrains.on_updated_from_nodedb(pos, newnode)
+ ndef.advtrains.on_updated_from_nodedb(pos, newnode, node)
end
return true
end
diff --git a/advtrains/path.lua b/advtrains/path.lua
index 714781a..f2b8a13 100644
--- a/advtrains/path.lua
+++ b/advtrains/path.lua
@@ -417,3 +417,42 @@ function advtrains.path_lookup(train, pos)
end
return nil
end
+
+-- Projects the path of "train" onto the path of "onto_train_id", and returns the index on onto_train's path
+-- that corresponds to "index" on "train"'s path, as well as whether both trains face each other
+-- index may be fractional
+-- returns: res_index, trains_facing
+-- returns nil when path can not be projected, either because trains are on different tracks or
+-- node at "index" happens to be on a turnout and it's the wrong direction
+-- Note - duplicate with similar functionality is in train_step_b() - that code combines train detection with projecting
+function advtrains.path_project(train, index, onto_train_id)
+ local base_idx = atfloor(index)
+ local frac_part = index - base_idx
+ local base_pos = advtrains.path_get(train, base_idx)
+ local base_cn = train.path_cn[base_idx]
+ local otrn = advtrains.trains[onto_train_id]
+ -- query occupation
+ local occ = advtrains.occ.get_trains_over(base_pos)
+ -- is wanted train id contained?
+ local ob_idx = occ[onto_train_id]
+ if not ob_idx then
+ return nil
+ end
+
+ -- retrieve other train's cn and cp
+ local ocn = otrn.path_cn[ob_idx]
+ local ocp = otrn.path_cp[ob_idx]
+
+ if base_cn == ocn then
+ -- same direction
+ return ob_idx + frac_part, false
+ elseif base_cn == ocp then
+ -- facing trains - subtract index frac
+ return ob_idx - frac_part, true
+ else
+ -- same path item but no common connections - deny
+ return nil
+ end
+end
+
+
diff --git a/advtrains/settingtypes.txt b/advtrains/settingtypes.txt
index 6acff80..2b627cb 100644
--- a/advtrains/settingtypes.txt
+++ b/advtrains/settingtypes.txt
@@ -56,3 +56,8 @@ advtrains_dtime_limit (DTime Limit for slow-down) float 0.2 0 5
# Time interval in seconds in which advtrains stores its save data to disk
# Nevertheless, advtrains saves all data when shutting down the server.
advtrains_save_interval (Save Interval) int 60 20 3600
+
+# Enable forgiving collision mode
+# If enabled, trains only collide with nodes with "normal" drawtype.
+advtrains_forgiving_collision (Forgiving Collision mode) bool false
+
diff --git a/advtrains/signals.lua b/advtrains/signals.lua
index 5fb1d1b..b26c950 100644
--- a/advtrains/signals.lua
+++ b/advtrains/signals.lua
@@ -18,7 +18,7 @@ end
local function aspect(b)
return {
- main = (not b) and 0, -- b ? false : 0
+ main = b and -1 or 0,
shunt = false,
proceed_as_main = true,
dst = false,
@@ -27,7 +27,7 @@ return {
end
local suppasp = {
- main = {0, false},
+ main = {0, -1},
dst = {false},
shunt = nil,
proceed_as_main = true,
diff --git a/advtrains/spec/speed_spec.lua b/advtrains/spec/speed_spec.lua
new file mode 100644
index 0000000..97f8ffa
--- /dev/null
+++ b/advtrains/spec/speed_spec.lua
@@ -0,0 +1,70 @@
+package.path = "../?.lua;" .. package.path
+advtrains = {}
+_G.advtrains = advtrains
+local speed = require("speed")
+
+describe("Arithmetic functions on speed restrictions", function()
+ it("should work", function()
+ local a = math.random()
+ local b = math.random(20)
+ -- This test is basically a "typo check"
+ assert.is_true (speed.lessp(a, b))
+ assert.is_false(speed.greaterp(a, b))
+ assert.is_false(speed.not_lessp(a, b))
+ assert.is_true (speed.not_greaterp(a, b))
+ assert.is_false(speed.lessp(a, a))
+ assert.is_false(speed.greaterp(a, a))
+ assert.is_true (speed.equalp(a, a))
+ assert.is_false(speed.not_equalp(a, a))
+ assert.equal(b, speed.max(a, b))
+ assert.equal(a, speed.min(a, b))
+ end)
+ it("should handle -1", function()
+ assert.is_false(speed.lessp(-1, math.random()))
+ end)
+ it("should handle nil", function()
+ assert.is_true(speed.greaterp(nil, math.random()))
+ end)
+ it("should handle mixed nil and -1", function()
+ assert.is_true(speed.equalp(nil, -1))
+ end)
+end)
+
+describe("The speed restriction setter", function()
+ it("should set the signal aspect", function()
+ local t = {speed_restrictions_t = {x = 5, y = 9}}
+ local u = {speed_restrictions_t = {x = 7, y = 9}, speed_restriction = 7}
+ speed.merge_aspect(t, {main = 7, type = "x"})
+ assert.same(u, t)
+ end)
+ it("should work with existing signal aspect tables", function()
+ local t = {speed_restrictions_t = {main = 5, foo = 3}}
+ local u = {speed_restrictions_t = {main = 7, foo = 3}, speed_restriction = 3}
+ speed.merge_aspect(t, {main = 7})
+ assert.same(u, t)
+ end)
+ it("should work with distant signals", function()
+ local t = {speed_restrictions_t = {main = 5}}
+ local u = {speed_restrictions_t = {main = 5}, speed_restriction = 5}
+ speed.merge_aspect(t, {})
+ assert.same(u, t)
+ end)
+ it("should create the restriction table if necessary", function()
+ local t = {speed_restriction = 5}
+ local u = {speed_restriction = 3, speed_restrictions_t = {main = 5, foo = 3}}
+ speed.merge_aspect(t, {main = 3, type = "foo"})
+ assert.same(u, t)
+ end)
+ it("should also create the restriction table for trains without any speed limit", function()
+ local t = {}
+ local u = {speed_restrictions_t = {}}
+ speed.merge_aspect(t, {})
+ assert.same(u, t)
+ end)
+ it("should set the speed restriction to nil if that is the case", function()
+ local t = {speed_restriction = math.random(20)}
+ local u = {speed_restrictions_t = {main = -1}}
+ speed.merge_aspect(t, {main = -1})
+ assert.same(u, t)
+ end)
+end)
diff --git a/advtrains/speed.lua b/advtrains/speed.lua
new file mode 100644
index 0000000..ec4f928
--- /dev/null
+++ b/advtrains/speed.lua
@@ -0,0 +1,88 @@
+-- auxiliary functions for the reworked speed restriction system
+
+local function s_lessp(a, b)
+ if not a or a == -1 then
+ return false
+ elseif not b or b == -1 then
+ return true
+ else
+ return a < b
+ end
+end
+
+local function s_greaterp(a, b)
+ return s_lessp(b, a)
+end
+
+local function s_not_lessp(a, b)
+ return not s_lessp(a, b)
+end
+
+local function s_not_greaterp(a, b)
+ return not s_greaterp(a, b)
+end
+
+local function s_equalp(a, b)
+ return (a or -1) == (b or -1)
+end
+
+local function s_not_equalp(a, b)
+ return (a or -1) ~= (b or -1)
+end
+
+local function s_max(a, b)
+ if s_lessp(a, b) then
+ return b
+ else
+ return a
+ end
+end
+
+local function s_min(a, b)
+ if s_lessp(a, b) then
+ return a
+ else
+ return b
+ end
+end
+
+local function get_speed_restriction_from_table (tbl)
+ local strictest = -1
+ for _, v in pairs(tbl) do
+ strictest = s_min(strictest, v)
+ end
+ if strictest == -1 then
+ return nil
+ end
+ return strictest
+end
+
+local function set_speed_restriction (tbl, rtype, rval)
+ if rval then
+ tbl[rtype or "main"] = rval
+ end
+ return tbl
+end
+
+local function set_speed_restriction_for_train (train, rtype, rval)
+ local t = train.speed_restrictions_t or {main = train.speed_restriction}
+ train.speed_restrictions_t = set_speed_restriction(t, rtype, rval)
+ train.speed_restriction = get_speed_restriction_from_table(t)
+end
+
+local function merge_speed_restriction_from_aspect_to_train (train, asp)
+ return set_speed_restriction_for_train(train, asp.type, asp.main)
+end
+
+return {
+ lessp = s_lessp,
+ greaterp = s_greaterp,
+ not_lessp = s_not_lessp,
+ not_greaterp = s_not_greaterp,
+ equalp = s_equalp,
+ not_equalp = s_not_equalp,
+ max = s_max,
+ min = s_min,
+ set_restriction = set_speed_restriction_for_train,
+ merge_aspect = merge_speed_restriction_from_aspect_to_train,
+}
diff --git a/advtrains/trainhud.lua b/advtrains/trainhud.lua
index 6e69455..22aa6cf 100644
--- a/advtrains/trainhud.lua
+++ b/advtrains/trainhud.lua
@@ -281,6 +281,8 @@ function advtrains.hud_train_format(train, flip)
local oc = lzb.checkpoints
for i = 1, #oc do
local spd = oc[i].speed
+ spd = advtrains.speed.min(spd, train.speed_restriction)
+ if spd == -1 then spd = nil end
local c = not spd and "lime" or (type(spd) == "number" and (spd == 0) and "red" or "orange") or nil
if c then
ht[#ht+1] = sformat("130,10=(advtrains_hud_bg.png^[resize\\:30x5^[colorize\\:%s)",c)
diff --git a/advtrains/trainlogic.lua b/advtrains/trainlogic.lua
index 187e5ba..a0fdaa1 100644
--- a/advtrains/trainlogic.lua
+++ b/advtrains/trainlogic.lua
@@ -139,11 +139,15 @@ minetest.register_on_joinplayer(function(player)
advtrains.hhud[player:get_player_name()] = nil
--independent of this, cause all wagons of the train which are loaded to reattach their players
--needed because already loaded wagons won't call reattach_all()
+ local pname = player:get_player_name()
local id=advtrains.player_to_train_mapping[pname]
if id then
for _,wagon in pairs(minetest.luaentities) do
- if wagon.is_wagon and wagon.initialized and wagon.train_id==id then
- wagon:reattach_all()
+ if wagon.is_wagon and wagon.initialized and wagon.id then
+ local wdata = advtrains.wagons[wagon.id]
+ if wdata and wdata.train_id == id then
+ wagon:reattach_all()
+ end
end
end
end
@@ -250,6 +254,11 @@ local callbacks_update, run_callbacks_update = mkcallback("update")
local callbacks_create, run_callbacks_create = mkcallback("create")
local callbacks_remove, run_callbacks_remove = mkcallback("remove")
+-- required to call from couple.lua
+function advtrains.update_train_start_and_end(train)
+ recalc_end_index(train)
+ run_callbacks_update(train.id, train)
+end
-- train_ensure_init: responsible for creating a state that we can work on, after one of the following events has happened:
-- - the train's path got cleared
@@ -386,7 +395,7 @@ function advtrains.train_step_b(id, train, dtime)
-- interlocking speed restriction
elseif train.speed_restriction then
--atprint("in train_step_b: applying interlocking speed restriction",train.speed_restriction)
- sit_v_cap = train.speed_restriction
+ sit_v_cap = math.min(sit_v_cap or math.huge, train.speed_restriction)
end
--apply off-track handling:
@@ -394,7 +403,7 @@ function advtrains.train_step_b(id, train, dtime)
local back_off_track=train.end_index<train.path_trk_b
train.off_track = front_off_track or back_off_track
- if back_off_track and (not v_cap or v_cap > 1) then
+ if back_off_track and (not sit_v_cap or sit_v_cap > 1) then
--atprint("in train_step_b: applying back_off_track")
sit_v_cap = 1
elseif front_off_track then
@@ -417,9 +426,11 @@ function advtrains.train_step_b(id, train, dtime)
ctrl_lever = userc
else
if train.atc_command then
- if (not train.atc_delay or train.atc_delay<=0) and not train.atc_wait_finish then
+ if (not train.atc_delay or train.atc_delay<=0)
+ and not train.atc_wait_finish
+ and not train.atc_wait_autocouple then
advtrains.atc.execute_atc_command(id, train)
- else
+ elseif train.atc_delay and train.atc_delay > 0 then
train.atc_delay=train.atc_delay-dtime
end
elseif train.atc_delay then
@@ -587,12 +598,77 @@ function advtrains.train_step_b(id, train, dtime)
else
--atprint("in train_step_b: movement calculation reusing from LZB newindex=",new_index_curr_tv)
end
-
+
-- if the zeroappr mechanism has hit, go no further than zeroappr index
if lzb_next_zero_barrier and new_index_curr_tv > lzb_next_zero_barrier then
--atprint("in train_step_b: Zero barrier hit, clipping to newidx_tv=",new_index_curr_tv, "zb_idx=",lzb_next_zero_barrier)
new_index_curr_tv = lzb_next_zero_barrier
end
+
+ -- New same-track collision system - check for any other trains within the range we're going to move
+ -- do the checks if we either are moving or about to start moving
+ if new_index_curr_tv > train.index or accelerating then -- only if train is actually advancing
+ -- Note: duplicate code from path_project() because of subtle differences: no frac processing and scanning all occupations
+ --[[train.debug = ""
+ local atdebug = function(t, ...)
+ local text=advtrains.print_concat_table({t, ...})
+ train.debug = train.debug..text.."\n"
+ end]]
+ local base_idx = atfloor(new_index_curr_tv + 1)
+ local base_pos = advtrains.path_get(train, base_idx)
+ local base_cn = train.path_cn[base_idx]
+ --atdebug(id,"Begin Checking for on-track collisions new_idx=",new_index_curr_tv,"base_idx=",base_idx,"base_pos=",base_pos,"base_cn=",base_cn)
+ -- query occupation
+ local occ = advtrains.occ.get_trains_over(base_pos)
+ -- iterate other trains
+ for otid, ob_idx in pairs(occ) do
+ if otid ~= id then
+ --atdebug(id,"Found other train",otid," with matching index ",ob_idx)
+ -- Phase 1 - determine if trains are facing and which is the relefant stpo index
+ local otrn = advtrains.trains[otid]
+
+ -- retrieve other train's cn and cp
+ local ocn = otrn.path_cn[ob_idx]
+ local ocp = otrn.path_cp[ob_idx]
+
+ local target_is_inside, ref_index, facing
+
+ if base_cn == ocn then
+ -- same direction
+ ref_index = otrn.end_index
+ same_dir = true
+ target_is_inside = (ob_idx >= ref_index)
+ --atdebug("Same direction: ref_index",ref_index,"inside=",target_is_inside)
+ elseif base_cn == ocp then
+ -- facing trains - subtract index frac
+ ref_index = otrn.index
+ same_dir = false
+ target_is_inside = (ob_idx <= ref_index)
+ --atdebug("Facing direction: ref_index",ref_index,"inside=",target_is_inside)
+ end
+
+ -- Phase 2 - project ref_index back onto our path and check again (necessary because there might be a turnout on the way and we are driving into the flank
+ if target_is_inside then
+ local our_index = advtrains.path_project(otrn, ref_index, id)
+ --atdebug("Backprojected our_index",our_index)
+ if our_index and our_index <= new_index_curr_tv
+ and our_index >= train.index then --FIX: If train was already past the collision point in the previous step, there is no collision! Fixes bug with split_at_index
+ -- ON_TRACK COLLISION IS HAPPENING
+ -- the actual collision is handled in train_step_c, so set appropriate signal variables
+ train.ontrack_collision_info = {
+ otid = otid,
+ same_dir = same_dir,
+ }
+ -- clip newindex
+ --atdebug("-- Collision detected!")
+ new_index_curr_tv = our_index
+ end
+ end
+ end
+ end
+ end
+
+ -- ## Movement happens here ##
train.index = new_index_curr_tv
recalc_end_index(train)
@@ -638,21 +714,45 @@ function advtrains.train_step_c(id, train, dtime)
advtrains.spawn_wagons(id)
train.check_trainpartload=2
end
-
- --- 8. check for collisions with other trains and damage players ---
-
+
local train_moves=(train.velocity~=0)
-
- --- Check whether this train can be coupled to another, and set couple entities accordingly
- if not train.was_standing and not train_moves then
- advtrains.train_check_couples(train)
+ local very_short_train = train.trainlen < 3
+
+ --- On-track collision handling - detected in train_step_b, but handled here so all other train movements have already happened.
+ if train.ontrack_collision_info then
+ train.velocity = 0
+ train.acceleration = 0
+ --advtrains.atc.train_reset_command(train) will occur in couple_initiate_with if required
+
+ local otrn = advtrains.trains[train.ontrack_collision_info.otid]
+
+ if otrn.velocity == 0 then -- other train must be standing, else don't initiate coupling
+ advtrains.couple_initiate_with(train, otrn, not train.ontrack_collision_info.same_dir)
+ else
+ -- other collision - stop any ATC control
+ advtrains.atc.train_reset_command(train)
+ end
+
+ train.ontrack_collision_info = nil
+ train.couples_up_to_date = true
end
- train.was_standing = not train_moves
-
+
+ -- handle couples if on_track collision handling did not fire
if train_moves then
-
+ train.couples_up_to_date = nil
+ elseif not train.couples_up_to_date then
+ if not very_short_train then -- old coupling system is buggy for short trains
+ advtrains.train_check_couples(train) -- no guarantee for train order here
+ end
+ train.couples_up_to_date = true
+ end
+
+ --- 8. check for collisions with other trains and damage players ---
+ if train_moves then
+ -- Note: this code handles collisions with trains that are not on the same path as the current train
+ -- The same-track collisions and coupling handling is found in couple.lua and handled from train_step_b() and code 2 blocks above.
local collided = false
- local coll_grace=1
+ local coll_grace=2
local collindex = advtrains.path_get_index_by_offset(train, train.index, -coll_grace)
local collpos = advtrains.path_get(train, atround(collindex))
if collpos then
@@ -663,13 +763,14 @@ function advtrains.train_step_c(id, train, dtime)
local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
--- 8a Check collision ---
if not collided then
-
- local col_tr = advtrains.occ.check_collision(testpos, id)
- if col_tr then
- advtrains.train_check_couples(train)
- train.velocity = 0
- advtrains.atc.train_reset_command(train)
- collided = true
+ if not very_short_train then -- position collision system is buggy for short trains
+ local col_tr = advtrains.occ.check_collision(testpos, id)
+ if col_tr then
+ train.velocity = 0
+ train.acceleration = 0
+ advtrains.atc.train_reset_command(train)
+ collided = true
+ end
end
--- 8b damage players ---
@@ -703,7 +804,7 @@ function advtrains.train_step_c(id, train, dtime)
local objs = minetest.get_objects_inside_radius(rcollpos, 2)
for _,obj in ipairs(objs) do
if not obj:is_player() and obj:get_armor_groups().fleshy and obj:get_armor_groups().fleshy > 0
- and obj:get_luaentity() and obj:get_luaentity().name~="signs:text" then
+ and obj:get_luaentity() and obj:get_luaentity().name~="signs_lib:text" then
obj:punch(obj, 1, { full_punch_interval = 1.0, damage_groups = {fleshy = 1000}, }, nil)
end
end
@@ -899,7 +1000,7 @@ function advtrains.remove_train(id)
run_callbacks_remove(id, train)
- advtrains.path_invalidate(train)
+ advtrains.path_invalidate(train, true)
advtrains.couple_invalidate(train)
local tp = train.trainparts
@@ -1019,55 +1120,9 @@ function advtrains.spawn_wagons(train_id)
end
end
-function advtrains.split_train_at_fc(train, count_empty, length_limit)
- -- splits train at first different current FC by convention,
- -- locomotives have empty FC so are ignored
- -- count_empty is used to split off locomotives
- -- length_limit limits the length of the first train to length_limit wagons
- local train_id = train.id
- local fc = false
- local ind = 0
- for i = 1, #train.trainparts do
- local w_id = train.trainparts[i]
- local data = advtrains.wagons[w_id]
- if length_limit and i > length_limit then
- ind = i
- break
- end
- if data then
- local wfc = advtrains.get_cur_fc(data)
- if wfc ~= "" or count_empty then
- if fc then
- if fc ~= wfc then
- ind = i
- break
- end
- else
- fc = wfc
- end
- end
- end
- end
- if ind > 0 then
- return advtrains.split_train_at_index(train, ind), fc
- end
- if fc then
- return nil, fc
- end
-end
-
-function advtrains.train_step_fc(train)
- for i=1,#train.trainparts do
- local w_id = train.trainparts[i]
- local data = advtrains.wagons[w_id]
- if data then
- advtrains.step_fc(data)
- end
- end
-end
-
function advtrains.split_train_at_index(train, index)
-- this function splits a train at index, creating a new train from the back part of the train.
+ --atdebug("split_train_at_index invoked on",train.id,"index",index)
local train_id=train.id
if index > #train.trainparts then
@@ -1090,6 +1145,7 @@ function advtrains.split_train_at_index(train, index)
local p_index=advtrains.path_get_index_by_offset(train, train.index, - data.pos_in_train + wagon.wagon_span)
local pos, connid, frac = advtrains.path_getrestore(train, p_index)
+ --atdebug("new train position p_index",p_index,"pos",pos,"connid",connid,"frac",frac)
local tp = {}
for k,v in ipairs(train.trainparts) do
if k >= index then
@@ -1099,12 +1155,14 @@ function advtrains.split_train_at_index(train, index)
end
advtrains.update_trainpart_properties(train_id)
recalc_end_index(train)
+ --atdebug("old train index",train.index,"end_index",train.end_index)
run_callbacks_update(train_id, train)
--create subtrain
local newtrain_id=advtrains.create_new_train_at(pos, connid, frac, tp)
local newtrain=advtrains.trains[newtrain_id]
-
+ --atdebug("new train created with ID",newtrain_id,"index",newtrain.index,"end_index",newtrain.end_index)
+
newtrain.velocity=train.velocity
-- copy various properties from the old to the new train
newtrain.door_open = train.door_open
@@ -1113,6 +1171,7 @@ function advtrains.split_train_at_index(train, index)
newtrain.line = train.line
newtrain.routingcode = train.routingcode
newtrain.speed_restriction = train.speed_restriction
+ newtrain.speed_restrictions_t = table.copy(train.speed_restrictions_t or {main=train.speed_restriction})
newtrain.is_shunt = train.is_shunt
newtrain.points_split = advtrains.merge_tables(train.points_split)
newtrain.autocouple = train.autocouple
@@ -1121,167 +1180,6 @@ function advtrains.split_train_at_index(train, index)
end
-function advtrains.split_train_at_wagon(wagon_id)
- --get train
- local data = advtrains.wagons[wagon_id]
- advtrains.split_train_at_index(advtrains.trains[data.train_id], data.pos_in_trainparts)
-end
-
--- coupling
-local CPL_CHK_DST = -1
-local CPL_ZONE = 2
-
--- train.couple_* contain references to ObjectRefs of couple objects, which contain all relevant information
--- These objectRefs will delete themselves once the couples no longer match
-local function createcouple(pos, train1, t1_is_front, train2, t2_is_front)
- local id1 = train1.id
- local id2 = train2.id
- if train1.autocouple or train2.autocouple then
- -- couple trains
- train1.autocouple = nil
- train2.autocouple = nil
- minetest.after(0, advtrains.safe_couple_trains, id1, id2, t1_is_front, t2_is_front, false, false, train1.velocity, train2.velocity)
- return
- end
-
- local obj=minetest.add_entity(pos, "advtrains:couple")
- if not obj then error("Failed creating couple object!") return end
- local le=obj:get_luaentity()
- le.train_id_1=id1
- le.train_id_2=id2
- le.t1_is_front=t1_is_front
- le.t2_is_front=t2_is_front
- --atdebug("created couple between",train1.id,t1_is_front,train2.id,t2_is_front)
- if t1_is_front then
- train1.cpl_front = obj
- else
- train1.cpl_back = obj
- end
- if t2_is_front then
- train2.cpl_front = obj
- else
- train2.cpl_back = obj
- end
-
-end
-
-function advtrains.train_check_couples(train)
- --atdebug("rechecking couples")
- if train.cpl_front then
- if not train.cpl_front:get_yaw() then
- -- objectref is no longer valid. reset.
- train.cpl_front = nil
- end
- end
- if not train.cpl_front then
- -- recheck front couple
- local front_trains, pos = advtrains.occ.get_occupations(train, atround(train.index) + CPL_CHK_DST)
- if advtrains.is_node_loaded(pos) then -- if the position is loaded...
- for tid, idx in pairs(front_trains) do
- local other_train = advtrains.trains[tid]
- if not advtrains.train_ensure_init(tid, other_train) then
- atwarn("Train",tid,"is not initialized! Couldn't check couples!")
- return
- end
- --atdebug(train.id,"front: ",idx,"on",tid,atround(other_train.index),atround(other_train.end_index))
- if other_train.velocity == 0 then
- if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
- createcouple(pos, train, true, other_train, true)
- break
- end
- if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
- createcouple(pos, train, true, other_train, false)
- break
- end
- end
- end
- end
- end
- if train.cpl_back then
- if not train.cpl_back:get_yaw() then
- -- objectref is no longer valid. reset.
- train.cpl_back = nil
- end
- end
- if not train.cpl_back then
- -- recheck back couple
- local back_trains, pos = advtrains.occ.get_occupations(train, atround(train.end_index) - CPL_CHK_DST)
- if advtrains.is_node_loaded(pos) then -- if the position is loaded...
- for tid, idx in pairs(back_trains) do
- local other_train = advtrains.trains[tid]
- if not advtrains.train_ensure_init(tid, other_train) then
- atwarn("Train",tid,"is not initialized! Couldn't check couples!")
- return
- end
- if other_train.velocity == 0 then
- if idx>=other_train.index and idx<=other_train.index + CPL_ZONE then
- createcouple(pos, train, false, other_train, true)
- break
- end
- if idx<=other_train.end_index and idx>=other_train.end_index - CPL_ZONE then
- createcouple(pos, train, false, other_train, false)
- break
- end
- end
- end
- end
- end
-end
-
-function advtrains.couple_invalidate(train)
- if train.cpl_back then
- train.cpl_back:remove()
- train.cpl_back = nil
- end
- if train.cpl_front then
- train.cpl_front:remove()
- train.cpl_front = nil
- end
- train.was_standing = nil
-end
-
--- relevant code for this comment is in couple.lua
-
---there are 4 cases:
---1/2. F<->R F<->R regular, put second train behind first
---->frontpos of first train will match backpos of second
---3. F<->R R<->F flip one of these trains, take the other as new train
---->backpos's will match
---4. R<->F F<->R flip one of these trains and take it as new parent
---->frontpos's will match
-
-
-function advtrains.do_connect_trains(first_id, second_id, vel)
- local first, second=advtrains.trains[first_id], advtrains.trains[second_id]
-
- if not advtrains.train_ensure_init(first_id, first) then
- atwarn("Train",first_id,"is not initialized! Operation aborted!")
- return
- end
- if not advtrains.train_ensure_init(second_id, second) then
- atwarn("Train",second_id,"is not initialized! Operation aborted!")
- return
- end
-
- local first_wagoncnt=#first.trainparts
- local second_wagoncnt=#second.trainparts
-
- for _,v in ipairs(second.trainparts) do
- table.insert(first.trainparts, v)
- end
-
- advtrains.remove_train(second_id)
- if vel < 0 then
- advtrains.invert_train(first_id)
- vel = -vel
- end
- first.velocity= vel or 0
-
- advtrains.update_trainpart_properties(first_id)
- advtrains.couple_invalidate(first)
- return true
-end
-
function advtrains.invert_train(train_id)
local train=advtrains.trains[train_id]
@@ -1316,10 +1214,10 @@ function advtrains.invert_train(train_id)
-- If interlocking present, check whether this train is in a section and then set as shunt move after reversion
if advtrains.interlocking and train.il_sections and #train.il_sections > 0 then
train.is_shunt = true
- train.speed_restriction = advtrains.SHUNT_SPEED_MAX
+ advtrains.speed.set_restriction(train, "main", advtrains.SHUNT_SPEED_MAX)
else
train.is_shunt = false
- train.speed_restriction = nil
+ advtrains.speed.set_restriction(train, "main", -1)
end
end
@@ -1368,44 +1266,61 @@ function advtrains.invalidate_path(id)
end
--not blocking trains group
-function advtrains.train_collides(node)
- if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable then
+
+if minetest.settings:get_bool("advtrains_forgiving_collision") then
+ function advtrains.train_collides(node)
+ if node and minetest.registered_nodes[node.name] then
+ local ndef = minetest.registered_nodes[node.name]
+ -- if the node is drawtype normal (that is a full cube) then it does collide
+ if ndef.drawtype == "normal" then
+ -- except if it is not_blocking_trains
+ if ndef.groups.not_blocking_trains and ndef.groups.not_blocking_trains ~= 0 then
+ return false
+ end
+ return true
+ end
+ end
+ return false
+ end
+else
+ function advtrains.train_collides(node)
+ if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable then
if not minetest.registered_nodes[node.name].groups.not_blocking_trains then
return true
end
- end
- return false
-end
-
-local nonblocknodes={
- "default:fence_wood",
- "default:fence_acacia_wood",
- "default:fence_aspen_wood",
- "default:fence_pine_wood",
- "default:fence_junglewood",
- "default:torch",
- "bones:bones",
-
- "default:sign_wall",
- "signs:sign_wall",
- "signs:sign_wall_blue",
- "signs:sign_wall_brown",
- "signs:sign_wall_orange",
- "signs:sign_wall_green",
- "signs:sign_yard",
- "signs:sign_wall_white_black",
- "signs:sign_wall_red",
- "signs:sign_wall_white_red",
- "signs:sign_wall_yellow",
- "signs:sign_post",
- "signs:sign_hanging",
-
-
-}
-minetest.after(0, function()
- for _,name in ipairs(nonblocknodes) do
- if minetest.registered_nodes[name] then
- minetest.registered_nodes[name].groups.not_blocking_trains=1
end
+ return false
end
-end)
+
+ local nonblocknodes={
+ "default:fence_wood",
+ "default:fence_acacia_wood",
+ "default:fence_aspen_wood",
+ "default:fence_pine_wood",
+ "default:fence_junglewood",
+ "default:torch",
+ "bones:bones",
+
+ "default:sign_wall",
+ "signs:sign_wall",
+ "signs:sign_wall_blue",
+ "signs:sign_wall_brown",
+ "signs:sign_wall_orange",
+ "signs:sign_wall_green",
+ "signs:sign_yard",
+ "signs:sign_wall_white_black",
+ "signs:sign_wall_red",
+ "signs:sign_wall_white_red",
+ "signs:sign_wall_yellow",
+ "signs:sign_post",
+ "signs:sign_hanging",
+
+ }
+ minetest.after(0, function()
+ for _,name in ipairs(nonblocknodes) do
+ if minetest.registered_nodes[name] then
+ minetest.registered_nodes[name].groups.not_blocking_trains=1
+ end
+ end
+ end)
+end
diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua
index e9b6d7a..fe1a0f8 100644
--- a/advtrains/wagons.lua
+++ b/advtrains/wagons.lua
@@ -413,13 +413,38 @@ function wagon:on_step(dtime)
end
-- Calculate new position, yaw and direction vector
+ -- note: "index" is needed to be the center index, required by door code
local index = advtrains.path_get_index_by_offset(train, train.index, -data.pos_in_train)
- local pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
- local vdir = vector.normalize(vector.subtract(npos2, npos))
+ local pos, yaw, npos, npos2, vdir
+
+ -- use new position logic?
+ if self.wheel_positions then
+ -- request two positions, calculate difference and yaw from this
+ -- depending on flipstate, need to invert wheel pos indices -> wheelpos * fct
+ local index1 = advtrains.path_get_index_by_offset(train, index, self.wheel_positions[1] * fct)
+ local index2 = advtrains.path_get_index_by_offset(train, index, self.wheel_positions[2] * fct)
+ local pos1 = advtrains.path_get_interpolated(train, index1)
+ local pos2 = advtrains.path_get_interpolated(train, index2)
+ npos = advtrains.path_get(train, atfloor(index)) -- need npos just for node loaded check
+ -- calculate center of 2 positions and vdir vector
+ -- if wheel positions are asymmetric, needs to weight by the difference!
+ local fact = self.wheel_positions[1] / (self.wheel_positions[1]-self.wheel_positions[2])
+ pos = {x=pos1.x-(pos1.x-pos2.x)*fact, y=pos1.y-(pos1.y-pos2.y)*fact, z=pos1.z-(pos1.z-pos2.z)*fact}
+ if data.wagon_flipped then
+ vdir = vector.normalize(vector.subtract(pos2, pos1))
+ else
+ vdir = vector.normalize(vector.subtract(pos1, pos2))
+ end
+ yaw = math.atan2(-vdir.x, vdir.z)
+ else
+ --old position logic (for small wagons): use center index and just get position
+ pos, yaw, npos, npos2 = advtrains.path_get_interpolated(train, index)
+ vdir = vector.normalize(vector.subtract(npos2, npos))
+ end
--automatic get_on
--needs to know index and path
- if self.door_entry and train.door_open and train.door_open~=0 and train.velocity==0 then
+ if train.velocity==0 and self.door_entry and train.door_open and train.door_open~=0 then
--using the mapping created by the trainlogic globalstep
for i, ino in ipairs(self.door_entry) do
--fct is the flipstate flag from door animation above
@@ -470,28 +495,32 @@ function wagon:on_step(dtime)
end
end
- --DisCouple
+ -- Spawn discouple object when train stands, in all other cases remove it.
-- FIX: Need to do this after the yaw calculation
- if is_in_loaded_area and data.pos_in_trainparts and data.pos_in_trainparts>1 then
- if train.velocity==0 then
- if not self.discouple or not self.discouple.object:get_yaw() then
- atprint(self.id,"trying to spawn discouple")
- local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})
- local object=minetest.add_entity(dcpl_pos, "advtrains:discouple")
- if object then
- local le=object:get_luaentity()
- le.wagon=self
- --box is hidden when attached, so unuseful.
- --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
- self.discouple=le
- end
- end
- else
- if self.discouple and self.discouple.object:get_yaw() then
- self.discouple.object:remove()
- atprint(self.id," removing discouple")
+ if train.velocity==0 and is_in_loaded_area and data.pos_in_trainparts and data.pos_in_trainparts>1 then
+ if not self.discouple or not self.discouple.object:get_yaw() then
+ atprint(self.id,"trying to spawn discouple")
+ local dcpl_pos = vector.add(pos, {y=0, x=-math.sin(yaw)*self.wagon_span, z=math.cos(yaw)*self.wagon_span})
+ local object=minetest.add_entity(dcpl_pos, "advtrains:discouple")
+ if object then
+ local le=object:get_luaentity()
+ le.wagon=self
+ --box is hidden when attached, so unuseful.
+ --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
+ self.discouple=le
end
end
+ else
+ if self.discouple and self.discouple.object:get_yaw() then
+ self.discouple.object:remove()
+ atprint(self.id," removing discouple")
+ end
+ end
+
+ -- object yaw (corrected by flipstate)
+ local oyaw = yaw
+ if data.wagon_flipped then
+ oyaw = yaw + math.pi
end
--FIX: use index of the wagon, not of the train.
@@ -500,10 +529,6 @@ function wagon:on_step(dtime)
local velocityvec = vector.multiply(vdir, velocity)
local accelerationvec = vector.multiply(vdir, acceleration)
- if data.wagon_flipped then
- yaw=yaw+math.pi
- end
-
-- this timer runs off every 2 seconds.
self.updatepct_timer=(self.updatepct_timer or 0)-dtime
local updatepct_timer_elapsed = self.updatepct_timer<=0
@@ -540,19 +565,19 @@ function wagon:on_step(dtime)
or not vector.equals(velocityvec, self.old_velocity_vector)
or not self.old_acceleration_vector
or not vector.equals(accelerationvec, self.old_acceleration_vector)
- or self.old_yaw~=yaw
+ or self.old_yaw~=oyaw
or updatepct_timer_elapsed then--only send update packet if something changed
self.object:set_pos(pos)
self.object:set_velocity(velocityvec)
self.object:set_acceleration(accelerationvec)
- if #self.seats > 0 and self.old_yaw ~= yaw then
+ if #self.seats > 0 and self.old_yaw ~= oyaw then
if not self.player_yaw then
self.player_yaw = {}
end
if not self.old_yaw then
- self.old_yaw=yaw
+ self.old_yaw=oyaw
end
for _,name in pairs(data.seatp) do
local p = minetest.get_player_by_name(name)
@@ -562,11 +587,11 @@ function wagon:on_step(dtime)
self.player_yaw[name] = p:get_look_horizontal()-self.old_yaw
end
-- set player looking direction using calculated offset
- p:set_look_horizontal((self.player_yaw[name] or 0)+yaw)
+ p:set_look_horizontal((self.player_yaw[name] or 0)+oyaw)
end
end
self.turning = true
- elseif self.old_yaw == yaw then
+ elseif self.old_yaw == oyaw then
-- train is no longer turning
self.turning = false
end
@@ -576,9 +601,9 @@ function wagon:on_step(dtime)
if data.wagon_flipped then
pitch = -pitch
end
- self.object:set_rotation({x=pitch, y=yaw, z=0})
+ self.object:set_rotation({x=pitch, y=oyaw, z=0})
else
- self.object:set_yaw(yaw)
+ self.object:set_yaw(oyaw)
end
if self.update_animation then
@@ -597,7 +622,7 @@ function wagon:on_step(dtime)
self.old_velocity_vector=velocityvec
self.old_velocity = train.velocity
self.old_acceleration_vector=accelerationvec
- self.old_yaw=yaw
+ self.old_yaw=oyaw
atprintbm("wagon step", t)
end
@@ -1240,70 +1265,6 @@ function wagon:reattach_all()
end
end
-local function check_twagon_owner(train, b_first, pname)
- local wtp = b_first and 1 or #train.trainparts
- local wid = train.trainparts[wtp]
- local wdata = advtrains.wagons[wid]
- if wdata then
- return advtrains.check_driving_couple_protection(pname, wdata.owner, wdata.whitelist)
- end
- return false
-end
-
-function advtrains.safe_couple_trains(id1, id2, t1f, t2f, pname, try_run,v1,v2)
-
- if pname and not minetest.check_player_privs(pname, "train_operator") then
- minetest.chat_send_player(pname, "Missing train_operator privilege")
- return false
- end
-
- local train1=advtrains.trains[id1]
- local train2=advtrains.trains[id2]
-
- if not advtrains.train_ensure_init(id1, train1)
- or not advtrains.train_ensure_init(id2, train2) then
- return false
- end
- local wck_t1, wck_t2
- if pname then
- wck_t1 = check_twagon_owner(train1, t1f, pname)
- wck_t2 = check_twagon_owner(train2, t2f, pname)
- end
- if (wck_t1 or wck_t2) or not pname then
- if not v1 then
- v1 = 0
- end
- if not v2 then
- v2 = 0
- end
- if try_run then
- return true
- end
- if t1f then
- if t2f then
- v1 = -v1
- advtrains.invert_train(id1)
- advtrains.do_connect_trains(id1, id2, v1+v2)
- else
- advtrains.do_connect_trains(id2, id1, v1+v2)
- end
- else
- if t2f then
- advtrains.do_connect_trains(id1, id2, v1+v2)
- else
- v2 = -v2
- advtrains.invert_train(id2)
- advtrains.do_connect_trains(id1, id2, v1+v2)
- end
- end
- return true
- else
- minetest.chat_send_player(pname, "You must be authorized for at least one wagon.")
- return false
- end
-end
-
-
function advtrains.safe_decouple_wagon(w_id, pname, try_run)
if not minetest.check_player_privs(pname, "train_operator") then
minetest.chat_send_player(pname, "Missing train_operator privilege")
@@ -1380,14 +1341,23 @@ function advtrains.register_wagon(sysname_p, prototype, desc, inv_img, nincreati
minetest.register_entity(":"..sysname,prototype)
advtrains.wagon_prototypes[sysname] = prototype
+ --group classification to make recipe searching easier
+ local wagon_groups = { not_in_creative_inventory = nincreative and 1 or 0}
+ if prototype.is_locomotive then wagon_groups['at_loco'] = 1 end
+ if prototype.seat_groups then
+ if prototype.seat_groups.dstand then wagon_groups['at_control'] = 1 end
+ if prototype.seat_groups.pass then wagon_groups['at_pax'] = 1 end
+ end
+ if prototype.has_inventory then wagon_groups['at_freight'] = 1 end
+
minetest.register_craftitem(":"..sysname, {
description = desc,
inventory_image = inv_img,
wield_image = inv_img,
stack_max = 1,
- groups = { not_in_creative_inventory = nincreative and 1 or 0},
-
+ groups = wagon_groups,
+
on_place = function(itemstack, placer, pointed_thing)
if not pointed_thing.type == "node" then
return