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 le
= cart
:get_luaentity()
328 le
._railtype
= railtype
330 local cart_dir
= mcl_minecarts
:get_rail_direction(railpos
, {x
=1, y
=0, z
=0}, nil, nil, railtype
)
331 cart
:setyaw(minetest
.dir_to_yaw(cart_dir
))
333 if not minetest
.settings
:get_bool("creative_mode") then
334 itemstack
:take_item()
340 local register_craftitem
= function(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
341 entity_mapping
[itemstring
] = entity_id
343 local groups
= { minecart
= 1, transport
= 1 }
344 if creative
== false then
345 groups
.not_in_creative_inventory
= 1
349 on_place
= function(itemstack
, placer
, pointed_thing
)
350 if not pointed_thing
.type == "node" then
354 -- Call on_rightclick if the pointed node defines it
355 local node
= minetest
.get_node(pointed_thing
.under
)
356 if placer
and not placer
:get_player_control().sneak
then
357 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
358 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
362 return mcl_minecarts
.place_minecart(itemstack
, pointed_thing
)
364 _on_dispense
= function(stack
, pos
, droppos
, dropnode
, dropdir
)
365 -- Place minecart as entity on rail. If there's no rail, just drop it.
367 if minetest
.get_item_group(dropnode
.name
, "rail") ~= 0 then
368 -- FIXME: This places minecarts even if the spot is already occupied
369 local pointed_thing
= { under
= droppos
, above
= { x
=droppos
.x
, y
=droppos
.y
+1, z
=droppos
.z
} }
370 placed
= mcl_minecarts
.place_minecart(stack
, pointed_thing
)
372 if placed
== nil then
374 minetest
.add_item(droppos
, stack
)
379 def
.description
= description
380 def
._doc_items_longdesc
= longdesc
381 def
._doc_items_usagehelp
= usagehelp
382 def
.inventory_image
= icon
383 def
.wield_image
= icon
384 minetest
.register_craftitem(itemstring
, def
)
389 * itemstring: Itemstring of minecart item
390 * entity_id: ID of minecart entity
391 * description: Item name / description
392 * longdesc: Long help text
393 * usagehelp: Usage help text
394 * mesh: Minecart mesh
395 * textures: Minecart textures table
397 * drop: Dropped items after destroying minecart
398 * on_rightclick: Called after rightclick
399 * on_activate_by_rail: Called when above activator rail
400 * creative: If false, don't show in Creative Inventory
402 local function register_minecart(itemstring
, entity_id
, description
, longdesc
, usagehelp
, mesh
, textures
, icon
, drop
, on_rightclick
, on_activate_by_rail
, creative
)
403 register_entity(entity_id
, mesh
, textures
, drop
, on_rightclick
)
404 register_craftitem(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
405 if minetest
.get_modpath("doc_identifier") ~= nil then
406 doc
.sub
.identifier
.register_object(entity_id
, "craftitems", itemstring
)
412 "mcl_minecarts:minecart",
413 "mcl_minecarts:minecart",
415 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
416 "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.",
417 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
418 "To obtain the minecart, punch it while holding down the sneak key.",
419 "mcl_minecarts_minecart.b3d",
420 {"mcl_minecarts_minecart.png"},
421 "mcl_minecarts_minecart_normal.png",
422 {"mcl_minecarts:minecart"},
423 function(self
, clicker
)
424 local name
= clicker
:get_player_name()
425 if not clicker
or not clicker
:is_player() then
428 local player_name
= clicker
:get_player_name()
429 if self
._driver
and player_name
== self
._driver
then
431 self
._start_pos
= nil
433 local player
= minetest
.get_player_by_name(name
)
434 player
:set_eye_offset({x
=0, y
=0, z
=0},{x
=0, y
=0, z
=0})
435 elseif not self
._driver
then
436 self
._driver
= player_name
437 self
._start_pos
= self
.object
:getpos()
438 mcl_player
.player_attached
[player_name
] = true
439 clicker
:set_attach(self
.object
, "", {x
=0, y
=8.25, z
=-2}, {x
=0, y
=0, z
=0})
440 mcl_player
.player_attached
[name
] = true
441 minetest
.after(0.2, function(name
)
442 local player
= minetest
.get_player_by_name(name
)
444 mcl_player
.player_set_animation(player
, "sit" , 30)
445 player
:set_eye_offset({x
=0, y
=-5.5, z
=0},{x
=0, y
=-4, z
=0})
452 -- Minecart with Chest
454 "mcl_minecarts:chest_minecart",
455 "mcl_minecarts:chest_minecart",
456 "Minecart with Chest",
458 "mcl_minecarts_minecart_chest.b3d",
459 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
460 "mcl_minecarts_minecart_chest.png",
461 {"mcl_minecarts:minecart", "mcl_chests:chest"},
464 -- Minecart with Furnace
466 "mcl_minecarts:furnace_minecart",
467 "mcl_minecarts:furnace_minecart",
468 "Minecart with Furnace",
470 "mcl_minecarts_minecart_block.b3d",
472 "default_furnace_top.png",
473 "default_furnace_top.png",
474 "default_furnace_front.png",
475 "default_furnace_side.png",
476 "default_furnace_side.png",
477 "default_furnace_side.png",
478 "mcl_minecarts_minecart.png",
480 "mcl_minecarts_minecart_furnace.png",
481 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
482 -- Feed furnace with coal
483 function(self
, clicker
)
484 if not clicker
or not clicker
:is_player() then
487 if not self
._fueltime
then
490 local held
= clicker
:get_wielded_item()
491 if minetest
.get_item_group(held
:get_name(), "coal") == 1 then
492 self
._fueltime
= self
._fueltime
+ 180
494 if not minetest
.settings
:get_bool("creative_mode") then
496 local index
= clicker
:get_wielded_index()
497 local inv
= clicker
:get_inventory()
498 inv
:set_stack("main", index
, held
)
502 minetest
.chat_send_player(clicker
:get_player_name(), "Fuel: " .. tostring(self
._fueltime
))
507 -- Minecart with Command Block
509 "mcl_minecarts:command_block_minecart",
510 "mcl_minecarts:command_block_minecart",
511 "Minecart with Command Block",
513 "mcl_minecarts_minecart_block.b3d",
515 "jeija_commandblock_off.png^[verticalframe:2:0",
516 "jeija_commandblock_off.png^[verticalframe:2:0",
517 "jeija_commandblock_off.png^[verticalframe:2:0",
518 "jeija_commandblock_off.png^[verticalframe:2:0",
519 "jeija_commandblock_off.png^[verticalframe:2:0",
520 "jeija_commandblock_off.png^[verticalframe:2:0",
521 "mcl_minecarts_minecart.png",
523 "mcl_minecarts_minecart_command_block.png",
524 {"mcl_minecarts:minecart"},
528 -- Minecart with Hopper
530 "mcl_minecarts:hopper_minecart",
531 "mcl_minecarts:hopper_minecart",
532 "Minecart with Hopper",
534 "mcl_minecarts_minecart_hopper.b3d",
536 "mcl_hoppers_hopper_inside.png",
537 "mcl_minecarts_minecart.png",
538 "mcl_hoppers_hopper_outside.png",
539 "mcl_hoppers_hopper_top.png",
541 "mcl_minecarts_minecart_hopper.png",
542 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
548 "mcl_minecarts:tnt_minecart",
549 "mcl_minecarts:tnt_minecart",
552 "mcl_minecarts_minecart_block.b3d",
554 "default_tnt_top.png",
555 "default_tnt_bottom.png",
556 "default_tnt_side.png",
557 "default_tnt_side.png",
558 "default_tnt_side.png",
559 "default_tnt_side.png",
560 "mcl_minecarts_minecart.png",
562 "mcl_minecarts_minecart_tnt.png",
563 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
568 minetest
.register_craft({
569 output
= "mcl_minecarts:minecart",
571 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
572 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
576 -- TODO: Re-enable crafting of special minecarts when they have been implemented
578 minetest
.register_craft({
579 output
= "mcl_minecarts:hopper_minecart",
581 {"mcl_hoppers:hopper"},
582 {"mcl_minecarts:minecart"},
586 minetest
.register_craft({
587 output
= "mcl_minecarts:chest_minecart",
589 {"mcl_chests:chest"},
590 {"mcl_minecarts:minecart"},
594 minetest
.register_craft({
595 output
= "mcl_minecarts:tnt_minecart",
598 {"mcl_minecarts:minecart"},
602 minetest
.register_craft({
603 output
= "mcl_minecarts:furnace_minecart",
605 {"mcl_furnaces:furnace"},
606 {"mcl_minecarts:minecart"},