From 1f3a4c3bfc2a462ff51bd6b391be0d39a6a6c944 Mon Sep 17 00:00:00 2001 From: orwell96 Date: Wed, 7 Jul 2021 22:14:20 +0200 Subject: Rework train same-track collision system and deterministic coupling - Adds a separate collision system for trains sharing a path - Moved some coupling-related code to couple.lua and refactor it - Handle coupling in a way that the initiating train always keeps its ID - As a side effect, engine has its direction reversed after coupling --- advtrains/couple.lua | 448 ++++++++++++++++++++++++++++++++++++----------- advtrains/path.lua | 39 +++++ advtrains/trainlogic.lua | 321 +++++++++++---------------------- advtrains/wagons.lua | 64 ------- 4 files changed, 478 insertions(+), 394 deletions(-) diff --git a/advtrains/couple.lua b/advtrains/couple.lua index 3dc336f..336a6d4 100644 --- a/advtrains/couple.lua +++ b/advtrains/couple.lua @@ -1,14 +1,290 @@ --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: + -- initiating train is reversed + -- stationary train is reversed if required, so that it points towards the initiating train + -- do_connect_trains(initiating, stationary) +-- As a result, the coupled train is reversed in direction. Alternative way of doing things (might be considered later): + -- stationary train is reversed if required, so that it points away from the initiating train + -- index of initiating train is set so that it matches the front pos of stationary train + -- wagons of stationary train are inserted at the beginning of initiating train + -- remove stationary train -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) +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("Initiating couplign between init=",init_train.id,"stat=",stat_train.id,"backside=",stat_is_backside) + if init_train.autocouple then + advtrains.couple_trains(init_train, true, stat_train, stat_is_front) + else + local pos = advtrains.path_get_interpolated(init_train, init_train.index) + create_couple_entity(pos, init_train, true, stat_train, stat_is_front) + end + +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 + advtrains.couple_trains(train1, t1_is_front, train2, t2_is_front) + end +end + +-- Actually performs the train coupling. Always retains train ID of train1 +function advtrains.couple_trains(train1, t1_is_front, train2, t2_is_front) + --atdebug("Couple trains init=",init_train.id,"stat=",stat_train.id,"statreverse=",stat_must_reverse) + -- see comment on top of file + if t1_is_front then + advtrains.invert_train(train1.id) + end + if not t2_is_front then + advtrains.invert_train(train2.id) + end + + advtrains.do_connect_trains(train1, train2) +end + +-- Adds the wagons of first to second and deletes second_id afterwards +-- Assumes that second_id stands right behind first_id and both trains point to the same direction +function advtrains.do_connect_trains(first, second) + + if not advtrains.train_ensure_init(first.id, first) then + atwarn("Coupling: first train",first.id,"is not initialized! Operation aborted!") + return + end + if not advtrains.train_ensure_init(second.id, second) then + atwarn("Coupling: second 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) + + first.velocity = 0 + + advtrains.update_trainpart_properties(first.id) + advtrains.couple_invalidate(first) + return true +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 +312,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 +334,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 +345,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/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/trainlogic.lua b/advtrains/trainlogic.lua index 0f2a66e..442d6bf 100644 --- a/advtrains/trainlogic.lua +++ b/advtrains/trainlogic.lua @@ -139,6 +139,7 @@ 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 @@ -394,7 +395,7 @@ function advtrains.train_step_b(id, train, dtime) local back_off_track=train.end_index 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 @@ -587,12 +588,76 @@ 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 then + -- 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 +703,39 @@ 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) + + --- 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) + + 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) + 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 + advtrains.train_check_couples(train) -- no guarantee for train order here + 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 @@ -666,8 +749,8 @@ function advtrains.train_step_c(id, train, dtime) local col_tr = advtrains.occ.check_collision(testpos, id) if col_tr then - advtrains.train_check_couples(train) train.velocity = 0 + train.acceleration = 0 advtrains.atc.train_reset_command(train) collided = true end @@ -899,7 +982,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,53 +1102,6 @@ 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. @@ -1121,167 +1157,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] diff --git a/advtrains/wagons.lua b/advtrains/wagons.lua index e9b6d7a..4093f06 100644 --- a/advtrains/wagons.lua +++ b/advtrains/wagons.lua @@ -1240,70 +1240,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") -- cgit v1.2.3