Clean up debug message code
[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 return true end
155 self.object:setvelocity({x=0,y=0,z=0})
156 return false
157 end
158
159 -- Remove the wagon
160 function wagon:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
161 if not self:ensure_init() then return end
162 if not puncher or not puncher:is_player() then
163 return
164 end
165 if self.owner and puncher:get_player_name()~=self.owner then
166 minetest.chat_send_player(puncher:get_player_name(), "This wagon is owned by "..self.owner..", you can't destroy it.")
167 return
168 end
169
170 if minetest.setting_getbool("creative_mode") then
171 if not self:destroy() then return end
172
173 local inv = puncher:get_inventory()
174 if not inv:contains_item("main", self.name) then
175 inv:add_item("main", self.name)
176 end
177 else
178 local pc=puncher:get_player_control()
179 if not pc.sneak then
180 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.")
181 return
182 end
183
184 if not self:destroy() then return end
185
186 local inv = puncher:get_inventory()
187 for _,item in ipairs(self.drops or {self.name}) do
188 inv:add_item("main", item)
189 end
190 end
191 end
192 function wagon:destroy()
193 --some rules:
194 -- you get only some items back
195 -- single left-click shows warning
196 -- shift leftclick destroys
197 -- not when a driver is inside
198
199 for _,_ in pairs(self.seatp) do
200 return
201 end
202
203 if self.custom_may_destroy then
204 if not self.custom_may_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction) then
205 return
206 end
207 end
208 if self.custom_on_destroy then
209 self.custom_on_destroy(self, puncher, time_from_last_punch, tool_capabilities, direction)
210 end
211
212 atprint("[wagon "..((self.unique_id and self.unique_id~="" and self.unique_id) or "no-id").."]: destroying")
213
214 self.object:remove()
215
216 table.remove(self:train().trainparts, self.pos_in_trainparts)
217 advtrains.update_trainpart_properties(self.train_id)
218 advtrains.wagon_save[self.unique_id]=nil
219 if self.discouple then self.discouple.object:remove() end--will have no effect on unloaded objects
220 return true
221 end
222
223
224 function wagon:on_step(dtime)
225 if not self:ensure_init() then return end
226
227 local t=os.clock()
228 local pos = self.object:getpos()
229
230 if not pos then
231 atprint("["..self.unique_id.."][fatal] missing position (object:getpos() returned nil)")
232 return
233 end
234
235 self.entity_name=self.name
236
237 --is my train still here
238 if not self.train_id or not self:train() then
239 atprint("[wagon "..self.unique_id.."] missing train_id, destroying")
240 self.object:remove()
241 return
242 elseif not self.initialized then
243 self.initialized=true
244 end
245 if not self.seatp then
246 self.seatp={}
247 end
248
249 --custom on_step function
250 if self.custom_on_step then
251 self:custom_on_step(self, dtime)
252 end
253
254 --driver control
255 for seatno, seat in ipairs(self.seats) do
256 if seat.driving_ctrl_access then
257 local driver=self.seatp[seatno] and minetest.get_player_by_name(self.seatp[seatno])
258 local get_off_pressed=false
259 if driver and driver:get_player_control_bits()~=self.old_player_control_bits then
260 local pc=driver:get_player_control()
261
262 advtrains.on_control_change(pc, self:train(), self.wagon_flipped)
263 if pc.aux1 and pc.sneak then
264 get_off_pressed=true
265 end
266
267 self.old_player_control_bits=driver:get_player_control_bits()
268 end
269 if driver then
270 if get_off_pressed then
271 self:get_off(seatno)
272 else
273 advtrains.update_driver_hud(driver:get_player_name(), self:train(), self.wagon_flipped)
274 end
275 end
276 end
277 end
278
279 local gp=self:train()
280
281 --DisCouple
282 if self.pos_in_trainparts and self.pos_in_trainparts>1 then
283 if gp.velocity==0 then
284 if not self.discouple or not self.discouple.object:getyaw() then
285 local object=minetest.add_entity(pos, "advtrains:discouple")
286 if object then
287 local le=object:get_luaentity()
288 le.wagon=self
289 --box is hidden when attached, so unuseful.
290 --object:set_attach(self.object, "", {x=0, y=0, z=self.wagon_span*10}, {x=0, y=0, z=0})
291 self.discouple=le
292 else
293 atprint("Couldn't spawn DisCouple")
294 end
295 end
296 else
297 if self.discouple and self.discouple.object:getyaw() then
298 self.discouple.object:remove()
299 end
300 end
301 end
302 --for path to be available. if not, skip step
303 if not advtrains.get_or_create_path(self.train_id, gp) then
304 self.object:setvelocity({x=0, y=0, z=0})
305 return
306 end
307 if not self.pos_in_train then
308 --why ever. but better continue next step...
309 advtrains.update_trainpart_properties(self.train_id)
310 return
311 end
312
313 local index=advtrains.get_real_path_index(self:train(), self.pos_in_train)
314 --atprint("trainindex "..gp.index.." wagonindex "..index)
315
316 --position recalculation
317 local first_pos=gp.path[math.floor(index)]
318 local second_pos=gp.path[math.floor(index)+1]
319 if not first_pos or not second_pos then
320 --atprint(" object "..self.unique_id.." path end reached!")
321 self.object:setvelocity({x=0,y=0,z=0})
322 return
323 end
324
325 --checking for environment collisions(a 3x3 cube around the center)
326 if not gp.recently_collided_with_env then
327 local collides=false
328 for x=-1,1 do
329 for y=0,2 do
330 for z=-1,1 do
331 local node=minetest.get_node_or_nil(vector.add(first_pos, {x=x, y=y, z=z}))
332 if (advtrains.train_collides(node)) then
333 collides=true
334 end
335 end
336 end
337 end
338 if collides then
339 gp.recently_collided_with_env=true
340 gp.velocity=-0.5*gp.velocity
341 gp.tarvelocity=0
342 end
343 end
344
345 --FIX: use index of the wagon, not of the train.
346 local velocity=(gp.velocity*gp.movedir)/(gp.path_dist[math.floor(index)] or 1)
347 local acceleration=(gp.last_accel or 0)/(gp.path_dist[math.floor(index)] or 1)
348 local factor=index-math.floor(index)
349 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,}
350 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}
351 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}
352
353 --some additional positions to determine orientation
354 local aposfwd=gp.path[math.floor(index+2)]
355 local aposbwd=gp.path[math.floor(index-1)]
356
357 local yaw
358 if aposfwd and aposbwd then
359 yaw=advtrains.get_wagon_yaw(aposfwd, second_pos, first_pos, aposbwd, factor)+math.pi--TODO remove when cleaning up
360 else
361 yaw=math.atan2((first_pos.x-second_pos.x), (second_pos.z-first_pos.z))
362 end
363 if self.wagon_flipped then
364 yaw=yaw+math.pi
365 end
366
367 self.updatepct_timer=(self.updatepct_timer or 0)-dtime
368 if not self.old_velocity_vector
369 or not vector.equals(velocityvec, self.old_velocity_vector)
370 or not self.old_acceleration_vector
371 or not vector.equals(accelerationvec, self.old_acceleration_vector)
372 or self.old_yaw~=yaw
373 or self.updatepct_timer<=0 then--only send update packet if something changed
374 self.object:setpos(actual_pos)
375 self.object:setvelocity(velocityvec)
376 self.object:setacceleration(accelerationvec)
377 self.object:setyaw(yaw)
378 self.updatepct_timer=2
379 if self.update_animation then
380 self:update_animation(gp.velocity)
381 end
382 end
383
384
385 self.old_velocity_vector=velocityvec
386 self.old_acceleration_vector=accelerationvec
387 self.old_yaw=yaw
388 atprintbm("wagon step", t)
389 end
390
391 function advtrains.get_real_path_index(train, pit)
392 local pos_in_train_left=pit
393 local index=train.index
394 if pos_in_train_left>(index-math.floor(index))*(train.path_dist[math.floor(index)] or 1) then
395 pos_in_train_left=pos_in_train_left - (index-math.floor(index))*(train.path_dist[math.floor(index)] or 1)
396 index=math.floor(index)
397 while pos_in_train_left>(train.path_dist[index-1] or 1) do
398 pos_in_train_left=pos_in_train_left - (train.path_dist[index-1] or 1)
399 index=index-1
400 end
401 index=index-(pos_in_train_left/(train.path_dist[index-1] or 1))
402 else
403 index=index-(pos_in_train_left/(train.path_dist[math.floor(index-1)] or 1))
404 end
405 return index
406 end
407
408 function wagon:get_on(clicker, seatno)
409 if not self.seatp then
410 self.seatp={}
411 end
412 if not self.seats[seatno] then return end
413 if self.seatp[seatno] and self.seatp[seatno]~=clicker:get_player_name() then
414 self:get_off(seatno)
415 end
416 self.seatp[seatno] = clicker:get_player_name()
417 advtrains.player_to_train_mapping[clicker:get_player_name()]=self.train_id
418 clicker:set_attach(self.object, "", self.seats[seatno].attach_offset, {x=0,y=0,z=0})
419 clicker:set_eye_offset(self.seats[seatno].view_offset, self.seats[seatno].view_offset)
420 end
421 function wagon:get_off_plr(pname)
422 local no=self:get_seatno(pname)
423 if no then
424 self:get_off(no)
425 end
426 end
427 function wagon:get_seatno(pname)
428 for no, cont in pairs(self.seatp) do
429 if cont==pname then
430 return no
431 end
432 end
433 return nil
434 end
435 function wagon:get_off(seatno)
436 if not self.seatp[seatno] then return end
437 local pname = self.seatp[seatno]
438 local clicker = minetest.get_player_by_name(pname)
439 advtrains.player_to_train_mapping[pname]=nil
440 advtrains.clear_driver_hud(pname)
441 if clicker then
442 clicker:set_detach()
443 clicker:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
444 local objpos=advtrains.round_vector_floor_y(self.object:getpos())
445 local yaw=self.object:getyaw()
446 local isx=(yaw < math.pi/4) or (yaw > 3*math.pi/4 and yaw < 5*math.pi/4) or (yaw > 7*math.pi/4)
447 --abuse helper function
448 for _,r in ipairs({-1, 1}) do
449 local p=vector.add({x=isx and r or 0, y=0, z=not isx and r or 0}, objpos)
450 if minetest.get_item_group(minetest.get_node(p).name, "platform")>0 then
451 minetest.after(0.2, function() clicker:setpos({x=p.x, y=p.y+1, z=p.z}) end)
452 end
453 end
454 end
455 self.seatp[seatno]=nil
456 end
457 function wagon:show_get_on_form(pname)
458 if not self.initialized then return end
459 if #self.seats==0 then
460 if self.has_inventory and self.get_inventory_formspec then
461 minetest.show_formspec(pname, "advtrains_inv_"..self.unique_id, self:get_inventory_formspec(pname))
462 end
463 return
464 end
465 local form, comma="size[5,8]label[0.5,0.5;Select seat:]textlist[0.5,1;4,6;seat;", ""
466 for seatno, seattbl in ipairs(self.seats) do
467 local addtext, colorcode="", ""
468 if self.seatp and self.seatp[seatno] then
469 colorcode="#FF0000"
470 addtext=" ("..self.seatp[seatno]..")"
471 end
472 form=form..comma..colorcode..seattbl.name..addtext
473 comma=","
474 end
475 form=form..";0,false]"
476 if self.has_inventory and self.get_inventory_formspec then
477 form=form.."button_exit[1,7;3,1;inv;Show Inventory]"
478 end
479 minetest.show_formspec(pname, "advtrains_geton_"..self.unique_id, form)
480 end
481 minetest.register_on_player_receive_fields(function(player, formname, fields)
482 local uid=string.match(formname, "^advtrains_geton_(.+)$")
483 if uid then
484 for _,wagon in pairs(minetest.luaentities) do
485 if wagon.is_wagon and wagon.initialized and wagon.unique_id==uid then
486 if fields.inv then
487 if wagon.has_inventory and wagon.get_inventory_formspec then
488 minetest.show_formspec(player:get_player_name(), "advtrains_inv_"..uid, wagon:get_inventory_formspec(player:get_player_name()))
489 end
490 elseif fields.seat then
491 local val=minetest.explode_textlist_event(fields.seat)
492 if val and val.type~="INV" and not wagon.seatp[player:get_player_name()] then
493 --get on
494 wagon:get_on(player, val.index)
495 --will work with the new close_formspec functionality. close exactly this formspec.
496 minetest.show_formspec(player:get_player_name(), formname, "")
497 end
498 end
499 end
500 end
501 end
502 end)
503 function wagon:reattach_all()
504 if not self.seatp then self.seatp={} end
505 for seatno, pname in pairs(self.seatp) do
506 local p=minetest.get_player_by_name(pname)
507 if p then
508 self:get_on(p ,seatno)
509 end
510 end
511 end
512 minetest.register_on_joinplayer(function(player)
513 for _,wagon in pairs(minetest.luaentities) do
514 if wagon.is_wagon and wagon.initialized then
515 wagon:reattach_all()
516 end
517 end
518 end)
519
520 function advtrains.register_wagon(sysname, prototype, desc, inv_img)
521 setmetatable(prototype, {__index=wagon})
522 minetest.register_entity(":advtrains:"..sysname,prototype)
523
524 minetest.register_craftitem(":advtrains:"..sysname, {
525 description = desc,
526 inventory_image = inv_img,
527 wield_image = inv_img,
528 stack_max = 1,
529
530 on_place = function(itemstack, placer, pointed_thing)
531 if not pointed_thing.type == "node" then
532 return
533 end
534
535 local node=minetest.env:get_node_or_nil(pointed_thing.under)
536 if not node then atprint("Ignore at placer position") return itemstack end
537 local nodename=node.name
538 if(not advtrains.is_track_and_drives_on(nodename, prototype.drives_on)) then
539 atprint("no track here, not placing.")
540 return itemstack
541 end
542 local conn1=advtrains.get_track_connections(node.name, node.param2)
543 local id=advtrains.create_new_train_at(pointed_thing.under, advtrains.dirCoordSet(pointed_thing.under, conn1))
544
545 local ob=minetest.env:add_entity(pointed_thing.under, "advtrains:"..sysname)
546 if not ob then
547 atprint("couldn't add_entity, aborting")
548 end
549 local le=ob:get_luaentity()
550
551 le.owner=placer:get_player_name()
552 le.infotext=desc..", owned by "..placer:get_player_name()
553
554 local wagon_uid=le:init_new_instance(id, {})
555
556 advtrains.add_wagon_to_train(le, id)
557 if not minetest.setting_getbool("creative_mode") then
558 itemstack:take_item()
559 end
560 return itemstack
561
562 end,
563 })
564 end
565
566 --[[
567 wagons can define update_animation(self, velocity) if they have a speed-dependent animation
568 this function will be called when the velocity vector changes or every 2 seconds.
569 ]]
570
571