Multiple major changes:
authororwell96 <mono96.mml@gmail.com>
Fri, 27 Jan 2017 22:43:01 +0000 (23:43 +0100)
committerorwell96 <mono96.mml@gmail.com>
Sat, 28 Jan 2017 15:46:24 +0000 (16:46 +0100)
- implement wagon properties and seat group access check
- fix a server warning about unassigned variable
- refill advtrains.detector.on_node every step
- reorder train step function(s):
  - fixed bug that some atc rails were not recognized
  - saving some extra calculations
  - integrate path prediction directly to step functions and separate it
  (also see comment directly above train_step_a())
- add couple lock feature (can't couple or discouple from wagon with locked couples)
- ...

advtrains/advtrains/atc.lua
advtrains/advtrains/couple.lua
advtrains/advtrains/tracks.lua
advtrains/advtrains/trainlogic.lua
advtrains/advtrains/wagons.lua

index cd9476c..dcddc65 100644 (file)
@@ -198,7 +198,7 @@ function atc.execute_atc_command(id, train)
                return
        end
        --conditional statement?
-       local is_cond, cond_applies
+       local is_cond, cond_applies, compare
        local cond, rest=string.match(command, "^I([%+%-])(.+)$")
        if cond then
                is_cond=true
index b50eec9..7a1e07a 100644 (file)
@@ -31,7 +31,7 @@ minetest.register_entity("advtrains:discouple", {
        on_punch=function(self, player)
                --only if player owns at least one wagon next to this
                local own=player:get_player_name()
-               if self.wagon.owner and self.wagon.owner==own then
+               if self.wagon.owner and self.wagon.owner==own and not self.wagon.lock_couples then
                        local train=advtrains.trains[self.wagon.train_id]
                        local nextwgn_id=train.trainparts[self.wagon.pos_in_trainparts-1]
                        for aoi, le in pairs(minetest.luaentities) do
@@ -46,6 +46,8 @@ minetest.register_entity("advtrains:discouple", {
                        end
                        advtrains.split_train_at_wagon(self.wagon)--found in trainlogic.lua
                        self.object:remove()
+               elseif self.wagon.lock_couples then
+                       minetest.chat_send_player(own, "Couples of one of the wagons are locked, can't discouple!")
                else
                        minetest.chat_send_player(own, attrans("You need to own at least one neighboring wagon to destroy this couple."))
                end
@@ -99,24 +101,24 @@ minetest.register_entity("advtrains:couple", {
                end
        end,
        get_staticdata=function(self) return "COUPLE" end,
-       on_rightclick=function(self)
+       on_rightclick=function(self, clicker)
                if not self.train_id_1 or not self.train_id_2 then return end
                
                local id1, id2=self.train_id_1, self.train_id_2
                
                if self.train1_is_backpos and not self.train2_is_backpos then
-                       advtrains.do_connect_trains(id1, id2)
+                       advtrains.do_connect_trains(id1, id2, clicker)
                        --case 2 (second train is front)
                elseif self.train2_is_backpos and not self.train1_is_backpos then
-                       advtrains.do_connect_trains(id2, id1)
+                       advtrains.do_connect_trains(id2, id1, clicker)
                        --case 3 
                elseif self.train1_is_backpos and self.train2_is_backpos then
                        advtrains.invert_train(id2)
-                       advtrains.do_connect_trains(id1, id2)
+                       advtrains.do_connect_trains(id1, id2, clicker)
                        --case 4 
                elseif not self.train1_is_backpos and not self.train2_is_backpos then
                        advtrains.invert_train(id1)
-                       advtrains.do_connect_trains(id1, id2)
+                       advtrains.do_connect_trains(id1, id2, clicker)
                end
                self.object:remove()
        end,
index 3f170f5..a44acb3 100644 (file)
@@ -386,76 +386,27 @@ end
 \r
 advtrains.detector = {}\r
 advtrains.detector.on_node = {}\r
-advtrains.detector.on_node_restore = {}\r
---set if paths were invalidated before. tells trainlogic.lua to call advtrains.detector.finalize_restore()\r
-advtrains.detector.clean_step_before = false\r
 \r
---when train enters a node, call this\r
---The entry already being contained in advtrains.detector.on_node_restore will not trigger an on_train_enter event on the node.  (when path is reset, this is saved).\r
 function advtrains.detector.enter_node(pos, train_id)\r
-       local pts = minetest.pos_to_string(advtrains.round_vector_floor_y(pos))\r
-       --atprint("enterNode "..pts.." "..sid(train_id))\r
-       if advtrains.detector.on_node[pts] then\r
-               if advtrains.trains[advtrains.detector.on_node[pts]] then\r
-                       --atprint(""..pts.." already occupied")\r
-                       return false\r
-               else\r
-                       advtrains.detector.leave_node(pos, advtrains.detector.on_node[pts])\r
-               end\r
-       end\r
+       local ppos=advtrains.round_vector_floor_y(pos)\r
+       local pts=minetest.pos_to_string(ppos)\r
        advtrains.detector.on_node[pts]=train_id\r
-       if advtrains.detector.on_node_restore[pts]==train_id then\r
-               advtrains.detector.on_node_restore[pts]=nil\r
-       else\r
-               advtrains.detector.call_enter_callback(advtrains.round_vector_floor_y(pos), train_id)\r
-       end\r
-       return true\r
+       advtrains.detector.call_enter_callback(ppos, train_id)\r
 end\r
 function advtrains.detector.leave_node(pos, train_id)\r
-       local pts = minetest.pos_to_string(advtrains.round_vector_floor_y(pos))\r
-       --atprint("leaveNode "..pts.." "..sid(train_id))\r
-       if not advtrains.detector.on_node[pts] then\r
-               --atprint(""..pts.." leave: nothing here")\r
-               return false\r
-       end\r
-       if advtrains.detector.on_node[pts]==train_id then\r
-               advtrains.detector.call_leave_callback(advtrains.round_vector_floor_y(pos), train_id)\r
-               advtrains.detector.on_node[pts]=nil\r
-       else\r
-               if advtrains.trains[advtrains.detector.on_node[pts]] then\r
-                       --atprint(""..pts.." occupied by another train")\r
-                       return false\r
-               else\r
-                       advtrains.detector.leave_node(pos, advtrains.detector.on_node[pts])\r
-                       return false\r
-               end\r
-       end\r
-       return true\r
+       local ppos=advtrains.round_vector_floor_y(pos)\r
+       local pts=minetest.pos_to_string(ppos)\r
+       advtrains.detector.on_node[pts]=nil\r
+       advtrains.detector.call_leave_callback(ppos, train_id)\r
 end\r
---called immediately before invalidating paths\r
-function advtrains.detector.setup_restore()\r
-       --atprint("setup_restore")\r
-       -- don't execute if it already has been called. For some reason it gets called twice...\r
-       if advtrains.detector.clean_step_before then\r
-               return\r
-       end\r
-       advtrains.detector.on_node_restore={}\r
-       for k, v in pairs(advtrains.detector.on_node) do\r
-               advtrains.detector.on_node_restore[k]=v\r
-       end\r
-       advtrains.detector.on_node = {}\r
-       advtrains.detector.clean_step_before = true\r
-end\r
---called one step after invalidating paths, when all trains have restored their path and called enter_node for their contents.\r
-function advtrains.detector.finalize_restore()\r
-       --atprint("finalize_restore")\r
-       for pts, train_id in pairs(advtrains.detector.on_node_restore) do\r
-               --atprint("called leave callback "..pts.." "..train_id)\r
-               advtrains.detector.call_leave_callback(minetest.string_to_pos(pts), train_id)\r
-       end\r
-       advtrains.detector.on_node_restore = {}\r
-       advtrains.detector.clean_step_before = false\r
+function advtrains.detector.stay_node(pos, train_id)\r
+       local ppos=advtrains.round_vector_floor_y(pos)\r
+       local pts=minetest.pos_to_string(ppos)\r
+       advtrains.detector.on_node[pts]=train_id\r
 end\r
+\r
+\r
+\r
 function advtrains.detector.call_enter_callback(pos, train_id)\r
        --atprint("instructed to call enter calback")\r
 \r
index d5658a0..b1ee31a 100644 (file)
@@ -50,22 +50,47 @@ minetest.register_globalstep(function(dtime)
                atprintbm("saving", t)
        end
        --regular train step
+       -- do in two steps: 
+       --  a: predict path and add all nodes to the advtrains.detector.on_node table
+       --  b: check for collisions based on these data
+       --  (and more)
        local t=os.clock()
+       advtrains.detector.on_node={}
        for k,v in pairs(advtrains.trains) do
-               advtrains.train_step(k, v, dtime)
+               advtrains.train_step_a(k, v, dtime)
        end
-       
-       --see tracks.lua
-       if advtrains.detector.clean_step_before then
-               advtrains.detector.finalize_restore()
+       for k,v in pairs(advtrains.trains) do
+               advtrains.train_step_b(k, v, dtime)
        end
        
        atprintbm("trainsteps", t)
        endstep()
 end)
 
-function advtrains.train_step(id, train, dtime)
-       --Legacy: set drives_on and max_speed
+--[[
+train step structure:
+
+
+- legacy stuff
+- preparing the initial path and creating index
+- setting node coverage old indices
+- handle velocity influences:
+       - off-track
+       - atc
+       - player controls
+       - environment collision
+- update index = move
+- create path
+- update node coverage
+- do less important stuff such as checking trainpartload or removing
+
+-- break --
+- handle train collisions
+
+]]
+
+function advtrains.train_step_a(id, train, dtime)
+       --- 1. LEGACY STUFF ---
        if not train.drives_on or not train.max_speed then
                advtrains.update_trainpart_properties(id)
        end
@@ -76,143 +101,73 @@ function advtrains.train_step(id, train, dtime)
        if not train.movedir or (train.movedir~=1 and train.movedir~=-1) then
                train.movedir=1
        end
-       --very unimportant thing: check if couple is here
-       if train.couple_eid_front and (not minetest.luaentities[train.couple_eid_front] or not minetest.luaentities[train.couple_eid_front].is_couple) then train.couple_eid_front=nil end
-       if train.couple_eid_back and (not minetest.luaentities[train.couple_eid_back] or not minetest.luaentities[train.couple_eid_back].is_couple) then train.couple_eid_back=nil end
-       
-       --skip certain things (esp. collision) when not moving
-       local train_moves=(train.velocity~=0)
-       
-       --if not train.last_pos then advtrains.trains[id]=nil return end
-       
-       if not advtrains.pathpredict(id, train) then 
-               atprint("pathpredict failed(returned false)")
-               train.velocity=0
-               train.tarvelocity=0
-               return
-       end
-       
-       local path=advtrains.get_or_create_path(id, train)
-       if not path then
-               train.velocity=0
-               train.tarvelocity=0
-               atprint("train has no path for whatever reason")
-               return 
-       end
-       
-       local train_end_index=advtrains.get_train_end_index(train)
-       --apply off-track handling:
-       local front_off_track=train.max_index_on_track and train.index>train.max_index_on_track
-       local back_off_track=train.min_index_on_track and train_end_index<train.min_index_on_track
-       if front_off_track and back_off_track then--allow movement in both directions
-               if train.tarvelocity>1 then train.tarvelocity=1 end
-       elseif front_off_track then--allow movement only backward
-               if train.movedir==1 and train.tarvelocity>0 then train.tarvelocity=0 end
-               if train.movedir==-1 and train.tarvelocity>1 then train.tarvelocity=1 end
-       elseif back_off_track then--allow movement only forward
-               if train.movedir==-1 and train.tarvelocity>0 then train.tarvelocity=0 end
-               if train.movedir==1 and train.tarvelocity>1 then train.tarvelocity=1 end
-       end
-       
-       --update advtrains.detector
-       if not train.detector_old_index then
-               train.detector_old_index = math.floor(train_end_index)
-               train.detector_old_end_index = math.floor(train_end_index)
-       end
-       local ifo, ifn, ibo, ibn = train.detector_old_index, math.floor(train.index), train.detector_old_end_index, math.floor(train_end_index)
-       if ifn>ifo then
-               for i=ifo, ifn do
-                       if path[i] then
-                               advtrains.detector.enter_node(path[i], id)
-                       end
+       --- 2. prepare initial path and index if needed ---
+       if not train.index then train.index=0 end
+       if not train.path or #train.path<2 then
+               if not train.last_pos then
+                       --no chance to recover
+                       atprint("train hasn't saved last-pos, removing train.")
+                       advtrains.trains[id]=nil
+                       return false
                end
-       elseif ifn<ifo then
-               for i=ifn, ifo do
-                       if path[i] then
-                               advtrains.detector.leave_node(path[i], id)
-                       end
+               
+               local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos), train.drives_on)
+               
+               if node_ok==nil then
+                       --block not loaded, do nothing
+                       atprint("last_pos not available")
+                       return nil
+               elseif node_ok==false then
+                       atprint("no track here, (fail) removing train.")
+                       advtrains.trains[id]=nil
+                       return false
                end
-       end
-       if ibn<ibo then
-               for i=ibn, ibn do
-                       if path[i] then
-                               advtrains.detector.enter_node(path[i], id)
-                       end
+               
+               if not train.last_pos_prev then
+                       --no chance to recover
+                       atprint("train hasn't saved last-pos_prev, removing train.")
+                       advtrains.trains[id]=nil
+                       return false
                end
-       elseif ibn>ibo then
-               for i=ibo, ibn do
-                       if path[i] then
-                               advtrains.detector.leave_node(path[i], id)
-                       end
+               
+               local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos_prev), train.drives_on)
+               
+               if prevnode_ok==nil then
+                       --block not loaded, do nothing
+                       atprint("prev not available")
+                       return nil
+               elseif prevnode_ok==false then
+                       atprint("no track at prev, (fail) removing train.")
+                       advtrains.trains[id]=nil
+                       return false
                end
+               
+               train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0)
+               --restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5
+               --savedpos_off_track_index_offset is set if train went off track. see below.
+               train.path={}
+               train.path_dist={}
+               train.path[0]=train.last_pos
+               train.path[-1]=train.last_pos_prev
+               train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev)
+               train.path_extent_min=-1
+               train.path_extent_max=0
        end
-       train.detector_old_index = math.floor(train.index)
-       train.detector_old_end_index = math.floor(train_end_index)
        
-       --remove?
-       if #train.trainparts==0 then
-               atprint("[train "..sid(id).."] has empty trainparts, removing.")
-               advtrains.detector.leave_node(path[train.detector_old_index], id)
-               advtrains.trains[id]=nil
-               return
+       --- 2a. set train.end_index which is required in different places, IF IT IS NOT SET YET by STMT afterwards. ---
+       ---   table entry to avoid triple recalculation ---
+       if not train.end_index then
+               train.end_index=advtrains.get_train_end_index(train)
        end
        
-       if train_moves then
-               --check for collisions by finding objects
-               
-               --heh, new collision again.
-               --this time, based on NODES and the advtrains.detector.on_node table.
-               local collpos
-               local coll_grace=1
-               if train.movedir==1 then
-                       collpos=advtrains.get_real_index_position(path, train.index-coll_grace)
-               else
-                       collpos=advtrains.get_real_index_position(path, train_end_index+coll_grace)
-               end
-               if collpos then
-                       local rcollpos=advtrains.round_vector_floor_y(collpos)
-                       for x=-1,1 do
-                               for z=-1,1 do
-                                       local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
-                                       local testpts=minetest.pos_to_string(testpos)
-                                       if advtrains.detector.on_node[testpts] and advtrains.detector.on_node[testpts]~=id then
-                                               if advtrains.trains[advtrains.detector.on_node[testpts]] then
-                                                       --collides
-                                                       advtrains.spawn_couple_on_collide(id, testpos, advtrains.detector.on_node[testpts], train.movedir==-1)
-                                                       
-                                                       train.recently_collided_with_env=true
-                                                       train.velocity=0.5*train.velocity
-                                                       train.movedir=train.movedir*-1
-                                                       train.tarvelocity=0
-                                               else
-                                                       --unexistant train left in this place
-                                                       advtrains.detector.on_node[testpts]=nil
-                                               end
-                                       end
-                               end
-                       end
-               end
-       end
-       --check for any trainpart entities if they have been unloaded. do this only if train is near a player, to not spawn entities into unloaded areas
-       --todo function will be taken by update_trainpart_properties
-       train.check_trainpartload=(train.check_trainpartload or 0)-dtime
-       local node_range=(math.max((minetest.setting_get("active_block_range") or 0),1)*16)
-       if train.check_trainpartload<=0 then
-               local ori_pos=advtrains.get_real_index_position(path, train.index) --not much to calculate
-               --atprint("[train "..id.."] at "..minetest.pos_to_string(vector.round(ori_pos)))
-               
-               local should_check=false
-               for _,p in ipairs(minetest.get_connected_players()) do
-                       should_check=should_check or ((vector.distance(ori_pos, p:getpos())<node_range))
-               end
-               if should_check then
-                       advtrains.update_trainpart_properties(id)
-               end
-               train.check_trainpartload=2
-       end
+       --- 2b. set node coverage old indices ---
+       
+       train.detector_old_index = math.floor(train.index)
+       train.detector_old_end_index = math.floor(train.end_index)
        
+       --- 3. handle velocity influences ---
+       local train_moves=(train.velocity~=0)
        
-       --handle collided_with_env
        if train.recently_collided_with_env then
                train.tarvelocity=0
                if not train_moves then
@@ -223,6 +178,20 @@ function advtrains.train_step(id, train, dtime)
                train.tarvelocity=0
        end
        
+       --apply off-track handling:
+       --won't take any effect immediately after path reset because index_on_track not set, but that's not severe.
+       local front_off_track=train.max_index_on_track and train.index>train.max_index_on_track
+       local back_off_track=train.min_index_on_track and train.end_index<train.min_index_on_track
+       if front_off_track and back_off_track then--allow movement in both directions
+               if train.tarvelocity>1 then train.tarvelocity=1 end
+       elseif front_off_track then--allow movement only backward
+               if train.movedir==1 and train.tarvelocity>0 then train.tarvelocity=0 end
+               if train.movedir==-1 and train.tarvelocity>1 then train.tarvelocity=1 end
+       elseif back_off_track then--allow movement only forward
+               if train.movedir==-1 and train.tarvelocity>0 then train.tarvelocity=0 end
+               if train.movedir==1 and train.tarvelocity>1 then train.tarvelocity=1 end
+       end
+       
        --interpret ATC command
        if train.atc_brake_target and train.atc_brake_target>=train.velocity then
                train.atc_brake_target=nil
@@ -244,7 +213,8 @@ function advtrains.train_step(id, train, dtime)
        if train.brake and (math.ceil(train.velocity)-1)<train.tarvelocity then
                train.tarvelocity=math.max((math.ceil(train.velocity)-1), 0)
        end
-       --apply tarvel(but with physics in mind!)
+       
+       --- 3a. actually calculate new velocity ---
        if train.velocity~=train.tarvelocity then
                local applydiff=0
                local mass=#train.trainparts
@@ -272,125 +242,20 @@ function advtrains.train_step(id, train, dtime)
                train.last_accel=0
        end
        
-       --move
-       --TODO 3,5 + 0.7
-       train.index=train.index and train.index+(((train.velocity*train.movedir)/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
+       --- 4. move train ---
        
-end
-
-
---structure of train table:
---[[
-trains={
-       [train_id]={
-               trainparts={
-                       [n]=wagon_id
-               }
-               path={path}
-               velocity
-               tarvelocity
-               index
-               trainlen
-               path_inv_level
-               last_pos       |
-               last_dir       | for pathpredicting.
-       }
-}
---a wagon itself has the following properties:
-wagon={
-       unique_id
-       train_id
-       pos_in_train (is index difference, including train_span stuff)
-       pos_in_trainparts (is index in trainparts tabel of trains)
-}
-inherited by metatable:
-wagon_proto={
-       wagon_span
-}
-]]
-
---returns new id
-function advtrains.create_new_train_at(pos, pos_prev)
-       local newtrain_id=os.time()..os.clock()
-       while advtrains.trains[newtrain_id] do newtrain_id=os.time()..os.clock() end--ensure uniqueness(will be unneccessary)
-       
-       advtrains.trains[newtrain_id]={}
-       advtrains.trains[newtrain_id].last_pos=pos
-       advtrains.trains[newtrain_id].last_pos_prev=pos_prev
-       advtrains.trains[newtrain_id].tarvelocity=0
-       advtrains.trains[newtrain_id].velocity=0
-       advtrains.trains[newtrain_id].trainparts={}
-       return newtrain_id
-end
-
---returns false on failure. handle this case!
-function advtrains.pathpredict(id, train)
+       train.index=train.index and train.index+(((train.velocity*train.movedir)/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
        
-       --atprint("pos ",x,y,z)
-       --::rerun::
-       if not train.index then train.index=0 end
-       if not train.path or #train.path<2 then
-               if not train.last_pos then
-                       --no chance to recover
-                       atprint("train hasn't saved last-pos, removing train.")
-                       advtrains.trains[id]=nil
-                       return false
-               end
-               
-               local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos), train.drives_on)
-               
-               if node_ok==nil then
-                       --block not loaded, do nothing
-                       atprint("last_pos not available")
-                       return nil
-               elseif node_ok==false then
-                       atprint("no track here, (fail) removing train.")
-                       advtrains.trains[id]=nil
-                       return false
-               end
-               
-               if not train.last_pos_prev then
-                       --no chance to recover
-                       atprint("train hasn't saved last-pos_prev, removing train.")
-                       advtrains.trains[id]=nil
-                       return false
-               end
-               
-               local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos_prev), train.drives_on)
-               
-               if prevnode_ok==nil then
-                       --block not loaded, do nothing
-                       atprint("prev not available")
-                       return nil
-               elseif prevnode_ok==false then
-                       atprint("no track at prev, (fail) removing train.")
-                       advtrains.trains[id]=nil
-                       return false
-               end
-               
-               train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0)
-               --restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5
-               --savedpos_off_track_index_offset is set if train went off track. see below.
-               train.path={}
-               train.path_dist={}
-               train.path[0]=train.last_pos
-               train.path[-1]=train.last_pos_prev
-               train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev)
-       end
+       --- 4a. update train.end_index to the new position ---
+       train.end_index=advtrains.get_train_end_index(train)
        
-       local pregen_front=2
-       local pregen_back=2
-       if train.velocity>0 then
-               if train.movedir>0 then
-                       pregen_front=2+math.ceil(train.velocity*0.15) --assumes server step of 0.1 seconds, +50% tolerance
-               else
-                       pregen_back=2+math.ceil(train.velocity*0.15)
-               end
-       end
+       --- 5. extend path as necessary ---
        
+       local gen_front=math.max(train.index, train.detector_old_index) + 2
+       local gen_back=math.min(train.end_index, train.detector_old_end_index) - 2
        
-       local maxn=train.max_index_on_track or 0
-       while (maxn-train.index) < pregen_front do--pregenerate
+       local maxn=train.path_extent_max or 0
+       while maxn < gen_front do--pregenerate
                --atprint("maxn conway for ",maxn,minetest.pos_to_string(path[maxn]),maxn-1,minetest.pos_to_string(path[maxn-1]))
                local conway=advtrains.conway(train.path[maxn], train.path[maxn-1], train.drives_on)
                if conway then
@@ -405,9 +270,10 @@ function advtrains.pathpredict(id, train)
                train.path_dist[maxn]=vector.distance(train.path[maxn+1], train.path[maxn])
                maxn=advtrains.maxN(train.path)
        end
+       train.path_extent_max=maxn
        
-       local minn=train.min_index_on_track or -1
-       while (train.index-minn) < (train.trainlen or 0) + pregen_back do --post_generate. has to be at least trainlen. (we let go of the exact calculation here since this would be unuseful here)
+       local minn=train.path_extent_min or -1
+       while minn > gen_back do
                --atprint("minn conway for ",minn,minetest.pos_to_string(path[minn]),minn+1,minetest.pos_to_string(path[minn+1]))
                local conway=advtrains.conway(train.path[minn], train.path[minn+1], train.drives_on)
                if conway then
@@ -422,7 +288,8 @@ function advtrains.pathpredict(id, train)
                train.path_dist[minn-1]=vector.distance(train.path[minn], train.path[minn-1])
                minn=advtrains.minN(train.path)
        end
-       if not train.min_index_on_track then train.min_index_on_track=0 end
+       train.path_extent_min=minn
+       if not train.min_index_on_track then train.min_index_on_track=-1 end
        if not train.max_index_on_track then train.max_index_on_track=0 end
        
        --make pos/yaw available for possible recover calls
@@ -441,15 +308,160 @@ function advtrains.pathpredict(id, train)
                train.last_pos=train.path[math.floor(train.index+0.5)]
                train.last_pos_prev=train.path[math.floor(train.index-0.5)]
        end
-       return train.path
+       
+       --- 6. update node coverage ---
+       
+       -- when paths get cleared, the old indices set above will be up-to-date and represent the state in which the last run of this code was made
+       local ifo, ifn, ibo, ibn = train.detector_old_index, math.floor(train.index), train.detector_old_end_index, math.floor(train.end_index)
+       local path=train.path
+       
+       for i=ibn, ifn do
+               if path[i] then
+                       advtrains.detector.stay_node(path[i], id)
+               end
+       end
+       
+       if ifn>ifo then
+               for i=ifo+1, ifn do
+                       if path[i] then
+                               advtrains.detector.enter_node(path[i], id)
+                       end
+               end
+       elseif ifn<ifo then
+               for i=ifn, ifo-1 do
+                       if path[i] then
+                               advtrains.detector.leave_node(path[i], id)
+                       end
+               end
+       end
+       if ibn<ibo then
+               for i=ibn, ibo-1 do
+                       if path[i] then
+                               advtrains.detector.enter_node(path[i], id)
+                       end
+               end
+       elseif ibn>ibo then
+               for i=ibo+1, ibn do
+                       if path[i] then
+                               advtrains.detector.leave_node(path[i], id)
+                       end
+               end
+       end
+       
+       --- 7. do less important stuff ---
+       --check for any trainpart entities if they have been unloaded. do this only if train is near a player, to not spawn entities into unloaded areas
+       
+       train.check_trainpartload=(train.check_trainpartload or 0)-dtime
+       local node_range=(math.max((minetest.setting_get("active_block_range") or 0),1)*16)
+       if train.check_trainpartload<=0 then
+               local ori_pos=advtrains.get_real_index_position(path, train.index) --not much to calculate
+               --atprint("[train "..id.."] at "..minetest.pos_to_string(vector.round(ori_pos)))
+               
+               local should_check=false
+               for _,p in ipairs(minetest.get_connected_players()) do
+                       should_check=should_check or ((vector.distance(ori_pos, p:getpos())<node_range))
+               end
+               if should_check then
+                       advtrains.update_trainpart_properties(id)
+               end
+               train.check_trainpartload=2
+       end
+       --remove?
+       if #train.trainparts==0 then
+               atprint("[train "..sid(id).."] has empty trainparts, removing.")
+               advtrains.detector.leave_node(path[train.detector_old_index], id)
+               advtrains.trains[id]=nil
+               return
+       end
 end
-function advtrains.get_train_end_index(train)
-       return advtrains.get_real_path_index(train, train.trainlen or 2)--this function can be found inside wagons.lua since it's more related to wagons. we just set trainlen as pos_in_train
+
+function advtrains.train_step_b(id, train, dtime)
+
+       --- 8. check for collisions with other trains ---
+       
+       local train_moves=(train.velocity~=0)
+       
+       if train_moves then
+               
+               --heh, new collision again.
+               --this time, based on NODES and the advtrains.detector.on_node table.
+               local collpos
+               local coll_grace=1
+               if train.movedir==1 then
+                       collpos=advtrains.get_real_index_position(train.path, train.index-coll_grace)
+               else
+                       collpos=advtrains.get_real_index_position(train.path, train.end_index+coll_grace)
+               end
+               if collpos then
+                       local rcollpos=advtrains.round_vector_floor_y(collpos)
+                       for x=-1,1 do
+                               for z=-1,1 do
+                                       local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
+                                       local testpts=minetest.pos_to_string(testpos)
+                                       if advtrains.detector.on_node[testpts] and advtrains.detector.on_node[testpts]~=id then
+                                               --collides
+                                               advtrains.spawn_couple_on_collide(id, testpos, advtrains.detector.on_node[testpts], train.movedir==-1)
+                                               
+                                               train.recently_collided_with_env=true
+                                               train.velocity=0.5*train.velocity
+                                               train.movedir=train.movedir*-1
+                                               train.tarvelocity=0
+                                       end
+                               end
+                       end
+               end
+       end
+       
 end
 
-function advtrains.get_or_create_path(id, train)
-       if not train.path then return advtrains.pathpredict(id, train) end
-       return train.path
+
+--structure of train table:
+--[[
+trains={
+       [train_id]={
+               trainparts={
+                       [n]=wagon_id
+               }
+               path={path}
+               velocity
+               tarvelocity
+               index
+               trainlen
+               path_inv_level
+               last_pos       |
+               last_dir       | for pathpredicting.
+       }
+}
+--a wagon itself has the following properties:
+wagon={
+       unique_id
+       train_id
+       pos_in_train (is index difference, including train_span stuff)
+       pos_in_trainparts (is index in trainparts tabel of trains)
+}
+inherited by metatable:
+wagon_proto={
+       wagon_span
+}
+]]
+
+--returns new id
+function advtrains.create_new_train_at(pos, pos_prev)
+       local newtrain_id=os.time()..os.clock()
+       while advtrains.trains[newtrain_id] do newtrain_id=os.time()..os.clock() end--ensure uniqueness(will be unneccessary)
+       
+       advtrains.trains[newtrain_id]={}
+       advtrains.trains[newtrain_id].last_pos=pos
+       advtrains.trains[newtrain_id].last_pos_prev=pos_prev
+       advtrains.trains[newtrain_id].tarvelocity=0
+       advtrains.trains[newtrain_id].velocity=0
+       advtrains.trains[newtrain_id].trainparts={}
+       return newtrain_id
+end
+
+
+function advtrains.get_train_end_index(train)
+       return advtrains.get_real_path_index(train, train.trainlen or 2)--this function can be found inside wagons.lua since it's more related to wagons. we just set trainlen as pos_in_train
 end
 
 function advtrains.add_wagon_to_train(wagon, train_id, index)
@@ -508,7 +520,6 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate)
                                wagon.wagon_flipped = not wagon.wagon_flipped
                        end
                        rel_pos=rel_pos+wagon.wagon_span
-                       any_loaded=true
                        
                        if wagon.drives_on then
                                for k,_ in pairs(train.drives_on) do
@@ -518,6 +529,13 @@ function advtrains.update_trainpart_properties(train_id, invert_flipstate)
                                end
                        end
                        train.max_speed=math.min(train.max_speed, wagon.max_speed)
+                       if i==1 then
+                               train.couple_lock_front=wagon.lock_couples
+                       end
+                       if i==#train.trainparts then
+                               train.couple_lock_back=wagon.lock_couples
+                       end
+                       
                else
                        atprint(w_id.." not loaded and no save available")
                        --what the hell...
@@ -531,9 +549,11 @@ end
 function advtrains.split_train_at_wagon(wagon)
        --get train
        local train=advtrains.trains[wagon.train_id]
+       if not train.path then return end
+       
        local real_pos_in_train=advtrains.get_real_path_index(train, wagon.pos_in_train)
-       local pos_for_new_train=advtrains.get_or_create_path(wagon.train_id, train)[math.floor(real_pos_in_train+wagon.wagon_span)]
-       local pos_for_new_train_prev=advtrains.get_or_create_path(wagon.train_id, train)[math.floor(real_pos_in_train-1+wagon.wagon_span)]
+       local pos_for_new_train=train.path[math.floor(real_pos_in_train+wagon.wagon_span)]
+       local pos_for_new_train_prev=train.path[math.floor(real_pos_in_train-1+wagon.wagon_span)]
        
        --before doing anything, check if both are rails. else do not allow
        if not pos_for_new_train then
@@ -683,15 +703,25 @@ end
 --pos1 and pos2 are just needed to form a median.
 
 
-function advtrains.do_connect_trains(first_id, second_id)
-       local first_wagoncnt=#advtrains.trains[first_id].trainparts
-       local second_wagoncnt=#advtrains.trains[second_id].trainparts
+function advtrains.do_connect_trains(first_id, second_id, player)
+       local first, second=advtrains.trains[first_id], advtrains.trains[second_id]
+       
+       if first.couple_lock_back or second.couple_lock_front then
+               -- trains are ordered correctly!
+               if player then
+                       minetest.chat_send_player(player:get_player_name(), "Can't couple: couples locked!")
+               end
+               return
+       end
+       
+       local first_wagoncnt=#first.trainparts
+       local second_wagoncnt=#second.trainparts
        
-       for _,v in ipairs(advtrains.trains[second_id].trainparts) do
-               table.insert(advtrains.trains[first_id].trainparts, v)
+       for _,v in ipairs(second.trainparts) do
+               table.insert(first.trainparts, v)
        end
        --kick it like physics (with mass being #wagons)
-       local new_velocity=((advtrains.trains[first_id].velocity*first_wagoncnt)+(advtrains.trains[second_id].velocity*second_wagoncnt))/(first_wagoncnt+second_wagoncnt)
+       local new_velocity=((first.velocity*first_wagoncnt)+(second.velocity*second_wagoncnt))/(first_wagoncnt+second_wagoncnt)
        advtrains.trains[second_id]=nil
        advtrains.update_trainpart_properties(first_id)
        advtrains.trains[first_id].velocity=new_velocity
@@ -751,10 +781,12 @@ function advtrains.invalidate_all_paths()
                v.path=nil
                v.path_dist=nil
                v.index=nil
+               v.end_index=nil
                v.min_index_on_track=nil
                v.max_index_on_track=nil
+               v.path_extent_min=nil
+               v.path_extent_max=nil
                
-               advtrains.detector.setup_restore()
                v.detector_old_index=nil
                v.detector_old_end_index=nil
        end
index 331f857..728a5eb 100644 (file)
@@ -92,7 +92,6 @@ function wagon:init_from_wagon_save(uid)
        self.initialized=true\r
        minetest.after(1, function() self:reattach_all() end)\r
        atprint("init_from_wagon_save "..self.unique_id.." ("..self.train_id..")")\r
-       advtrains.update_trainpart_properties(self.train_id)\r
 end\r
 function wagon:init_shared()\r
        if self.has_inventory then\r
@@ -294,7 +293,7 @@ function wagon:on_step(dtime)
        end\r
        --DisCouple\r
        if self.pos_in_trainparts and self.pos_in_trainparts>1 then\r
-               if gp.velocity==0 then\r
+               if gp.velocity==0 and not self.lock_couples then\r
                        if not self.discouple or not self.discouple.object:getyaw() then\r
                                local object=minetest.add_entity(pos, "advtrains:discouple")\r
                                if object then\r
@@ -314,7 +313,7 @@ function wagon:on_step(dtime)
                end\r
        end\r
        --for path to be available. if not, skip step\r
-       if not advtrains.get_or_create_path(self.train_id, gp) then\r
+       if not gp.path then\r
                self.object:setvelocity({x=0, y=0, z=0})\r
                return\r
        end\r
@@ -593,6 +592,28 @@ function wagon:show_get_on_form(pname)
        end\r
        minetest.show_formspec(pname, "advtrains_geton_"..self.unique_id, form)\r
 end\r
+function wagon:show_wagon_properties(pname)\r
+       if not self.seat_groups then\r
+               return\r
+       end\r
+       if not self.seat_access then\r
+               self.seat_access={}\r
+       end\r
+       --[[\r
+       fields: seat access: empty: everyone\r
+       checkbox: lock couples\r
+       button: save\r
+       ]]\r
+       local form="size[5,"..(#self.seat_groups*1.5+5).."]"\r
+       local at=0\r
+       for sgr,sgrdef in pairs(self.seat_groups) do\r
+               form=form.."field[0.5,"..(0.5+at*1.5)..";4,1;sgr_"..sgr..";"..sgrdef.name..";"..(self.seat_access[sgr] or "").."]"\r
+               at=at+1\r
+       end\r
+       form=form.."checkbox[0,"..(at*1.5)..";lock_couples;Lock couples;"..(self.lock_couples and "true" or "false").."]"\r
+       form=form.."button_exit[0.5,"..(1+at*1.5)..";4,1;save;Save wagon properties]"\r
+       minetest.show_formspec(pname, "advtrains_prop_"..self.unique_id, form)\r
+end\r
 minetest.register_on_player_receive_fields(function(player, formname, fields)\r
        local uid=string.match(formname, "^advtrains_geton_(.+)$")\r
        if uid then\r
@@ -628,6 +649,27 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
                        end\r
                end\r
        end\r
+       uid=string.match(formname, "^advtrains_prop_(.+)$")\r
+       if uid then\r
+               atprint(fields)\r
+               for _,wagon in pairs(minetest.luaentities) do\r
+                       if wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then\r
+                               local pname=player:get_player_name()\r
+                               if pname~=wagon.owner then\r
+                                       return true\r
+                               end\r
+                               if not fields.quit then\r
+                                       for sgr,sgrdef in pairs(wagon.seat_groups) do\r
+                                               if fields["sgr_"..sgr] then\r
+                                                       local fcont = fields["sgr_"..sgr]\r
+                                                       wagon.seat_access[sgr] = fcont~="" and fcont or nil\r
+                                               end\r
+                                       end\r
+                                       wagon.lock_couples = fields.lock_couples == "true"\r
+                               end\r
+                       end\r
+               end\r
+       end\r
 end)\r
 function wagon:seating_from_key_helper(pname, fields, no)\r
        local sgr=self.seats[no].group\r
@@ -648,15 +690,26 @@ function wagon:seating_from_key_helper(pname, fields, no)
                self:show_wagon_properties(pname)\r
        end\r
        if fields.dcwarn then\r
-               minetest.chat_send_player(pname, "Use shift-rightclick to open doors with force and get off!")\r
+               minetest.chat_send_player(pname, "Doors are closed! Use shift-rightclick to open doors with force and get off!")\r
        end\r
        if fields.off then\r
                self:get_off(no)\r
        end\r
 end\r
 function wagon:check_seat_group_access(pname, sgr)\r
-       --TODO implement\r
-       return sgr~="driverstand" or pname=="orwell"\r
+       if not self.seat_access then\r
+               return true\r
+       end\r
+       local sae=self.seat_access[sgr]\r
+       if not sae or sae=="" then\r
+               return true\r
+       end\r
+       for name in string.gmatch(sae, "%S+") do\r
+               if name==pname then\r
+                       return true\r
+               end\r
+       end\r
+       return false\r
 end\r
 function wagon:reattach_all()\r
        if not self.seatp then self.seatp={} end\r