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