Rewrite seating system
[advtrains.git] / advtrains / advtrains / wagons.lua
1 --atan2 counts angles clockwise, minetest does counterclockwise
2
3 minetest.register_privilege("train_place", {
4 description = "Player can place trains on tracks not owned by player",
5 give_to_singleplayer= false,
6 });
7 minetest.register_privilege("train_remove", {
8 description = "Player can remove trains not owned by player",
9 give_to_singleplayer= false,
10 });
11
12 local wagon={
13 collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
14 --physical = true,
15 visual = "mesh",
16 mesh = "wagon.b3d",
17 visual_size = {x=3, y=3},
18 textures = {"black.png"},
19 is_wagon=true,
20 wagon_span=1,--how many index units of space does this wagon consume
21 has_inventory=false,
22 }
23
24
25 function wagon:train()
26 return advtrains.trains[self.train_id]
27 end
28
29 --[[about 'initalized':
30 when initialized is false, the entity hasn't got any data yet and should wait for these to be set before doing anything
31 when loading an existing object (with staticdata), it will be set
32 when instanciating a new object via add_entity, it is not set at the time on_activate is called.
33 then, wagon:initialize() will be called
34
35 wagon will save only uid in staticdata, no serialized table
36 ]]
37 function wagon:on_activate(sd_uid, dtime_s)
38 if sd_uid~="" then
39 --destroy when loaded from static block.
40 self.object:remove()
41 return
42 end
43 self.object:set_armor_groups({immortal=1})
44 self.entity_name=self.name
45 end
46
47 function wagon:get_staticdata()
48 if not self:ensure_init() then return end
49 atprint("[wagon "..((self.unique_id and self.unique_id~="" and self.unique_id) or "no-id").."]: saving to wagon_save")
50 --serialize inventory, if it has one
51 if self.has_inventory then
52 local inv=minetest.get_inventory({type="detached", name="advtrains_wgn_"..self.unique_id})
53 self.ser_inv=advtrains.serialize_inventory(inv)
54 end
55 --save to table before being unloaded
56 advtrains.wagon_save[self.unique_id]=advtrains.merge_tables(self)
57 advtrains.wagon_save[self.unique_id].entity_name=self.name
58 advtrains.wagon_save[self.unique_id].name=nil
59 advtrains.wagon_save[self.unique_id].object=nil
60 return self.unique_id
61 end
62 --returns: uid of wagon
63 function wagon:init_new_instance(train_id, properties)
64 self.unique_id=os.time()..os.clock()
65 self.train_id=train_id
66 for k,v in pairs(properties) do
67 if k~="name" and k~="object" then
68 self[k]=v
69 end
70 end
71 self:init_shared()
72 self.initialized=true
73 atprint("init_new_instance "..self.unique_id.." ("..self.train_id..")")
74 return self.unique_id
75 end
76 function wagon:init_from_wagon_save(uid)
77 if not advtrains.wagon_save[uid] then
78 self.object:remove()
79 return
80 end
81 self.unique_id=uid
82 for k,v in pairs(advtrains.wagon_save[uid]) do
83 if k~="name" and k~="object" then
84 self[k]=v
85 end
86 end
87 if not self.train_id or not self:train() then
88 self.object:remove()
89 return
90 end
91 self:init_shared()
92 self.initialized=true
93 minetest.after(1, function() self:reattach_all() end)
94 atprint("init_from_wagon_save "..self.unique_id.." ("..self.train_id..")")
95 advtrains.update_trainpart_properties(self.train_id)
96 end
97 function wagon:init_shared()
98 if self.has_inventory then
99 local uid_noptr=self.unique_id..""
100 --to be used later
101 local inv=minetest.create_detached_inventory("advtrains_wgn_"..self.unique_id, {
102 allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
103 return count
104 end,
105 allow_put = function(inv, listname, index, stack, player)
106 return stack:get_count()
107 end,
108 allow_take = function(inv, listname, index, stack, player)
109 return stack:get_count()
110 end
111 })
112 if self.ser_inv then
113 advtrains.deserialize_inventory(self.ser_inv, inv)
114 end
115 if self.inventory_list_sizes then
116 for lst, siz in pairs(self.inventory_list_sizes) do
117 inv:set_size(lst, siz)
118 end
119 end
120 end
121 if self.doors then
122 self.door_anim_timer=0
123 self.door_state=0
124 end
125 if self.custom_on_activate then
126 self:custom_on_activate(dtime_s)
127 end
128 end
129 function wagon:ensure_init()
130 if self.initialized then
131 if self.noninitticks then self.noninitticks=nil end
132 return true
133 end
134 if not self.noninitticks then self.noninitticks=0 end
135 self.noninitticks=self.noninitticks+1
136 if self.noninitticks>20 then
137 self.object:remove()
138 else
139 self.object:setvelocity({x=0,y=0,z=0})
140 end
141 return false
142 end
143
144 -- Remove the wagon
145 function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
146 if not self:ensure_init() then return end
147 if not puncher or not puncher:is_player() then
148 return
149 end
150 if self.owner and puncher:get_player_name()~=self.owner and (not minetest.check_player_privs(puncher, {train_remove = true })) then
151 minetest.chat_send_player(puncher:get_player_name(), attrans("This wagon is owned by @1, you can't destroy it.", self.owner));
152 return
153 end
154
155 if minetest.setting_getbool("creative_mode") then
156 if not self:destroy() then return end
157
158 local inv = puncher:get_inventory()
159 if not inv:contains_item("main", self.name) then
160 inv:add_item("main", self.name)
161 end
162 else
163 local pc=puncher:get_player_control()
164 if not pc.sneak then
165 minetest.chat_send_player(puncher:get_player_name(), attrans("Warning: If you destroy this wagon, you only get some steel back! If you are sure, shift-leftclick the wagon."))
166 return
167 end
168
169 if not self:destroy() then return end
170
171 local inv = puncher:get_inventory()
172 for _,item in ipairs(self.drops or {self.name}) do
173 inv:add_item("main", item)
174 end
175 end
176 end
177 function wagon:destroy()
178 --some rules:
179 -- you get only some items back
180 -- single left-click shows warning
181 -- shift leftclick destroys
182 -- not when a driver is inside
183
184 for _,_ in pairs(self.seatp) do
185 return
186 end
187
188 if self.custom_may_destroy then
189 if not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction) then
190 return
191 end
192 end
193 if self.custom_on_destroy then
194 self.custom_on_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction)
195 end
196
197 atprint("[wagon "..((self.unique_id and self.unique_id~="" and self.unique_id) or "no-id").."]: destroying")
198
199 self.object:remove()
200
201 table.remove(self:train().trainparts, self.pos_in_trainparts)
202 advtrains.update_trainpart_properties(self.train_id)
203 advtrains.wagon_save[self.unique_id]=nil
204 if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects
205 return true
206 end
207
208
209 function wagon:on_step(dtime)
210 if not self:ensure_init() then return end
211
212 local t=os.clock()
213 local pos = self.object:getpos()
214
215 if not pos then
216 atprint("["..self.unique_id.."][fatal] missing position (object:getpos() returned nil)")
217 return
218 end
219
220 self.entity_name=self.name
221
222 --is my train still here
223 if not self.train_id or not self:train() then
224 atprint("[wagon "..self.unique_id.."] missing train_id, destroying")
225 self.object:remove()
226 return
227 elseif not self.initialized then
228 self.initialized=true
229 end
230 if not self.seatp then
231 self.seatp={}
232 end
233
234 --custom on_step function
235 if self.custom_on_step then
236 self:custom_on_step(self, dtime)
237 end
238
239 --driver control
240 for seatno, seat in ipairs(self.seats) do
241 if seat.driving_ctrl_access then
242 local driver=self.seatp[seatno] and minetest.get_player_by_name(self.seatp[seatno])
243 local get_off_pressed=false
244 if driver and driver:get_player_control_bits()~=self.old_player_control_bits then
245 local pc=driver:get_player_control()
246
247 advtrains.on_control_change(pc, self:train(), self.wagon_flipped)
248 if pc.aux1 and pc.sneak then
249 get_off_pressed=true
250 end
251
252 self.old_player_control_bits=driver:get_player_control_bits()
253 end
254 if driver then
255 if get_off_pressed then
256 self:get_off(seatno)
257 else
258 advtrains.update_driver_hud(driver:get_player_name(), self:train(), self.wagon_flipped)
259 end
260 end
261 end
262 end
263
264 local gp=self:train()
265
266 --door animation
267 if self.doors then
268 if (self.door_anim_timer or 0)<=0 then
269 local fct=self.wagon_flipped and -1 or 1
270 local dstate = (gp.door_open or 0) * fct
271 if dstate ~= self.door_state then
272 local at
273 --meaning of the train.door_open field:
274 -- -1: left doors (rel. to train orientation)
275 -- 0: closed
276 -- 1: right doors
277 --this code produces the following behavior:
278 -- if changed from 0 to +-1, play open anim. if changed from +-1 to 0, play close.
279 -- if changed from +-1 to -+1, first close and set 0, then it will detect state change again and run open.
280 if self.door_state == 0 then
281 at=self.doors.open[dstate]
282 self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false)
283 self.door_state = dstate
284 else
285 at=self.doors.close[self.door_state or 1]--in case it has not been set yet
286 self.object:set_animation(at.frames, at.speed or 15, at.blend or 0, false)
287 self.door_state = 0
288 end
289 self.door_anim_timer = at.time
290 end
291 else
292 self.door_anim_timer = (self.door_anim_timer or 0) - dtime
293 end
294 end
295 --DisCouple
296 if self.pos_in_trainparts and self.pos_in_trainparts>1 then
297 if gp.velocity==0 then
298 if not self.discouple or not self.discouple.object:getyaw() then
299 local object=minetest.add_entity(pos, "advtrains:discouple")
300 if object then
301 local le=object:get_luaentity()
302 le.wagon=self
303 --box is hidden when attached, so unuseful.
304 --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
305 self.discouple=le
306 else
307 atprint("Couldn't spawn DisCouple")
308 end
309 end
310 else
311 if self.discouple and self.discouple.object:getyaw() then
312 self.discouple.object:remove()
313 end
314 end
315 end
316 --for path to be available. if not, skip step
317 if not advtrains.get_or_create_path(self.train_id, gp) then
318 self.object:setvelocity({x=0, y=0, z=0})
319 return
320 end
321 if not self.pos_in_train then
322 --why ever. but better continue next step...
323 advtrains.update_trainpart_properties(self.train_id)
324 return
325 end
326
327 local index=advtrains.get_real_path_index(self:train(), self.pos_in_train)
328 --atprint("trainindex "..gp.index.." wagonindex "..index)
329
330 --position recalculation
331 local first_pos=gp.path[math.floor(index)]
332 local second_pos=gp.path[math.floor(index)+1]
333 if not first_pos or not second_pos then
334 --atprint(" object "..self.unique_id.." path end reached!")
335 self.object:setvelocity({x=0,y=0,z=0})
336 return
337 end
338
339 --checking for environment collisions(a 3x3 cube around the center)
340 if not gp.recently_collided_with_env then
341 local collides=false
342 for x=-1,1 do
343 for y=0,2 do
344 for z=-1,1 do
345 local node=minetest.get_node_or_nil(vector.add(first_pos, {x=x, y=y, z=z}))
346 if (advtrains.train_collides(node)) then
347 collides=true
348 end
349 end
350 end
351 end
352 if collides then
353 if self.collision_count and self.collision_count>10 then
354 --enable collision mercy to get trains stuck in walls out of walls
355 --actually do nothing except limiting the velocity to 1
356 gp.velocity=math.min(gp.velocity, 1)
357 gp.tarvelocity=math.min(gp.tarvelocity, 1)
358 else
359 gp.recently_collided_with_env=true
360 gp.velocity=2*gp.velocity
361 gp.movedir=-gp.movedir
362 gp.tarvelocity=0
363 self.collision_count=(self.collision_count or 0)+1
364 end
365 else
366 self.collision_count=nil
367 end
368 end
369
370 --FIX: use index of the wagon, not of the train.
371 local velocity=(gp.velocity*gp.movedir)/(gp.path_dist[math.floor(index)] or 1)
372 local acceleration=(gp.last_accel or 0)/(gp.path_dist[math.floor(index)] or 1)
373 local factor=index-math.floor(index)
374 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,}
375 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}
376 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}
377
378 --some additional positions to determine orientation
379 local aposfwd=gp.path[math.floor(index+2)]
380 local aposbwd=gp.path[math.floor(index-1)]
381
382 local yaw
383 if aposfwd and aposbwd then
384 yaw=advtrains.get_wagon_yaw(aposfwd, second_pos, first_pos, aposbwd, factor)+math.pi--TODO remove when cleaning up
385 else
386 yaw=math.atan2((first_pos.x-second_pos.x), (second_pos.z-first_pos.z))
387 end
388 if self.wagon_flipped then
389 yaw=yaw+math.pi
390 end
391
392 self.updatepct_timer=(self.updatepct_timer or 0)-dtime
393 if not self.old_velocity_vector
394 or not vector.equals(velocityvec, self.old_velocity_vector)
395 or not self.old_acceleration_vector
396 or not vector.equals(accelerationvec, self.old_acceleration_vector)
397 or self.old_yaw~=yaw
398 or self.updatepct_timer<=0 then--only send update packet if something changed
399 self.object:setpos(actual_pos)
400 self.object:setvelocity(velocityvec)
401 self.object:setacceleration(accelerationvec)
402 self.object:setyaw(yaw)
403 self.updatepct_timer=2
404 if self.update_animation then
405 self:update_animation(gp.velocity)
406 end
407 end
408
409
410 self.old_velocity_vector=velocityvec
411 self.old_acceleration_vector=accelerationvec
412 self.old_yaw=yaw
413 atprintbm("wagon step", t)
414 end
415
416 function advtrains.get_real_path_index(train, pit)
417 local pos_in_train_left=pit
418 local index=train.index
419 if pos_in_train_left>(index-math.floor(index))*(train.path_dist[math.floor(index)] or 1) then
420 pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(train.path_dist[math.floor(index)] or 1)
421 index=math.floor(index)
422 while pos_in_train_left>(train.path_dist[index-1] or 1) do
423 pos_in_train_left=pos_in_train_left - (train.path_dist[index-1] or 1)
424 index=index-1
425 end
426 index=index-(pos_in_train_left/(train.path_dist[index-1] or 1))
427 else
428 index=index-(pos_in_train_left/(train.path_dist[math.floor(index-1)] or 1))
429 end
430 return index
431 end
432
433 function wagon:on_rightclick(clicker)
434 if not self:ensure_init() then return end
435 if not clicker or not clicker:is_player() then
436 return
437 end
438 if clicker:get_player_control().aux1 then
439 --advtrains.dumppath(self:train().path)
440 --minetest.chat_send_all("at index "..(self:train().index or "nil"))
441 --advtrains.invert_train(self.train_id)
442 atprint(dump(self))
443 return
444 end
445 local pname=clicker:get_player_name()
446 local no=self:get_seatno(pname)
447 if no then
448 if self.seat_groups then
449 local poss={}
450 local sgr=self.seats[no].group
451 for _,access in ipairs(self.seat_groups[sgr].access_to) do
452 if self:check_seat_group_access(pname, access) then
453 poss[#poss+1]={name=self.seat_groups[access].name, key="sgr_"..access}
454 end
455 end
456 if self.has_inventory and self.get_inventory_formspec then
457 poss[#poss+1]={name="Show inventory", key="inv"}
458 end
459 if self.owner==pname then
460 poss[#poss+1]={name="Wagon properties", key="prop"}
461 end
462 if not self.seat_groups[sgr].require_doors_open or self:train().door_open~=0 then
463 poss[#poss+1]={name="Get off", key="off"}
464 end
465 if #poss==0 then
466 --can't do anything.
467 elseif #poss==1 then
468 self:seating_from_key_helper(pname, {[poss[1].key]=true}, no)
469 else
470 local form = "size[5,"..1+(#poss).."]"
471 for pos,ent in ipairs(poss) do
472 form = form .. "button_exit[0.5,"..(pos-0.5)..";4,1;"..ent.key..";"..ent.name.."]"
473 end
474 minetest.show_formspec(pname, "advtrains_seating_"..self.unique_id, form)
475 end
476 else
477 self:get_off(no)
478 end
479 else
480 if self.seat_groups then
481 if #self.seats==0 then
482 if self.has_inventory and self.get_inventory_formspec then
483 minetest.show_formspec(pname, "advtrains_inv_"..self.unique_id, self:get_inventory_formspec(pname))
484 end
485 return
486 end
487
488 local doors_open = self:train().door_open~=0
489 for _,sgr in ipairs(self.assign_to_seat_group) do
490 if self:check_seat_group_access(pname, sgr) then
491 for seatid, seatdef in ipairs(self.seats) do
492 atprint(sgr, seatid, seatdef, self.seat_groups[sgr], doors_open)
493 if seatdef.group==sgr and not self.seatp[seatid] and (not self.seat_groups[sgr].require_doors_open or doors_open) then
494 self:get_on(clicker, seatid)
495 return
496 end
497 end
498 end
499 end
500 minetest.chat_send_player(pname, "Can't get on: wagon full or doors closed!")
501 else
502 self:show_get_on_form(pname)
503 end
504 end
505 end
506
507 function wagon:get_on(clicker, seatno)
508 if not self.seatp then
509 self.seatp={}
510 end
511 if not self.seats[seatno] then return end
512 local oldno=self:get_seatno(clicker:get_player_name())
513 if oldno then
514 atprint("get_on: clearing oldno",seatno)
515 advtrains.player_to_train_mapping[clicker:get_player_name()]=nil
516 advtrains.clear_driver_hud(clicker:get_player_name())
517 self.seatp[oldno]=nil
518 end
519 if self.seatp[seatno] and self.seatp[seatno]~=clicker:get_player_name() then
520 atprint("get_on: throwing off",self.seatp[seatno],"from seat",seatno)
521 self:get_off(seatno)
522 end
523 atprint("get_on: attaching",clicker:get_player_name())
524 self.seatp[seatno] = clicker:get_player_name()
525 advtrains.player_to_train_mapping[clicker:get_player_name()]=self.train_id
526 clicker:set_attach(self.object, "", self.seats[seatno].attach_offset, {x=0,y=0,z=0})
527 clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset)
528 end
529 function wagon:get_off_plr(pname)
530 local no=self:get_seatno(pname)
531 if no then
532 self:get_off(no)
533 end
534 end
535 function wagon:get_seatno(pname)
536 for no, cont in pairs(self.seatp) do
537 if cont==pname then
538 return no
539 end
540 end
541 return nil
542 end
543 function wagon:get_off(seatno)
544 if not self.seatp[seatno] then return end
545 local pname = self.seatp[seatno]
546 local clicker = minetest.get_player_by_name(pname)
547 advtrains.player_to_train_mapping[pname]=nil
548 advtrains.clear_driver_hud(pname)
549 if clicker then
550 atprint("get_off: detaching",clicker:get_player_name())
551 clicker:set_detach()
552 clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
553 local objpos=advtrains.round_vector_floor_y(self.object:getpos())
554 local yaw=self.object:getyaw()
555 local isx=(yaw < math.pi/4) or (yaw > 3*math.pi/4 and yaw < 5*math.pi/4) or (yaw > 7*math.pi/4)
556 --abuse helper function
557 for _,r in ipairs({-1, 1}) do
558 local p=vector.add({x=isx and r or 0, y=0, z=not isx and r or 0}, objpos)
559 if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then
560 minetest.after(0.2, function() clicker:setpos({x=p.x, y=p.y+1, z=p.z}) end)
561 end
562 end
563 end
564 self.seatp[seatno]=nil
565 end
566 function wagon:show_get_on_form(pname)
567 if not self.initialized then return end
568 if #self.seats==0 then
569 if self.has_inventory and self.get_inventory_formspec then
570 minetest.show_formspec(pname, "advtrains_inv_"..self.unique_id, self:get_inventory_formspec(pname))
571 end
572 return
573 end
574 local form, comma="size[5,8]label[0.5,0.5;"..attrans("Select seat:").."]textlist[0.5,1;4,6;seat;", ""
575 for seatno, seattbl in ipairs(self.seats) do
576 local addtext, colorcode="", ""
577 if self.seatp and self.seatp[seatno] then
578 colorcode="#FF0000"
579 addtext=" ("..self.seatp[seatno]..")"
580 end
581 form=form..comma..colorcode..seattbl.name..addtext
582 comma=","
583 end
584 form=form..";0,false]"
585 if self.has_inventory and self.get_inventory_formspec then
586 form=form.."button_exit[1,7;3,1;inv;"..attrans("Show Inventory").."]"
587 end
588 minetest.show_formspec(pname, "advtrains_geton_"..self.unique_id, form)
589 end
590 minetest.register_on_player_receive_fields(function(player, formname, fields)
591 local uid=string.match(formname, "^advtrains_geton_(.+)$")
592 if uid then
593 for _,wagon in pairs(minetest.luaentities) do
594 if wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then
595 if fields.inv then
596 if wagon.has_inventory and wagon.get_inventory_formspec then
597 minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name()))
598 end
599 elseif fields.seat then
600 local val=minetest.explode_textlist_event(fields.seat)
601 if val and val.type~="INV" and not wagon.seatp[player:get_player_name()] then
602 --get on
603 wagon:get_on(player, val.index)
604 --will work with the new close_formspec functionality. close exactly this formspec.
605 minetest.show_formspec(player:get_player_name(), formname, "")
606 end
607 end
608 end
609 end
610 end
611 uid=string.match(formname, "^advtrains_seating_(.+)$")
612 if uid then
613 for _,wagon in pairs(minetest.luaentities) do
614 if wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then
615 local pname=player:get_player_name()
616 local no=wagon:get_seatno(pname)
617 if no then
618 if wagon.seat_groups then
619 wagon:seating_from_key_helper(pname, fields, no)
620 end
621 end
622 end
623 end
624 end
625 end)
626 function wagon:seating_from_key_helper(pname, fields, no)
627 local sgr=self.seats[no].group
628 for _,access in ipairs(self.seat_groups[sgr].access_to) do
629 if fields["sgr_"..access] and self:check_seat_group_access(pname, access) then
630 for seatid, seatdef in ipairs(self.seats) do
631 if seatdef.group==access and not self.seatp[seatid] then
632 self:get_on(minetest.get_player_by_name(pname), seatid)
633 return
634 end
635 end
636 end
637 end
638 if fields.inv and self.has_inventory and self.get_inventory_formspec then
639 minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..self.unique_id, wagon:get_inventory_formspec(player:get_player_name()))
640 end
641 if fields.prop and self.owner==pname then
642 self:show_wagon_properties(pname)
643 end
644 if fields.off and (not self.seat_groups[sgr].require_doors_open or self:train().door_open~=0) then
645 self:get_off(no)
646 end
647 end
648 function wagon:check_seat_group_access(pname, sgr)
649 --TODO implement
650 return sgr~="driverstand" or pname=="orwell"
651 end
652 function wagon:reattach_all()
653 if not self.seatp then self.seatp={} end
654 for seatno, pname in pairs(self.seatp) do
655 local p=minetest.get_player_by_name(pname)
656 if p then
657 self:get_on(p ,seatno)
658 end
659 end
660 end
661 minetest.register_on_joinplayer(function(player)
662 for _,wagon in pairs(minetest.luaentities) do
663 if wagon.is_wagon and wagon.initialized then
664 wagon:reattach_all()
665 end
666 end
667 end)
668
669 function advtrains.register_wagon(sysname, prototype, desc, inv_img)
670 setmetatable(prototype, {__index=wagon})
671 minetest.register_entity(":advtrains:"..sysname,prototype)
672
673 minetest.register_craftitem(":advtrains:"..sysname, {
674 description = desc,
675 inventory_image = inv_img,
676 wield_image = inv_img,
677 stack_max = 1,
678
679 on_place = function(itemstack, placer, pointed_thing)
680 if not pointed_thing.type == "node" then
681 return
682 end
683
684
685 local node=minetest.get_node_or_nil(pointed_thing.under)
686 if not node then atprint("[advtrains]Ignore at placer position") return itemstack end
687 local nodename=node.name
688 if(not advtrains.is_track_and_drives_on(nodename, prototype.drives_on)) then
689 atprint("no track here, not placing.")
690 return itemstack
691 end
692 local conn1=advtrains.get_track_connections(node.name, node.param2)
693 local id=advtrains.create_new_train_at(pointed_thing.under, advtrains.dirCoordSet(pointed_thing.under, conn1))
694
695 local ob=minetest.add_entity(pointed_thing.under, "advtrains:"..sysname)
696 if not ob then
697 atprint("couldn't add_entity, aborting")
698 end
699 local le=ob:get_luaentity()
700
701 le.owner=placer:get_player_name()
702 le.infotext=desc..", owned by "..placer:get_player_name()
703
704 local wagon_uid=le:init_new_instance(id, {})
705
706 advtrains.add_wagon_to_train(le, id)
707 if not minetest.setting_getbool("creative_mode") then
708 itemstack:take_item()
709 end
710 return itemstack
711
712 end,
713 })
714 end
715
716 --[[
717 wagons can define update_animation(self, velocity) if they have a speed-dependent animation
718 this function will be called when the velocity vector changes or every 2 seconds.
719 ]]
720
721