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