ac870c0e49977b3d47292d551d8e3f42e4a64167
[advtrains.git] / advtrains / advtrains / trainlogic.lua
1 --trainlogic.lua
2 --controls train entities stuff about connecting/disconnecting/colliding trains and other things
3
4
5 local benchmark=false
6 local bm={}
7 local bmlt=0
8 local bmsteps=0
9 local bmstepint=200
10 atprintbm=function(action, ta)
11 if not benchmark then return end
12 local t=(os.clock()-ta)*1000
13 if not bm[action] then
14 bm[action]=t
15 else
16 bm[action]=bm[action]+t
17 end
18 bmlt=bmlt+t
19 end
20 function endstep()
21 if not benchmark then return end
22 bmsteps=bmsteps-1
23 if bmsteps<=0 then
24 bmsteps=bmstepint
25 for key, value in pairs(bm) do
26 minetest.chat_send_all(key.." "..(value/bmstepint).." ms avg.")
27 end
28 minetest.chat_send_all("Total time consumed by all advtrains actions per step: "..(bmlt/bmstepint).." ms avg.")
29 bm={}
30 bmlt=0
31 end
32 end
33
34 advtrains.train_accel_force=2--per second and divided by number of wagons
35 advtrains.train_brake_force=3--per second, not divided by number of wagons
36 advtrains.train_roll_force=0.5--per second, not divided by number of wagons, acceleration when rolling without brake
37 advtrains.train_emerg_force=10--for emergency brakes(when going off track)
38
39 advtrains.audit_interval=10
40
41
42 advtrains.trains={}
43 advtrains.wagon_save={}
44
45 --load initially
46 advtrains.fpath=minetest.get_worldpath().."/advtrains"
47 local file, err = io.open(advtrains.fpath, "r")
48 if not file then
49 local er=err or "Unknown Error"
50 atprint("[advtrains]Failed loading advtrains save file "..er)
51 else
52 local tbl = minetest.deserialize(file:read("*a"))
53 if type(tbl) == "table" then
54 advtrains.trains=tbl
55 end
56 file:close()
57 end
58 advtrains.fpath_ws=minetest.get_worldpath().."/advtrains_wagon_save"
59 local file, err = io.open(advtrains.fpath_ws, "r")
60 if not file then
61 local er=err or "Unknown Error"
62 atprint("[advtrains]Failed loading advtrains save file "..er)
63 else
64 local tbl = minetest.deserialize(file:read("*a"))
65 if type(tbl) == "table" then
66 advtrains.wagon_save=tbl
67 end
68 file:close()
69 end
70
71
72 advtrains.save = function()
73 atprint("[advtrains]saving")
74 advtrains.invalidate_all_paths()
75 local datastr = minetest.serialize(advtrains.trains)
76 if not datastr then
77 minetest.log("error", "[advtrains] Failed to serialize train data!")
78 return
79 end
80 local file, err = io.open(advtrains.fpath, "w")
81 if err then
82 return err
83 end
84 file:write(datastr)
85 file:close()
86
87 -- update wagon saves
88 for _,wagon in pairs(minetest.luaentities) do
89 if wagon.is_wagon and wagon.initialized then
90 wagon:get_staticdata()
91 end
92 end
93 --cross out userdata
94 for w_id, data in pairs(advtrains.wagon_save) do
95 data.name=nil
96 data.object=nil
97 if data.driver then
98 data.driver_name=data.driver:get_player_name()
99 data.driver=nil
100 else
101 data.driver_name=nil
102 end
103 if data.discouple then
104 data.discouple.object:remove()
105 data.discouple=nil
106 end
107 end
108 --atprint(dump(advtrains.wagon_save))
109 datastr = minetest.serialize(advtrains.wagon_save)
110 if not datastr then
111 minetest.log("error", "[advtrains] Failed to serialize train data!")
112 return
113 end
114 file, err = io.open(advtrains.fpath_ws, "w")
115 if err then
116 return err
117 end
118 file:write(datastr)
119 file:close()
120
121 advtrains.save_trackdb()
122 advtrains.atc.save()
123 end
124 minetest.register_on_shutdown(advtrains.save)
125
126 advtrains.save_and_audit_timer=advtrains.audit_interval
127 minetest.register_globalstep(function(dtime)
128 advtrains.save_and_audit_timer=advtrains.save_and_audit_timer-dtime
129 if advtrains.save_and_audit_timer<=0 then
130 local t=os.clock()
131
132 --save
133 advtrains.save()
134 advtrains.save_and_audit_timer=advtrains.audit_interval
135 atprintbm("saving", t)
136 end
137 --regular train step
138 local t=os.clock()
139 for k,v in pairs(advtrains.trains) do
140 advtrains.train_step(k, v, dtime)
141 end
142
143 --see tracks.lua
144 if advtrains.detector.clean_step_before then
145 advtrains.detector.finalize_restore()
146 end
147
148 atprintbm("trainsteps", t)
149 endstep()
150 end)
151
152 function advtrains.train_step(id, train, dtime)
153 --Legacy: set drives_on and max_speed
154 if not train.drives_on or not train.max_speed then
155 advtrains.update_trainpart_properties(id)
156 end
157 --TODO check for all vars to be present
158 if not train.velocity then
159 train.velocity=0
160 end
161 if not train.movedir or (train.movedir~=1 and train.movedir~=-1) then
162 train.movedir=1
163 end
164 --very unimportant thing: check if couple is here
165 if train.couple_eid_front and (not minetest.luaentities[train.couple_eid_front] or not minetest.luaentities[train.couple_eid_front].is_couple) then train.couple_eid_front=nil end
166 if train.couple_eid_back and (not minetest.luaentities[train.couple_eid_back] or not minetest.luaentities[train.couple_eid_back].is_couple) then train.couple_eid_back=nil end
167
168 --skip certain things (esp. collision) when not moving
169 local train_moves=(train.velocity~=0)
170
171 --if not train.last_pos then advtrains.trains[id]=nil return end
172
173 if not advtrains.pathpredict(id, train) then
174 atprint("pathpredict failed(returned false)")
175 train.velocity=0
176 train.tarvelocity=0
177 return
178 end
179
180 local path=advtrains.get_or_create_path(id, train)
181 if not path then
182 train.velocity=0
183 train.tarvelocity=0
184 atprint("train has no path for whatever reason")
185 return
186 end
187
188 local train_end_index=advtrains.get_train_end_index(train)
189 --apply off-track handling:
190 local front_off_track=train.max_index_on_track and train.index>train.max_index_on_track
191 local back_off_track=train.min_index_on_track and train_end_index<train.min_index_on_track
192 if front_off_track and back_off_track then--allow movement in both directions
193 if train.tarvelocity>1 then train.tarvelocity=1 end
194 elseif front_off_track then--allow movement only backward
195 if train.movedir==1 and train.tarvelocity>0 then train.tarvelocity=0 end
196 if train.movedir==-1 and train.tarvelocity>1 then train.tarvelocity=1 end
197 elseif back_off_track then--allow movement only forward
198 if train.movedir==-1 and train.tarvelocity>0 then train.tarvelocity=0 end
199 if train.movedir==1 and train.tarvelocity>1 then train.tarvelocity=1 end
200 end
201
202 --update advtrains.detector
203 if not train.detector_old_index then
204 train.detector_old_index = math.floor(train_end_index)
205 train.detector_old_end_index = math.floor(train_end_index)
206 end
207 local ifo, ifn, ibo, ibn = train.detector_old_index, math.floor(train.index), train.detector_old_end_index, math.floor(train_end_index)
208 if ifn>ifo then
209 for i=ifo, ifn do
210 if path[i] then
211 advtrains.detector.enter_node(path[i], id)
212 end
213 end
214 elseif ifn<ifo then
215 for i=ifn, ifo do
216 if path[i] then
217 advtrains.detector.leave_node(path[i], id)
218 end
219 end
220 end
221 if ibn<ibo then
222 for i=ibn, ibn do
223 if path[i] then
224 advtrains.detector.enter_node(path[i], id)
225 end
226 end
227 elseif ibn>ibo then
228 for i=ibo, ibn do
229 if path[i] then
230 advtrains.detector.leave_node(path[i], id)
231 end
232 end
233 end
234 train.detector_old_index = math.floor(train.index)
235 train.detector_old_end_index = math.floor(train_end_index)
236
237 --remove?
238 if #train.trainparts==0 then
239 atprint("[advtrains][train "..sid(id).."] has empty trainparts, removing.")
240 advtrains.detector.leave_node(path[train.detector_old_index], id)
241 advtrains.trains[id]=nil
242 return
243 end
244
245 if train_moves then
246 --check for collisions by finding objects
247
248 --heh, new collision again.
249 --this time, based on NODES and the advtrains.detector.on_node table.
250 local collpos
251 local coll_grace=1
252 if train.movedir==1 then
253 collpos=advtrains.get_real_index_position(path, train.index-coll_grace)
254 else
255 collpos=advtrains.get_real_index_position(path, train_end_index+coll_grace)
256 end
257 if collpos then
258 local rcollpos=advtrains.round_vector_floor_y(collpos)
259 for x=-1,1 do
260 for z=-1,1 do
261 local testpos=vector.add(rcollpos, {x=x, y=0, z=z})
262 local testpts=minetest.pos_to_string(testpos)
263 if advtrains.detector.on_node[testpts] and advtrains.detector.on_node[testpts]~=id then
264 if advtrains.trains[advtrains.detector.on_node[testpts]] then
265 --collides
266 advtrains.spawn_couple_on_collide(id, testpos, advtrains.detector.on_node[testpts], train.movedir==-1)
267
268 train.recently_collided_with_env=true
269 train.velocity=0.5*train.velocity
270 train.movedir=train.movedir*-1
271 train.tarvelocity=0
272 else
273 --unexistant train left in this place
274 advtrains.detector.on_node[testpts]=nil
275 end
276 end
277 end
278 end
279 end
280 end
281 --check for any trainpart entities if they have been unloaded. do this only if train is near a player, to not spawn entities into unloaded areas
282 --todo function will be taken by update_trainpart_properties
283 train.check_trainpartload=(train.check_trainpartload or 0)-dtime
284 local node_range=(math.max((minetest.setting_get("active_block_range") or 0),1)*16)
285 if train.check_trainpartload<=0 then
286 local ori_pos=advtrains.get_real_index_position(path, train.index) --not much to calculate
287 --atprint("[advtrains][train "..id.."] at "..minetest.pos_to_string(vector.round(ori_pos)))
288
289 local should_check=false
290 for _,p in ipairs(minetest.get_connected_players()) do
291 should_check=should_check or ((vector.distance(ori_pos, p:getpos())<node_range))
292 end
293 if should_check then
294 advtrains.update_trainpart_properties(id)
295 end
296 train.check_trainpartload=2
297 end
298
299
300 --handle collided_with_env
301 if train.recently_collided_with_env then
302 train.tarvelocity=0
303 if not train_moves then
304 train.recently_collided_with_env=false--reset status when stopped
305 end
306 end
307 if train.locomotives_in_train==0 then
308 train.tarvelocity=0
309 end
310
311 --interpret ATC command
312 if train.atc_brake_target and train.atc_brake_target>=train.velocity then
313 train.atc_brake_target=nil
314 end
315 if train.atc_wait_finish then
316 if not train.atc_brake_target and train.velocity==train.tarvelocity then
317 train.atc_wait_finish=nil
318 end
319 end
320 if train.atc_command then
321 if train.atc_delay<=0 and not train.atc_wait_finish then
322 advtrains.atc.execute_atc_command(id, train)
323 else
324 train.atc_delay=train.atc_delay-dtime
325 end
326 end
327
328 --make brake adjust the tarvelocity if necessary
329 if train.brake and (math.ceil(train.velocity)-1)<train.tarvelocity then
330 train.tarvelocity=math.max((math.ceil(train.velocity)-1), 0)
331 end
332 --apply tarvel(but with physics in mind!)
333 if train.velocity~=train.tarvelocity then
334 local applydiff=0
335 local mass=#train.trainparts
336 local diff=train.tarvelocity-train.velocity
337 if diff>0 then--accelerating, force will be brought on only by locomotives.
338 --atprint("accelerating with default force")
339 applydiff=(math.min((advtrains.train_accel_force*train.locomotives_in_train*dtime)/mass, math.abs(diff)))
340 else--decelerating
341 if front_off_track or back_off_track or train.recently_collided_with_env then --every wagon has a brake, so not divided by mass.
342 --atprint("braking with emergency force")
343 applydiff= -(math.min((advtrains.train_emerg_force*dtime), math.abs(diff)))
344 elseif train.brake or (train.atc_brake_target and train.atc_brake_target<train.velocity) then
345 --atprint("braking with default force")
346 --no math.min, because it can grow beyond tarvelocity, see up there
347 --dont worry, it will never fall below zero.
348 applydiff= -((advtrains.train_brake_force*dtime))
349 else
350 --atprint("roll")
351 applydiff= -(math.min((advtrains.train_roll_force*dtime), math.abs(diff)))
352 end
353 end
354 train.last_accel=(applydiff*train.movedir)
355 train.velocity=math.min(math.max( train.velocity+applydiff , 0), train.max_speed or 10)
356 else
357 train.last_accel=0
358 end
359
360 --move
361 --TODO 3,5 + 0.7
362 train.index=train.index and train.index+(((train.velocity*train.movedir)/(train.path_dist[math.floor(train.index)] or 1))*dtime) or 0
363
364 end
365
366
367 --structure of train table:
368 --[[
369 trains={
370 [train_id]={
371 trainparts={
372 [n]=wagon_id
373 }
374 path={path}
375 velocity
376 tarvelocity
377 index
378 trainlen
379 path_inv_level
380 last_pos |
381 last_dir | for pathpredicting.
382 }
383 }
384 --a wagon itself has the following properties:
385 wagon={
386 unique_id
387 train_id
388 pos_in_train (is index difference, including train_span stuff)
389 pos_in_trainparts (is index in trainparts tabel of trains)
390 }
391 inherited by metatable:
392 wagon_proto={
393 wagon_span
394 }
395 ]]
396
397 --returns new id
398 function advtrains.create_new_train_at(pos, pos_prev)
399 local newtrain_id=os.time()..os.clock()
400 while advtrains.trains[newtrain_id] do newtrain_id=os.time()..os.clock() end--ensure uniqueness(will be unneccessary)
401
402 advtrains.trains[newtrain_id]={}
403 advtrains.trains[newtrain_id].last_pos=pos
404 advtrains.trains[newtrain_id].last_pos_prev=pos_prev
405 advtrains.trains[newtrain_id].tarvelocity=0
406 advtrains.trains[newtrain_id].velocity=0
407 advtrains.trains[newtrain_id].trainparts={}
408 return newtrain_id
409 end
410
411 --returns false on failure. handle this case!
412 function advtrains.pathpredict(id, train)
413
414 --atprint("pos ",x,y,z)
415 --::rerun::
416 if not train.index then train.index=0 end
417 if not train.path or #train.path<2 then
418 if not train.last_pos then
419 --no chance to recover
420 atprint("[advtrains]train hasn't saved last-pos, removing train.")
421 advtrains.train[id]=nil
422 return false
423 end
424
425 local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos), train.drives_on)
426
427 if node_ok==nil then
428 --block not loaded, do nothing
429 atprint("[advtrains]last_pos not available")
430 return nil
431 elseif node_ok==false then
432 atprint("[advtrains]no track here, (fail) removing train.")
433 advtrains.trains[id]=nil
434 return false
435 end
436
437 if not train.last_pos_prev then
438 --no chance to recover
439 atprint("[advtrains]train hasn't saved last-pos_prev, removing train.")
440 advtrains.trains[id]=nil
441 return false
442 end
443
444 local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(train.last_pos_prev), train.drives_on)
445
446 if prevnode_ok==nil then
447 --block not loaded, do nothing
448 atprint("[advtrains]prev not available")
449 return nil
450 elseif prevnode_ok==false then
451 atprint("[advtrains]no track at prev, (fail) removing train.")
452 advtrains.trains[id]=nil
453 return false
454 end
455
456 train.index=(train.restore_add_index or 0)+(train.savedpos_off_track_index_offset or 0)
457 --restore_add_index is set by save() to prevent trains hopping to next round index. should be between -0.5 and 0.5
458 --savedpos_off_track_index_offset is set if train went off track. see below.
459 train.path={}
460 train.path_dist={}
461 train.path[0]=train.last_pos
462 train.path[-1]=train.last_pos_prev
463 train.path_dist[-1]=vector.distance(train.last_pos, train.last_pos_prev)
464 end
465
466 local pregen_front=2
467 local pregen_back=2
468 if train.velocity>0 then
469 if train.movedir>0 then
470 pregen_front=2+math.ceil(train.velocity*0.15) --assumes server step of 0.1 seconds, +50% tolerance
471 else
472 pregen_back=2+math.ceil(train.velocity*0.15)
473 end
474 end
475
476
477 local maxn=train.max_index_on_track or 0
478 while (maxn-train.index) < pregen_front do--pregenerate
479 --atprint("[advtrains]maxn conway for ",maxn,minetest.pos_to_string(path[maxn]),maxn-1,minetest.pos_to_string(path[maxn-1]))
480 local conway=advtrains.conway(train.path[maxn], train.path[maxn-1], train.drives_on)
481 if conway then
482 train.path[maxn+1]=conway
483 train.max_index_on_track=maxn
484 else
485 --do as if nothing has happened and preceed with path
486 --but do not update max_index_on_track
487 atprint("over-generating path max to index "..(maxn+1).." (position "..minetest.pos_to_string(train.path[maxn]).." )")
488 train.path[maxn+1]=vector.add(train.path[maxn], vector.subtract(train.path[maxn], train.path[maxn-1]))
489 end
490 train.path_dist[maxn]=vector.distance(train.path[maxn+1], train.path[maxn])
491 maxn=advtrains.maxN(train.path)
492 end
493
494 local minn=train.min_index_on_track or 0
495 while (train.index-minn) < (train.trainlen or 0) + pregen_back do --post_generate. has to be at least trainlen. (we let go of the exact calculation here since this would be unuseful here)
496 --atprint("[advtrains]minn conway for ",minn,minetest.pos_to_string(path[minn]),minn+1,minetest.pos_to_string(path[minn+1]))
497 local conway=advtrains.conway(train.path[minn], train.path[minn+1], train.drives_on)
498 if conway then
499 train.path[minn-1]=conway
500 train.min_index_on_track=minn
501 else
502 --do as if nothing has happened and preceed with path
503 --but do not update min_index_on_track
504 atprint("over-generating path min to index "..(minn-1).." (position "..minetest.pos_to_string(train.path[minn]).." )")
505 train.path[minn-1]=vector.add(train.path[minn], vector.subtract(train.path[minn], train.path[minn+1]))
506 end
507 train.path_dist[minn-1]=vector.distance(train.path[minn], train.path[minn-1])
508 minn=advtrains.minN(train.path)
509 end
510 if not train.min_index_on_track then train.min_index_on_track=0 end
511 if not train.max_index_on_track then train.max_index_on_track=0 end
512
513 --make pos/yaw available for possible recover calls
514 if train.max_index_on_track<train.index then --whoops, train went too far. the saved position will be the last one that lies on a track, and savedpos_off_track_index_offset will hold how far to go from here
515 train.savedpos_off_track_index_offset=train.index-train.max_index_on_track
516 train.last_pos=train.path[train.max_index_on_track]
517 train.last_pos_prev=train.path[train.max_index_on_track-1]
518 atprint("train is off-track (front), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev))
519 elseif train.min_index_on_track+1>train.index then --whoops, train went even more far. same behavior
520 train.savedpos_off_track_index_offset=train.index-train.min_index_on_track
521 train.last_pos=train.path[train.min_index_on_track+1]
522 train.last_pos_prev=train.path[train.min_index_on_track]
523 atprint("train is off-track (back), last positions kept at "..minetest.pos_to_string(train.last_pos).." / "..minetest.pos_to_string(train.last_pos_prev))
524 else --regular case
525 train.savedpos_off_track_index_offset=nil
526 train.last_pos=train.path[math.floor(train.index+0.5)]
527 train.last_pos_prev=train.path[math.floor(train.index-0.5)]
528 end
529 return train.path
530 end
531 function advtrains.get_train_end_index(train)
532 return advtrains.get_real_path_index(train, train.trainlen or 2)--this function can be found inside wagons.lua since it's more related to wagons. we just set trainlen as pos_in_train
533 end
534
535 function advtrains.get_or_create_path(id, train)
536 if not train.path then return advtrains.pathpredict(id, train) end
537 return train.path
538 end
539
540 function advtrains.add_wagon_to_train(wagon, train_id, index)
541 local train=advtrains.trains[train_id]
542 if index then
543 table.insert(train.trainparts, index, wagon.unique_id)
544 else
545 table.insert(train.trainparts, wagon.unique_id)
546 end
547 --this is not the usual case!!!
548 --we may set initialized because the wagon has no chance to step()
549 wagon.initialized=true
550 --TODO is this art or can we throw it away?
551 advtrains.update_trainpart_properties(train_id)
552 end
553 function advtrains.update_trainpart_properties(train_id, invert_flipstate)
554 local train=advtrains.trains[train_id]
555 train.drives_on=advtrains.all_tracktypes
556 train.max_speed=20
557 local rel_pos=0
558 local count_l=0
559 for i, w_id in ipairs(train.trainparts) do
560 local wagon=nil
561 for _,iwagon in pairs(minetest.luaentities) do
562 if iwagon.is_wagon and iwagon.initialized and iwagon.unique_id==w_id then
563 if wagon then
564 --duplicate
565 iwagon.object:remove()
566 else
567 wagon=iwagon
568 end
569 end
570 end
571 if not wagon then
572 if advtrains.wagon_save[w_id] then
573 --spawn a new and initialize it with the properties from wagon_save
574 wagon=minetest.env:add_entity(train.last_pos, advtrains.wagon_save[w_id].entity_name):get_luaentity()
575 wagon:init_from_wagon_save(w_id)
576 end
577 end
578 if wagon then
579 rel_pos=rel_pos+wagon.wagon_span
580 wagon.train_id=train_id
581 wagon.pos_in_train=rel_pos
582 wagon.pos_in_trainparts=i
583 wagon.old_velocity_vector=nil
584 if wagon.is_locomotive then
585 count_l=count_l+1
586 end
587 if invert_flipstate then
588 wagon.wagon_flipped = not wagon.wagon_flipped
589 end
590 rel_pos=rel_pos+wagon.wagon_span
591 any_loaded=true
592
593 if wagon.drives_on then
594 for k,_ in pairs(train.drives_on) do
595 if not wagon.drives_on[k] then
596 train.drives_on[k]=nil
597 end
598 end
599 end
600 train.max_speed=math.min(train.max_speed, wagon.max_speed)
601 else
602 atprint(w_id.." not loaded and no save available")
603 --what the hell...
604 table.remove(train.trainparts, pit)
605 end
606 end
607 train.trainlen=rel_pos
608 train.locomotives_in_train=count_l
609 end
610
611 function advtrains.split_train_at_wagon(wagon)
612 --get train
613 local train=advtrains.trains[wagon.train_id]
614 local real_pos_in_train=advtrains.get_real_path_index(train, wagon.pos_in_train)
615 local pos_for_new_train=advtrains.get_or_create_path(wagon.train_id, train)[math.floor(real_pos_in_train+wagon.wagon_span)]
616 local pos_for_new_train_prev=advtrains.get_or_create_path(wagon.train_id, train)[math.floor(real_pos_in_train-1+wagon.wagon_span)]
617
618 --before doing anything, check if both are rails. else do not allow
619 if not pos_for_new_train then
620 atprint("split_train: pos_for_new_train not set")
621 return false
622 end
623 local node_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(pos_for_new_train), train.drives_on)
624 if not node_ok then
625 atprint("split_train: pos_for_new_train "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." not loaded or is not a rail")
626 return false
627 end
628
629 if not train.last_pos_prev then
630 atprint("split_train: pos_for_new_train_prev not set")
631 return false
632 end
633
634 local prevnode_ok=advtrains.get_rail_info_at(advtrains.round_vector_floor_y(pos_for_new_train), train.drives_on)
635 if not prevnode_ok then
636 atprint("split_train: pos_for_new_train_prev "..minetest.pos_to_string(advtrains.round_vector_floor_y(pos_for_new_train_prev)).." not loaded or is not a rail")
637 return false
638 end
639
640 --create subtrain
641 local newtrain_id=advtrains.create_new_train_at(pos_for_new_train, pos_for_new_train_prev)
642 local newtrain=advtrains.trains[newtrain_id]
643 --insert all wagons to new train
644 for k,v in ipairs(train.trainparts) do
645 if k>=wagon.pos_in_trainparts then
646 table.insert(newtrain.trainparts, v)
647 train.trainparts[k]=nil
648 end
649 end
650 --update train parts
651 advtrains.update_trainpart_properties(wagon.train_id)--atm it still is the desierd id.
652 advtrains.update_trainpart_properties(newtrain_id)
653 train.tarvelocity=0
654 newtrain.velocity=train.velocity
655 newtrain.tarvelocity=0
656 end
657
658 --there are 4 cases:
659 --1/2. F<->R F<->R regular, put second train behind first
660 --->frontpos of first train will match backpos of second
661 --3. F<->R R<->F flip one of these trains, take the other as new train
662 --->backpos's will match
663 --4. R<->F F<->R flip one of these trains and take it as new parent
664 --->frontpos's will match
665
666 --true when trains are facing each other. needed on colliding.
667 -- check done by iterating paths and checking their direction
668 --returns nil when not on the same track at all OR when required path items are not generated. this distinction may not always be needed.
669 function advtrains.trains_facing(train1, train2)
670 local sr_pos=train1.path[math.floor(train1.index)]
671 local sr_pos_p=train1.path[math.floor(train1.index)-1]
672
673 for i=advtrains.minN(train2.path), advtrains.maxN(train2.path) do
674 if vector.equals(sr_pos, train2.path[i]) then
675 if train2.path[i+1] and vector.equals(sr_pos_p, train2.path[i+1]) then return true end
676 if train2.path[i-1] and vector.equals(sr_pos_p, train2.path[i-1]) then return false end
677 return nil
678 end
679 end
680 return nil
681 end
682
683 function advtrains.spawn_couple_on_collide(id1, pos, id2, t1_is_backpos)
684 atprint("COLLISION: "..sid(id1).." and "..sid(id2).." at "..minetest.pos_to_string(pos)..", t1_is_backpos="..(t1_is_backpos and "true" or "false"))
685 --TODO:
686 local train1=advtrains.trains[id1]
687 local train2=advtrains.trains[id2]
688
689 if not train1 or not train2 then return end
690
691 local found
692 for i=advtrains.minN(train1.path), advtrains.maxN(train1.path) do
693 if vector.equals(train1.path[i], pos) then
694 found=true
695 end
696 end
697 if not found then
698 atprint("Err: pos not in path")
699 return
700 end
701
702 local frontpos2=train2.path[math.floor(train2.detector_old_index)]
703 local backpos2=train2.path[math.floor(train2.detector_old_end_index)]
704 local t2_is_backpos
705 atprint("End positions: "..minetest.pos_to_string(frontpos2)..minetest.pos_to_string(backpos2))
706
707 if vector.distance(frontpos2, pos)<2 then
708 t2_is_backpos=false
709 elseif vector.distance(backpos2, pos)<2 then
710 t2_is_backpos=true
711 else
712 atprint("Err: not a endpos")
713 return --the collision position is not the end position.
714 end
715 atprint("t2_is_backpos="..(t2_is_backpos and "true" or "false"))
716
717 local t1_has_couple
718 if t1_is_backpos then
719 t1_has_couple=train1.couple_eid_back
720 else
721 t1_has_couple=train1.couple_eid_front
722 end
723 local t2_has_couple
724 if t2_is_backpos then
725 t2_has_couple=train2.couple_eid_back
726 else
727 t2_has_couple=train2.couple_eid_front
728 end
729
730 if t1_has_couple then
731 if minetest.object_refs[t1_has_couple] then minetest.object_refs[t1_has_couple]:remove() end
732 end
733 if t2_has_couple then
734 if minetest.object_refs[t2_has_couple] then minetest.object_refs[t2_has_couple]:remove() end
735 end
736 local obj=minetest.add_entity(pos, "advtrains:couple")
737 if not obj then atprint("failed creating object") return end
738 local le=obj:get_luaentity()
739 le.train_id_1=id1
740 le.train_id_2=id2
741 le.train1_is_backpos=t1_is_backpos
742 le.train2_is_backpos=t2_is_backpos
743 --find in object_refs
744 for aoi, compare in pairs(minetest.object_refs) do
745 if compare==obj then
746 if t1_is_backpos then
747 train1.couple_eid_back=aoi
748 else
749 train1.couple_eid_front=aoi
750 end
751 if t2_is_backpos then
752 train2.couple_eid_back=aoi
753 else
754 train2.couple_eid_front=aoi
755 end
756 end
757 end
758 atprint("Couple entity:"..dump(le))
759
760 --also TODO: integrate check_trainpartload into update_trainpart_properties.
761 end
762 --order of trains may be irrelevant in some cases. check opposite cases. TODO does this work?
763 --pos1 and pos2 are just needed to form a median.
764
765
766 function advtrains.do_connect_trains(first_id, second_id)
767 local first_wagoncnt=#advtrains.trains[first_id].trainparts
768 local second_wagoncnt=#advtrains.trains[second_id].trainparts
769
770 for _,v in ipairs(advtrains.trains[second_id].trainparts) do
771 table.insert(advtrains.trains[first_id].trainparts, v)
772 end
773 --kick it like physics (with mass being #wagons)
774 local new_velocity=((advtrains.trains[first_id].velocity*first_wagoncnt)+(advtrains.trains[second_id].velocity*second_wagoncnt))/(first_wagoncnt+second_wagoncnt)
775 advtrains.trains[second_id]=nil
776 advtrains.update_trainpart_properties(first_id)
777 advtrains.trains[first_id].velocity=new_velocity
778 advtrains.trains[first_id].tarvelocity=0
779 end
780
781 function advtrains.invert_train(train_id)
782 local train=advtrains.trains[train_id]
783
784 local old_path=advtrains.get_or_create_path(train_id, train)
785 train.path={}
786 train.index= - advtrains.get_train_end_index(train)
787 train.velocity=-train.velocity
788 train.tarvelocity=-train.tarvelocity
789 for k,v in pairs(old_path) do
790 train.path[-k]=v
791 end
792 local old_trainparts=train.trainparts
793 train.trainparts={}
794 for k,v in ipairs(old_trainparts) do
795 table.insert(train.trainparts, 1, v)--notice insertion at first place
796 end
797 advtrains.update_trainpart_properties(train_id, true)
798 end
799
800 function advtrains.is_train_at_pos(pos)
801 --atprint("istrainat: pos "..minetest.pos_to_string(pos))
802 local checked_trains={}
803 local objrefs=minetest.get_objects_inside_radius(pos, 2)
804 for _,v in pairs(objrefs) do
805 local le=v:get_luaentity()
806 if le and le.is_wagon and le.initialized and le.train_id and not checked_trains[le.train_id] then
807 --atprint("istrainat: checking "..le.train_id)
808 checked_trains[le.train_id]=true
809 local path=advtrains.get_or_create_path(le.train_id, le:train())
810 if path then
811 --atprint("has path")
812 for i=math.floor(advtrains.get_train_end_index(le:train())+0.5),math.floor(le:train().index+0.5) do
813 if path[i] then
814 --atprint("has pathitem "..i.." "..minetest.pos_to_string(path[i]))
815 if vector.equals(advtrains.round_vector_floor_y(path[i]), pos) then
816 return true
817 end
818 end
819 end
820 end
821 end
822 end
823 return false
824 end
825 function advtrains.invalidate_all_paths()
826 --atprint("invalidating all paths")
827 for k,v in pairs(advtrains.trains) do
828 if v.index then
829 v.restore_add_index=v.index-math.floor(v.index+0.5)
830 end
831 v.path=nil
832 v.path_dist=nil
833 v.index=nil
834 v.min_index_on_track=nil
835 v.max_index_on_track=nil
836
837 advtrains.detector.setup_restore()
838 v.detector_old_index=nil
839 v.detector_old_end_index=nil
840 end
841 end
842
843 --not blocking trains group
844 function advtrains.train_collides(node)
845 if node and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable then
846 if not minetest.registered_nodes[node.name].groups.not_blocking_trains then
847 return true
848 end
849 end
850 return false
851 end
852
853 local nonblocknodes={
854 "default:fence_wood",
855 "default:fence_acacia_wood",
856 "default:fence_aspen_wood",
857 "default:fence_pine_wood",
858 "default:fence_junglewood",
859 "default:torch",
860
861 "default:sign_wall",
862 "signs:sign_wall",
863 "signs:sign_wall_blue",
864 "signs:sign_wall_brown",
865 "signs:sign_wall_orange",
866 "signs:sign_wall_green",
867 "signs:sign_yard",
868 "signs:sign_wall_white_black",
869 "signs:sign_wall_red",
870 "signs:sign_wall_white_red",
871 "signs:sign_wall_yellow",
872 "signs:sign_post",
873 "signs:sign_hanging",
874
875
876 }
877 minetest.after(0, function()
878 for _,name in ipairs(nonblocknodes) do
879 if minetest.registered_nodes[name] then
880 minetest.registered_nodes[name].groups.not_blocking_trains=1
881 end
882 end
883 end)