diff --git a/advtrains/advtrains/atc.lua b/advtrains/advtrains/atc.lua index cd9476c..dcddc65 100644 --- a/advtrains/advtrains/atc.lua +++ b/advtrains/advtrains/atc.lua @@ -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 diff --git a/advtrains/advtrains/couple.lua b/advtrains/advtrains/couple.lua index b50eec9..7a1e07a 100644 --- a/advtrains/advtrains/couple.lua +++ b/advtrains/advtrains/couple.lua @@ -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, diff --git a/advtrains/advtrains/tracks.lua b/advtrains/advtrains/tracks.lua index 3f170f5..a44acb3 100644 --- a/advtrains/advtrains/tracks.lua +++ b/advtrains/advtrains/tracks.lua @@ -386,76 +386,27 @@ end advtrains.detector = {} advtrains.detector.on_node = {} -advtrains.detector.on_node_restore = {} ---set if paths were invalidated before. tells trainlogic.lua to call advtrains.detector.finalize_restore() -advtrains.detector.clean_step_before = false ---when train enters a node, call this ---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). function advtrains.detector.enter_node(pos, train_id) - local pts = minetest.pos_to_string(advtrains.round_vector_floor_y(pos)) - --atprint("enterNode "..pts.." "..sid(train_id)) - if advtrains.detector.on_node[pts] then - if advtrains.trains[advtrains.detector.on_node[pts]] then - --atprint(""..pts.." already occupied") - return false - else - advtrains.detector.leave_node(pos, advtrains.detector.on_node[pts]) - end - end + local ppos=advtrains.round_vector_floor_y(pos) + local pts=minetest.pos_to_string(ppos) advtrains.detector.on_node[pts]=train_id - if advtrains.detector.on_node_restore[pts]==train_id then - advtrains.detector.on_node_restore[pts]=nil - else - advtrains.detector.call_enter_callback(advtrains.round_vector_floor_y(pos), train_id) - end - return true + advtrains.detector.call_enter_callback(ppos, train_id) end function advtrains.detector.leave_node(pos, train_id) - local pts = minetest.pos_to_string(advtrains.round_vector_floor_y(pos)) - --atprint("leaveNode "..pts.." "..sid(train_id)) - if not advtrains.detector.on_node[pts] then - --atprint(""..pts.." leave: nothing here") - return false - end - if advtrains.detector.on_node[pts]==train_id then - advtrains.detector.call_leave_callback(advtrains.round_vector_floor_y(pos), train_id) - advtrains.detector.on_node[pts]=nil - else - if advtrains.trains[advtrains.detector.on_node[pts]] then - --atprint(""..pts.." occupied by another train") - return false - else - advtrains.detector.leave_node(pos, advtrains.detector.on_node[pts]) - return false - end - end - return true + local ppos=advtrains.round_vector_floor_y(pos) + local pts=minetest.pos_to_string(ppos) + advtrains.detector.on_node[pts]=nil + advtrains.detector.call_leave_callback(ppos, train_id) end ---called immediately before invalidating paths -function advtrains.detector.setup_restore() - --atprint("setup_restore") - -- don't execute if it already has been called. For some reason it gets called twice... - if advtrains.detector.clean_step_before then - return - end - advtrains.detector.on_node_restore={} - for k, v in pairs(advtrains.detector.on_node) do - advtrains.detector.on_node_restore[k]=v - end - advtrains.detector.on_node = {} - advtrains.detector.clean_step_before = true -end ---called one step after invalidating paths, when all trains have restored their path and called enter_node for their contents. -function advtrains.detector.finalize_restore() - --atprint("finalize_restore") - for pts, train_id in pairs(advtrains.detector.on_node_restore) do - --atprint("called leave callback "..pts.." "..train_id) - advtrains.detector.call_leave_callback(minetest.string_to_pos(pts), train_id) - end - advtrains.detector.on_node_restore = {} - advtrains.detector.clean_step_before = false +function advtrains.detector.stay_node(pos, train_id) + local ppos=advtrains.round_vector_floor_y(pos) + local pts=minetest.pos_to_string(ppos) + advtrains.detector.on_node[pts]=train_id end + + + function advtrains.detector.call_enter_callback(pos, train_id) --atprint("instructed to call enter calback") diff --git a/advtrains/advtrains/trainlogic.lua b/advtrains/advtrains/trainlogic.lua index d5658a0..b1ee31a 100644 --- a/advtrains/advtrains/trainlogic.lua +++ b/advtrains/advtrains/trainlogic.lua @@ -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,258 +101,7 @@ 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_index1 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 - end - elseif ifnibo then - for i=ibo, ibn do - if path[i] then - advtrains.detector.leave_node(path[i], id) - end - end - 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 - 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())=train.velocity then - train.atc_brake_target=nil - end - if train.atc_wait_finish then - if not train.atc_brake_target and train.velocity==train.tarvelocity then - train.atc_wait_finish=nil - end - end - if train.atc_command then - if train.atc_delay<=0 and not train.atc_wait_finish then - advtrains.atc.execute_atc_command(id, train) - else - train.atc_delay=train.atc_delay-dtime - end - end - - --make brake adjust the tarvelocity if necessary - if train.brake and (math.ceil(train.velocity)-1)0 then--accelerating, force will be brought on only by locomotives. - --atprint("accelerating with default force") - applydiff=(math.min((advtrains.train_accel_force*train.locomotives_in_train*dtime)/mass, math.abs(diff))) - else--decelerating - if front_off_track or back_off_track or train.recently_collided_with_env then --every wagon has a brake, so not divided by mass. - --atprint("braking with emergency force") - applydiff= -(math.min((advtrains.train_emerg_force*dtime), math.abs(diff))) - elseif train.brake or (train.atc_brake_target and train.atc_brake_target0 then - if train.movedir>0 then - pregen_front=2+math.ceil(train.velocity*0.15) --assumes server step of 0.1 seconds, +50% tolerance + --- 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 + + --- 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) + + if train.recently_collided_with_env then + train.tarvelocity=0 + if not train_moves then + train.recently_collided_with_env=false--reset status when stopped + end + end + if train.locomotives_in_train==0 then + 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_index1 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 + end + if train.atc_wait_finish then + if not train.atc_brake_target and train.velocity==train.tarvelocity then + train.atc_wait_finish=nil + end + end + if train.atc_command then + if train.atc_delay<=0 and not train.atc_wait_finish then + advtrains.atc.execute_atc_command(id, train) else - pregen_back=2+math.ceil(train.velocity*0.15) + train.atc_delay=train.atc_delay-dtime end end + --make brake adjust the tarvelocity if necessary + if train.brake and (math.ceil(train.velocity)-1)0 then--accelerating, force will be brought on only by locomotives. + --atprint("accelerating with default force") + applydiff=(math.min((advtrains.train_accel_force*train.locomotives_in_train*dtime)/mass, math.abs(diff))) + else--decelerating + if front_off_track or back_off_track or train.recently_collided_with_env then --every wagon has a brake, so not divided by mass. + --atprint("braking with emergency force") + applydiff= -(math.min((advtrains.train_emerg_force*dtime), math.abs(diff))) + elseif train.brake or (train.atc_brake_target and train.atc_brake_target 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 -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 + + --- 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 ifnibo 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())1 then - if gp.velocity==0 then + if gp.velocity==0 and not self.lock_couples then if not self.discouple or not self.discouple.object:getyaw() then local object=minetest.add_entity(pos, "advtrains:discouple") if object then @@ -314,7 +313,7 @@ function wagon:on_step(dtime) end end --for path to be available. if not, skip step - if not advtrains.get_or_create_path(self.train_id, gp) then + if not gp.path then self.object:setvelocity({x=0, y=0, z=0}) return end @@ -593,6 +592,28 @@ function wagon:show_get_on_form(pname) end minetest.show_formspec(pname, "advtrains_geton_"..self.unique_id, form) end +function wagon:show_wagon_properties(pname) + if not self.seat_groups then + return + end + if not self.seat_access then + self.seat_access={} + end + --[[ + fields: seat access: empty: everyone + checkbox: lock couples + button: save + ]] + local form="size[5,"..(#self.seat_groups*1.5+5).."]" + local at=0 + for sgr,sgrdef in pairs(self.seat_groups) do + form=form.."field[0.5,"..(0.5+at*1.5)..";4,1;sgr_"..sgr..";"..sgrdef.name..";"..(self.seat_access[sgr] or "").."]" + at=at+1 + end + form=form.."checkbox[0,"..(at*1.5)..";lock_couples;Lock couples;"..(self.lock_couples and "true" or "false").."]" + form=form.."button_exit[0.5,"..(1+at*1.5)..";4,1;save;Save wagon properties]" + minetest.show_formspec(pname, "advtrains_prop_"..self.unique_id, form) +end minetest.register_on_player_receive_fields(function(player, formname, fields) local uid=string.match(formname, "^advtrains_geton_(.+)$") if uid then @@ -628,6 +649,27 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) end end end + uid=string.match(formname, "^advtrains_prop_(.+)$") + if uid then + atprint(fields) + for _,wagon in pairs(minetest.luaentities) do + if wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then + local pname=player:get_player_name() + if pname~=wagon.owner then + return true + end + if not fields.quit then + for sgr,sgrdef in pairs(wagon.seat_groups) do + if fields["sgr_"..sgr] then + local fcont = fields["sgr_"..sgr] + wagon.seat_access[sgr] = fcont~="" and fcont or nil + end + end + wagon.lock_couples = fields.lock_couples == "true" + end + end + end + end end) function wagon:seating_from_key_helper(pname, fields, no) local sgr=self.seats[no].group @@ -648,15 +690,26 @@ function wagon:seating_from_key_helper(pname, fields, no) self:show_wagon_properties(pname) end if fields.dcwarn then - minetest.chat_send_player(pname, "Use shift-rightclick to open doors with force and get off!") + minetest.chat_send_player(pname, "Doors are closed! Use shift-rightclick to open doors with force and get off!") end if fields.off then self:get_off(no) end end function wagon:check_seat_group_access(pname, sgr) - --TODO implement - return sgr~="driverstand" or pname=="orwell" + if not self.seat_access then + return true + end + local sae=self.seat_access[sgr] + if not sae or sae=="" then + return true + end + for name in string.gmatch(sae, "%S+") do + if name==pname then + return true + end + end + return false end function wagon:reattach_all() if not self.seatp then self.seatp={} end