]> git.lizzy.rs Git - Crafter.git/blob - mods/train/init.lua
Remove debug info
[Crafter.git] / mods / train / init.lua
1 local pool = {}
2
3 local player_pool = {}
4
5
6 local dirs = {
7         {x= 1,y= 0,z= 0},
8         {x=-1,y= 0,z= 0},
9
10         {x= 1,y= 1,z= 0}, 
11         {x=-1,y= 1,z= 0},
12
13         {x= 1,y=-1,z= 0},
14         {x=-1,y=-1,z= 0},
15
16         {x= 0,y= 0,z= 1},
17         {x= 0,y= 0,z=-1},
18
19         {x= 0,y= 1,z= 1},
20         {x= 0,y= 1,z=-1},
21
22         {x= 0,y=-1,z= 1},
23         {x= 0,y=-1,z=-1},
24 }
25
26 local function coupling_particles(pos,truth)
27         local color = "red"
28         if truth then
29                 color = "green"
30         end
31
32         minetest.add_particlespawner({
33                 amount = 15,
34                 time = 0.001,
35                 minpos = pos,
36                 maxpos = pos,
37                 minvel = vector.new(-10,-10,-10),
38                 maxvel = vector.new(10,10,10),
39                 minacc = {x=0, y=0, z=0},
40                 maxacc = {x=0, y=0, z=0},
41                 minexptime = 1.1,
42                 maxexptime = 1.5,
43                 minsize = 1,
44                 maxsize = 2,
45                 collisiondetection = false,
46                 collision_removal = false,
47                 vertical = false,
48                 texture = "couple_particle.png^[colorize:"..color..":200",
49                 glow = 14,
50         })
51 end
52
53 local function data_injection(pos,data)
54         if data then
55                 pool[minetest.hash_node_position(pos)] = true
56         else
57                 pool[minetest.hash_node_position(pos)] = nil
58         end
59 end
60
61 local function speed_limiter(self,speed)
62         local test = self.object:get_velocity()--vector.multiply(self.velocity,new_vel)
63
64         if test.x > speed then
65                 test.x = speed
66         elseif test.x < -speed then
67                 test.x = -speed
68         end
69         if test.z > speed then
70                 test.z = speed
71         elseif test.z < -speed then
72                 test.z = -speed         
73         end
74         self.object:set_velocity(test)
75 end
76
77 local function create_axis(pos)
78         local possible_dirs = {}
79         for _,dir in pairs(dirs) do
80                 local pos2 = vector.add(pos,dir)
81                 if pool[minetest.hash_node_position(pos2)] then
82                         table.insert(possible_dirs,dir)
83                 end
84         end
85         return(possible_dirs)
86 end
87
88 local function collision_detect(self)
89         if not self.axis_lock then return end
90         local pos = self.object:get_pos()
91         for _,object in ipairs(minetest.get_objects_inside_radius(pos, 1)) do
92                 if object:is_player() then
93                         local pos2 = object:get_pos()
94                         if self.axis_lock == "x" then
95
96                                 local velocity = (1-vector.distance(vector.new(pos.x,0,0),vector.new(pos2.x,0,0)))*5
97                                 local dir = vector.direction(vector.new(pos2.x,0,0),vector.new(pos.x,0,0))
98                                 local new_vel = vector.multiply(dir,velocity)
99                                 self.object:add_velocity(new_vel)
100                                 self.dir = dir
101                         elseif self.axis_lock == "z" then
102                                 local velocity = (1-vector.distance(vector.new(0,0,pos.z),vector.new(0,0,pos2.z)))*5
103                                 local dir = vector.direction(vector.new(0,0,pos2.z),vector.new(0,0,pos.z))
104                                 local new_vel = vector.multiply(dir,velocity)
105                                 self.object:add_velocity(new_vel)
106                                 self.dir = dir
107                         end
108                         return
109                 end
110         end
111 end
112
113 local function direction_snap(self)
114         local dir = self.dir
115         local pitch = 0
116         if dir.y == 1 then pitch = math.pi/4 end
117         if dir.y == -1 then pitch = -math.pi/4 end
118
119         local yaw = minetest.dir_to_yaw(dir)
120
121         if self.driver then
122                 self.driver:set_look_vertical(-pitch)
123                 self.driver:set_look_horizontal(yaw)
124         end
125         self.object:set_rotation(vector.new(pitch,yaw,0))
126
127         
128 end
129
130 local function turn_snap(pos,self,dir,dir2)
131         if self.axis_lock == "x" then
132                 if dir.x ~= 0 and dir2.z ~= 0 then
133                         local velocity = self.object:get_velocity()
134                         local inertia = math.abs(velocity.x)
135                         self.object:set_velocity(vector.multiply(dir2,inertia))
136                         self.dir = dir2
137                         self.axis_lock = "z"
138                         self.object:set_pos(pos)
139                         direction_snap(self)
140                         return(true)
141                 end
142         end
143         if self.axis_lock == "z" then
144                 if dir.z ~= 0 and dir2.x ~= 0 then
145                         local velocity = self.object:get_velocity()
146                         local inertia = math.abs(velocity.z)
147                         self.object:set_velocity(vector.multiply(dir2,inertia))
148                         self.dir = dir2
149                         self.axis_lock = "x"
150                         self.object:set_pos(pos)
151                         direction_snap(self)
152                         return(true)
153                 end
154         end
155         return(false)
156 end
157
158 local function climb_snap(pos,self,dir,dir2)
159         if self.axis_lock == "x" then
160                 if dir.x == dir2.x and dir2.y ~= 0 then
161                         local velocity = self.object:get_velocity()
162                         local inertia = math.abs(velocity.x)
163                         self.object:set_velocity(vector.multiply(dir2,inertia))
164                         self.dir = dir2
165                         self.axis_lock = "x"
166                         self.object:set_pos(pos)
167                         direction_snap(self)
168                         return(true)
169                 end
170         end
171         if self.axis_lock == "z" then
172                 if dir.z == dir2.z and dir2.y ~= 0 then
173                         local velocity = self.object:get_velocity()
174                         local inertia = math.abs(velocity.z)
175                         self.object:set_velocity(vector.multiply(dir2,inertia))
176                         self.dir = dir2
177                         self.axis_lock = "z"
178                         self.object:set_pos(pos)
179                         direction_snap(self)
180                         return(true)
181                 end
182         end
183         return(false)
184 end
185
186 local function straight_snap(pos,self,dir)
187         if self.axis_lock == "x" then
188                 if dir.x ~= 0 and pool[minetest.hash_node_position(vector.add(pos,vector.new(dir.x,0,0)))] then
189                         local velocity = self.object:get_velocity()
190                         self.object:set_velocity(vector.new(velocity.x,0,0))
191                         self.dir = vector.new(dir.x,0,0)
192                         self.axis_lock = "x"
193                         self.object:set_pos(pos)
194                         direction_snap(self)
195                         return(true)
196                 end
197         end
198         if self.axis_lock == "z" then
199                 if dir.z ~= 0 and pool[minetest.hash_node_position(vector.add(pos,vector.new(0,0,dir.z)))] then
200                         local velocity = self.object:get_velocity()
201                         self.object:set_velocity(vector.new(0,0,velocity.z))
202                         self.dir = vector.new(0,0,dir.z)
203                         self.axis_lock = "z"
204                         self.object:set_pos(pos)
205                         direction_snap(self)
206                         return(true)
207                 end
208         end
209         return(false)
210 end
211
212
213 local function coupling_logic(self)
214         
215         if not self.axis_lock then return end
216
217         if not self.coupler1 then return end
218
219         if self.dir.y ~= 0 then return end
220
221         local pos = self.object:get_pos()
222         
223         local pos2 = self.coupler1:get_pos()
224
225         local coupler_goal = self.coupler1:get_luaentity().coupler_distance
226
227         local coupler_velocity = self.coupler1:get_velocity()
228
229         if self.axis_lock == "x" then
230                 local velocity_real = self.object:get_velocity()
231                 local distance = vector.distance(pos,pos2)
232                 local new_vel = vector.new(0,0,0)
233                 if distance > coupler_goal then
234                         local velocity = (distance-coupler_goal)*5
235                         local dir = vector.direction(vector.new(pos.x,0,0),vector.new(pos2.x,0,0))
236                         self.dir = dir
237                         new_vel = vector.multiply(dir,velocity)
238                 else
239                         --if vector.equals(coupler_velocity,vector.new(0,0,0)) then
240                                 --new_vel = vector.multiply(velocity_real,-1)
241                         if distance > coupler_goal-0.2 then
242                                 local c_vel = vector.distance(vector.new(0,0,0),coupler_velocity)
243                                 local a_vel = vector.distance(vector.new(0,0,0),velocity_real)
244                                 local d_vel = a_vel-c_vel
245                                 if d_vel < 0 then
246                                         d_vel = 0
247                                 end
248                                 new_vel = vector.multiply(self.dir,d_vel)
249                         else
250                                 new_vel = vector.multiply(velocity_real,-1)
251                         end
252                 end
253                 self.object:add_velocity(new_vel)
254         elseif self.axis_lock == "z" then
255                 local velocity_real = self.object:get_velocity()
256                 local distance = vector.distance(pos,pos2)
257                 local new_vel = vector.new(0,0,0)
258                 if distance > coupler_goal then
259                         local velocity = (distance-coupler_goal)*5
260                         local dir = vector.direction(vector.new(0,0,pos.z),vector.new(0,0,pos2.z))
261                         self.dir = dir
262                         new_vel = vector.multiply(dir,velocity)
263                 else
264                         --if vector.equals(coupler_velocity,vector.new(0,0,0)) then
265                                 --new_vel = vector.multiply(velocity_real,-1)
266                         if distance > coupler_goal-0.2 then
267                                 local c_vel = vector.distance(vector.new(0,0,0),coupler_velocity)
268                                 local a_vel = vector.distance(vector.new(0,0,0),velocity_real)
269                                 local d_vel = a_vel-c_vel
270                                 if d_vel < 0 then
271                                         d_vel = 0
272                                 end
273                                 new_vel = vector.multiply(self.dir,d_vel)
274                         else
275                                 new_vel = vector.multiply(velocity_real,-1)
276                         end
277                 end
278                 self.object:add_velocity(new_vel)
279         end
280
281         return
282 end
283
284
285 local function rail_brain(self,pos)
286
287         if not self.dir then self.dir = vector.new(0,0,0) end
288
289         local pos2 = self.object:get_pos()
290
291         local dir = self.dir
292
293         speed_limiter(self,6)
294
295         if not pool[minetest.hash_node_position(vector.add(pos,dir))] then
296
297                 if straight_snap(pos,self,dir) then
298                         return
299                 end
300
301                 local possible_dirs = create_axis(pos)
302
303                 if table.getn(possible_dirs) == 0 then
304                         --stop slow down become physical
305                 else
306                         for _,dir2 in pairs(possible_dirs) do
307                                 if turn_snap(pos,self,dir,dir2) then
308                                         return
309                                 end
310                                 if climb_snap(pos,self,dir,dir2) then
311                                         return
312                                 end
313                         end
314                 end
315         else
316                 if self.is_car then
317                         coupling_logic(self)
318                 end
319         end
320
321 end
322
323
324 --[[
325  █████╗ ██████╗ ██╗    ██████╗ ███████╗ ██████╗ ██╗███╗   ██╗
326 ██╔══██╗██╔══██╗██║    ██╔══██╗██╔════╝██╔════╝ ██║████╗  ██║
327 ███████║██████╔╝██║    ██████╔╝█████╗  ██║  ███╗██║██╔██╗ ██║
328 ██╔══██║██╔═══╝ ██║    ██╔══██╗██╔══╝  ██║   ██║██║██║╚██╗██║
329 ██║  ██║██║     ██║    ██████╔╝███████╗╚██████╔╝██║██║ ╚████║
330 ╚═╝  ╚═╝╚═╝     ╚═╝    ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝╚═╝  ╚═══╝
331 ]]--
332
333
334 function register_train(name,data)
335 local train = {}
336
337 train.power            = data.power
338 train.coupler_distance = data.coupler_distance
339 train.is_car           = data.is_car
340 train.is_engine        = data.is_engine
341 train.max_speed        = data.max_speed
342 train.driver           = nil
343
344 train.initial_properties = {
345         physical = false, -- otherwise going uphill breaks
346         collisionbox = {-0.4, -0.5, -0.4, 0.4, 0.45, 0.4},
347         visual = "mesh",
348         mesh = data.mesh,
349         visual_size = {x=1, y=1},
350         textures = {data.texture},
351 }
352
353
354 train.on_step = function(self,dtime)
355         if dtime > 0.1 then
356                 self.object:set_pos(self.old_pos)
357         end
358         local pos = vector.round(self.object:get_pos())
359         if not self.axis_lock then
360                 local possible_dirs = create_axis(pos)
361                 for _,dir in pairs(possible_dirs) do
362                         if dir.x ~=0 then
363                                 self.axis_lock = "x"
364                                 self.dir = dir
365                                 direction_snap(self)
366                                 break
367                         elseif dir.z ~= 0 then
368                                 self.axis_lock = "z"
369                                 self.dir = dir
370                                 direction_snap(self)
371                                 break
372                         end
373                 end
374         else
375                 rail_brain(self,pos)
376                 --collision_detect(self)
377         end
378         self.old_pos = self.object:get_pos()
379 end
380
381
382
383
384 train.on_punch = function(self, puncher)
385         if not puncher:get_wielded_item():get_name() == "train:wrench" then
386                 return
387         end
388
389         if self.is_engine and puncher:get_player_control().sneak then
390                 if vector.equals(self.object:get_velocity(),vector.new(0,0,0)) then
391                         if self.dir.y == 0 then
392                                 self.dir = vector.multiply(self.dir,-1)
393                                 direction_snap(self)
394                                 minetest.sound_play("wrench",{
395                                         object = self.object,
396                                         gain = 1.0,
397                                         max_hear_distance = 64,
398                                 })
399                         end
400                 end
401                 return
402         end
403
404         if self.is_engine then
405                 self.object:set_velocity(vector.multiply(self.dir,self.max_speed))
406                 return
407         end
408
409         if self.coupler1 then
410                 self.coupler1:get_luaentity().coupler2 = nil
411                 self.coupler1 = nil
412         end
413
414         if self.coupler2 then
415                 self.coupler2:get_luaentity().coupler1 = nil
416                 self.coupler2 = nil
417         end
418
419 end
420
421
422 train.on_rightclick = function(self,clicker)
423         --[[
424         if clicker:get_wielded_item():get_name() == "utility:furnace" then
425                 local obj = minetest.add_entity(pos, "train:furnace")
426                 obj:set_attach(self.object,"",vector.new(0,0,0),vector.new(0,0,0))
427                 minetest.sound_play("wrench",{
428                         object = self.object,
429                         gain = 1.0,
430                         max_hear_distance = 64,
431                 })
432                 coupling_particles(pos,true)
433                 self.furnace = true
434                 return
435         end
436         ]]--
437
438         if clicker:get_wielded_item():get_name() ~= "train:wrench" then
439                 if self.is_engine then
440                         if not self.driver then
441                                 print("jump on in")
442                                 clicker:set_attach(self.object, "", data.body_pos, data.body_rotation)
443                                 clicker:set_eye_offset(data.eye_offset,{x=0,y=0,z=0})
444                                 player_is_attached(clicker,true)
445                                 set_player_animation(clicker,"stand",0)
446                                 local rotation = self.object:get_rotation()
447                                 clicker:set_look_vertical(0)
448                                 clicker:set_look_horizontal(rotation.y)
449                                 self.object:set_velocity(vector.multiply(self.dir,self.max_speed))
450                                 self.driver = clicker
451                         elseif clicker == self.driver then
452                                 print("jumpin off!")
453                                 clicker:set_detach()
454                                 clicker:set_eye_offset({x=0,y=0,z=0},{x=0,y=0,z=0})
455                                 player_is_attached(clicker,false)
456                                 set_player_animation(clicker,"stand",0)
457                                 self.object:set_velocity(vector.new(0,0,0))
458                                 self.driver = nil
459                         end
460                         return
461                 end
462                 return
463         end
464
465         local pos = self.object:get_pos()
466
467         local name = clicker:get_player_name()
468         if not pool[name] then
469                 if not self.coupler2 then
470                         pool[name] = self.object
471                         minetest.sound_play("wrench",{
472                                 object = self.object,
473                                 gain = 1.0,
474                                 max_hear_distance = 64,
475                         })
476                         coupling_particles(pos,true)
477                 else
478                         minetest.sound_play("wrench",{
479                                 object = self.object,
480                                 gain = 1.0,
481                                 max_hear_distance = 64,
482                                 pitch = 0.7,
483                         })
484                         coupling_particles(pos,false)
485                 end
486         else
487                 if not self.is_engine and pool[name] ~= self.object and not (pool[name]:get_luaentity().coupler1 and pool[name]:get_luaentity().coupler1 == self.object or self.coupler2) then
488                         self.coupler1 = pool[name]
489                         pool[name]:get_luaentity().coupler2 = self.object
490                         minetest.sound_play("wrench",{
491                                 object = self.object,
492                                 gain = 1.0,
493                                 max_hear_distance = 64,
494                         })
495                         coupling_particles(pos,true)
496                 else
497                         minetest.sound_play("wrench",{
498                                 object = self.object,
499                                 gain = 1.0,
500                                 max_hear_distance = 64,
501                                 pitch = 0.7,
502                         })
503                         coupling_particles(pos,false)
504                 end
505                 pool[name] = nil
506         end
507 end
508
509 --get old data
510 train.on_activate = function(self,staticdata, dtime_s)
511         self.object:set_armor_groups({immortal=1})
512         if string.sub(staticdata, 1, string.len("return")) ~= "return" then
513                 return
514         end
515         local data = minetest.deserialize(staticdata)
516         if type(data) ~= "table" then
517                 return
518         end
519         self.old_pos = self.object:get_pos()
520         self.velocity = vector.new(0,0,0)
521 end
522
523 train.get_staticdata = function(self)
524         return minetest.serialize({
525         })
526 end
527
528 minetest.register_entity(name, train)
529
530 end
531
532 --[[
533 ███████╗███╗   ██╗██████╗ 
534 ██╔════╝████╗  ██║██╔══██╗
535 █████╗  ██╔██╗ ██║██║  ██║
536 ██╔══╝  ██║╚██╗██║██║  ██║
537 ███████╗██║ ╚████║██████╔╝
538 ╚══════╝╚═╝  ╚═══╝╚═════╝ 
539 ]]--
540
541
542
543
544 register_train("train:steam_train",{
545         mesh = "steam_train.b3d",
546         texture = "steam_train.png",
547         is_engine = true,
548         power = 6,
549         max_speed = 6,
550         coupler_distance = 3,
551         body_pos = vector.new(0,0,-15),
552         body_rotation = vector.new(0,0,0),
553         eye_offset = vector.new(6,-1,-10)
554 })
555
556 register_train("train:steam_train_small",{
557         mesh = "steam_train_small.b3d",
558         texture = "steam_train_small.png",
559         is_engine = true,
560         power = 6,
561         max_speed = 6,
562         coupler_distance = 3,
563         body_pos = vector.new(0,0,-15),
564         body_rotation = vector.new(0,0,0),
565         eye_offset = vector.new(6,-1,-10)
566 })
567
568
569 register_train("train:minecart",{
570         mesh = "minecart.x",
571         texture = "minecart.png",
572         --is_engine = true,
573         is_car = true,
574         --power = 6,
575         max_speed = 6,
576         coupler_distance = 1.3,
577         --body_pos = vector.new(0,0,-15),
578         --body_rotation = vector.new(0,0,0),
579         --eye_offset = vector.new(6,-1,-10)
580 })
581
582
583
584 minetest.register_craftitem("train:train", {
585         description = "Steam Train",
586         inventory_image = "minecartitem.png",
587         wield_image = "minecartitem.png",
588         on_place = function(itemstack, placer, pointed_thing)
589                 if not pointed_thing.type == "node" then
590                         return
591                 end
592                 
593                 local sneak = placer:get_player_control().sneak
594                 local noddef = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
595                 if not sneak and noddef.on_rightclick then
596                         minetest.item_place(itemstack, placer, pointed_thing)
597                         return
598                 end
599                 
600                 if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "rail")>0 then
601                         minetest.add_entity(pointed_thing.under, "train:steam_train")
602                 else
603                         return
604                 end
605
606                 itemstack:take_item()
607
608                 return itemstack
609         end,
610 })
611
612 minetest.register_craft({
613         output = "train:minecart",
614         recipe = {
615                 {"main:iron", "main:iron", "main:iron"},
616                 {"main:iron", "main:iron", "main:iron"},
617         },
618 })
619
620
621 minetest.register_craftitem("train:minecart", {
622         description = "Minecart",
623         inventory_image = "minecartitem.png",
624         wield_image = "minecartitem.png",
625         on_place = function(itemstack, placer, pointed_thing)
626                 if not pointed_thing.type == "node" then
627                         return
628                 end
629                 
630                 local sneak = placer:get_player_control().sneak
631                 local noddef = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
632                 if not sneak and noddef.on_rightclick then
633                         minetest.item_place(itemstack, placer, pointed_thing)
634                         return
635                 end
636                 
637                 if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "rail")>0 then
638                         minetest.add_entity(pointed_thing.under, "train:minecart")
639                 else
640                         return
641                 end
642
643                 itemstack:take_item()
644
645                 return itemstack
646         end,
647 })
648
649 minetest.register_craft({
650         output = "train:train",
651         recipe = {
652                 {"main:iron", "", "main:iron"},
653                 {"main:iron", "main:iron", "main:iron"},
654         },
655 })
656
657
658
659 minetest.register_node("train:rail",{
660         description = "Rail",
661         wield_image = "rail.png",
662         tiles = {
663                 "rail.png", "railcurve.png",
664                 "railt.png", "railcross.png"
665         },
666         drawtype = "raillike",
667         paramtype = "light",
668         sunlight_propagates = true,
669         is_ground_content = false,
670         walkable = false,
671         node_placement_prediction = "",
672         selection_box = {
673                 type = "fixed",
674                 fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
675         },
676         sounds = main.stoneSound(),
677         after_place_node = function(pos)
678                 data_injection(pos,true)
679         end,
680         after_destruct = function(pos)
681                 data_injection(pos)
682         end,
683         groups={stone=1,wood=1,rail=1,attached_node=1},
684 })
685
686
687 minetest.register_lbm({
688         name = "train:rail",
689         nodenames = {"train:rail"},
690         run_at_every_load = true,
691         action = function(pos)
692                 data_injection(pos,true)
693         end,
694 })
695
696 minetest.register_craft({
697         output = "train:rail 32",
698         recipe = {
699                 {"main:iron","","main:iron"},
700                 {"main:iron","main:stick","main:iron"},
701                 {"main:iron","","main:iron"}
702         }
703 })
704
705
706 minetest.register_food("train:wrench",{
707         description = "Train Wrench",
708         texture = "wrench.png",
709 })
710
711 minetest.register_craft({
712         output = "train:wrench",
713         recipe = {
714                 {"main:iron", "", "main:iron"},
715                 {"main:iron", "main:lapis", "main:iron"},
716                 {"", "main:lapis", ""}
717         }
718 })
719
720
721
722 minetest.register_entity("train:furnace", {
723         initial_properties = {
724                 visual = "wielditem",
725                 visual_size = {x = 0.6, y = 0.6},
726                 textures = {},
727                 physical = true,
728                 is_visible = false,
729                 collide_with_objects = false,
730                 pointable=false,
731                 collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
732         },
733         set_node = function(self)
734                 self.object:set_properties({
735                         is_visible = true,
736                         textures = {"utility:furnace"},
737                 })
738         end,
739
740
741         on_activate = function(self, staticdata)
742                 self.object:set_armor_groups({immortal = 1})
743
744                 self:set_node()
745         end,
746 })
747
748 local steam_check_dirs = {
749         {x= 1,y= 0,z= 0},
750         {x=-1,y= 0,z= 0},
751         {x= 0,y= 0,z= 1},
752         {x= 0,y= 0,z=-1},
753 }
754 local buffer_pool = {}
755 local function do_craft_effects(pos)
756         local hash_pos = minetest.hash_node_position(pos)
757
758         if buffer_pool[hash_pos] then return end
759
760         buffer_pool[hash_pos] = true
761
762         minetest.sound_play("steam_whistle_1",{pos=pos,gain=3,max_hear_distance=128})
763         minetest.add_particlespawner({
764                 amount = 275,
765                 time = 1.3,
766                 minpos = vector.new(pos.x-0.1,pos.y+0.5,pos.z-0.1),
767                 maxpos = vector.new(pos.x+0.1,pos.y+0.5,pos.z+0.1),
768                 minvel = vector.new(-0.5,3,-0.5),
769                 maxvel = vector.new(0.5,5,0.5),
770                 minacc = {x=0, y=3, z=0},
771                 maxacc = {x=0, y=5, z=0},
772                 minexptime = 1.1,
773                 maxexptime = 1.5,
774                 minsize = 1,
775                 maxsize = 2,
776                 collisiondetection = false,
777                 collision_removal = false,
778                 vertical = false,
779                 texture = "smoke.png",
780         })
781
782         minetest.after(1.3, function()
783                 for _,dir in pairs(steam_check_dirs) do
784                         local n_pos = vector.add(pos,dir)
785                         local node2 = minetest.get_node(n_pos).name
786                         if not minetest.get_nodedef(node2, "walkable") then
787                                 local dir_mod = vector.multiply(dir,0.5)
788                                 local x_min
789                                 local x_max
790                                 local z_min
791                                 local z_max
792                                 if dir.z == 0 then
793                                         x_min = dir_mod.x
794                                         x_max = dir_mod.x
795                                         z_min = -0.2
796                                         z_max = 0.2
797                                 elseif dir.x == 0 then
798                                         x_min = -0.2
799                                         x_max = 0.2
800                                         z_min = dir_mod.z
801                                         z_max = dir_mod.z
802                                 end
803
804                                 local p_min = vector.new(pos.x+x_min,pos.y-0.2,pos.z+z_min)
805                                 local p_max = vector.new(pos.x+x_max,pos.y+0.2,pos.z+z_max)
806
807                                 local v_min = vector.new(dir_mod.x,0.2,dir_mod.z)
808                                 local v_max = vector.new(dir_mod.x*2,0.3,dir_mod.z*2)
809
810                                 minetest.add_particlespawner({
811                                         amount = 200,
812                                         time = 1.95,
813                                         minpos = p_min,
814                                         maxpos = p_max,
815                                         minvel = v_min,
816                                         maxvel = v_max,
817                                         minacc = vector.new(0,1,0),
818                                         maxacc = vector.new(0,3,0),
819                                         minexptime = 1.1,
820                                         maxexptime = 1.5,
821                                         minsize = 1,
822                                         maxsize = 2,
823                                         collisiondetection = false,
824                                         collision_removal = false,
825                                         vertical = false,
826                                         texture = "smoke.png^[colorize:white:255",
827                                 })
828                                 
829                         end
830                 end
831                 minetest.sound_play("steam_release",{pos=pos,gain=1,max_hear_distance=128})
832                 minetest.after(1, function()
833                         buffer_pool[hash_pos] = nil
834                 end)
835         end)
836 end
837
838 minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
839         if minetest.registered_items[itemstack:get_name()].mod_origin == "train" then
840                 local pos = player:get_pos()
841                 pos.y = pos.y + 1.625
842                 local look_dir = player:get_look_dir()
843                 look_dir = vector.multiply(look_dir,4)
844                 local pos2 = vector.add(pos,look_dir)
845                 local ray = minetest.raycast(pos, pos2, false, true)            
846                 if ray then
847                         for pointed_thing in ray do
848                                 if pointed_thing then
849                                         if minetest.get_node(pointed_thing.under).name == "craftingtable:craftingtable" then
850                                                 do_craft_effects(pointed_thing.under)
851                                         end
852                                 end
853                         end
854                 end
855         end
856 end)