--atan2 counts angles clockwise, minetest does counterclockwiselocalprint=function(t) minetest.log("action", t) minetest.chat_send_all(t)endlocal wagon={
collisionbox = {-0.5,-0.5,-0.5,0.5,0.5,0.5},--physical = true,
visual ="mesh",
mesh ="wagon.b3d",
visual_size = {x=3, y=3},
textures = {"black.png"},
is_wagon=true,
wagon_span=1,--how many index units of space does this wagon consume
attach_offset={x=0, y=0, z=0},
view_offset={x=0, y=0, z=0},}function wagon:on_rightclick(clicker)--print("[advtrains] wagon rightclick")if not clicker or not clicker:is_player()thenreturnendif not self.initialized thenprint("[advtrains] not initiaalized")returnendif clicker:get_player_control().aux1 then--advtrains.dumppath(self:train().path)--minetest.chat_send_all("at index "..(self:train().index or "nil"))--advtrains.invert_train(self.train_id)
minetest.chat_send_all(dump(self:train()))returnendlocal no=self:get_seatno(clicker:get_player_name())if no then
self:get_off(no)else
self:show_get_on_form(clicker:get_player_name())endendfunction wagon:train()return advtrains.trains[self.train_id]endfunction wagon:on_activate(staticdata, dtime_s)--print("[advtrains][wagon "..(self.unique_id or "no-id").."] activated")
self.object:set_armor_groups({immortal=1})if staticdata thenlocal tmp = minetest.deserialize(staticdata)if tmp then
self.unique_id=tmp.unique_id
self.train_id=tmp.train_id
self.wagon_flipped=tmp.wagon_flipped
self.owner=tmp.owner
self.seatp=tmp.seatp
endend
self.old_pos = self.object:getpos()
self.old_velocity = self.velocity
self.initialized_pre=true
self.entity_name=self.name
--same code is in on_step--does this object already have an ID?if not self.unique_id then
self.unique_id=os.time()..os.clock()--should be random enough.elsefor _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.unique_id==self.unique_id then--i am a duplicate!
self.object:remove()returnendendend--is my train still hereif not self.train_id or not self:train()thenif self.initialized thenprint("[advtrains][wagon "..self.unique_id.."] missing train_id, destroying")
self.object:remove()returnendprint("[advtrains][wagon "..self.unique_id.."] missing train_id, but not yet initialized, returning")returnelseif not self.initialized then
self.initialized=trueend
advtrains.update_trainpart_properties(self.train_id)
minetest.after(1,function() self:reattach_all()end)endfunction wagon:get_staticdata()--save to table before being unloaded
advtrains.wagon_save[self.unique_id]=advtrains.merge_tables(self)return minetest.serialize({
unique_id=self.unique_id,
train_id=self.train_id,
wagon_flipped=self.wagon_flipped,
owner=self.owner,
seatp=self.seatp,})end-- Remove the wagonfunction wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)if not puncher or not puncher:is_player()thenreturnendif self.owner and puncher:get_player_name()~=self.owner then
minetest.chat_send_player(puncher:get_player_name(),"This wagon is owned by "..self.owner..", you can't destroy it.")returnendif minetest.setting_getbool("creative_mode")thenif not self:destroy()then return endlocal inv = puncher:get_inventory()if not inv:contains_item("main", self.name)then
inv:add_item("main", self.name)endelselocal pc=puncher:get_player_control()if not pc.sneak then
minetest.chat_send_player(puncher:get_player_name(),"Warning: If you destroy this wagon, you only get some steel back! If you are sure, shift-leftclick the wagon.")returnendif not self:destroy()then return endlocal inv = puncher:get_inventory()for _,item inipairs(self.drops or{self.name})do
inv:add_item("main", item)endendendfunction wagon:destroy()--some rules:-- you get only some items back-- single left-click shows warning-- shift leftclick destroys-- not when a driver is insidefor _,_ inpairs(self.seatp)doreturnendif self.custom_may_destroy thenif not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction)thenreturnendendif self.custom_on_destroy then
self.custom_on_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction)end
self.object:remove()if not self.initialized then return endtable.remove(self:train().trainparts, self.pos_in_trainparts)
advtrains.update_trainpart_properties(self.train_id)
advtrains.wagon_save[self.unique_id]=nilif self.discouple then self.discouple.object:remove()end--will have no effect on unloaded objectsreturn trueendfunction wagon:on_step(dtime)local t=os.clock()local pos = self.object:getpos()if not self.initialized_pre thenprint("[advtrains] wagon stepping while not yet initialized_pre, returning")
self.object:setvelocity({x=0,y=0,z=0})returnend
self.entity_name=self.name
--does this object already have an ID?if not self.unique_id then
self.unique_id=os.time()..os.clock()--should be random enough.end--is my train still hereif not self.train_id or not self:train()thenprint("[advtrains][wagon "..self.unique_id.."] missing train_id, destroying")
self.object:remove()returnelseif not self.initialized then
self.initialized=trueend--re-attach driver if he got lost--if not self.driver and self.driver_name then-- local clicker=minetest.get_player_by_name(self.driver_name)-- if clicker then-- self.driver = clicker-- advtrains.player_to_wagon_mapping[clicker:get_player_name()]=self-- clicker:set_attach(self.object, "", self.attach_offset, {x=0,y=0,z=0})-- clicker:set_eye_offset(self.view_offset, self.view_offset)-- end--end--custom on_step functionif self.custom_on_step then
self.custom_on_step(self, dtime)end--driver controlfor seatno, seat inipairs(self.seats)doif seat.driving_ctrl_access thenif not self.seatp then
self.seatp={}endlocal driver=self.seatp[seatno]and minetest.get_player_by_name(self.seatp[seatno])if driver and driver:get_player_control_bits()~=self.old_player_control_bits thenlocal pc=driver:get_player_control()if pc.sneak then--stop
self:train().tarvelocity=0elseif(not self.wagon_flipped and pc.up)or(self.wagon_flipped and pc.down)then--faster
self:train().tarvelocity=math.min(self:train().tarvelocity+1, advtrains.all_traintypes[self:train().traintype].max_speed or10)elseif(not self.wagon_flipped and pc.down)or(self.wagon_flipped and pc.up)then--slower
self:train().tarvelocity=math.max(self:train().tarvelocity-1, -(advtrains.all_traintypes[self:train().traintype].max_speed or10))elseif pc.aux1 then--slowerif true or math.abs(self:train().velocity)<=3then--TODO debug
self:get_off(seatno)returnelse
minetest.chat_send_player(driver:get_player_name(),"Can't get off driving train!")endend
self.old_player_control_bits=driver:get_player_control_bits()endif driver then
advtrains.set_trainhud(driver:get_player_name(), advtrains.hud_train_format(self:train(), self.wagon_flipped))endendendlocal gp=self:train()--DisCoupleif self.pos_in_trainparts and self.pos_in_trainparts>1thenif gp.velocity==0thenif not self.discouple or not self.discouple.object:getyaw()thenlocal object=minetest.add_entity(pos,"advtrains:discouple")if object thenlocal le=object:get_luaentity()
le.wagon=self
--box is hidden when attached, so unuseful.--object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
self.discouple=le
elseprint("Couldn't spawn DisCouple")endendelseif self.discouple and self.discouple.object:getyaw()then
self.discouple.object:remove()endendend--for path to be available. if not, skip stepif not advtrains.get_or_create_path(self.train_id, gp)then
self.object:setvelocity({x=0, y=0, z=0})returnendlocal index=advtrains.get_real_path_index(self:train(), self.pos_in_train)--print("trainindex "..gp.index.." wagonindex "..index)--position recalculationlocal first_pos=gp.path[math.floor(index)]local second_pos=gp.path[math.floor(index)+1]if not first_pos or not second_pos then--print("[advtrains] object "..self.unique_id.." path end reached!")
self.object:setvelocity({x=0,y=0,z=0})returnend--checking for environment collisions(a 3x3 cube around the center)if not gp.recently_collided_with_env thenlocal collides=falsefor x=-1,1dofor y=0,2dofor z=-1,1dolocal node=minetest.get_node_or_nil(vector.add(first_pos, {x=x, y=y, z=z}))if(advtrains.train_collides(node))then
collides=trueendendendendif collides then
gp.recently_collided_with_env=true
gp.velocity=-0.5*gp.velocity
gp.tarvelocity=0endend--FIX: use index of the wagon, not of the train.local velocity=gp.velocity/(gp.path_dist[math.floor(index)]or1)local acceleration=(gp.last_accel or0)/(gp.path_dist[math.floor(index)]or1)local factor=index-math.floor(index)local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,}local velocityvec={x=(first_pos.x-second_pos.x)*velocity*-1, z=(first_pos.z-second_pos.z)*velocity*-1, y=(first_pos.y-second_pos.y)*velocity*-1}local accelerationvec={x=(first_pos.x-second_pos.x)*acceleration*-1, z=(first_pos.z-second_pos.z)*acceleration*-1, y=(first_pos.y-second_pos.y)*acceleration*-1}--some additional positions to determine orientationlocal aposfwd=gp.path[math.floor(index+2)]local aposbwd=gp.path[math.floor(index-1)]local yaw
if aposfwd and aposbwd then
yaw=advtrains.get_wagon_yaw(aposfwd, second_pos, first_pos, aposbwd, factor)+math.pi--TODO remove when cleaning upelse
yaw=math.atan2((first_pos.x-second_pos.x), (second_pos.z-first_pos.z))endif self.wagon_flipped then
yaw=yaw+math.pi
end
self.updatepct_timer=(self.updatepct_timer or0)-dtime
if not self.old_velocity_vector
or not vector.equals(velocityvec, self.old_velocity_vector)or not self.old_acceleration_vector
or not vector.equals(accelerationvec, self.old_acceleration_vector)or self.old_yaw~=yaw
or self.updatepct_timer<=0then--only send update packet if something changed
self.object:setpos(actual_pos)
self.object:setvelocity(velocityvec)
self.object:setacceleration(accelerationvec)
self.object:setyaw(yaw)
self.updatepct_timer=2if self.update_animation then
self:update_animation(gp.velocity)endend
self.old_velocity_vector=velocityvec
self.old_acceleration_vector=accelerationvec
self.old_yaw=yaw
printbm("wagon step", t)endfunction advtrains.get_real_path_index(train, pit)local pos_in_train_left=pit
local index=train.index
if pos_in_train_left>(index-math.floor(index))*(train.path_dist[math.floor(index)]or1)then
pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(train.path_dist[math.floor(index)]or1)
index=math.floor(index)while pos_in_train_left>(train.path_dist[index-1]or1)do
pos_in_train_left=pos_in_train_left - (train.path_dist[index-1]or1)
index=index-1end
index=index-(pos_in_train_left/(train.path_dist[index-1]or1))else
index=index-(pos_in_train_left/(train.path_dist[math.floor(index-1)]or1))endreturn index
endfunction wagon:get_on(clicker, seatno)if not self.seatp then
self.seatp={}endif not self.seats[seatno]then return endif self.seatp[seatno]then
self:get_off(seatno)end
self.seatp[seatno] = clicker:get_player_name()
advtrains.player_to_wagon_mapping[clicker:get_player_name()]={wagon=self, seatno=seatno}
clicker:set_attach(self.object,"", self.seats[seatno].attach_offset, {x=0,y=0,z=0})
clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset)endfunction wagon:get_off_plr(pname)local no=self:get_seatno(pname)if no then
self:get_off(no)endendfunction wagon:get_seatno(pname)for no, cont inipairs(self.seatp)doif cont==pname thenreturn no
endendreturn nilendfunction wagon:get_off(seatno)if not self.seatp[seatno]then return endlocal pname = self.seatp[seatno]local clicker = minetest.get_player_by_name(pname)
advtrains.player_to_wagon_mapping[pname]=nil
advtrains.set_trainhud(pname,"")if clicker then
clicker:set_detach()
clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})end
self.seatp[seatno]=nilendfunction wagon:show_get_on_form(pname)if not self.initialized then return endlocal form, comma="size[5,7]label[0.5,0.5;Select seat:]textlist[0.5,1;4,6;seat;",""for seatno, seattbl inipairs(self.seats)dolocal addtext, colorcode="",""if self.seatp and self.seatp[seatno]then
colorcode="#FF0000"
addtext=" ("..self.seatp[seatno]..")"end
form=form..comma..colorcode..seattbl.name..addtext
comma=","end
minetest.show_formspec(pname,"advtrains_geton_"..self.unique_id, form..";0,false")end
minetest.register_on_player_receive_fields(function(player, formname, fields)local uid=string.match(formname,"^advtrains_geton_(.+)$")if uid and fields.seat thenlocal val=minetest.explode_textlist_event(fields.seat)if val and val.type=="CHG"then--get onfor _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then
wagon:get_on(player, val.index)
minetest.show_formspec(player:get_player_name(),"none","")endendendendend)function wagon:reattach_all()for seatno, pname inpairs(self.seatp)dolocal p=minetest.get_player_by_name(pname)if p then
self:get_on(p ,seatno)endendend
minetest.register_on_joinplayer(function(player)for _,wagon inpairs(minetest.luaentities)doif wagon.is_wagon and wagon.initialized then
wagon:reattach_all()endendend)function advtrains.register_wagon(sysname, traintype, prototype, desc, inv_img)setmetatable(prototype, {__index=wagon})
minetest.register_entity("advtrains:"..sysname,prototype)
minetest.register_craftitem("advtrains:"..sysname, {
description = desc,
inventory_image = inv_img,
wield_image = inv_img,
stack_max =1,
on_place =function(itemstack, placer, pointed_thing)if not pointed_thing.type=="node"thenreturnendlocal ob=minetest.env:add_entity(pointed_thing.under,"advtrains:"..sysname)if not ob thenprint("[advtrains]couldn't add_entity, aborting")endlocal le=ob:get_luaentity()
le.owner=placer:get_player_name()
le.infotext=desc..", owned by "..placer:get_player_name()local node=minetest.env:get_node_or_nil(pointed_thing.under)if not node thenprint("[advtrains]Ignore at placer position")return itemstack endlocal nodename=node.name
if(not advtrains.is_track_and_drives_on(nodename, advtrains.all_traintypes[traintype].drives_on))thenprint("[advtrains]no trck here, not placing.")return itemstack
endlocal conn1=advtrains.get_track_connections(node.name, node.param2)local id=advtrains.create_new_train_at(pointed_thing.under, advtrains.dirCoordSet(pointed_thing.under, conn1), traintype)
advtrains.add_wagon_to_train(le, id)if not minetest.setting_getbool("creative_mode")then
itemstack:take_item()endreturn itemstack
end,})end
advtrains.register_train_type("steam", {"regular","default"})--[[advtrains.register_wagon("blackwagon", "steam",{textures = {"black.png"}})advtrains.register_wagon("bluewagon", "steam",{textures = {"blue.png"}})advtrains.register_wagon("greenwagon", "steam",{textures = {"green.png"}})advtrains.register_wagon("redwagon", "steam",{textures = {"red.png"}})advtrains.register_wagon("yellowwagon", "steam",{textures = {"yellow.png"}})]]--[[ wagons can define update_animation(self, velocity) if they have a speed-dependent animation this function will be called when the velocity vector changes or every 2 seconds.]]
advtrains.register_wagon("newlocomotive","steam",{
mesh="advtrains_engine_steam.b3d",
textures = {"advtrains_newlocomotive.png"},
is_locomotive=true,
seats = {{
name="Driver Stand (left)",
attach_offset={x=-5, y=10, z=-10},
view_offset={x=0, y=6, z=0},
driving_ctrl_access=true,},{
name="Driver Stand (right)",
attach_offset={x=5, y=10, z=-10},
view_offset={x=0, y=6, z=0},
driving_ctrl_access=true,},},
visual_size = {x=1, y=1},
wagon_span=1.85,
collisionbox = {-1.0,-0.5,-1.0,1.0,2.5,1.0},
update_animation=function(self, velocity)--if self.old_anim_velocity~=advtrains.abs_ceil(velocity) then
self.object:set_animation({x=1,y=60},100)--math.floor(velocity))--self.old_anim_velocity=advtrains.abs_ceil(velocity)--endend,
drops={"default:steelblock 4"},},"Steam Engine","advtrains_newlocomotive_inv.png")
advtrains.register_wagon("wagon_default","steam",{
mesh="wagon.b3d",
textures = {"advtrains_wagon.png"},
seats = {{
name="Default Seat",
attach_offset={x=0, y=10, z=0},
view_offset={x=0, y=6, z=0},},},
visual_size = {x=1, y=1},
wagon_span=1.8,
collisionbox = {-1.0,-0.5,-1.0,1.0,2.5,1.0},
drops={"default:steelblock 4"},},"Passenger Wagon","advtrains_wagon_inv.png")
advtrains.register_train_type("electric", {"regular","default"},20)
advtrains.register_wagon("engine_japan","electric",{
mesh="advtrains_engine_japan.b3d",
textures = {"advtrains_engine_japan.png"},
seats = {{
name="Default Seat (driver stand)",
attach_offset={x=0, y=10, z=0},
view_offset={x=0, y=6, z=0},
driving_ctrl_access=true,},},
visual_size = {x=1, y=1},
wagon_span=2,
is_locomotive=true,
collisionbox = {-1.0,-0.5,-1.0,1.0,2.5,1.0},
drops={"default:steelblock 4"},},"Japanese Train Engine","green.png")
advtrains.register_wagon("wagon_japan","electric",{
mesh="advtrains_wagon_japan.b3d",
textures = {"advtrains_wagon_japan.png"},
seats = {{
name="Default Seat",
attach_offset={x=0, y=10, z=0},
view_offset={x=0, y=6, z=0},},},
visual_size = {x=1, y=1},
wagon_span=2,
collisionbox = {-1.0,-0.5,-1.0,1.0,2.5,1.0},
drops={"default:steelblock 4"},},"Japanese Train Wagon","blue.png")
advtrains.register_train_type("subway", {"default"},15)
advtrains.register_wagon("subway_wagon","subway",{
mesh="advtrains_subway_train.b3d",
textures = {"advtrains_subway_train.png"},
seats = {{
name="Default Seat (driver stand)",
attach_offset={x=0, y=10, z=0},
view_offset={x=0, y=6, z=0},
driving_ctrl_access=true,},},
visual_size = {x=1, y=1},
wagon_span=1.8,
collisionbox = {-1.0,-0.5,-1.0,1.0,2.5,1.0},
is_locomotive=true,
drops={"default:steelblock 4"},},"Subway Passenger Wagon","advtrains_subway_train_inv.png")--[[advtrains.register_wagon("wagontype1",{on_rightclick=function(self, clicker) if clicker:get_player_control().sneak then advtrains.disconnect_train_before_wagon(self) return end --just debugging. look for first active wagon and attach to it. for _,v in pairs(minetest.luaentities) do if v.is_wagon and v.unique_id and v.unique_id~=self.unique_id then self.train_id=v.unique_id end end if not self.train_id then minetest.chat_send_all("not found") return end minetest.chat_send_all(self.train_id.." found and attached.")end})]]