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 local data
= minetest
.deserialize(staticdata
)
35 if type(data
) == "table" then
36 self
._railtype
= data
._railtype
38 self
.object
:set_armor_groups({immortal
=1})
41 function cart
:on_punch(puncher
, time_from_last_punch
, tool_capabilities
, direction
)
42 local pos
= self
.object
:getpos()
43 if not self
._railtype
then
44 local node
= minetest
.get_node(vector
.floor(pos
)).name
45 self
._railtype
= minetest
.get_item_group(node
, "connect_to_raillike")
48 if not puncher
or not puncher
:is_player() then
49 local cart_dir
= mcl_minecarts
:get_rail_direction(pos
, {x
=1, y
=0, z
=0}, nil, nil, self
._railtype
)
50 if vector
.equals(cart_dir
, {x
=0, y
=0, z
=0}) then
53 self
._velocity
= vector
.multiply(cart_dir
, 3)
59 if puncher
:get_player_control().sneak
then
62 self
.object
:setpos(self
._old_pos
)
64 mcl_player
.player_attached
[self
._driver
] = nil
65 local player
= minetest
.get_player_by_name(self
._driver
)
71 -- Disable detector rail
72 local rou_pos
= vector
.round(pos
)
73 local node
= minetest
.get_node(rou_pos
)
74 if node
.name
== "mcl_minecarts:detector_rail_on" then
75 local newnode
= {name
="mcl_minecarts:detector_rail", param2
= node
.param2
}
76 minetest
.swap_node(rou_pos
, newnode
)
77 mesecon
.receptor_off(rou_pos
)
80 -- Drop items and remove cart entity
81 if not minetest
.settings
:get_bool("creative_mode") then
83 minetest
.add_item(self
.object
:getpos(), drop
[d
])
91 local vel
= self
.object
:getvelocity()
92 if puncher
:get_player_name() == self
._driver
then
93 if math
.abs(vel
.x
+ vel
.z
) > 7 then
98 local punch_dir
= mcl_minecarts
:velocity_to_dir(puncher
:get_look_dir())
100 local cart_dir
= mcl_minecarts
:get_rail_direction(pos
, punch_dir
, nil, nil, self
._railtype
)
101 if vector
.equals(cart_dir
, {x
=0, y
=0, z
=0}) then
105 time_from_last_punch
= math
.min(time_from_last_punch
, tool_capabilities
.full_punch_interval
)
106 local f
= 3 * (time_from_last_punch
/ tool_capabilities
.full_punch_interval
)
108 self
._velocity
= vector
.multiply(cart_dir
, f
)
113 function cart
:on_step(dtime
)
114 local vel
= self
.object
:getvelocity()
116 if self
._punched
then
117 vel
= vector
.add(vel
, self
._velocity
)
118 self
.object
:setvelocity(vel
)
120 elseif vector
.equals(vel
, {x
=0, y
=0, z
=0}) then
124 local dir
, last_switch
= nil, nil
125 local pos
= self
.object
:getpos()
126 if self
._old_pos
and not self
._punched
then
127 local flo_pos
= vector
.floor(pos
)
128 local flo_old
= vector
.floor(self
._old_pos
)
129 if vector
.equals(flo_pos
, flo_old
) then
131 -- Prevent querying the same node over and over again
134 -- Update detector rails
135 local rou_pos
= vector
.round(pos
)
136 local rou_old
= vector
.round(self
._old_pos
)
137 local node
= minetest
.get_node(rou_pos
)
138 local node_old
= minetest
.get_node(rou_old
)
140 if node
.name
== "mcl_minecarts:detector_rail" then
141 local newnode
= {name
="mcl_minecarts:detector_rail_on", param2
= node
.param2
}
142 minetest
.swap_node(rou_pos
, newnode
)
143 mesecon
.receptor_on(rou_pos
)
145 if node_old
.name
== "mcl_minecarts:detector_rail_on" then
146 local newnode
= {name
="mcl_minecarts:detector_rail", param2
= node_old
.param2
}
147 minetest
.swap_node(rou_old
, newnode
)
148 mesecon
.receptor_off(rou_old
)
152 local ctrl
, player
= nil, nil
154 player
= minetest
.get_player_by_name(self
._driver
)
156 ctrl
= player
:get_player_control()
160 -- Stop cart if velocity vector flips
161 if self
._old_vel
and self
._old_vel
.y
== 0 and
162 (self
._old_vel
.x
* vel
.x
< 0 or self
._old_vel
.z
* vel
.z
< 0) then
163 self
._old_vel
= {x
= 0, y
= 0, z
= 0}
165 self
.object
:setvelocity(vector
.new())
166 self
.object
:setacceleration(vector
.new())
169 self
._old_vel
= vector
.new(vel
)
171 if self
._old_pos
then
172 local diff
= vector
.subtract(self
._old_pos
, pos
)
173 for _
,v
in ipairs({"x","y","z"}) do
174 if math
.abs(diff
[v
]) > 1.1 then
175 local expected_pos
= vector
.add(self
._old_pos
, self
._old_dir
)
176 dir
, last_switch
= mcl_minecarts
:get_rail_direction(pos
, self
._old_dir
, ctrl
, self
._old_switch
, self
._railtype
)
177 if vector
.equals(dir
, {x
=0, y
=0, z
=0}) then
179 pos
= vector
.new(expected_pos
)
188 for _
,v
in ipairs({"x", "z"}) do
189 if vel
[v
] ~= 0 and math
.abs(vel
[v
]) < 0.9 then
196 local cart_dir
= mcl_minecarts
:velocity_to_dir(vel
)
197 local max_vel
= mcl_minecarts
.speed_max
199 dir
, last_switch
= mcl_minecarts
:get_rail_direction(pos
, cart_dir
, ctrl
, self
._old_switch
, self
._railtype
)
202 local new_acc
= {x
=0, y
=0, z
=0}
203 if vector
.equals(dir
, {x
=0, y
=0, z
=0}) then
204 vel
= {x
=0, y
=0, z
=0}
207 -- If the direction changed
208 if dir
.x
~= 0 and self
._old_dir
.z
~= 0 then
209 vel
.x
= dir
.x
* math
.abs(vel
.z
)
211 pos
.z
= math
.floor(pos
.z
+ 0.5)
214 if dir
.z
~= 0 and self
._old_dir
.x
~= 0 then
215 vel
.z
= dir
.z
* math
.abs(vel
.x
)
217 pos
.x
= math
.floor(pos
.x
+ 0.5)
221 if dir
.y
~= self
._old_dir
.y
then
222 vel
.y
= dir
.y
* math
.abs(vel
.x
+ vel
.z
)
223 pos
= vector
.round(pos
)
227 -- Slow down or speed up
228 local acc
= dir
.y
* -1.8
230 local speed_mod
= minetest
.registered_nodes
[minetest
.get_node(pos
).name
]._rail_acceleration
231 if speed_mod
and speed_mod
~= 0 then
232 acc
= acc
+ speed_mod
237 new_acc
= vector
.multiply(dir
, acc
)
240 self
.object
:setacceleration(new_acc
)
241 self
._old_pos
= vector
.new(pos
)
242 self
._old_dir
= vector
.new(dir
)
243 self
._old_switch
= last_switch
246 for _
,v
in ipairs({"x","y","z"}) do
247 if math
.abs(vel
[v
]) > max_vel
then
248 vel
[v
] = mcl_minecarts
:get_sign(vel
[v
]) * max_vel
254 -- Give achievement when player reached a distance of 1000 nodes from the start position
255 if self
._driver
and (vector
.distance(self
._start_pos
, pos
) >= 1000) then
256 awards
.unlock(self
._driver
, "mcl:onARail")
260 if update
.pos
or self
._punched
then
264 elseif dir
.x
> 0 then
266 elseif dir
.z
< 0 then
269 self
.object
:setyaw(yaw
* math
.pi
)
272 if self
._punched
then
273 self
._punched
= false
276 if not (update
.vel
or update
.pos
) then
281 local anim
= {x
=0, y
=0}
284 elseif dir
.y
== 1 then
287 self
.object
:set_animation(anim
, 1, 0)
289 self
.object
:setvelocity(vel
)
291 self
.object
:setpos(pos
)
296 function cart
:get_staticdata()
297 return minetest
.serialize({_railtype
= self
._railtype
})
300 minetest
.register_entity(entity_id
, cart
)
303 -- Place a minecart at pointed_thing
304 mcl_minecarts
.place_minecart
= function(itemstack
, pointed_thing
)
305 if not pointed_thing
.type == "node" then
310 if mcl_minecarts
:is_rail(pointed_thing
.under
) then
311 railpos
= pointed_thing
.under
312 node
= minetest
.get_node(pointed_thing
.under
)
313 elseif mcl_minecarts
:is_rail(pointed_thing
.above
) then
314 railpos
= pointed_thing
.above
315 node
= minetest
.get_node(pointed_thing
.above
)
320 -- Activate detector rail
321 if node
.name
== "mcl_minecarts:detector_rail" then
322 local newnode
= {name
="mcl_minecarts:detector_rail_on", param2
= node
.param2
}
323 minetest
.swap_node(railpos
, newnode
)
324 mesecon
.receptor_on(railpos
)
327 local entity_id
= entity_mapping
[itemstack
:get_name()]
328 local cart
= minetest
.add_entity(railpos
, entity_id
)
329 local railtype
= minetest
.get_item_group(node
.name
, "connect_to_raillike")
330 local le
= cart
:get_luaentity()
332 le
._railtype
= railtype
334 local cart_dir
= mcl_minecarts
:get_rail_direction(railpos
, {x
=1, y
=0, z
=0}, nil, nil, railtype
)
335 cart
:setyaw(minetest
.dir_to_yaw(cart_dir
))
337 if not minetest
.settings
:get_bool("creative_mode") then
338 itemstack
:take_item()
344 local register_craftitem
= function(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
345 entity_mapping
[itemstring
] = entity_id
347 local groups
= { minecart
= 1, transport
= 1 }
348 if creative
== false then
349 groups
.not_in_creative_inventory
= 1
353 on_place
= function(itemstack
, placer
, pointed_thing
)
354 if not pointed_thing
.type == "node" then
358 -- Call on_rightclick if the pointed node defines it
359 local node
= minetest
.get_node(pointed_thing
.under
)
360 if placer
and not placer
:get_player_control().sneak
then
361 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
362 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
366 return mcl_minecarts
.place_minecart(itemstack
, pointed_thing
)
368 _on_dispense
= function(stack
, pos
, droppos
, dropnode
, dropdir
)
369 -- Place minecart as entity on rail. If there's no rail, just drop it.
371 if minetest
.get_item_group(dropnode
.name
, "rail") ~= 0 then
372 -- FIXME: This places minecarts even if the spot is already occupied
373 local pointed_thing
= { under
= droppos
, above
= { x
=droppos
.x
, y
=droppos
.y
+1, z
=droppos
.z
} }
374 placed
= mcl_minecarts
.place_minecart(stack
, pointed_thing
)
376 if placed
== nil then
378 minetest
.add_item(droppos
, stack
)
383 def
.description
= description
384 def
._doc_items_longdesc
= longdesc
385 def
._doc_items_usagehelp
= usagehelp
386 def
.inventory_image
= icon
387 def
.wield_image
= icon
388 minetest
.register_craftitem(itemstring
, def
)
393 * itemstring: Itemstring of minecart item
394 * entity_id: ID of minecart entity
395 * description: Item name / description
396 * longdesc: Long help text
397 * usagehelp: Usage help text
398 * mesh: Minecart mesh
399 * textures: Minecart textures table
401 * drop: Dropped items after destroying minecart
402 * on_rightclick: Called after rightclick
403 * on_activate_by_rail: Called when above activator rail
404 * creative: If false, don't show in Creative Inventory
406 local function register_minecart(itemstring
, entity_id
, description
, longdesc
, usagehelp
, mesh
, textures
, icon
, drop
, on_rightclick
, on_activate_by_rail
, creative
)
407 register_entity(entity_id
, mesh
, textures
, drop
, on_rightclick
)
408 register_craftitem(itemstring
, entity_id
, description
, longdesc
, usagehelp
, icon
, creative
)
409 if minetest
.get_modpath("doc_identifier") ~= nil then
410 doc
.sub
.identifier
.register_object(entity_id
, "craftitems", itemstring
)
416 "mcl_minecarts:minecart",
417 "mcl_minecarts:minecart",
419 "Minecarts can be used for a quick transportion on rails." .. "\n" ..
420 "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.",
421 "You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving." .. "\n" ..
422 "To obtain the minecart, punch it while holding down the sneak key.",
423 "mcl_minecarts_minecart.b3d",
424 {"mcl_minecarts_minecart.png"},
425 "mcl_minecarts_minecart_normal.png",
426 {"mcl_minecarts:minecart"},
427 function(self
, clicker
)
428 local name
= clicker
:get_player_name()
429 if not clicker
or not clicker
:is_player() then
432 local player_name
= clicker
:get_player_name()
433 if self
._driver
and player_name
== self
._driver
then
435 self
._start_pos
= nil
437 local player
= minetest
.get_player_by_name(name
)
438 player
:set_eye_offset({x
=0, y
=0, z
=0},{x
=0, y
=0, z
=0})
439 elseif not self
._driver
then
440 self
._driver
= player_name
441 self
._start_pos
= self
.object
:getpos()
442 mcl_player
.player_attached
[player_name
] = true
443 clicker
:set_attach(self
.object
, "", {x
=0, y
=8.25, z
=-2}, {x
=0, y
=0, z
=0})
444 mcl_player
.player_attached
[name
] = true
445 minetest
.after(0.2, function(name
)
446 local player
= minetest
.get_player_by_name(name
)
448 mcl_player
.player_set_animation(player
, "sit" , 30)
449 player
:set_eye_offset({x
=0, y
=-5.5, z
=0},{x
=0, y
=-4, z
=0})
456 -- Minecart with Chest
458 "mcl_minecarts:chest_minecart",
459 "mcl_minecarts:chest_minecart",
460 "Minecart with Chest",
462 "mcl_minecarts_minecart_chest.b3d",
463 { "mcl_chests_normal.png", "mcl_minecarts_minecart.png" },
464 "mcl_minecarts_minecart_chest.png",
465 {"mcl_minecarts:minecart", "mcl_chests:chest"},
468 -- Minecart with Furnace
470 "mcl_minecarts:furnace_minecart",
471 "mcl_minecarts:furnace_minecart",
472 "Minecart with Furnace",
474 "mcl_minecarts_minecart_block.b3d",
476 "default_furnace_top.png",
477 "default_furnace_top.png",
478 "default_furnace_front.png",
479 "default_furnace_side.png",
480 "default_furnace_side.png",
481 "default_furnace_side.png",
482 "mcl_minecarts_minecart.png",
484 "mcl_minecarts_minecart_furnace.png",
485 {"mcl_minecarts:minecart", "mcl_furnaces:furnace"},
486 -- Feed furnace with coal
487 function(self
, clicker
)
488 if not clicker
or not clicker
:is_player() then
491 if not self
._fueltime
then
494 local held
= clicker
:get_wielded_item()
495 if minetest
.get_item_group(held
:get_name(), "coal") == 1 then
496 self
._fueltime
= self
._fueltime
+ 180
498 if not minetest
.settings
:get_bool("creative_mode") then
500 local index
= clicker
:get_wielded_index()
501 local inv
= clicker
:get_inventory()
502 inv
:set_stack("main", index
, held
)
506 minetest
.chat_send_player(clicker
:get_player_name(), "Fuel: " .. tostring(self
._fueltime
))
511 -- Minecart with Command Block
513 "mcl_minecarts:command_block_minecart",
514 "mcl_minecarts:command_block_minecart",
515 "Minecart with Command Block",
517 "mcl_minecarts_minecart_block.b3d",
519 "jeija_commandblock_off.png^[verticalframe:2:0",
520 "jeija_commandblock_off.png^[verticalframe:2:0",
521 "jeija_commandblock_off.png^[verticalframe:2:0",
522 "jeija_commandblock_off.png^[verticalframe:2:0",
523 "jeija_commandblock_off.png^[verticalframe:2:0",
524 "jeija_commandblock_off.png^[verticalframe:2:0",
525 "mcl_minecarts_minecart.png",
527 "mcl_minecarts_minecart_command_block.png",
528 {"mcl_minecarts:minecart"},
532 -- Minecart with Hopper
534 "mcl_minecarts:hopper_minecart",
535 "mcl_minecarts:hopper_minecart",
536 "Minecart with Hopper",
538 "mcl_minecarts_minecart_hopper.b3d",
540 "mcl_hoppers_hopper_inside.png",
541 "mcl_minecarts_minecart.png",
542 "mcl_hoppers_hopper_outside.png",
543 "mcl_hoppers_hopper_top.png",
545 "mcl_minecarts_minecart_hopper.png",
546 {"mcl_minecarts:minecart", "mcl_hoppers:hopper"},
552 "mcl_minecarts:tnt_minecart",
553 "mcl_minecarts:tnt_minecart",
556 "mcl_minecarts_minecart_block.b3d",
558 "default_tnt_top.png",
559 "default_tnt_bottom.png",
560 "default_tnt_side.png",
561 "default_tnt_side.png",
562 "default_tnt_side.png",
563 "default_tnt_side.png",
564 "mcl_minecarts_minecart.png",
566 "mcl_minecarts_minecart_tnt.png",
567 {"mcl_minecarts:minecart", "mcl_tnt:tnt"},
572 minetest
.register_craft({
573 output
= "mcl_minecarts:minecart",
575 {"mcl_core:iron_ingot", "", "mcl_core:iron_ingot"},
576 {"mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot"},
580 -- TODO: Re-enable crafting of special minecarts when they have been implemented
582 minetest
.register_craft({
583 output
= "mcl_minecarts:hopper_minecart",
585 {"mcl_hoppers:hopper"},
586 {"mcl_minecarts:minecart"},
590 minetest
.register_craft({
591 output
= "mcl_minecarts:chest_minecart",
593 {"mcl_chests:chest"},
594 {"mcl_minecarts:minecart"},
598 minetest
.register_craft({
599 output
= "mcl_minecarts:tnt_minecart",
602 {"mcl_minecarts:minecart"},
606 minetest
.register_craft({
607 output
= "mcl_minecarts:furnace_minecart",
609 {"mcl_furnaces:furnace"},
610 {"mcl_minecarts:minecart"},