Merged priv
authorGabriel Pérez-Cerezo <gabriel@gpcf.eu>
Wed, 18 Jan 2017 22:22:06 +0000 (23:22 +0100)
committerGabriel Pérez-Cerezo <gabriel@gpcf.eu>
Wed, 18 Jan 2017 22:22:06 +0000 (23:22 +0100)
1  2 
advtrains/advtrains/wagons.lua

index 0000000,13811ad..029d2d1
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,580 +1,590 @@@
 -      if self.owner and puncher:get_player_name()~=self.owner then\r
 -              minetest.chat_send_player(puncher:get_player_name(), "This wagon is owned by "..self.owner..", you can't destroy it.")\r
 -              return\r
+ --atan2 counts angles clockwise, minetest does counterclockwise\r
\r
++minetest.register_privilege("train_place", {\r
++      description = "Player can place trains on tracks not owned by player",\r
++      give_to_singleplayer= false,\r
++});\r
++minetest.register_privilege("train_remove", {\r
++      description = "Player can remove trains not owned by player",\r
++      give_to_singleplayer= false,\r
++});\r
++\r
+ local wagon={\r
+       collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},\r
+       --physical = true,\r
+       visual = "mesh",\r
+       mesh = "wagon.b3d",\r
+       visual_size = {x=3, y=3},\r
+       textures = {"black.png"},\r
+       is_wagon=true,\r
+       wagon_span=1,--how many index units of space does this wagon consume\r
+       has_inventory=false,\r
+ }\r
\r
\r
\r
+ function wagon:on_rightclick(clicker)\r
+       if not self:ensure_init() then return end\r
+       if not clicker or not clicker:is_player() then\r
+               return\r
+       end\r
+       if clicker:get_player_control().aux1 then\r
+               --advtrains.dumppath(self:train().path)\r
+               --minetest.chat_send_all("at index "..(self:train().index or "nil"))\r
+               --advtrains.invert_train(self.train_id)\r
+               minetest.chat_send_all(dump(self:train()))\r
+               return\r
+       end     \r
+       local no=self:get_seatno(clicker:get_player_name())\r
+       if no then\r
+               self:get_off(no)\r
+       else\r
+               self:show_get_on_form(clicker:get_player_name())\r
+       end\r
+ end\r
\r
+ function wagon:train()\r
+       return advtrains.trains[self.train_id]\r
+ end\r
\r
+ --[[about 'initalized':\r
+       when initialized is false, the entity hasn't got any data yet and should wait for these to be set before doing anything\r
+       when loading an existing object (with staticdata), it will be set\r
+       when instanciating a new object via add_entity, it is not set at the time on_activate is called.\r
+       then, wagon:initialize() will be called\r
+       \r
+       wagon will save only uid in staticdata, no serialized table\r
+ ]]\r
+ function wagon:on_activate(sd_uid, dtime_s)\r
+       atprint("[wagon "..((sd_uid and sd_uid~="" and sd_uid) or "no-id").."] activated")\r
+       self.object:set_armor_groups({immortal=1})\r
+       if sd_uid and sd_uid~="" then\r
+               --legacy\r
+               --expect this to be a serialized table and handle\r
+               if minetest.deserialize(sd_uid) then\r
+                       self:init_from_wagon_save(minetest.deserialize(sd_uid).unique_id)\r
+               else\r
+                       self:init_from_wagon_save(sd_uid)\r
+               end\r
+       end\r
+       self.entity_name=self.name\r
+       \r
+       --duplicates?\r
+       for ao_id,wagon in pairs(minetest.luaentities) do\r
+               if wagon.is_wagon and wagon.initialized and wagon.unique_id==self.unique_id and wagon~=self then--i am a duplicate!\r
+                       atprint("[wagon "..((sd_uid and sd_uid~="" and sd_uid) or "no-id").."] duplicate found(ao_id:"..ao_id.."), removing")\r
+                       self.object:remove()\r
+                       minetest.after(0.5, function() advtrains.update_trainpart_properties(self.train_id) end)\r
+                       return\r
+               end\r
+       end\r
+       \r
+       if self.custom_on_activate then\r
+               self:custom_on_activate(dtime_s)\r
+       end\r
+ end\r
\r
+ function wagon:get_staticdata()\r
+       if not self:ensure_init() then return end\r
+       atprint("[wagon "..((self.unique_id and self.unique_id~="" and self.unique_id) or "no-id").."]: saving to wagon_save")\r
+       --serialize inventory, if it has one\r
+       if self.has_inventory then\r
+               local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.unique_id})\r
+               self.ser_inv=advtrains.serialize_inventory(inv)\r
+       end\r
+       --save to table before being unloaded\r
+       advtrains.wagon_save[self.unique_id]=advtrains.merge_tables(self)\r
+       advtrains.wagon_save[self.unique_id].entity_name=self.name\r
+       advtrains.wagon_save[self.unique_id].name=nil\r
+       advtrains.wagon_save[self.unique_id].object=nil\r
+       return self.unique_id\r
+ end\r
+ --returns: uid of wagon\r
+ function wagon:init_new_instance(train_id, properties)\r
+       self.unique_id=os.time()..os.clock()\r
+       self.train_id=train_id\r
+       for k,v in pairs(properties) do\r
+               if k~="name" and k~="object" then\r
+                       self[k]=v\r
+               end\r
+       end\r
+       self:init_shared()\r
+       self.initialized=true\r
+       atprint("init_new_instance "..self.unique_id.." ("..self.train_id..")")\r
+       return self.unique_id\r
+ end\r
+ function wagon:init_from_wagon_save(uid)\r
+       if not advtrains.wagon_save[uid] then\r
+               self.object:remove()\r
+               return\r
+       end\r
+       self.unique_id=uid\r
+       for k,v in pairs(advtrains.wagon_save[uid]) do\r
+               if k~="name" and k~="object" then\r
+                       self[k]=v\r
+               end\r
+       end\r
+       if not self.train_id or not self:train() then\r
+               self.object:remove()\r
+               return\r
+       end\r
+       self:init_shared()\r
+       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
+               local uid_noptr=self.unique_id..""\r
+               --to be used later\r
+               local inv=minetest.create_detached_inventory("advtrains_wgn_"..self.unique_id, {\r
+                       allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)\r
+                               return count\r
+                       end,\r
+                       allow_put = function(inv, listname, index, stack, player)\r
+                               return stack:get_count()\r
+                       end,\r
+                       allow_take = function(inv, listname, index, stack, player)\r
+                               return stack:get_count()\r
+                       end\r
+               })\r
+               if self.ser_inv then\r
+                       advtrains.deserialize_inventory(self.ser_inv, inv)\r
+               end\r
+               if self.inventory_list_sizes then\r
+                       for lst, siz in pairs(self.inventory_list_sizes) do\r
+                               inv:set_size(lst, siz)\r
+                       end\r
+               end\r
+       end\r
+ end\r
+ function wagon:ensure_init()\r
+       if self.initialized then\r
+               if self.noninitticks then self.noninitticks=nil end\r
+               return true\r
+       end\r
+       if not self.noninitticks then self.noninitticks=0 end\r
+       self.noninitticks=self.noninitticks+1\r
+       if self.noninitticks>20 then\r
+               self.object:remove()\r
+       else\r
+               self.object:setvelocity({x=0,y=0,z=0})\r
+       end\r
+       return false\r
+ end\r
\r
+ -- Remove the wagon\r
+ function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)\r
+       if not self:ensure_init() then return end\r
+       if not puncher or not puncher:is_player() then\r
+               return\r
+       end\r
 -                      local node=minetest.env:get_node_or_nil(pointed_thing.under)\r
 -                      if not node then atprint("Ignore at placer position") return itemstack end\r
++      if self.owner and puncher:get_player_name()~=self.owner and (not minetest.check_player_privs(puncher, {train_remove = true })) then\r
++         minetest.chat_send_player(puncher:get_player_name(), "This wagon is owned by "..self.owner..", you can't destroy it.");\r
++         return\r
+       end\r
+       \r
+       if minetest.setting_getbool("creative_mode") then\r
+               if not self:destroy() then return end\r
+               \r
+               local inv = puncher:get_inventory()\r
+               if not inv:contains_item("main", self.name) then\r
+                       inv:add_item("main", self.name)\r
+               end\r
+       else\r
+               local pc=puncher:get_player_control()\r
+               if not pc.sneak then\r
+                       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.")\r
+                       return\r
+               end\r
\r
+               if not self:destroy() then return end\r
\r
+               local inv = puncher:get_inventory()\r
+               for _,item in ipairs(self.drops or {self.name}) do\r
+                       inv:add_item("main", item)\r
+               end\r
+       end\r
+ end\r
+ function wagon:destroy()\r
+       --some rules:\r
+       -- you get only some items back\r
+       -- single left-click shows warning\r
+       -- shift leftclick destroys\r
+       -- not when a driver is inside\r
+       \r
+       for _,_ in pairs(self.seatp) do\r
+               return\r
+       end\r
+       \r
+       if self.custom_may_destroy then\r
+               if not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction) then\r
+                       return\r
+               end\r
+       end\r
+       if self.custom_on_destroy then\r
+               self.custom_on_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction)\r
+       end\r
+       \r
+       atprint("[wagon "..((self.unique_id and self.unique_id~="" and self.unique_id) or "no-id").."]: destroying")\r
+       \r
+       self.object:remove()\r
\r
+       table.remove(self:train().trainparts, self.pos_in_trainparts)\r
+       advtrains.update_trainpart_properties(self.train_id)\r
+       advtrains.wagon_save[self.unique_id]=nil\r
+       if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects\r
+       return true\r
+ end\r
\r
\r
+ function wagon:on_step(dtime)\r
+       if not self:ensure_init() then return end\r
+       \r
+       local t=os.clock()\r
+       local pos = self.object:getpos()\r
+       \r
+       if not pos then\r
+               atprint("["..self.unique_id.."][fatal] missing position (object:getpos() returned nil)")\r
+               return\r
+       end\r
\r
+       self.entity_name=self.name\r
+       \r
+       --is my train still here\r
+       if not self.train_id or not self:train() then\r
+               atprint("[wagon "..self.unique_id.."] missing train_id, destroying")\r
+               self.object:remove()\r
+               return\r
+       elseif not self.initialized then\r
+               self.initialized=true\r
+       end\r
+       if not self.seatp then\r
+               self.seatp={}\r
+       end\r
\r
+       --custom on_step function\r
+       if self.custom_on_step then\r
+               self:custom_on_step(self, dtime)\r
+       end\r
\r
+       --driver control\r
+       for seatno, seat in ipairs(self.seats) do\r
+               if seat.driving_ctrl_access then\r
+                       local driver=self.seatp[seatno] and minetest.get_player_by_name(self.seatp[seatno])\r
+                       local get_off_pressed=false\r
+                       if driver and driver:get_player_control_bits()~=self.old_player_control_bits then\r
+                               local pc=driver:get_player_control()\r
+                               \r
+                               advtrains.on_control_change(pc, self:train(), self.wagon_flipped)\r
+                               if pc.aux1 and pc.sneak then\r
+                                       get_off_pressed=true\r
+                               end\r
+                               \r
+                               self.old_player_control_bits=driver:get_player_control_bits()\r
+                       end\r
+                       if driver then\r
+                               if get_off_pressed then\r
+                                       self:get_off(seatno)\r
+                               else\r
+                                       advtrains.update_driver_hud(driver:get_player_name(), self:train(), self.wagon_flipped)\r
+                               end\r
+                       end\r
+               end\r
+       end\r
\r
+       local gp=self:train()\r
\r
+       --DisCouple\r
+       if self.pos_in_trainparts and self.pos_in_trainparts>1 then\r
+               if gp.velocity==0 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
+                                       local le=object:get_luaentity()\r
+                                       le.wagon=self\r
+                                       --box is hidden when attached, so unuseful.\r
+                                       --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})\r
+                                       self.discouple=le\r
+                               else\r
+                                       atprint("Couldn't spawn DisCouple")\r
+                               end\r
+                       end\r
+               else\r
+                       if self.discouple and self.discouple.object:getyaw() then\r
+                               self.discouple.object:remove()\r
+                       end\r
+               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
+               self.object:setvelocity({x=0, y=0, z=0})\r
+               return\r
+       end\r
+       if not self.pos_in_train then\r
+               --why ever. but better continue next step...\r
+               advtrains.update_trainpart_properties(self.train_id)\r
+               return\r
+       end\r
+       \r
+       local index=advtrains.get_real_path_index(self:train(), self.pos_in_train)\r
+       --atprint("trainindex "..gp.index.." wagonindex "..index)\r
+       \r
+       --position recalculation\r
+       local first_pos=gp.path[math.floor(index)]\r
+       local second_pos=gp.path[math.floor(index)+1]\r
+       if not first_pos or not second_pos then\r
+               --atprint(" object "..self.unique_id.." path end reached!")\r
+               self.object:setvelocity({x=0,y=0,z=0})\r
+               return\r
+       end\r
+       \r
+       --checking for environment collisions(a 3x3 cube around the center)\r
+       if not gp.recently_collided_with_env then\r
+               local collides=false\r
+               for x=-1,1 do\r
+                       for y=0,2 do\r
+                               for z=-1,1 do\r
+                                       local node=minetest.get_node_or_nil(vector.add(first_pos, {x=x, y=y, z=z}))\r
+                                       if (advtrains.train_collides(node)) then\r
+                                               collides=true\r
+                                       end\r
+                               end\r
+                       end\r
+               end\r
+               if collides then\r
+                       gp.recently_collided_with_env=true\r
+                       gp.velocity=-0.5*gp.velocity\r
+                       gp.tarvelocity=0\r
+               end\r
+       end\r
+       \r
+       --FIX: use index of the wagon, not of the train.\r
+       local velocity=(gp.velocity*gp.movedir)/(gp.path_dist[math.floor(index)] or 1)\r
+       local acceleration=(gp.last_accel or 0)/(gp.path_dist[math.floor(index)] or 1)\r
+       local factor=index-math.floor(index)\r
+       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,}\r
+       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}\r
+       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}\r
+       \r
+       --some additional positions to determine orientation\r
+       local aposfwd=gp.path[math.floor(index+2)]\r
+       local aposbwd=gp.path[math.floor(index-1)]\r
+       \r
+       local yaw\r
+       if aposfwd and aposbwd then\r
+               yaw=advtrains.get_wagon_yaw(aposfwd, second_pos, first_pos, aposbwd, factor)+math.pi--TODO remove when cleaning up\r
+       else\r
+               yaw=math.atan2((first_pos.x-second_pos.x), (second_pos.z-first_pos.z))\r
+       end\r
+       if self.wagon_flipped then\r
+               yaw=yaw+math.pi\r
+       end\r
+       \r
+       self.updatepct_timer=(self.updatepct_timer or 0)-dtime\r
+       if not self.old_velocity_vector \r
+                       or not vector.equals(velocityvec, self.old_velocity_vector)\r
+                       or not self.old_acceleration_vector \r
+                       or not vector.equals(accelerationvec, self.old_acceleration_vector)\r
+                       or self.old_yaw~=yaw\r
+                       or self.updatepct_timer<=0 then--only send update packet if something changed\r
+                       self.object:setpos(actual_pos)\r
+               self.object:setvelocity(velocityvec)\r
+               self.object:setacceleration(accelerationvec)\r
+               self.object:setyaw(yaw)\r
+               self.updatepct_timer=2\r
+               if self.update_animation then\r
+                       self:update_animation(gp.velocity)\r
+               end\r
+       end\r
+       \r
+       \r
+       self.old_velocity_vector=velocityvec\r
+       self.old_acceleration_vector=accelerationvec\r
+       self.old_yaw=yaw\r
+       atprintbm("wagon step", t)\r
+ end\r
\r
+ function advtrains.get_real_path_index(train, pit)\r
+       local pos_in_train_left=pit\r
+       local index=train.index\r
+       if pos_in_train_left>(index-math.floor(index))*(train.path_dist[math.floor(index)] or 1) then\r
+               pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(train.path_dist[math.floor(index)] or 1)\r
+               index=math.floor(index)\r
+               while pos_in_train_left>(train.path_dist[index-1] or 1) do\r
+                       pos_in_train_left=pos_in_train_left - (train.path_dist[index-1] or 1)\r
+                       index=index-1\r
+               end\r
+               index=index-(pos_in_train_left/(train.path_dist[index-1] or 1))\r
+       else\r
+               index=index-(pos_in_train_left/(train.path_dist[math.floor(index-1)] or 1))\r
+       end\r
+       return index\r
+ end\r
\r
+ function wagon:get_on(clicker, seatno)\r
+       if not self.seatp then\r
+               self.seatp={}\r
+       end\r
+       if not self.seats[seatno] then return end\r
+       if self.seatp[seatno] and self.seatp[seatno]~=clicker:get_player_name() then\r
+               self:get_off(seatno)\r
+       end\r
+       self.seatp[seatno] = clicker:get_player_name()\r
+       advtrains.player_to_train_mapping[clicker:get_player_name()]=self.train_id\r
+       clicker:set_attach(self.object, "", self.seats[seatno].attach_offset, {x=0,y=0,z=0})\r
+       clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset)\r
+ end\r
+ function wagon:get_off_plr(pname)\r
+       local no=self:get_seatno(pname)\r
+       if no then\r
+               self:get_off(no)\r
+       end\r
+ end\r
+ function wagon:get_seatno(pname)\r
+       for no, cont in pairs(self.seatp) do\r
+               if cont==pname then\r
+                       return no\r
+               end\r
+       end\r
+       return nil\r
+ end\r
+ function wagon:get_off(seatno)\r
+       if not self.seatp[seatno] then return end\r
+       local pname = self.seatp[seatno]\r
+       local clicker = minetest.get_player_by_name(pname)\r
+       advtrains.player_to_train_mapping[pname]=nil\r
+       advtrains.clear_driver_hud(pname)\r
+       if clicker then\r
+               clicker:set_detach()\r
+               clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})\r
+               local objpos=advtrains.round_vector_floor_y(self.object:getpos())\r
+               local yaw=self.object:getyaw()\r
+               local isx=(yaw < math.pi/4) or (yaw > 3*math.pi/4 and yaw < 5*math.pi/4) or (yaw > 7*math.pi/4)\r
+               --abuse helper function\r
+               for _,r in ipairs({-1, 1}) do\r
+                       local p=vector.add({x=isx and r or 0, y=0, z=not isx and r or 0}, objpos)\r
+                       if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then\r
+                               minetest.after(0.2, function() clicker:setpos({x=p.x, y=p.y+1, z=p.z}) end)\r
+                       end\r
+               end\r
+       end\r
+       self.seatp[seatno]=nil\r
+ end\r
+ function wagon:show_get_on_form(pname)\r
+       if not self.initialized then return end\r
+       if #self.seats==0 then\r
+               if self.has_inventory and self.get_inventory_formspec then\r
+                       minetest.show_formspec(pname, "advtrains_inv_"..self.unique_id, self:get_inventory_formspec(pname))\r
+               end\r
+               return\r
+       end\r
+       local form, comma="size[5,8]label[0.5,0.5;Select seat:]textlist[0.5,1;4,6;seat;", ""\r
+       for seatno, seattbl in ipairs(self.seats) do\r
+               local addtext, colorcode="", ""\r
+               if self.seatp and self.seatp[seatno] then\r
+                       colorcode="#FF0000"\r
+                       addtext=" ("..self.seatp[seatno]..")"\r
+               end\r
+               form=form..comma..colorcode..seattbl.name..addtext\r
+               comma=","\r
+       end\r
+       form=form..";0,false]"\r
+       if self.has_inventory and self.get_inventory_formspec then\r
+               form=form.."button_exit[1,7;3,1;inv;Show Inventory]"\r
+       end\r
+       minetest.show_formspec(pname, "advtrains_geton_"..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
+               for _,wagon in pairs(minetest.luaentities) do\r
+                       if wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then\r
+                               if fields.inv then\r
+                                       if wagon.has_inventory and wagon.get_inventory_formspec then\r
+                                               minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name()))\r
+                                       end\r
+                               elseif fields.seat then\r
+                                       local val=minetest.explode_textlist_event(fields.seat)\r
+                                       if val and val.type~="INV" and not wagon.seatp[player:get_player_name()] then\r
+                                       --get on\r
+                                               wagon:get_on(player, val.index)\r
+                                               --will work with the new close_formspec functionality. close exactly this formspec.\r
+                                               minetest.show_formspec(player:get_player_name(), formname, "")\r
+                                       end\r
+                               end\r
+                       end\r
+               end\r
+       end\r
+ end)\r
+ function wagon:reattach_all()\r
+       if not self.seatp then self.seatp={} end\r
+       for seatno, pname in pairs(self.seatp) do\r
+               local p=minetest.get_player_by_name(pname)\r
+               if p then\r
+                       self:get_on(p ,seatno)\r
+               end\r
+       end\r
+ end\r
+ minetest.register_on_joinplayer(function(player)\r
+       for _,wagon in pairs(minetest.luaentities) do\r
+               if wagon.is_wagon and wagon.initialized then\r
+                       wagon:reattach_all()\r
+               end\r
+       end\r
+ end)\r
\r
+ function advtrains.register_wagon(sysname, prototype, desc, inv_img)\r
+       setmetatable(prototype, {__index=wagon})\r
+       minetest.register_entity(":advtrains:"..sysname,prototype)\r
+       \r
+       minetest.register_craftitem(":advtrains:"..sysname, {\r
+               description = desc,\r
+               inventory_image = inv_img,\r
+               wield_image = inv_img,\r
+               stack_max = 1,\r
+               \r
+               on_place = function(itemstack, placer, pointed_thing)\r
+                       if not pointed_thing.type == "node" then\r
+                               return\r
+                       end\r
+                       \r
 -                      local ob=minetest.env:add_entity(pointed_thing.under, "advtrains:"..sysname)\r
++
++                      local node=minetest.get_node_or_nil(pointed_thing.under)\r
++                      if not node then atprint("[advtrains]Ignore at placer position") return itemstack end\r
+                       local nodename=node.name\r
+                       if(not advtrains.is_track_and_drives_on(nodename, prototype.drives_on)) then\r
+                               atprint("no track here, not placing.")\r
+                               return itemstack\r
+                       end\r
+                       local conn1=advtrains.get_track_connections(node.name, node.param2)\r
+                       local id=advtrains.create_new_train_at(pointed_thing.under, advtrains.dirCoordSet(pointed_thing.under, conn1))\r
+                       \r
++                      local ob=minetest.add_entity(pointed_thing.under, "advtrains:"..sysname)\r
+                       if not ob then\r
+                               atprint("couldn't add_entity, aborting")\r
+                       end\r
+                       local le=ob:get_luaentity()\r
+                       \r
+                       le.owner=placer:get_player_name()\r
+                       le.infotext=desc..", owned by "..placer:get_player_name()\r
+                       \r
+                       local wagon_uid=le:init_new_instance(id, {})\r
+                       \r
+                       advtrains.add_wagon_to_train(le, id)\r
+                       if not minetest.setting_getbool("creative_mode") then\r
+                               itemstack:take_item()\r
+                       end\r
+                       return itemstack\r
+                       \r
+               end,\r
+       })\r
+ end\r
\r
+ --[[\r
+       wagons can define update_animation(self, velocity) if they have a speed-dependent animation\r
+       this function will be called when the velocity vector changes or every 2 seconds.\r
+ ]]\r
\r
\r