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