2021-05-29 16:12:33 +02:00
local modname = minetest.get_current_modname ( )
local S = minetest.get_translator ( modname )
2019-03-07 20:43:39 +01:00
2021-03-30 00:58:45 +02:00
local has_mcl_wip = minetest.get_modpath ( " mcl_wip " )
2017-01-05 15:23:14 +01:00
mcl_minecarts = { }
2021-05-29 16:12:33 +02:00
mcl_minecarts.modpath = minetest.get_modpath ( modname )
2017-01-05 15:23:14 +01:00
mcl_minecarts.speed_max = 10
2019-01-30 03:36:36 +01:00
mcl_minecarts.check_float_time = 15
2024-02-14 11:48:55 +01:00
local max_step_distance = 0.5
2017-01-05 15:23:14 +01:00
dofile ( mcl_minecarts.modpath .. " /functions.lua " )
dofile ( mcl_minecarts.modpath .. " /rails.lua " )
2023-03-28 02:51:51 +02:00
local LOGGING_ON = minetest.settings : get_bool ( " mcl_logging_minecarts " , false )
2024-02-14 00:31:48 +01:00
local DEBUG = false
2023-03-28 02:51:51 +02:00
local function mcl_log ( message )
if LOGGING_ON then
mcl_util.mcl_log ( message , " [Minecarts] " , true )
end
end
2020-01-30 22:42:53 +01:00
local function detach_driver ( self )
if not self._driver then
return
end
2021-03-13 12:30:33 +01:00
mcl_player.player_attached [ self._driver ] = nil
local player = minetest.get_player_by_name ( self._driver )
2020-01-30 22:42:53 +01:00
self._driver = nil
self._start_pos = nil
2021-03-13 12:30:33 +01:00
if player then
player : set_detach ( )
2024-02-13 15:57:45 +01:00
player : set_eye_offset ( vector.new ( 0 , 0 , 0 ) , vector.new ( 0 , 0 , 0 ) )
2021-03-13 12:30:33 +01:00
mcl_player.player_set_animation ( player , " stand " , 30 )
end
2020-01-30 22:42:53 +01:00
end
2020-01-30 23:11:16 +01:00
local function activate_tnt_minecart ( self , timer )
2020-01-30 22:05:18 +01:00
if self._boomtimer then
return
end
self.object : set_armor_groups ( { immortal = 1 } )
2020-01-30 23:11:16 +01:00
if timer then
self._boomtimer = timer
else
self._boomtimer = tnt.BOOMTIMER
end
2020-01-30 22:05:18 +01:00
self.object : set_properties ( { textures = {
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_minecarts_minecart.png " ,
} } )
self._blinktimer = tnt.BLINKTIMER
2020-04-07 00:55:45 +02:00
minetest.sound_play ( " tnt_ignite " , { pos = self.object : get_pos ( ) , gain = 1.0 , max_hear_distance = 15 } , true )
2020-01-30 22:05:18 +01:00
end
2020-01-30 22:42:53 +01:00
local activate_normal_minecart = detach_driver
2023-03-28 02:51:51 +02:00
local function hopper_take_item ( self , dtime )
local pos = self.object : get_pos ( )
if not pos then return end
if not self or self.name ~= " mcl_minecarts:hopper_minecart " then return end
if mcl_util.check_dtime_timer ( self , dtime , " hoppermc_take " , 0.15 ) then
--minetest.log("The check timer was triggered: " .. dump(pos) .. ", name:" .. self.name)
else
--minetest.log("The check timer was not triggered")
return
end
--mcl_log("self.itemstring: ".. self.itemstring)
local above_pos = vector.offset ( pos , 0 , 0.9 , 0 )
--mcl_log("self.itemstring: ".. minetest.pos_to_string(above_pos))
local objs = minetest.get_objects_inside_radius ( above_pos , 1.25 )
if objs then
mcl_log ( " there is an itemstring. Number of objs: " .. # objs )
for k , v in pairs ( objs ) do
local ent = v : get_luaentity ( )
2023-06-27 19:06:09 +02:00
if ent and not ent._removed and ent.itemstring and ent.itemstring ~= " " then
local taken_items = false
mcl_log ( " ent.name: " .. tostring ( ent.name ) )
mcl_log ( " ent pos: " .. tostring ( ent.object : get_pos ( ) ) )
local inv = mcl_entity_invs.load_inv ( self , 5 )
if not inv then return false end
local current_itemstack = ItemStack ( ent.itemstring )
mcl_log ( " inv. size: " .. self._inv_size )
if inv : room_for_item ( " main " , current_itemstack ) then
mcl_log ( " Room " )
inv : add_item ( " main " , current_itemstack )
ent.object : get_luaentity ( ) . itemstring = " "
ent.object : remove ( )
taken_items = true
else
mcl_log ( " no Room " )
end
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
if not taken_items then
local items_remaining = current_itemstack : get_count ( )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
-- This will take part of a floating item stack if no slot can hold the full amount
for i = 1 , self._inv_size , 1 do
local stack = inv : get_stack ( " main " , i )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
mcl_log ( " i: " .. tostring ( i ) )
mcl_log ( " Items remaining: " .. items_remaining )
mcl_log ( " Name: " .. tostring ( stack : get_name ( ) ) )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
if current_itemstack : get_name ( ) == stack : get_name ( ) then
mcl_log ( " We have a match. Name: " .. tostring ( stack : get_name ( ) ) )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
local room_for = stack : get_stack_max ( ) - stack : get_count ( )
mcl_log ( " Room for: " .. tostring ( room_for ) )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
if room_for == 0 then
-- Do nothing
mcl_log ( " No room " )
elseif room_for < items_remaining then
mcl_log ( " We have more items remaining than space " )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
items_remaining = items_remaining - room_for
stack : set_count ( stack : get_stack_max ( ) )
inv : set_stack ( " main " , i , stack )
taken_items = true
else
local new_stack_size = stack : get_count ( ) + items_remaining
stack : set_count ( new_stack_size )
mcl_log ( " We have more than enough space. Now holds: " .. new_stack_size )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
inv : set_stack ( " main " , i , stack )
items_remaining = 0
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
ent.object : get_luaentity ( ) . itemstring = " "
ent.object : remove ( )
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
taken_items = true
break
end
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
mcl_log ( " Count: " .. tostring ( stack : get_count ( ) ) )
mcl_log ( " stack max: " .. tostring ( stack : get_stack_max ( ) ) )
--mcl_log("Is it empty: " .. stack:to_string())
2023-03-28 02:51:51 +02:00
end
2023-06-27 19:06:09 +02:00
if i == self._inv_size and taken_items then
mcl_log ( " We are on last item and still have items left. Set final stack size: " .. items_remaining )
current_itemstack : set_count ( items_remaining )
--mcl_log("Itemstack2: " .. current_itemstack:to_string())
ent.itemstring = current_itemstack : to_string ( )
end
2023-03-28 02:51:51 +02:00
end
2023-06-27 19:06:09 +02:00
end
2023-03-28 02:51:51 +02:00
2023-06-27 19:06:09 +02:00
--Add in, and delete
if taken_items then
mcl_log ( " Saving " )
mcl_entity_invs.save_inv ( ent )
return taken_items
else
mcl_log ( " No need to save " )
2023-03-28 02:51:51 +02:00
end
end
end
end
return false
end
2017-08-29 02:36:50 +02:00
-- Table for item-to-entity mapping. Keys: itemstring, Values: Corresponding entity ID
local entity_mapping = { }
2017-08-28 14:19:46 +02:00
2024-02-14 00:31:48 +01:00
local function make_staticdata ( railtype , connected_at , dir )
return {
railtype = railtype ,
connected_at = connected_at ,
2024-02-17 00:09:29 +01:00
distance = 0 ,
velocity = 0 ,
2024-02-14 00:31:48 +01:00
dir = vector.new ( dir ) ,
}
end
2024-02-14 11:48:55 +01:00
local function to_dirstring ( dir )
if dir.x == 0 then
if dir.z == 1 then
return " north "
else
return " south "
end
elseif dir.z == 0 then
if dir.x == 1 then
return " east "
else
return " west "
end
end
end
2020-01-30 22:05:18 +01:00
local function register_entity ( entity_id , mesh , textures , drop , on_rightclick , on_activate_by_rail )
2017-08-28 14:19:46 +02:00
local cart = {
2024-02-13 16:33:37 +01:00
initial_properties = {
physical = false ,
collisionbox = { - 10 / 16. , - 0.5 , - 10 / 16 , 10 / 16 , 0.25 , 10 / 16 } ,
visual = " mesh " ,
mesh = mesh ,
visual_size = { x = 1 , y = 1 } ,
textures = textures ,
} ,
2017-08-28 14:19:46 +02:00
2017-08-28 14:35:56 +02:00
on_rightclick = on_rightclick ,
2021-03-13 12:30:33 +01:00
_driver = nil , -- player who sits in and controls the minecart (only for minecart!)
2023-03-04 21:14:59 +01:00
_passenger = nil , -- for mobs
2017-08-28 14:19:46 +02:00
_punched = false , -- used to re-send _velocity and position
_start_pos = nil , -- Used to calculate distance for “On A Rail” achievement
2019-01-30 03:16:59 +01:00
_last_float_check = nil , -- timestamp of last time the cart was checked to be still on a rail
2020-01-30 20:38:31 +01:00
_fueltime = nil , -- how many seconds worth of fuel is left. Only used by minecart with furnace
2020-01-30 22:05:18 +01:00
_boomtimer = nil , -- how many seconds are left before exploding
_blinktimer = nil , -- how many seconds are left before TNT blinking
_blink = false , -- is TNT blink texture active?
2017-08-28 14:19:46 +02:00
_old_pos = nil ,
2024-02-13 15:45:00 +01:00
_staticdata = nil ,
2017-08-28 14:19:46 +02:00
}
function cart : on_activate ( staticdata , dtime_s )
2020-01-30 22:05:18 +01:00
-- Initialize
2019-01-30 02:04:12 +01:00
local data = minetest.deserialize ( staticdata )
if type ( data ) == " table " then
2024-02-13 11:42:38 +01:00
-- Migrate old data
if data._railtype then
data.railtype = data._railtype
data._railtype = nil
end
2024-02-13 15:45:00 +01:00
-- Fix up types
data.dir = vector.new ( data.dir )
2024-02-13 11:42:38 +01:00
self._staticdata = data
2019-01-30 02:04:12 +01:00
end
2017-08-28 14:19:46 +02:00
self.object : set_armor_groups ( { immortal = 1 } )
2020-01-30 22:05:18 +01:00
-- Activate cart if on activator rail
if self.on_activate_by_rail then
local pos = self.object : get_pos ( )
local node = minetest.get_node ( vector.floor ( pos ) )
if node.name == " mcl_minecarts:activator_rail_on " then
self : on_activate_by_rail ( )
end
end
2017-01-05 15:23:14 +01:00
end
2017-08-28 14:19:46 +02:00
function cart : on_punch ( puncher , time_from_last_punch , tool_capabilities , direction )
2024-02-13 15:45:00 +01:00
local staticdata = self._staticdata
2024-02-14 00:31:48 +01:00
if not staticdata then
staticdata = make_staticdata ( )
self._staticdata = staticdata
end
2024-02-13 15:45:00 +01:00
local pos = staticdata.connected_at
if not pos then
2024-02-14 00:31:48 +01:00
pos = self.object : get_pos ( )
2024-02-13 15:45:00 +01:00
print ( " TODO: handle detached cart behavior " )
2024-02-14 00:31:48 +01:00
--return
2024-02-13 15:45:00 +01:00
end
-- Fix railtype field
if not staticdata.railtype then
2021-03-13 12:30:33 +01:00
local node = minetest.get_node ( vector.floor ( pos ) ) . name
2024-02-13 15:45:00 +01:00
staticdata.railtype = minetest.get_item_group ( node , " connect_to_raillike " )
2021-03-13 12:30:33 +01:00
end
2017-01-05 15:23:14 +01:00
2024-02-13 15:45:00 +01:00
-- Handle punches by something other than the player
2021-03-13 12:30:33 +01:00
if not puncher or not puncher : is_player ( ) then
2024-02-13 15:57:45 +01:00
local cart_dir = mcl_minecarts : get_rail_direction ( pos , vector.new ( 1 , 0 , 0 ) , nil , nil , staticdata.railtype )
if vector.equals ( cart_dir , vector.new ( 0 , 0 , 0 ) ) then
2021-03-13 12:30:33 +01:00
return
end
2024-02-13 15:45:00 +01:00
staticdata.dir = cart_dir
self._punched = true
2021-03-13 12:30:33 +01:00
return
2017-01-05 15:23:14 +01:00
end
2021-03-13 12:30:33 +01:00
-- Punch+sneak: Pick up minecart (unless TNT was ignited)
if puncher : get_player_control ( ) . sneak and not self._boomtimer then
if self._driver then
if self._old_pos then
self.object : set_pos ( self._old_pos )
end
detach_driver ( self )
end
-- Disable detector rail
local rou_pos = vector.round ( pos )
local node = minetest.get_node ( rou_pos )
if node.name == " mcl_minecarts:detector_rail_on " then
local newnode = { name = " mcl_minecarts:detector_rail " , param2 = node.param2 }
minetest.swap_node ( rou_pos , newnode )
mesecon.receptor_off ( rou_pos )
2017-08-29 01:28:32 +02:00
end
2021-03-13 12:30:33 +01:00
-- Drop items and remove cart entity
if not minetest.is_creative_enabled ( puncher : get_player_name ( ) ) then
for d = 1 , # drop do
minetest.add_item ( self.object : get_pos ( ) , drop [ d ] )
end
elseif puncher and puncher : is_player ( ) then
local inv = puncher : get_inventory ( )
for d = 1 , # drop do
if not inv : contains_item ( " main " , drop [ d ] ) then
inv : add_item ( " main " , drop [ d ] )
end
2019-02-06 21:56:52 +01:00
end
2017-01-05 15:23:14 +01:00
end
2021-03-13 12:30:33 +01:00
self.object : remove ( )
return
end
2024-02-13 15:45:00 +01:00
-- Handle player punches
2021-03-13 12:30:33 +01:00
local vel = self.object : get_velocity ( )
if puncher : get_player_name ( ) == self._driver then
if math.abs ( vel.x + vel.z ) > 7 then
return
end
2017-08-15 02:51:40 +02:00
end
2017-08-28 14:19:46 +02:00
2021-03-13 12:30:33 +01:00
local punch_dir = mcl_minecarts : velocity_to_dir ( puncher : get_look_dir ( ) )
punch_dir.y = 0
2024-02-13 15:45:00 +01:00
2024-02-13 11:42:38 +01:00
local cart_dir = mcl_minecarts : get_rail_direction ( pos , punch_dir , nil , nil , self._staticdata . railtype )
2024-02-13 15:57:45 +01:00
if vector.equals ( cart_dir , vector.new ( 0 , 0 , 0 ) ) then
2021-03-13 12:30:33 +01:00
return
end
2024-02-13 15:45:00 +01:00
staticdata.dir = cart_dir
2021-03-13 12:30:33 +01:00
time_from_last_punch = math.min ( time_from_last_punch , tool_capabilities.full_punch_interval )
local f = 3 * ( time_from_last_punch / tool_capabilities.full_punch_interval )
2024-02-13 15:45:00 +01:00
-- Perform acceleration here
staticdata.velocity = ( staticdata.velocity or 0 ) + f
local max_vel = mcl_minecarts.speed_max
if staticdata.velocity > max_vel then
staticdata.velocity = max_vel
end
2017-01-05 15:23:14 +01:00
end
2017-08-28 14:19:46 +02:00
2020-01-30 22:05:18 +01:00
cart.on_activate_by_rail = on_activate_by_rail
2023-03-20 14:29:25 +01:00
local passenger_attach_position = vector.new ( 0 , - 1.75 , 0 )
2024-02-14 11:48:55 +01:00
local function distance_to_next_block ( dir , pos )
if dir.x == 1 then
return 1 - ( pos.x - math.floor ( pos.x ) )
elseif dir.x == - 1 then
return pos.x - math.floor ( pos.x )
elseif dir.z == 1 then
return 1 - ( pos.z - math.floor ( pos.z ) )
elseif dir.z == - 1 then
return pos.z - math.floor ( pos.z )
elseif dir.y == 1 then
return 1 - ( pos.y - math.floor ( pos.z ) )
else
return pos.y - math.floor ( pos.y )
end
end
local function do_movement_step ( self , remaining_distance )
2024-02-13 15:45:00 +01:00
local staticdata = self._staticdata
2024-02-14 11:48:55 +01:00
2024-02-17 00:09:29 +01:00
local pos = staticdata.connected_at
if not pos then return remaining_distance end
if staticdata.velocity < 0.1 then return remaining_distance end
local remaining_in_block = 1 - ( staticdata.distance or 0 )
local dinstance = 0
if remaining_in_block > remaining_distance then
distance = remaining_distance
staticdata.distance = ( staticdata.distance or 0 ) + distance
pos = pos + staticdata.dir * staticdata.distance
else
distance = remaining_in_block
staticdata.distance = 0
2024-02-14 11:48:55 +01:00
2024-02-17 00:09:29 +01:00
-- Leave the old node
local old_node_name = minetest.get_node ( pos ) . name
local old_node_def = minetest.registered_nodes [ old_node_name ]
if old_node_def._mcl_minecarts_on_leave then
old_node_def._mcl_minecarts_on_leave ( pos , self )
2024-02-14 11:48:55 +01:00
end
2024-02-17 00:09:29 +01:00
-- Anchor at the next node
pos = pos + staticdata.dir
staticdata.connected_at = pos
-- Enter the new node
local new_node_name = minetest.get_node ( pos ) . name
local new_node_def = minetest.registered_nodes [ new_node_name ]
if new_node_def._mcl_minecarts_on_enter then
new_node_def._mcl_minecarts_on_enter ( pos , self )
end
-- check for hopper under the rail
local under_pos = pos - vector.new ( 0 , 1 , 0 )
local under_node_name = minetest.get_node ( under_pos ) . name
local under_node_def = minetest.registered_nodes [ under_node_name ]
print ( " under_node_name= " .. under_node_name .. " , hopper= " .. tostring ( under_node_def.groups . hopper ) )
if under_node_def and under_node_def.groups . hopper ~= 0 then
print ( " Attempt pull_from_minecart " )
if mcl_hoppers.pull_from_minecart ( self , under_pos , self._inv_size or 0 ) then
staticdata.delay = 1.5
end
end
-- Get the next direction
next_dir , last_switch = mcl_minecarts : get_rail_direction ( pos , staticdata.dir , nil , nil , staticdata.railtype )
-- Handle end of track
if next_dir == staticdata.dir * - 1 then
print ( " Stopping cart at " .. tostring ( pos ) )
staticdata.velocity = 0
distence = remaining_distance
end
-- Update cart direction
staticdata.dir = next_dir
2024-02-13 15:45:00 +01:00
end
2017-08-28 14:19:46 +02:00
2024-02-17 00:09:29 +01:00
self.object : move_to ( pos )
2024-02-13 15:45:00 +01:00
-- Update cart orientation
local yaw = 0
2024-02-17 00:09:29 +01:00
local dir = staticdata.dir
if dir.x < 0 then
2024-02-13 15:45:00 +01:00
yaw = 0.5
2024-02-17 00:09:29 +01:00
elseif dir.x > 0 then
2024-02-13 15:45:00 +01:00
yaw = 1.5
elseif dir.z < 0 then
yaw = 1
2019-01-30 03:16:59 +01:00
end
2024-02-13 15:45:00 +01:00
self.object : set_yaw ( yaw * math.pi )
2017-08-29 01:28:32 +02:00
2024-02-14 11:48:55 +01:00
-- Report distance traveled
return distance
2024-02-13 15:45:00 +01:00
end
local function process_acceleration ( self , timestep )
local staticdata = self._staticdata
2024-02-17 00:09:29 +01:00
if not staticdata.connected_at then return end
2024-02-13 15:45:00 +01:00
2024-02-17 00:09:29 +01:00
local pos = staticdata.connected_at
2024-02-13 15:45:00 +01:00
local node_name = minetest.get_node ( pos ) . name
local node_def = minetest.registered_nodes [ node_name ]
2024-02-17 00:09:29 +01:00
local max_vel = mcl_minecarts.speed_max
2024-02-13 15:45:00 +01:00
if self._go_forward then
self._acceleration = 2
elseif self._brake then
self._acceleration = - 1.5
elseif self._punched then
self._acceleration = 2
elseif self._fueltime and self._fueltime > 0 then
self._acceleration = 0.6
else
2024-02-17 00:09:29 +01:00
if staticdata.velocity >= ( node_def._max_acceleration_velocity or max_vel ) then
self._acceleration = 0
else
self._acceleration = node_def._rail_acceleration or - 0.4
end
2017-01-05 15:23:14 +01:00
end
2017-08-28 14:19:46 +02:00
2024-02-17 00:09:29 +01:00
if self._acceleration > 0 and ( staticdata.velocity or 0 ) < 0.1 then
2024-02-13 15:45:00 +01:00
staticdata.velocity = 0.1
2017-01-05 15:23:14 +01:00
end
2017-08-28 14:19:46 +02:00
2024-02-13 15:45:00 +01:00
if math.abs ( self._acceleration ) > 0.1 then
staticdata.velocity = ( staticdata.velocity or 0 ) + self._acceleration * timestep
if staticdata.velocity > max_vel then
staticdata.velocity = max_vel
elseif staticdata.velocity < 0.1 then
staticdata.velocity = 0
2017-08-28 14:19:46 +02:00
end
2024-02-13 15:45:00 +01:00
end
2017-08-28 14:19:46 +02:00
2024-02-17 00:09:29 +01:00
if DEBUG and staticdata.velocity > 0 then
print ( " acceleration= " .. tostring ( self._acceleration ) .. " ,velocity= " .. tostring ( staticdata.velocity ) ..
2024-02-13 15:45:00 +01:00
" ,timestep= " .. tostring ( timestep ) )
end
end
2020-03-17 21:00:12 +01:00
2024-02-13 15:45:00 +01:00
local function do_movement ( self , dtime )
local staticdata = self._staticdata
2020-03-17 21:00:12 +01:00
2024-02-17 00:09:29 +01:00
-- Allow the carts to be delay for the rest of the world to react before moving again
if ( staticdata.delay or 0 ) > dtime then
staticdata.delay = staticdata.delay - dtime
return
else
staticdata.delay = 0
end
2024-02-14 00:31:48 +01:00
-- Break long movements into fixed-size steps so that
-- it is impossible to jump across gaps due to server lag
-- causing large timesteps
2024-02-13 15:45:00 +01:00
local total_distance = dtime * ( staticdata.velocity or 0 )
local remaining_distance = total_distance
2017-08-28 14:19:46 +02:00
2024-02-14 00:31:48 +01:00
process_acceleration ( self , dtime * max_step_distance / total_distance )
2017-01-05 15:23:14 +01:00
2024-02-14 11:48:55 +01:00
while remaining_distance > 0.1 do
local step_distance = do_movement_step ( self , remaining_distance )
if step_distance > 0.1 then
2024-02-14 00:31:48 +01:00
process_acceleration ( self , dtime * step_distance / total_distance )
2017-03-02 15:44:31 +01:00
end
2024-02-14 11:48:55 +01:00
remaining_distance = remaining_distance - step_distance
2017-08-15 02:51:40 +02:00
end
2017-01-05 15:23:14 +01:00
2024-02-13 15:45:00 +01:00
-- Clear punched flag now that movement for this step has been completed
self._punched = false
2024-02-13 10:42:07 +01:00
end
function cart : on_step ( dtime )
hopper_take_item ( self , dtime )
2024-02-13 15:45:00 +01:00
local staticdata = self._staticdata
2024-02-14 00:31:48 +01:00
if not staticdata then
staticdata = make_staticdata ( )
self._staticdata = staticdata
end
2024-02-13 10:42:07 +01:00
local pos , rou_pos , node = self.object : get_pos ( )
2024-02-17 00:09:29 +01:00
--local update = {}
--local acceleration = 0
2024-02-13 10:42:07 +01:00
-- Controls
local ctrl , player = nil , nil
if self._driver then
player = minetest.get_player_by_name ( self._driver )
if player then
ctrl = player : get_player_control ( )
-- player detach
if ctrl.sneak then
detach_driver ( self )
return
end
2024-02-13 15:45:00 +01:00
-- Experimental controls
2024-02-14 11:48:55 +01:00
--self._go_forward = ctrl.up
--self._brake = ctrl.down
2024-02-13 10:42:07 +01:00
end
-- Give achievement when player reached a distance of 1000 nodes from the start position
if vector.distance ( self._start_pos , pos ) >= 1000 then
awards.unlock ( self._driver , " mcl:onARail " )
end
end
2024-02-13 15:45:00 +01:00
do_movement ( self , dtime )
2024-02-13 10:42:07 +01:00
2024-02-13 15:45:00 +01:00
-- TODO: move this into do_movement_step
2024-02-13 10:42:07 +01:00
-- Drop minecart if it collides with a cactus node
local r = 0.6
for _ , node_pos in pairs ( { { r , 0 } , { 0 , r } , { - r , 0 } , { 0 , - r } } ) do
if minetest.get_node ( vector.offset ( pos , node_pos [ 1 ] , 0 , node_pos [ 2 ] ) ) . name == " mcl_core:cactus " then
detach_driver ( self )
for d = 1 , # drop do
minetest.add_item ( pos , drop [ d ] )
end
self.object : remove ( )
return
2024-03-24 06:29:44 +01:00
end
end
2024-02-13 10:42:07 +01:00
-- Grab mob
if math.random ( 1 , 20 ) > 15 and not self._passenger then
if self.name == " mcl_minecarts:minecart " then
local mobsnear = minetest.get_objects_inside_radius ( self.object : get_pos ( ) , 1.3 )
for n = 1 , # mobsnear do
local mob = mobsnear [ n ]
if mob then
local entity = mob : get_luaentity ( )
if entity and entity.is_mob then
self._passenger = entity
mob : set_attach ( self.object , " " , passenger_attach_position , vector.zero ( ) )
break
end
end
end
end
elseif self._passenger then
local passenger_pos = self._passenger . object : get_pos ( )
if not passenger_pos then
self._passenger = nil
end
end
-- Drop minecart if it isn't on a rail anymore
if self._last_float_check == nil then
self._last_float_check = 0
else
self._last_float_check = self._last_float_check + dtime
end
if self._last_float_check >= mcl_minecarts.check_float_time then
pos = self.object : get_pos ( )
rou_pos = vector.round ( pos )
node = minetest.get_node ( rou_pos )
local g = minetest.get_item_group ( node.name , " connect_to_raillike " )
2024-02-13 11:42:38 +01:00
if g ~= self._staticdata . railtype and self._staticdata . railtype then
2024-02-13 10:42:07 +01:00
-- Detach driver
2024-02-14 11:48:55 +01:00
detach_driver ( self )
2024-02-13 10:42:07 +01:00
-- Explode if already ignited
if self._boomtimer then
self.object : remove ( )
mcl_explosions.explode ( pos , 4 , { drop_chance = 1.0 } )
return
end
-- Do not drop minecart. It goes off the rails too frequently, and anyone using them for farms won't
-- notice and lose their iron and not bother. Not cool until fixed.
end
self._last_float_check = 0
end
-- Update furnace stuff
if self._fueltime and self._fueltime > 0 then
self._fueltime = self._fueltime - dtime
if self._fueltime <= 0 then
self.object : set_properties ( { textures =
{
" default_furnace_top.png " ,
" default_furnace_top.png " ,
" default_furnace_front.png " ,
" default_furnace_side.png " ,
" default_furnace_side.png " ,
" default_furnace_side.png " ,
" mcl_minecarts_minecart.png " ,
} } )
self._fueltime = 0
end
end
local has_fuel = self._fueltime and self._fueltime > 0
-- Update TNT stuff
if self._boomtimer then
-- Explode
self._boomtimer = self._boomtimer - dtime
local pos = self.object : get_pos ( )
if self._boomtimer <= 0 then
self.object : remove ( )
mcl_explosions.explode ( pos , 4 , { drop_chance = 1.0 } )
return
else
tnt.smoke_step ( pos )
end
end
if self._blinktimer then
self._blinktimer = self._blinktimer - dtime
if self._blinktimer <= 0 then
self._blink = not self._blink
if self._blink then
self.object : set_properties ( { textures =
{
" default_tnt_top.png " ,
" default_tnt_bottom.png " ,
" default_tnt_side.png " ,
" default_tnt_side.png " ,
" default_tnt_side.png " ,
" default_tnt_side.png " ,
" mcl_minecarts_minecart.png " ,
} } )
else
self.object : set_properties ( { textures =
{
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_tnt_blink.png " ,
" mcl_minecarts_minecart.png " ,
} } )
end
self._blinktimer = tnt.BLINKTIMER
end
end
2017-08-28 14:19:46 +02:00
end
2017-09-05 15:28:04 +02:00
function cart : get_staticdata ( )
2024-02-14 12:47:42 +01:00
return minetest.serialize ( self._staticdata or { } )
2017-09-05 15:28:04 +02:00
end
2017-08-28 14:19:46 +02:00
minetest.register_entity ( entity_id , cart )
2017-03-21 03:58:53 +01:00
end
2017-08-29 02:36:50 +02:00
-- Place a minecart at pointed_thing
2021-05-25 12:52:25 +02:00
function mcl_minecarts . place_minecart ( itemstack , pointed_thing , placer )
2017-08-29 02:36:50 +02:00
if not pointed_thing.type == " node " then
return
end
local railpos , node
if mcl_minecarts : is_rail ( pointed_thing.under ) then
railpos = pointed_thing.under
node = minetest.get_node ( pointed_thing.under )
elseif mcl_minecarts : is_rail ( pointed_thing.above ) then
railpos = pointed_thing.above
node = minetest.get_node ( pointed_thing.above )
else
return
end
local entity_id = entity_mapping [ itemstack : get_name ( ) ]
local cart = minetest.add_entity ( railpos , entity_id )
local railtype = minetest.get_item_group ( node.name , " connect_to_raillike " )
2024-02-13 15:57:45 +01:00
local cart_dir = mcl_minecarts : get_rail_direction ( railpos , vector.new ( 1 , 0 , 0 ) , nil , nil , railtype )
2024-02-13 15:45:00 +01:00
cart : set_yaw ( minetest.dir_to_yaw ( cart_dir ) )
-- Update static data
2019-01-30 01:58:26 +01:00
local le = cart : get_luaentity ( )
2021-05-29 16:12:33 +02:00
if le then
2024-02-14 00:31:48 +01:00
le._staticdata = make_staticdata ( railtype , railpos , cart_dir )
2019-01-30 01:58:26 +01:00
end
2024-02-13 15:45:00 +01:00
-- Handle track behaviors
local node_def = minetest.registered_nodes [ node.name ]
if node_def._mcl_minecarts_on_enter then
node_def._mcl_minecarts_on_enter ( railpos , cart )
2024-03-24 06:29:44 +01:00
end
2017-08-29 02:36:50 +02:00
2020-07-10 16:08:40 +02:00
local pname = " "
if placer then
pname = placer : get_player_name ( )
end
if not minetest.is_creative_enabled ( pname ) then
2017-08-29 02:36:50 +02:00
itemstack : take_item ( )
end
return itemstack
end
2018-02-01 22:45:19 +01:00
2021-05-25 12:52:25 +02:00
local function register_craftitem ( itemstring , entity_id , description , tt_help , longdesc , usagehelp , icon , creative )
2017-08-29 02:36:50 +02:00
entity_mapping [ itemstring ] = entity_id
2017-08-30 21:51:27 +02:00
local groups = { minecart = 1 , transport = 1 }
if creative == false then
groups.not_in_creative_inventory = 1
end
2017-08-28 14:19:46 +02:00
local def = {
stack_max = 1 ,
on_place = function ( itemstack , placer , pointed_thing )
if not pointed_thing.type == " node " then
return
end
-- Call on_rightclick if the pointed node defines it
local node = minetest.get_node ( pointed_thing.under )
if placer and not placer : get_player_control ( ) . sneak then
if minetest.registered_nodes [ node.name ] and minetest.registered_nodes [ node.name ] . on_rightclick then
return minetest.registered_nodes [ node.name ] . on_rightclick ( pointed_thing.under , node , placer , itemstack ) or itemstack
end
end
2020-07-10 16:08:40 +02:00
return mcl_minecarts.place_minecart ( itemstack , pointed_thing , placer )
2017-08-28 14:19:46 +02:00
end ,
2018-02-01 22:45:19 +01:00
_on_dispense = function ( stack , pos , droppos , dropnode , dropdir )
-- Place minecart as entity on rail. If there's no rail, just drop it.
local placed
if minetest.get_item_group ( dropnode.name , " rail " ) ~= 0 then
-- FIXME: This places minecarts even if the spot is already occupied
2024-02-13 15:57:45 +01:00
local pointed_thing = { under = droppos , above = vector.new ( droppos.x , droppos.y + 1 , droppos.z ) }
2018-02-01 22:45:19 +01:00
placed = mcl_minecarts.place_minecart ( stack , pointed_thing )
end
if placed == nil then
-- Drop item
minetest.add_item ( droppos , stack )
end
end ,
2017-08-30 21:51:27 +02:00
groups = groups ,
2017-08-28 14:19:46 +02:00
}
def.description = description
2020-02-19 04:54:17 +01:00
def._tt_help = tt_help
2017-08-28 15:04:50 +02:00
def._doc_items_longdesc = longdesc
2017-08-28 14:19:46 +02:00
def._doc_items_usagehelp = usagehelp
def.inventory_image = icon
def.wield_image = icon
minetest.register_craftitem ( itemstring , def )
end
2018-02-02 01:21:19 +01:00
--[[
Register a minecart
* itemstring : Itemstring of minecart item
* entity_id : ID of minecart entity
* description : Item name / description
* longdesc : Long help text
* usagehelp : Usage help text
* mesh : Minecart mesh
* textures : Minecart textures table
* icon : Item icon
* drop : Dropped items after destroying minecart
* on_rightclick : Called after rightclick
* on_activate_by_rail : Called when above activator rail
* creative : If false , don ' t show in Creative Inventory
] ]
2020-02-19 04:54:17 +01:00
local function register_minecart ( itemstring , entity_id , description , tt_help , longdesc , usagehelp , mesh , textures , icon , drop , on_rightclick , on_activate_by_rail , creative )
2020-01-30 22:05:18 +01:00
register_entity ( entity_id , mesh , textures , drop , on_rightclick , on_activate_by_rail )
2020-02-19 04:54:17 +01:00
register_craftitem ( itemstring , entity_id , description , tt_help , longdesc , usagehelp , icon , creative )
2021-05-29 16:12:33 +02:00
if minetest.get_modpath ( " doc_identifier " ) then
2017-08-28 15:04:50 +02:00
doc.sub . identifier.register_object ( entity_id , " craftitems " , itemstring )
end
end
-- Minecart
register_minecart (
2017-08-28 14:19:46 +02:00
" mcl_minecarts:minecart " ,
" mcl_minecarts:minecart " ,
2019-03-07 20:43:39 +01:00
S ( " Minecart " ) ,
2020-02-19 04:54:17 +01:00
S ( " Vehicle for fast travel on rails " ) ,
2019-03-07 20:43:39 +01:00
S ( " Minecarts can be used for a quick transportion on rails. " ) .. " \n " ..
S ( " 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. " ) ,
S ( " You can place the minecart on rails. Right-click it to enter it. Punch it to get it moving. " ) .. " \n " ..
2020-01-30 23:11:16 +01:00
S ( " To obtain the minecart, punch it while holding down the sneak key. " ) .. " \n " ..
2020-01-30 22:42:53 +01:00
S ( " If it moves over a powered activator rail, you'll get ejected. " ) ,
2017-08-28 15:04:50 +02:00
" mcl_minecarts_minecart.b3d " ,
{ " mcl_minecarts_minecart.png " } ,
" mcl_minecarts_minecart_normal.png " ,
{ " mcl_minecarts:minecart " } ,
function ( self , clicker )
2021-03-13 12:30:33 +01:00
local name = clicker : get_player_name ( )
if not clicker or not clicker : is_player ( ) then
return
end
local player_name = clicker : get_player_name ( )
if self._driver and player_name == self._driver then
2020-01-30 22:42:53 +01:00
detach_driver ( self )
2021-03-13 12:30:33 +01:00
elseif not self._driver then
self._driver = player_name
2019-02-01 06:33:07 +01:00
self._start_pos = self.object : get_pos ( )
2021-03-13 12:30:33 +01:00
mcl_player.player_attached [ player_name ] = true
2024-02-13 15:57:45 +01:00
clicker : set_attach ( self.object , " " , vector.new ( 1 , - 1.75 , - 2 ) , vector.new ( 0 , 0 , 0 ) )
2018-07-09 23:45:54 +02:00
mcl_player.player_attached [ name ] = true
minetest.after ( 0.2 , function ( name )
local player = minetest.get_player_by_name ( name )
if player then
mcl_player.player_set_animation ( player , " sit " , 30 )
2024-02-13 15:57:45 +01:00
player : set_eye_offset ( vector.new ( 0 , - 5.5 , 0 ) , vector.new ( 0 , - 4 , 0 ) )
2021-07-20 16:14:34 +02:00
mcl_title.set ( clicker , " actionbar " , { text = S ( " Sneak to dismount " ) , color = " white " , stay = 60 } )
2018-07-09 23:45:54 +02:00
end
end , name )
2017-08-28 15:04:50 +02:00
end
2020-01-30 22:42:53 +01:00
end , activate_normal_minecart
2017-08-28 14:19:46 +02:00
)
2017-08-28 15:04:50 +02:00
-- Minecart with Chest
register_minecart (
2017-08-28 14:19:46 +02:00
" mcl_minecarts:chest_minecart " ,
" mcl_minecarts:chest_minecart " ,
2019-03-07 20:43:39 +01:00
S ( " Minecart with Chest " ) ,
2020-02-19 04:54:17 +01:00
nil , nil , nil ,
2017-08-28 15:04:50 +02:00
" mcl_minecarts_minecart_chest.b3d " ,
{ " mcl_chests_normal.png " , " mcl_minecarts_minecart.png " } ,
" mcl_minecarts_minecart_chest.png " ,
2018-05-08 01:40:31 +02:00
{ " mcl_minecarts:minecart " , " mcl_chests:chest " } ,
2022-09-23 05:43:31 +02:00
nil , nil , true )
2022-10-15 00:37:29 +02:00
mcl_entity_invs.register_inv ( " mcl_minecarts:chest_minecart " , " Minecart " , 27 , false , true )
2017-08-28 14:19:46 +02:00
2017-08-28 15:04:50 +02:00
-- Minecart with Furnace
register_minecart (
2017-08-28 14:19:46 +02:00
" mcl_minecarts:furnace_minecart " ,
" mcl_minecarts:furnace_minecart " ,
2019-03-07 20:43:39 +01:00
S ( " Minecart with Furnace " ) ,
2020-02-19 04:54:17 +01:00
nil ,
2020-01-30 22:31:44 +01:00
S ( " A minecart with furnace is a vehicle that travels on rails. It can propel itself with fuel. " ) ,
S ( " Place it on rails. If you give it some coal, the furnace will start burning for a long time and the minecart will be able to move itself. Punch it to get it moving. " ) .. " \n " ..
S ( " To obtain the minecart and furnace, punch them while holding down the sneak key. " ) ,
2017-08-28 15:04:50 +02:00
" mcl_minecarts_minecart_block.b3d " ,
{
" default_furnace_top.png " ,
" default_furnace_top.png " ,
" default_furnace_front.png " ,
" default_furnace_side.png " ,
" default_furnace_side.png " ,
" default_furnace_side.png " ,
" mcl_minecarts_minecart.png " ,
} ,
" mcl_minecarts_minecart_furnace.png " ,
2017-08-29 02:36:50 +02:00
{ " mcl_minecarts:minecart " , " mcl_furnaces:furnace " } ,
-- Feed furnace with coal
function ( self , clicker )
if not clicker or not clicker : is_player ( ) then
return
end
if not self._fueltime then
self._fueltime = 0
end
local held = clicker : get_wielded_item ( )
if minetest.get_item_group ( held : get_name ( ) , " coal " ) == 1 then
self._fueltime = self._fueltime + 180
2020-07-10 16:08:40 +02:00
if not minetest.is_creative_enabled ( clicker : get_player_name ( ) ) then
2017-08-29 02:36:50 +02:00
held : take_item ( )
2020-04-30 18:13:05 +02:00
local index = clicker : get_wield_index ( )
2017-08-29 02:36:50 +02:00
local inv = clicker : get_inventory ( )
inv : set_stack ( " main " , index , held )
end
2020-01-30 20:38:31 +01:00
self.object : set_properties ( { textures =
{
" default_furnace_top.png " ,
" default_furnace_top.png " ,
" default_furnace_front_active.png " ,
" default_furnace_side.png " ,
" default_furnace_side.png " ,
" default_furnace_side.png " ,
" mcl_minecarts_minecart.png " ,
} } )
2017-08-29 02:36:50 +02:00
end
2022-09-18 21:32:45 +02:00
end , nil , true
2017-08-28 14:19:46 +02:00
)
2017-08-28 15:04:50 +02:00
-- Minecart with Command Block
register_minecart (
2017-08-28 14:19:46 +02:00
" mcl_minecarts:command_block_minecart " ,
" mcl_minecarts:command_block_minecart " ,
2019-03-07 20:43:39 +01:00
S ( " Minecart with Command Block " ) ,
2020-02-19 04:54:17 +01:00
nil , nil , nil ,
2017-08-28 15:04:50 +02:00
" mcl_minecarts_minecart_block.b3d " ,
{
" jeija_commandblock_off.png^[verticalframe:2:0 " ,
" jeija_commandblock_off.png^[verticalframe:2:0 " ,
" jeija_commandblock_off.png^[verticalframe:2:0 " ,
" jeija_commandblock_off.png^[verticalframe:2:0 " ,
" jeija_commandblock_off.png^[verticalframe:2:0 " ,
" jeija_commandblock_off.png^[verticalframe:2:0 " ,
" mcl_minecarts_minecart.png " ,
} ,
" mcl_minecarts_minecart_command_block.png " ,
2017-08-30 21:51:27 +02:00
{ " mcl_minecarts:minecart " } ,
2018-05-08 01:40:31 +02:00
nil , nil , false
2017-08-28 15:04:50 +02:00
)
-- Minecart with Hopper
register_minecart (
" mcl_minecarts:hopper_minecart " ,
" mcl_minecarts:hopper_minecart " ,
2019-03-07 20:43:39 +01:00
S ( " Minecart with Hopper " ) ,
2020-02-19 04:54:17 +01:00
nil , nil , nil ,
2017-08-28 15:04:50 +02:00
" mcl_minecarts_minecart_hopper.b3d " ,
{
" mcl_hoppers_hopper_inside.png " ,
" mcl_minecarts_minecart.png " ,
" mcl_hoppers_hopper_outside.png " ,
" mcl_hoppers_hopper_top.png " ,
} ,
" mcl_minecarts_minecart_hopper.png " ,
2018-05-08 01:40:31 +02:00
{ " mcl_minecarts:minecart " , " mcl_hoppers:hopper " } ,
2022-11-12 18:47:20 +01:00
nil , nil , true
2017-08-28 14:19:46 +02:00
)
2022-11-12 18:47:20 +01:00
mcl_entity_invs.register_inv ( " mcl_minecarts:hopper_minecart " , " Hopper Minecart " , 5 , false , true )
2017-08-28 14:19:46 +02:00
2017-08-28 15:04:50 +02:00
-- Minecart with TNT
register_minecart (
" mcl_minecarts:tnt_minecart " ,
" mcl_minecarts:tnt_minecart " ,
2019-03-07 20:43:39 +01:00
S ( " Minecart with TNT " ) ,
2020-02-19 04:54:17 +01:00
S ( " Vehicle for fast travel on rails " ) .. " \n " .. S ( " Can be ignited by tools or powered activator rail " ) ,
2020-01-30 22:31:44 +01:00
S ( " A minecart with TNT is an explosive vehicle that travels on rail. " ) ,
2020-02-05 03:11:07 +01:00
S ( " Place it on rails. Punch it to move it. The TNT is ignited with a flint and steel or when the minecart is on an powered activator rail. " ) .. " \n " ..
2020-01-30 22:31:44 +01:00
S ( " To obtain the minecart and TNT, punch them while holding down the sneak key. You can't do this if the TNT was ignited. " ) ,
2017-08-28 15:04:50 +02:00
" mcl_minecarts_minecart_block.b3d " ,
{
" default_tnt_top.png " ,
" default_tnt_bottom.png " ,
" default_tnt_side.png " ,
" default_tnt_side.png " ,
" default_tnt_side.png " ,
" default_tnt_side.png " ,
" mcl_minecarts_minecart.png " ,
} ,
" mcl_minecarts_minecart_tnt.png " ,
2018-05-08 01:40:31 +02:00
{ " mcl_minecarts:minecart " , " mcl_tnt:tnt " } ,
2020-01-30 22:05:18 +01:00
-- Ingite
function ( self , clicker )
if not clicker or not clicker : is_player ( ) then
return
end
if self._boomtimer then
return
end
local held = clicker : get_wielded_item ( )
if held : get_name ( ) == " mcl_fire:flint_and_steel " then
2020-07-10 16:08:40 +02:00
if not minetest.is_creative_enabled ( clicker : get_player_name ( ) ) then
2020-01-30 22:05:18 +01:00
held : add_wear ( 65535 / 65 ) -- 65 uses
2020-02-14 21:09:36 +01:00
local index = clicker : get_wield_index ( )
2020-01-30 22:05:18 +01:00
local inv = clicker : get_inventory ( )
inv : set_stack ( " main " , index , held )
end
activate_tnt_minecart ( self )
end
2020-01-30 22:31:44 +01:00
end , activate_tnt_minecart )
2017-08-28 14:19:46 +02:00
2017-01-05 15:23:14 +01:00
minetest.register_craft ( {
output = " mcl_minecarts:minecart " ,
recipe = {
2017-02-11 21:14:40 +01:00
{ " mcl_core:iron_ingot " , " " , " mcl_core:iron_ingot " } ,
{ " mcl_core:iron_ingot " , " mcl_core:iron_ingot " , " mcl_core:iron_ingot " } ,
2017-01-05 15:23:14 +01:00
} ,
} )
2017-08-28 14:19:46 +02:00
minetest.register_craft ( {
2020-01-30 22:31:44 +01:00
output = " mcl_minecarts:tnt_minecart " ,
2017-08-28 14:19:46 +02:00
recipe = {
2020-01-30 22:31:44 +01:00
{ " mcl_tnt:tnt " } ,
2017-08-28 14:19:46 +02:00
{ " mcl_minecarts:minecart " } ,
} ,
} )
2022-09-18 21:32:45 +02:00
minetest.register_craft ( {
2021-05-25 00:43:08 +02:00
output = " mcl_minecarts:furnace_minecart " ,
recipe = {
{ " mcl_furnaces:furnace " } ,
{ " mcl_minecarts:minecart " } ,
} ,
} )
2022-11-12 18:47:20 +01:00
minetest.register_craft ( {
2021-05-25 00:43:08 +02:00
output = " mcl_minecarts:hopper_minecart " ,
recipe = {
{ " mcl_hoppers:hopper " } ,
{ " mcl_minecarts:minecart " } ,
} ,
} )
2022-11-12 18:47:20 +01:00
2021-05-25 00:43:08 +02:00
minetest.register_craft ( {
output = " mcl_minecarts:chest_minecart " ,
recipe = {
{ " mcl_chests:chest " } ,
{ " mcl_minecarts:minecart " } ,
} ,
2022-09-23 05:43:31 +02:00
} )
2021-05-25 00:43:08 +02:00
2021-03-30 00:58:45 +02:00
if has_mcl_wip then
mcl_wip.register_wip_item ( " mcl_minecarts:chest_minecart " )
mcl_wip.register_wip_item ( " mcl_minecarts:furnace_minecart " )
mcl_wip.register_wip_item ( " mcl_minecarts:command_block_minecart " )
2022-03-09 12:11:59 +01:00
end