]> git.lizzy.rs Git - Crafter.git/blob - mods/train/init.lua
Tune coupling collision detection more
[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                         else
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                         end
250                 end
251                 self.object:add_velocity(new_vel)
252         elseif self.axis_lock == "z" then
253                 local velocity_real = self.object:get_velocity()
254                 local distance = vector.distance(pos,pos2)
255                 local new_vel = vector.new(0,0,0)
256                 if distance > coupler_goal then
257                         local velocity = (distance-coupler_goal)*5
258                         local dir = vector.direction(vector.new(0,0,pos.z),vector.new(0,0,pos2.z))
259                         self.dir = dir
260                         new_vel = vector.multiply(dir,velocity)
261                 else
262                         if vector.equals(coupler_velocity,vector.new(0,0,0)) then
263                                 new_vel = vector.multiply(velocity_real,-1)
264                         else
265                                 local c_vel = vector.distance(vector.new(0,0,0),coupler_velocity)
266                                 local a_vel = vector.distance(vector.new(0,0,0),velocity_real)
267                                 local d_vel = a_vel-c_vel
268                                 if d_vel < 0 then
269                                         d_vel = 0
270                                 end
271                                 new_vel = vector.multiply(self.dir,d_vel)
272                         end
273                 end
274                 self.object:add_velocity(new_vel)
275         end
276
277         return
278 end
279
280
281 local function rail_brain(self,pos)
282
283         if not self.dir then self.dir = vector.new(0,0,0) end
284
285         local pos2 = self.object:get_pos()
286
287         local dir = self.dir
288
289         speed_limiter(self,6)
290
291         if not pool[minetest.hash_node_position(vector.add(pos,dir))] then
292
293                 if straight_snap(pos,self,dir) then
294                         return
295                 end
296
297                 local possible_dirs = create_axis(pos)
298
299                 if table.getn(possible_dirs) == 0 then
300                         --stop slow down become physical
301                 else
302                         for _,dir2 in pairs(possible_dirs) do
303                                 if turn_snap(pos,self,dir,dir2) then
304                                         return
305                                 end
306                                 if climb_snap(pos,self,dir,dir2) then
307                                         return
308                                 end
309                         end
310                 end
311         else
312                 if self.is_car then
313                         coupling_logic(self)
314                 end
315         end
316
317 end
318
319
320 --[[
321  █████╗ ██████╗ ██╗    ██████╗ ███████╗ ██████╗ ██╗███╗   ██╗
322 ██╔══██╗██╔══██╗██║    ██╔══██╗██╔════╝██╔════╝ ██║████╗  ██║
323 ███████║██████╔╝██║    ██████╔╝█████╗  ██║  ███╗██║██╔██╗ ██║
324 ██╔══██║██╔═══╝ ██║    ██╔══██╗██╔══╝  ██║   ██║██║██║╚██╗██║
325 ██║  ██║██║     ██║    ██████╔╝███████╗╚██████╔╝██║██║ ╚████║
326 ╚═╝  ╚═╝╚═╝     ╚═╝    ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝╚═╝  ╚═══╝
327 ]]--
328
329
330 function register_train(name,data)
331 local train = {}
332
333 train.power            = data.power
334 train.coupler_distance = data.coupler_distance
335 train.is_car           = data.is_car
336 train.is_engine        = data.is_engine
337 train.max_speed        = data.max_speed
338 train.driver           = nil
339
340 train.initial_properties = {
341         physical = false, -- otherwise going uphill breaks
342         collisionbox = {-0.4, -0.5, -0.4, 0.4, 0.45, 0.4},
343         visual = "mesh",
344         mesh = data.mesh,
345         visual_size = {x=1, y=1},
346         textures = {data.texture},
347 }
348
349
350 train.on_step = function(self,dtime)
351         if dtime > 0.1 then
352                 self.object:set_pos(self.old_pos)
353         end
354         local pos = vector.round(self.object:get_pos())
355         if not self.axis_lock then
356                 local possible_dirs = create_axis(pos)
357                 for _,dir in pairs(possible_dirs) do
358                         if dir.x ~=0 then
359                                 self.axis_lock = "x"
360                                 self.dir = dir
361                                 direction_snap(self)
362                                 break
363                         elseif dir.z ~= 0 then
364                                 self.axis_lock = "z"
365                                 self.dir = dir
366                                 direction_snap(self)
367                                 break
368                         end
369                 end
370         else
371                 rail_brain(self,pos)
372                 --collision_detect(self)
373         end
374         self.old_pos = self.object:get_pos()
375 end
376
377
378
379
380 train.on_punch = function(self, puncher)
381         if not puncher:get_wielded_item():get_name() == "train:wrench" then
382                 return
383         end
384
385         if self.is_engine and puncher:get_player_control().sneak then
386                 if vector.equals(self.object:get_velocity(),vector.new(0,0,0)) then
387                         if self.dir.y == 0 then
388                                 self.dir = vector.multiply(self.dir,-1)
389                                 direction_snap(self)
390                                 minetest.sound_play("wrench",{
391                                         object = self.object,
392                                         gain = 1.0,
393                                         max_hear_distance = 64,
394                                 })
395                         end
396                 end
397                 return
398         end
399
400         if self.is_engine then
401                 self.object:set_velocity(vector.multiply(self.dir,self.max_speed))
402                 return
403         end
404
405         if self.coupler1 then
406                 self.coupler1:get_luaentity().coupler2 = nil
407                 self.coupler1 = nil
408         end
409
410         if self.coupler2 then
411                 self.coupler2:get_luaentity().coupler1 = nil
412                 self.coupler2 = nil
413         end
414
415 end
416
417
418 train.on_rightclick = function(self,clicker)
419         --[[
420         if clicker:get_wielded_item():get_name() == "utility:furnace" then
421                 local obj = minetest.add_entity(pos, "train:furnace")
422                 obj:set_attach(self.object,"",vector.new(0,0,0),vector.new(0,0,0))
423                 minetest.sound_play("wrench",{
424                         object = self.object,
425                         gain = 1.0,
426                         max_hear_distance = 64,
427                 })
428                 coupling_particles(pos,true)
429                 self.furnace = true
430                 return
431         end
432         ]]--
433
434         if clicker:get_wielded_item():get_name() ~= "train:wrench" then
435                 if self.is_engine then
436                         if not self.driver then
437                                 print("jump on in")
438                                 clicker:set_attach(self.object, "", data.body_pos, data.body_rotation)
439                                 clicker:set_eye_offset(data.eye_offset,{x=0,y=0,z=0})
440                                 player_is_attached(clicker,true)
441                                 set_player_animation(clicker,"stand",0)
442                                 local rotation = self.object:get_rotation()
443                                 clicker:set_look_vertical(0)
444                                 clicker:set_look_horizontal(rotation.y)
445                                 self.object:set_velocity(vector.multiply(self.dir,self.max_speed))
446                                 self.driver = clicker
447                         elseif clicker == self.driver then
448                                 print("jumpin off!")
449                                 clicker:set_detach()
450                                 clicker:set_eye_offset({x=0,y=0,z=0},{x=0,y=0,z=0})
451                                 player_is_attached(clicker,false)
452                                 set_player_animation(clicker,"stand",0)
453                                 self.object:set_velocity(vector.new(0,0,0))
454                                 self.driver = nil
455                         end
456                         return
457                 end
458                 return
459         end
460
461         local pos = self.object:get_pos()
462
463         local name = clicker:get_player_name()
464         if not pool[name] then
465                 if not self.coupler2 then
466                         pool[name] = self.object
467                         minetest.sound_play("wrench",{
468                                 object = self.object,
469                                 gain = 1.0,
470                                 max_hear_distance = 64,
471                         })
472                         coupling_particles(pos,true)
473                 else
474                         minetest.sound_play("wrench",{
475                                 object = self.object,
476                                 gain = 1.0,
477                                 max_hear_distance = 64,
478                                 pitch = 0.7,
479                         })
480                         coupling_particles(pos,false)
481                 end
482         else
483                 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
484                         self.coupler1 = pool[name]
485                         pool[name]:get_luaentity().coupler2 = self.object
486                         minetest.sound_play("wrench",{
487                                 object = self.object,
488                                 gain = 1.0,
489                                 max_hear_distance = 64,
490                         })
491                         coupling_particles(pos,true)
492                 else
493                         minetest.sound_play("wrench",{
494                                 object = self.object,
495                                 gain = 1.0,
496                                 max_hear_distance = 64,
497                                 pitch = 0.7,
498                         })
499                         coupling_particles(pos,false)
500                 end
501                 pool[name] = nil
502         end
503 end
504
505 --get old data
506 train.on_activate = function(self,staticdata, dtime_s)
507         self.object:set_armor_groups({immortal=1})
508         if string.sub(staticdata, 1, string.len("return")) ~= "return" then
509                 return
510         end
511         local data = minetest.deserialize(staticdata)
512         if type(data) ~= "table" then
513                 return
514         end
515         self.old_pos = self.object:get_pos()
516         self.velocity = vector.new(0,0,0)
517 end
518
519 train.get_staticdata = function(self)
520         return minetest.serialize({
521         })
522 end
523
524 minetest.register_entity(name, train)
525
526 end
527
528 --[[
529 ███████╗███╗   ██╗██████╗ 
530 ██╔════╝████╗  ██║██╔══██╗
531 █████╗  ██╔██╗ ██║██║  ██║
532 ██╔══╝  ██║╚██╗██║██║  ██║
533 ███████╗██║ ╚████║██████╔╝
534 ╚══════╝╚═╝  ╚═══╝╚═════╝ 
535 ]]--
536
537
538
539
540 register_train("train:steam_train",{
541         mesh = "steam_train.b3d",
542         texture = "steam_train.png",
543         is_engine = true,
544         power = 6,
545         max_speed = 6,
546         coupler_distance = 3,
547         body_pos = vector.new(0,0,-15),
548         body_rotation = vector.new(0,0,0),
549         eye_offset = vector.new(6,-1,-10)
550 })
551
552 register_train("train:minecart",{
553         mesh = "minecart.x",
554         texture = "minecart.png",
555         --is_engine = true,
556         is_car = true,
557         --power = 6,
558         max_speed = 6,
559         coupler_distance = 1.3,
560         --body_pos = vector.new(0,0,-15),
561         --body_rotation = vector.new(0,0,0),
562         --eye_offset = vector.new(6,-1,-10)
563 })
564
565
566
567 minetest.register_craftitem("train:train", {
568         description = "train",
569         inventory_image = "minecartitem.png",
570         wield_image = "minecartitem.png",
571         on_place = function(itemstack, placer, pointed_thing)
572                 if not pointed_thing.type == "node" then
573                         return
574                 end
575                 
576                 local sneak = placer:get_player_control().sneak
577                 local noddef = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
578                 if not sneak and noddef.on_rightclick then
579                         minetest.item_place(itemstack, placer, pointed_thing)
580                         return
581                 end
582                 
583                 if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "rail")>0 then
584                         minetest.add_entity(pointed_thing.under, "train:steam_train")
585                 else
586                         return
587                 end
588
589                 itemstack:take_item()
590
591                 return itemstack
592         end,
593 })
594
595 minetest.register_craft({
596         output = "train:minecart",
597         recipe = {
598                 {"main:iron", "main:iron", "main:iron"},
599                 {"main:iron", "main:iron", "main:iron"},
600         },
601 })
602
603
604 minetest.register_craftitem("train:minecart", {
605         description = "train",
606         inventory_image = "minecartitem.png",
607         wield_image = "minecartitem.png",
608         on_place = function(itemstack, placer, pointed_thing)
609                 if not pointed_thing.type == "node" then
610                         return
611                 end
612                 
613                 local sneak = placer:get_player_control().sneak
614                 local noddef = minetest.registered_nodes[minetest.get_node(pointed_thing.under).name]
615                 if not sneak and noddef.on_rightclick then
616                         minetest.item_place(itemstack, placer, pointed_thing)
617                         return
618                 end
619                 
620                 if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "rail")>0 then
621                         minetest.add_entity(pointed_thing.under, "train:minecart")
622                 else
623                         return
624                 end
625
626                 itemstack:take_item()
627
628                 return itemstack
629         end,
630 })
631
632 minetest.register_craft({
633         output = "train:train",
634         recipe = {
635                 {"main:iron", "", "main:iron"},
636                 {"main:iron", "main:iron", "main:iron"},
637         },
638 })
639
640
641
642 minetest.register_node("train:rail",{
643         description = "Rail",
644         wield_image = "rail.png",
645         tiles = {
646                 "rail.png", "railcurve.png",
647                 "railt.png", "railcross.png"
648         },
649         drawtype = "raillike",
650         paramtype = "light",
651         sunlight_propagates = true,
652         is_ground_content = false,
653         walkable = false,
654         node_placement_prediction = "",
655         selection_box = {
656                 type = "fixed",
657                 fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
658         },
659         sounds = main.stoneSound(),
660         after_place_node = function(pos)
661                 data_injection(pos,true)
662         end,
663         after_destruct = function(pos)
664                 data_injection(pos)
665         end,
666         groups={stone=1,wood=1,rail=1,attached_node=1},
667 })
668
669
670 minetest.register_lbm({
671         name = "train:rail",
672         nodenames = {"train:rail"},
673         run_at_every_load = true,
674         action = function(pos)
675                 data_injection(pos,true)
676         end,
677 })
678
679 minetest.register_craft({
680         output = "train:rail 32",
681         recipe = {
682                 {"main:iron","","main:iron"},
683                 {"main:iron","main:stick","main:iron"},
684                 {"main:iron","","main:iron"}
685         }
686 })
687
688
689 minetest.register_food("train:wrench",{
690         description = "Train Wrench",
691         texture = "wrench.png",
692 })
693
694 minetest.register_craft({
695         output = "train:wrench",
696         recipe = {
697                 {"main:iron", "", "main:iron"},
698                 {"main:iron", "main:lapis", "main:iron"},
699                 {"", "main:lapis", ""}
700         }
701 })
702
703
704
705 minetest.register_entity("train:furnace", {
706         initial_properties = {
707                 visual = "wielditem",
708                 visual_size = {x = 0.6, y = 0.6},
709                 textures = {},
710                 physical = true,
711                 is_visible = false,
712                 collide_with_objects = false,
713                 pointable=false,
714                 collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
715         },
716         set_node = function(self)
717                 self.object:set_properties({
718                         is_visible = true,
719                         textures = {"utility:furnace"},
720                 })
721         end,
722
723
724         on_activate = function(self, staticdata)
725                 self.object:set_armor_groups({immortal = 1})
726
727                 self:set_node()
728         end,
729 })