2 mcl_minecarts
.modpath
= minetest
.get_modpath("mcl_minecarts")
3 mcl_minecarts
.speed_max
= 10
5 dofile(mcl_minecarts
.modpath
.."/functions.lua")
6 dofile(mcl_minecarts
.modpath
.."/rails.lua")
8 -- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
9 local entity_mapping
= {}
11 local function register_entity(entity_id
, mesh
, textures
, drop
, on_rightclick
)
14 collisionbox
= {-10/16., -0.5, -10/16, 10/16, 0.25, 10/16},
17 visual_size
= {x
=1, y
=1},
20 on_rightclick
= on_rightclick
,
22 _driver
= nil, -- player who sits in and controls the minecart (only for minecart!)
23 _punched
= false, -- used to re-send _velocity and position
24 _velocity
= {x
=0, y
=0, z
=0}, -- only used on punch
25 _start_pos
= nil, -- Used to calculate distance for “On A Rail” achievement
26 _old_dir
= {x
=0, y
=0, z
=0},
28 _old_vel
= {x
=0, y
=0, z
=0},
33 function cart
:on_activate(staticdata
, dtime_s
)
34 self
.object
:set_armor_groups({immortal
=1})
37 function cart
:on_punch(puncher
, time_from_last_punch
, tool_capabilities
, direction
)
38 local pos
= self
.object
:getpos()
39 if not self
._railtype
then
40 local node
= minetest
.get_node(vector
.floor(pos
)).name
41 self
._railtype
= minetest
.get_item_group(node
, "connect_to_raillike")
44 if not puncher
or not puncher
:is_player() then
45 local cart_dir
= mcl_minecarts
:get_rail_direction(pos
, {x
=1, y
=0, z
=0}, nil, nil, self
._railtype
)
46 if vector
.equals(cart_dir
, {x
=0, y
=0, z
=0}) then
49 self
._velocity
= vector
.multiply(cart_dir
, 3)
55 if puncher
:get_player_control().sneak
then
58 self
.object
:setpos(self
._old_pos
)
60 mcl_player
.player_attached
[self
._driver
] = nil
61 local player
= minetest
.get_player_by_name(self
._driver
)
67 -- Disable detector rail
68 local rou_pos
= vector
.round(pos
)
69 local node
= minetest
.get_node(rou_pos
)
70 if node
.name
== "mcl_minecarts:detector_rail_on" then
71 local newnode
= {name
="mcl_minecarts:detector_rail", param2
= node
.param2
}
72 minetest
.swap_node(rou_pos
, newnode
)
73 mesecon
.receptor_off(rou_pos
)
76 -- Drop items and remove cart entity
77 if not minetest
.settings
:get_bool("creative_mode") then
79 minetest
.add_item(self
.object
:getpos(), drop
[d
])
87 local vel
= self
.object
:getvelocity()
88 if puncher
:get_player_name() == self
._driver
then
89 if math
.abs(vel
.x
+ vel
.z
) > 7 then
94 local punch_dir
= mcl_minecarts
:velocity_to_dir(puncher
:get_look_dir())
96 local cart_dir
= mcl_minecarts
:get_rail_direction(pos
, punch_dir
, nil, nil, self
._railtype
)
97 if vector
.equals(cart_dir
, {x
=0, y
=0, z
=0}) then
101 time_from_last_punch
= math
.min(time_from_last_punch
, tool_capabilities
.full_punch_interval
)
102 local f
= 3 * (time_from_last_punch
/ tool_capabilities
.full_punch_interval
)
104 self
._velocity
= vector
.multiply(cart_dir
, f
)
109 function cart
:on_step(dtime
)
110 local vel
= self
.object
:getvelocity()
112 if self
._punched
then
113 vel
= vector
.add(vel
, self
._velocity
)
114 self
.object
:setvelocity(vel
)
116 elseif vector
.equals(vel
, {x
=0, y
=0, z
=0}) then
120 local dir
, last_switch
= nil, nil
121 local pos
= self
.object
:getpos()
122 if self
._old_pos
and not self
._punched
then
123 local flo_pos
= vector
.floor(pos
)
124 local flo_old
= vector
.floor(self
._old_pos
)
125 if vector
.equals(flo_pos
, flo_old
) then
127 -- Prevent querying the same node over and over again
130 -- Update detector rails
131 local rou_pos
= vector
.round(pos
)
132 local rou_old
= vector
.round(self
._old_pos
)
133 local node
= minetest
.get_node(rou_pos
)
134 local node_old
= minetest
.get_node(rou_old
)
136 if node
.name
== "mcl_minecarts:detector_rail" then
137 local newnode
= {name
="mcl_minecarts:detector_rail_on", param2
= node
.param2
}
138 minetest
.swap_node(rou_pos
, newnode
)
139 mesecon
.receptor_on(rou_pos
)
141 if node_old
.name
== "mcl_minecarts:detector_rail_on" then
142 local newnode
= {name
="mcl_minecarts:detector_rail", param2
= node_old
.param2
}
143 minetest
.swap_node(rou_old
, newnode
)
144 mesecon
.receptor_off(rou_old
)
148 local ctrl
, player
= nil, nil
150 player
= minetest
.get_player_by_name(self
._driver
)
152 ctrl
= player
:get_player_control()
156 -- Stop cart if velocity vector flips
157 if self
._old_vel
and self
._old_vel
.y
== 0 and
158 (self
._old_vel
.x
* vel
.x
< 0 or self
._old_vel
.z
* vel
.z
< 0) then
159 self
._old_vel
= {x
= 0, y
= 0, z
= 0}
161 self
.object
:setvelocity(vector
.new())
162 self
.object
:setacceleration(vector
.new())
165 self
._old_vel
= vector
.new(vel
)
167 if self
._old_pos
then
168 local diff
= vector
.subtract(self
._old_pos
, pos
)
169 for _
,v
in ipairs({"x","y","z"}) do
170 if math
.abs(diff
[v
]) > 1.1 then
171 local expected_pos
= vector
.add(self
._old_pos
, self
._old_dir
)
172 dir
, last_switch
= mcl_minecarts
:get_rail_direction(pos
, self
._old_dir
, ctrl
, self
._old_switch
, self
._railtype
)
173 if vector
.equals(dir
, {x
=0, y
=0, z
=0}) then
175 pos
= vector
.new(expected_pos
)
184 for _
,v
in ipairs({"x", "z"}) do
185 if vel
[v
] ~= 0 and math
.abs(vel
[v
]) < 0.9 then
192 local cart_dir
= mcl_minecarts
:velocity_to_dir(vel
)
193 local max_vel
= mcl_minecarts
.speed_max
195 dir
, last_switch
= mcl_minecarts
:get_rail_direction(pos
, cart_dir
, ctrl
, self
._old_switch
, self
._railtype
)
198 local new_acc
= {x
=0, y
=0, z
=0}
199 if vector
.equals(dir
, {x
=0, y
=0, z
=0}) then
200 vel
= {x
=0, y
=0, z
=0}
203 -- If the direction changed
204 if dir
.x
~= 0 and self
._old_dir
.z
~= 0 then
205 vel
.x
= dir
.x
* math
.abs(vel
.z
)
207 pos
.z
= math
.floor(pos
.z
+ 0.5)
210 if dir
.z
~= 0 and self
._old_dir
.x
~= 0 then
211 vel
.z
= dir
.z
* math
.abs(vel
.x
)
213 pos
.x
= math
.floor(pos
.x
+ 0.5)
217 if dir
.y
~= self
._old_dir
.y
then
218 vel
.y
= dir
.y
* math
.abs(vel
.x
+ vel
.z
)
219 pos
= vector
.round(pos
)
223 -- Slow down or speed up
224 local acc
= dir
.y
* -1.8
226 local speed_mod
= minetest
.registered_nodes
[minetest
.get_node(pos
).name
]._rail_acceleration
227 if speed_mod
and speed_mod
~= 0 then
228 acc
= acc
+ speed_mod
233 new_acc
= vector
.multiply(dir
, acc
)
236 self
.object
:setacceleration(new_acc
)
237 self
._old_pos
= vector
.new(pos
)
238 self
._old_dir
= vector
.new(dir
)
239 self
._old_switch
= last_switch
242 for _
,v
in ipairs({"x","y","z"}) do
243 if math
.abs(vel
[v
]) > max_vel
then
244 vel
[v
] = mcl_minecarts
:get_sign(vel
[v
]) * max_vel
250 -- Give achievement when player reached a distance of 1000 nodes from the start position
251 if self
._driver
and (vector
.distance(self
._start_pos
, pos
) >= 1000) then
252 awards
.unlock(self
._driver
, "mcl:onARail")
256 if update
.pos
or self
._punched
then
260 elseif dir
.x
> 0 then
262 elseif dir
.z
< 0 then
265 self
.object
:setyaw(yaw
* math
.pi
)
268 if self
._punched
then
269 self
._punched
= false
272 if not (update
.vel
or update
.pos
) then
277 local anim
= {x
=0, y
=0}
280 elseif dir
.y
== 1 then
283 self
.object
:set_animation(anim
, 1, 0)
285 self
.object
:setvelocity(vel
)
287 self
.object
:setpos(pos
)
292 function cart
:get_staticdata()
296 minetest
.register_entity(entity_id
, cart
)
299 -- Place a minecart at pointed_thing
300 mcl_minecarts
.place_minecart
= function(itemstack
, pointed_thing
)
301 if not pointed_thing
.type == "node" then
306 if mcl_minecarts
:is_rail(pointed_thing
.under
) then
307 railpos
= pointed_thing
.under
308 node
= minetest
.get_node(pointed_thing
.under
)
309 elseif mcl_minecarts
:is_rail(pointed_thing
.above
) then
310 railpos
= pointed_thing
.above
311 node
= minetest
.get_node(pointed_thing
.above
)
316 -- Activate detector rail
317 if node
.name
== "mcl_minecarts:detector_rail" then
318 local newnode
= {name
="mcl_minecarts:detector_rail_on", param2
= node
.param2
}
319 minetest
.swap_node(railpos
, newnode
)
320 mesecon
.receptor_on(railpos
)
323 local entity_id
= entity_mapping
[itemstack
:get_name()]
324 local cart
= minetest
.add_entity(railpos
, entity_id
)
325 local railtype
= minetest
.get_item_group(node
.name
, "connect_to_raillike")
326 local cart_dir
= mcl_minecarts
:get_rail_direction(railpos
, {x
=1, y
=0, z
=0}, nil, nil, railtype
)
327 cart
:setyaw(minetest
.dir_to_yaw(cart_dir
))
329 if not minetest
.settings
:get_bool("creative_mode") then
330 itemstack
:take_item()
336 local register_craftitem
= function(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
337 entity_mapping
[itemstring
] = entity_id
339 local groups
= { minecart
= 1, transport
= 1 }
340 if creative
== false then
341 groups
.not_in_creative_inventory
= 1
345 on_place
= function(itemstack
, placer
, pointed_thing
)
346 if not pointed_thing
.type == "node" then
350 -- Call on_rightclick if the pointed node defines it
351 local node
= minetest
.get_node(pointed_thing
.under
)
352 if placer
and not placer
:get_player_control().sneak
then
353 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
354 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
358 return mcl_minecarts
.place_minecart(itemstack
, pointed_thing
)
360 _on_dispense
= function(stack
, pos
, droppos
, dropnode
, dropdir
)
361 -- Place minecart as entity on rail. If there's no rail, just drop it.
363 if minetest
.get_item_group(dropnode
.name
, "rail") ~= 0 then
364 -- FIXME: This places minecarts even if the spot is already occupied
365 local pointed_thing
= { under
= droppos
, above
= { x
=droppos
.x
, y
=droppos
.y
+1, z
=droppos
.z
} }
366 placed
= mcl_minecarts
.place_minecart(stack
, pointed_thing
)
368 if placed
== nil then
370 minetest
.add_item(droppos
, stack
)
375 def
.description
= description
376 def
._doc_items_longdesc
= longdesc
377 def
._doc_items_usagehelp
= usagehelp
378 def
.inventory_image
= icon
379 def
.wield_image
= icon
380 minetest
.register_craftitem(itemstring
, def
)
385 * itemstring: Itemstring of minecart item
386 * entity_id: ID of minecart entity
387 * description: Item name / description
388 * longdesc: Long help text
389 * usagehelp: Usage help text
390 * mesh: Minecart mesh
391 * textures: Minecart textures table
393 * drop: Dropped items after destroying minecart
394 * on_rightclick: Called after rightclick
395 * on_activate_by_rail: Called when above activator rail
396 * creative: If false, don't show in Creative Inventory
398 local function register_minecart(itemstring
, entity_id
, description
, longdesc
, usagehelp
, mesh
, textures
, icon
, drop
, on_rightclick
, on_activate_by_rail
, creative
)
399 register_entity(entity_id
, mesh
, textures
, drop
, on_rightclick
)
400 register_craftitem(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
401 if minetest
.get_modpath("doc_identifier") ~= nil then
402 doc
.sub
.identifier
.register_object(entity_id
, "craftitems", itemstring
)
408 "mcl_minecarts:minecart",
409 "mcl_minecarts:minecart",
411 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
412 "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.",
413 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
414 "To obtain the minecart, punch it while holding down the sneak key.",
415 "mcl_minecarts_minecart.b3d",
416 {"mcl_minecarts_minecart.png"},
417 "mcl_minecarts_minecart_normal.png",
418 {"mcl_minecarts:minecart"},
419 function(self
, clicker
)
420 local name
= clicker
:get_player_name()
421 if not clicker
or not clicker
:is_player() then
424 local player_name
= clicker
:get_player_name()
425 if self
._driver
and player_name
== self
._driver
then
427 self
._start_pos
= nil
429 local player
= minetest
.get_player_by_name(name
)
430 player
:set_eye_offset({x
=0, y
=0, z
=0},{x
=0, y
=0, z
=0})
431 elseif not self
._driver
then
432 self
._driver
= player_name
433 self
._start_pos
= self
.object
:getpos()
434 mcl_player
.player_attached
[player_name
] = true
435 clicker
:set_attach(self
.object
, "", {x
=0, y
=8.25, z
=-2}, {x
=0, y
=0, z
=0})
436 mcl_player
.player_attached
[name
] = true
437 minetest
.after(0.2, function(name
)
438 local player
= minetest
.get_player_by_name(name
)
440 mcl_player
.player_set_animation(player
, "sit" , 30)
441 player
:set_eye_offset({x
=0, y
=-5.5, z
=0},{x
=0, y
=-4, z
=0})
448 -- Minecart with Chest
450 "mcl_minecarts:chest_minecart",
451 "mcl_minecarts:chest_minecart",
452 "Minecart with Chest",
454 "mcl_minecarts_minecart_chest.b3d",
455 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
456 "mcl_minecarts_minecart_chest.png",
457 {"mcl_minecarts:minecart", "mcl_chests:chest"},
460 -- Minecart with Furnace
462 "mcl_minecarts:furnace_minecart",
463 "mcl_minecarts:furnace_minecart",
464 "Minecart with Furnace",
466 "mcl_minecarts_minecart_block.b3d",
468 "default_furnace_top.png",
469 "default_furnace_top.png",
470 "default_furnace_front.png",
471 "default_furnace_side.png",
472 "default_furnace_side.png",
473 "default_furnace_side.png",
474 "mcl_minecarts_minecart.png",
476 "mcl_minecarts_minecart_furnace.png",
477 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
478 -- Feed furnace with coal
479 function(self
, clicker
)
480 if not clicker
or not clicker
:is_player() then
483 if not self
._fueltime
then
486 local held
= clicker
:get_wielded_item()
487 if minetest
.get_item_group(held
:get_name(), "coal") == 1 then
488 self
._fueltime
= self
._fueltime
+ 180
490 if not minetest
.settings
:get_bool("creative_mode") then
492 local index
= clicker
:get_wielded_index()
493 local inv
= clicker
:get_inventory()
494 inv
:set_stack("main", index
, held
)
498 minetest
.chat_send_player(clicker
:get_player_name(), "Fuel: " .. tostring(self
._fueltime
))
503 -- Minecart with Command Block
505 "mcl_minecarts:command_block_minecart",
506 "mcl_minecarts:command_block_minecart",
507 "Minecart with Command Block",
509 "mcl_minecarts_minecart_block.b3d",
511 "jeija_commandblock_off.png^[verticalframe:2:0",
512 "jeija_commandblock_off.png^[verticalframe:2:0",
513 "jeija_commandblock_off.png^[verticalframe:2:0",
514 "jeija_commandblock_off.png^[verticalframe:2:0",
515 "jeija_commandblock_off.png^[verticalframe:2:0",
516 "jeija_commandblock_off.png^[verticalframe:2:0",
517 "mcl_minecarts_minecart.png",
519 "mcl_minecarts_minecart_command_block.png",
520 {"mcl_minecarts:minecart"},
524 -- Minecart with Hopper
526 "mcl_minecarts:hopper_minecart",
527 "mcl_minecarts:hopper_minecart",
528 "Minecart with Hopper",
530 "mcl_minecarts_minecart_hopper.b3d",
532 "mcl_hoppers_hopper_inside.png",
533 "mcl_minecarts_minecart.png",
534 "mcl_hoppers_hopper_outside.png",
535 "mcl_hoppers_hopper_top.png",
537 "mcl_minecarts_minecart_hopper.png",
538 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
544 "mcl_minecarts:tnt_minecart",
545 "mcl_minecarts:tnt_minecart",
548 "mcl_minecarts_minecart_block.b3d",
550 "default_tnt_top.png",
551 "default_tnt_bottom.png",
552 "default_tnt_side.png",
553 "default_tnt_side.png",
554 "default_tnt_side.png",
555 "default_tnt_side.png",
556 "mcl_minecarts_minecart.png",
558 "mcl_minecarts_minecart_tnt.png",
559 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
564 minetest
.register_craft({
565 output
= "mcl_minecarts:minecart",
567 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
568 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
572 -- TODO: Re-enable crafting of special minecarts when they have been implemented
574 minetest
.register_craft({
575 output
= "mcl_minecarts:hopper_minecart",
577 {"mcl_hoppers:hopper"},
578 {"mcl_minecarts:minecart"},
582 minetest
.register_craft({
583 output
= "mcl_minecarts:chest_minecart",
585 {"mcl_chests:chest"},
586 {"mcl_minecarts:minecart"},
590 minetest
.register_craft({
591 output
= "mcl_minecarts:tnt_minecart",
594 {"mcl_minecarts:minecart"},
598 minetest
.register_craft({
599 output
= "mcl_minecarts:furnace_minecart",
601 {"mcl_furnaces:furnace"},
602 {"mcl_minecarts:minecart"},