2 mcl_minecarts
.modpath
= minetest
.get_modpath("mcl_minecarts")
3 mcl_minecarts
.speed_max
= 10
4 mcl_minecarts
.check_float_time
= 15
6 dofile(mcl_minecarts
.modpath
.."/functions.lua")
7 dofile(mcl_minecarts
.modpath
.."/rails.lua")
9 -- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
10 local entity_mapping
= {}
12 local function register_entity(entity_id
, mesh
, textures
, drop
, on_rightclick
)
15 collisionbox
= {-10/16., -0.5, -10/16, 10/16, 0.25, 10/16},
18 visual_size
= {x
=1, y
=1},
21 on_rightclick
= on_rightclick
,
23 _driver
= nil, -- player who sits in and controls the minecart (only for minecart!)
24 _punched
= false, -- used to re-send _velocity and position
25 _velocity
= {x
=0, y
=0, z
=0}, -- only used on punch
26 _start_pos
= nil, -- Used to calculate distance for “On A Rail” achievement
27 _last_float_check
= nil, -- timestamp of last time the cart was checked to be still on a rail
28 _old_dir
= {x
=0, y
=0, z
=0},
30 _old_vel
= {x
=0, y
=0, z
=0},
35 function cart
:on_activate(staticdata
, dtime_s
)
36 local data
= minetest
.deserialize(staticdata
)
37 if type(data
) == "table" then
38 self
._railtype
= data
._railtype
40 self
.object
:set_armor_groups({immortal
=1})
43 function cart
:on_punch(puncher
, time_from_last_punch
, tool_capabilities
, direction
)
44 local pos
= self
.object
:get_pos()
45 if not self
._railtype
then
46 local node
= minetest
.get_node(vector
.floor(pos
)).name
47 self
._railtype
= minetest
.get_item_group(node
, "connect_to_raillike")
50 if not puncher
or not puncher
:is_player() then
51 local cart_dir
= mcl_minecarts
:get_rail_direction(pos
, {x
=1, y
=0, z
=0}, nil, nil, self
._railtype
)
52 if vector
.equals(cart_dir
, {x
=0, y
=0, z
=0}) then
55 self
._velocity
= vector
.multiply(cart_dir
, 3)
61 if puncher
:get_player_control().sneak
then
64 self
.object
:setpos(self
._old_pos
)
66 mcl_player
.player_attached
[self
._driver
] = nil
67 local player
= minetest
.get_player_by_name(self
._driver
)
70 player
:set_eye_offset({x
=0, y
=0, z
=0},{x
=0, y
=0, z
=0})
74 -- Disable detector rail
75 local rou_pos
= vector
.round(pos
)
76 local node
= minetest
.get_node(rou_pos
)
77 if node
.name
== "mcl_minecarts:detector_rail_on" then
78 local newnode
= {name
="mcl_minecarts:detector_rail", param2
= node
.param2
}
79 minetest
.swap_node(rou_pos
, newnode
)
80 mesecon
.receptor_off(rou_pos
)
83 -- Drop items and remove cart entity
84 if not minetest
.settings
:get_bool("creative_mode") then
86 minetest
.add_item(self
.object
:get_pos(), drop
[d
])
94 local vel
= self
.object
:getvelocity()
95 if puncher
:get_player_name() == self
._driver
then
96 if math
.abs(vel
.x
+ vel
.z
) > 7 then
101 local punch_dir
= mcl_minecarts
:velocity_to_dir(puncher
:get_look_dir())
103 local cart_dir
= mcl_minecarts
:get_rail_direction(pos
, punch_dir
, nil, nil, self
._railtype
)
104 if vector
.equals(cart_dir
, {x
=0, y
=0, z
=0}) then
108 time_from_last_punch
= math
.min(time_from_last_punch
, tool_capabilities
.full_punch_interval
)
109 local f
= 3 * (time_from_last_punch
/ tool_capabilities
.full_punch_interval
)
111 self
._velocity
= vector
.multiply(cart_dir
, f
)
116 function cart
:on_step(dtime
)
117 local vel
= self
.object
:getvelocity()
119 if self
._last_float_check
== nil then
120 self
._last_float_check
= 0
122 self
._last_float_check
= self
._last_float_check
+ dtime
124 local pos
, rou_pos
, node
125 -- Drop minecart if it isn't on a rail anymore
126 if self
._last_float_check
>= mcl_minecarts
.check_float_time
then
127 pos
= self
.object
:get_pos()
128 rou_pos
= vector
.round(pos
)
129 node
= minetest
.get_node(rou_pos
)
130 local g
= minetest
.get_item_group(node
.name
, "connect_to_raillike")
131 if g
~= self
._railtype
and self
._railtype
~= nil then
134 if self
._old_pos
then
135 self
.object
:setpos(self
._old_pos
)
137 mcl_player
.player_attached
[self
._driver
] = nil
138 local player
= minetest
.get_player_by_name(self
._driver
)
141 player
:set_eye_offset({x
=0, y
=0, z
=0},{x
=0, y
=0, z
=0})
145 -- Drop items and remove cart entity
146 if not minetest
.settings
:get_bool("creative_mode") then
148 minetest
.add_item(self
.object
:get_pos(), drop
[d
])
155 self
._last_float_check
= 0
158 if self
._punched
then
159 vel
= vector
.add(vel
, self
._velocity
)
160 self
.object
:setvelocity(vel
)
162 elseif vector
.equals(vel
, {x
=0, y
=0, z
=0}) then
166 local dir
, last_switch
= nil, nil
168 pos
= self
.object
:get_pos()
170 if self
._old_pos
and not self
._punched
then
171 local flo_pos
= vector
.floor(pos
)
172 local flo_old
= vector
.floor(self
._old_pos
)
173 if vector
.equals(flo_pos
, flo_old
) then
175 -- Prevent querying the same node over and over again
179 rou_pos
= vector
.round(pos
)
181 rou_old
= vector
.round(self
._old_pos
)
183 node
= minetest
.get_node(rou_pos
)
185 local node_old
= minetest
.get_node(rou_old
)
187 -- Update detector rails
188 if node
.name
== "mcl_minecarts:detector_rail" then
189 local newnode
= {name
="mcl_minecarts:detector_rail_on", param2
= node
.param2
}
190 minetest
.swap_node(rou_pos
, newnode
)
191 mesecon
.receptor_on(rou_pos
)
193 if node_old
.name
== "mcl_minecarts:detector_rail_on" then
194 local newnode
= {name
="mcl_minecarts:detector_rail", param2
= node_old
.param2
}
195 minetest
.swap_node(rou_old
, newnode
)
196 mesecon
.receptor_off(rou_old
)
200 local ctrl
, player
= nil, nil
202 player
= minetest
.get_player_by_name(self
._driver
)
204 ctrl
= player
:get_player_control()
208 -- Stop cart if velocity vector flips
209 if self
._old_vel
and self
._old_vel
.y
== 0 and
210 (self
._old_vel
.x
* vel
.x
< 0 or self
._old_vel
.z
* vel
.z
< 0) then
211 self
._old_vel
= {x
= 0, y
= 0, z
= 0}
213 self
.object
:setvelocity(vector
.new())
214 self
.object
:setacceleration(vector
.new())
217 self
._old_vel
= vector
.new(vel
)
219 if self
._old_pos
then
220 local diff
= vector
.subtract(self
._old_pos
, pos
)
221 for _
,v
in ipairs({"x","y","z"}) do
222 if math
.abs(diff
[v
]) > 1.1 then
223 local expected_pos
= vector
.add(self
._old_pos
, self
._old_dir
)
224 dir
, last_switch
= mcl_minecarts
:get_rail_direction(pos
, self
._old_dir
, ctrl
, self
._old_switch
, self
._railtype
)
225 if vector
.equals(dir
, {x
=0, y
=0, z
=0}) then
227 pos
= vector
.new(expected_pos
)
236 for _
,v
in ipairs({"x", "z"}) do
237 if vel
[v
] ~= 0 and math
.abs(vel
[v
]) < 0.9 then
244 local cart_dir
= mcl_minecarts
:velocity_to_dir(vel
)
245 local max_vel
= mcl_minecarts
.speed_max
247 dir
, last_switch
= mcl_minecarts
:get_rail_direction(pos
, cart_dir
, ctrl
, self
._old_switch
, self
._railtype
)
250 local new_acc
= {x
=0, y
=0, z
=0}
251 if vector
.equals(dir
, {x
=0, y
=0, z
=0}) then
252 vel
= {x
=0, y
=0, z
=0}
255 -- If the direction changed
256 if dir
.x
~= 0 and self
._old_dir
.z
~= 0 then
257 vel
.x
= dir
.x
* math
.abs(vel
.z
)
259 pos
.z
= math
.floor(pos
.z
+ 0.5)
262 if dir
.z
~= 0 and self
._old_dir
.x
~= 0 then
263 vel
.z
= dir
.z
* math
.abs(vel
.x
)
265 pos
.x
= math
.floor(pos
.x
+ 0.5)
269 if dir
.y
~= self
._old_dir
.y
then
270 vel
.y
= dir
.y
* math
.abs(vel
.x
+ vel
.z
)
271 pos
= vector
.round(pos
)
275 -- Slow down or speed up
276 local acc
= dir
.y
* -1.8
278 local speed_mod
= minetest
.registered_nodes
[minetest
.get_node(pos
).name
]._rail_acceleration
279 if speed_mod
and speed_mod
~= 0 then
280 acc
= acc
+ speed_mod
285 new_acc
= vector
.multiply(dir
, acc
)
288 self
.object
:setacceleration(new_acc
)
289 self
._old_pos
= vector
.new(pos
)
290 self
._old_dir
= vector
.new(dir
)
291 self
._old_switch
= last_switch
294 for _
,v
in ipairs({"x","y","z"}) do
295 if math
.abs(vel
[v
]) > max_vel
then
296 vel
[v
] = mcl_minecarts
:get_sign(vel
[v
]) * max_vel
302 -- Give achievement when player reached a distance of 1000 nodes from the start position
303 if self
._driver
and (vector
.distance(self
._start_pos
, pos
) >= 1000) then
304 awards
.unlock(self
._driver
, "mcl:onARail")
308 if update
.pos
or self
._punched
then
312 elseif dir
.x
> 0 then
314 elseif dir
.z
< 0 then
317 self
.object
:setyaw(yaw
* math
.pi
)
320 if self
._punched
then
321 self
._punched
= false
324 if not (update
.vel
or update
.pos
) then
329 local anim
= {x
=0, y
=0}
332 elseif dir
.y
== 1 then
335 self
.object
:set_animation(anim
, 1, 0)
337 self
.object
:setvelocity(vel
)
339 self
.object
:setpos(pos
)
344 function cart
:get_staticdata()
345 return minetest
.serialize({_railtype
= self
._railtype
})
348 minetest
.register_entity(entity_id
, cart
)
351 -- Place a minecart at pointed_thing
352 mcl_minecarts
.place_minecart
= function(itemstack
, pointed_thing
)
353 if not pointed_thing
.type == "node" then
358 if mcl_minecarts
:is_rail(pointed_thing
.under
) then
359 railpos
= pointed_thing
.under
360 node
= minetest
.get_node(pointed_thing
.under
)
361 elseif mcl_minecarts
:is_rail(pointed_thing
.above
) then
362 railpos
= pointed_thing
.above
363 node
= minetest
.get_node(pointed_thing
.above
)
368 -- Activate detector rail
369 if node
.name
== "mcl_minecarts:detector_rail" then
370 local newnode
= {name
="mcl_minecarts:detector_rail_on", param2
= node
.param2
}
371 minetest
.swap_node(railpos
, newnode
)
372 mesecon
.receptor_on(railpos
)
375 local entity_id
= entity_mapping
[itemstack
:get_name()]
376 local cart
= minetest
.add_entity(railpos
, entity_id
)
377 local railtype
= minetest
.get_item_group(node
.name
, "connect_to_raillike")
378 local le
= cart
:get_luaentity()
380 le
._railtype
= railtype
382 local cart_dir
= mcl_minecarts
:get_rail_direction(railpos
, {x
=1, y
=0, z
=0}, nil, nil, railtype
)
383 cart
:setyaw(minetest
.dir_to_yaw(cart_dir
))
385 if not minetest
.settings
:get_bool("creative_mode") then
386 itemstack
:take_item()
392 local register_craftitem
= function(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
393 entity_mapping
[itemstring
] = entity_id
395 local groups
= { minecart
= 1, transport
= 1 }
396 if creative
== false then
397 groups
.not_in_creative_inventory
= 1
401 on_place
= function(itemstack
, placer
, pointed_thing
)
402 if not pointed_thing
.type == "node" then
406 -- Call on_rightclick if the pointed node defines it
407 local node
= minetest
.get_node(pointed_thing
.under
)
408 if placer
and not placer
:get_player_control().sneak
then
409 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
410 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
414 return mcl_minecarts
.place_minecart(itemstack
, pointed_thing
)
416 _on_dispense
= function(stack
, pos
, droppos
, dropnode
, dropdir
)
417 -- Place minecart as entity on rail. If there's no rail, just drop it.
419 if minetest
.get_item_group(dropnode
.name
, "rail") ~= 0 then
420 -- FIXME: This places minecarts even if the spot is already occupied
421 local pointed_thing
= { under
= droppos
, above
= { x
=droppos
.x
, y
=droppos
.y
+1, z
=droppos
.z
} }
422 placed
= mcl_minecarts
.place_minecart(stack
, pointed_thing
)
424 if placed
== nil then
426 minetest
.add_item(droppos
, stack
)
431 def
.description
= description
432 def
._doc_items_longdesc
= longdesc
433 def
._doc_items_usagehelp
= usagehelp
434 def
.inventory_image
= icon
435 def
.wield_image
= icon
436 minetest
.register_craftitem(itemstring
, def
)
441 * itemstring: Itemstring of minecart item
442 * entity_id: ID of minecart entity
443 * description: Item name / description
444 * longdesc: Long help text
445 * usagehelp: Usage help text
446 * mesh: Minecart mesh
447 * textures: Minecart textures table
449 * drop: Dropped items after destroying minecart
450 * on_rightclick: Called after rightclick
451 * on_activate_by_rail: Called when above activator rail
452 * creative: If false, don't show in Creative Inventory
454 local function register_minecart(itemstring
, entity_id
, description
, longdesc
, usagehelp
, mesh
, textures
, icon
, drop
, on_rightclick
, on_activate_by_rail
, creative
)
455 register_entity(entity_id
, mesh
, textures
, drop
, on_rightclick
)
456 register_craftitem(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
457 if minetest
.get_modpath("doc_identifier") ~= nil then
458 doc
.sub
.identifier
.register_object(entity_id
, "craftitems", itemstring
)
464 "mcl_minecarts:minecart",
465 "mcl_minecarts:minecart",
467 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
468 "Minecarts only ride on rails and always follow the tracks. At a T-junction with no straight way ahead, they turn left. The speed is affected by the rail type.",
469 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
470 "To obtain the minecart, punch it while holding down the sneak key.",
471 "mcl_minecarts_minecart.b3d",
472 {"mcl_minecarts_minecart.png"},
473 "mcl_minecarts_minecart_normal.png",
474 {"mcl_minecarts:minecart"},
475 function(self
, clicker
)
476 local name
= clicker
:get_player_name()
477 if not clicker
or not clicker
:is_player() then
480 local player_name
= clicker
:get_player_name()
481 if self
._driver
and player_name
== self
._driver
then
483 self
._start_pos
= nil
485 clicker
:set_eye_offset({x
=0, y
=0, z
=0},{x
=0, y
=0, z
=0})
486 elseif not self
._driver
then
487 self
._driver
= player_name
488 self
._start_pos
= self
.object
:get_pos()
489 mcl_player
.player_attached
[player_name
] = true
490 clicker
:set_attach(self
.object
, "", {x
=0, y
=8.25, z
=-2}, {x
=0, y
=0, z
=0})
491 mcl_player
.player_attached
[name
] = true
492 minetest
.after(0.2, function(name
)
493 local player
= minetest
.get_player_by_name(name
)
495 mcl_player
.player_set_animation(player
, "sit" , 30)
496 player
:set_eye_offset({x
=0, y
=-5.5, z
=0},{x
=0, y
=-4, z
=0})
503 -- Minecart with Chest
505 "mcl_minecarts:chest_minecart",
506 "mcl_minecarts:chest_minecart",
507 "Minecart with Chest",
509 "mcl_minecarts_minecart_chest.b3d",
510 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
511 "mcl_minecarts_minecart_chest.png",
512 {"mcl_minecarts:minecart", "mcl_chests:chest"},
515 -- Minecart with Furnace
517 "mcl_minecarts:furnace_minecart",
518 "mcl_minecarts:furnace_minecart",
519 "Minecart with Furnace",
521 "mcl_minecarts_minecart_block.b3d",
523 "default_furnace_top.png",
524 "default_furnace_top.png",
525 "default_furnace_front.png",
526 "default_furnace_side.png",
527 "default_furnace_side.png",
528 "default_furnace_side.png",
529 "mcl_minecarts_minecart.png",
531 "mcl_minecarts_minecart_furnace.png",
532 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
533 -- Feed furnace with coal
534 function(self
, clicker
)
535 if not clicker
or not clicker
:is_player() then
538 if not self
._fueltime
then
541 local held
= clicker
:get_wielded_item()
542 if minetest
.get_item_group(held
:get_name(), "coal") == 1 then
543 self
._fueltime
= self
._fueltime
+ 180
545 if not minetest
.settings
:get_bool("creative_mode") then
547 local index
= clicker
:get_wielded_index()
548 local inv
= clicker
:get_inventory()
549 inv
:set_stack("main", index
, held
)
553 minetest
.chat_send_player(clicker
:get_player_name(), "Fuel: " .. tostring(self
._fueltime
))
558 -- Minecart with Command Block
560 "mcl_minecarts:command_block_minecart",
561 "mcl_minecarts:command_block_minecart",
562 "Minecart with Command Block",
564 "mcl_minecarts_minecart_block.b3d",
566 "jeija_commandblock_off.png^[verticalframe:2:0",
567 "jeija_commandblock_off.png^[verticalframe:2:0",
568 "jeija_commandblock_off.png^[verticalframe:2:0",
569 "jeija_commandblock_off.png^[verticalframe:2:0",
570 "jeija_commandblock_off.png^[verticalframe:2:0",
571 "jeija_commandblock_off.png^[verticalframe:2:0",
572 "mcl_minecarts_minecart.png",
574 "mcl_minecarts_minecart_command_block.png",
575 {"mcl_minecarts:minecart"},
579 -- Minecart with Hopper
581 "mcl_minecarts:hopper_minecart",
582 "mcl_minecarts:hopper_minecart",
583 "Minecart with Hopper",
585 "mcl_minecarts_minecart_hopper.b3d",
587 "mcl_hoppers_hopper_inside.png",
588 "mcl_minecarts_minecart.png",
589 "mcl_hoppers_hopper_outside.png",
590 "mcl_hoppers_hopper_top.png",
592 "mcl_minecarts_minecart_hopper.png",
593 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
599 "mcl_minecarts:tnt_minecart",
600 "mcl_minecarts:tnt_minecart",
603 "mcl_minecarts_minecart_block.b3d",
605 "default_tnt_top.png",
606 "default_tnt_bottom.png",
607 "default_tnt_side.png",
608 "default_tnt_side.png",
609 "default_tnt_side.png",
610 "default_tnt_side.png",
611 "mcl_minecarts_minecart.png",
613 "mcl_minecarts_minecart_tnt.png",
614 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
619 minetest
.register_craft({
620 output
= "mcl_minecarts:minecart",
622 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
623 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
627 -- TODO: Re-enable crafting of special minecarts when they have been implemented
629 minetest
.register_craft({
630 output
= "mcl_minecarts:hopper_minecart",
632 {"mcl_hoppers:hopper"},
633 {"mcl_minecarts:minecart"},
637 minetest
.register_craft({
638 output
= "mcl_minecarts:chest_minecart",
640 {"mcl_chests:chest"},
641 {"mcl_minecarts:minecart"},
645 minetest
.register_craft({
646 output
= "mcl_minecarts:tnt_minecart",
649 {"mcl_minecarts:minecart"},
653 minetest
.register_craft({
654 output
= "mcl_minecarts:furnace_minecart",
656 {"mcl_furnaces:furnace"},
657 {"mcl_minecarts:minecart"},