mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2024-08-22 01:47:24 +02:00
Add utilities to convert between an ObjectRef, it's active object id and a 128bit uuid, move minecart data from entity staticdata to mod storage to eventually allow updating carts when out of range of players and also track what carts are alive, implement on-rail cart collisions
This commit is contained in:
parent
ce9e3481af
commit
bacc8bdf64
@ -1151,3 +1151,49 @@ function mcl_util.table_keys(t)
|
|||||||
return keys
|
return keys
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local uuid_to_aoid_cache = {}
|
||||||
|
local function scan_active_objects()
|
||||||
|
-- Update active object ids for all active objects
|
||||||
|
for active_object_id,o in pairs(minetest.luaentities) do
|
||||||
|
o._active_object_id = active_object_id
|
||||||
|
if o._uuid then
|
||||||
|
uuid_to_aoid_cache[o._uuid] = active_object_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function mcl_util.get_active_object_id(obj)
|
||||||
|
local le = obj:get_luaentity()
|
||||||
|
|
||||||
|
-- If the active object id in the lua entity is correct, return that
|
||||||
|
if le._active_object_id and minetest.luaentities[le._active_object_id] == le then
|
||||||
|
return le._active_object_id
|
||||||
|
end
|
||||||
|
|
||||||
|
scan_active_objects()
|
||||||
|
|
||||||
|
return le._active_object_id
|
||||||
|
end
|
||||||
|
function mcl_util.get_active_object_id_from_uuid(uuid)
|
||||||
|
return uuid_to_aoid_cache[uuid] or scan_active_objects() or uuid_to_aoid_cache[uuid]
|
||||||
|
end
|
||||||
|
function mcl_util.get_uuid(obj)
|
||||||
|
local le = obj:get_luaentity()
|
||||||
|
|
||||||
|
if le._uuid then return le._uuid end
|
||||||
|
|
||||||
|
-- Generate a random 128-bit ID that can be assumed to be unique
|
||||||
|
-- To have a 1% chance of a collision, there would have to be 1.6x10^76 IDs generated
|
||||||
|
-- https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
|
||||||
|
local u = {}
|
||||||
|
for i = 1,16 do
|
||||||
|
u[#u + 1] = string.format("%02X",math.random(1,255))
|
||||||
|
end
|
||||||
|
le._uuid = table.concat(u)
|
||||||
|
|
||||||
|
-- Update the cache with this new id
|
||||||
|
aoid = mcl_util.get_active_object_id(obj)
|
||||||
|
uuid_to_aoid_cache[le._uuid] = aoid
|
||||||
|
|
||||||
|
return le._uuid
|
||||||
|
end
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
local modname = minetest.get_current_modname()
|
local modname = minetest.get_current_modname()
|
||||||
local mod = mcl_minecarts
|
local mod = mcl_minecarts
|
||||||
|
local storage = minetest.get_mod_storage()
|
||||||
local S = minetest.get_translator(modname)
|
local S = minetest.get_translator(modname)
|
||||||
|
|
||||||
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false)
|
local LOGGING_ON = minetest.settings:get_bool("mcl_logging_minecarts", false)
|
||||||
@ -19,6 +20,32 @@ local friction = 0.4
|
|||||||
local MINECART_MAX_HP = 4
|
local MINECART_MAX_HP = 4
|
||||||
local PASSENGER_ATTACH_POSITION = vector.new(0, -1.75, 0)
|
local PASSENGER_ATTACH_POSITION = vector.new(0, -1.75, 0)
|
||||||
|
|
||||||
|
local cart_data = {}
|
||||||
|
local cart_data_fail_cache = {}
|
||||||
|
|
||||||
|
local function get_cart_data(uuid)
|
||||||
|
if cart_data[uuid] then return cart_data[uuid] end
|
||||||
|
if cart_data_fail_cache[uuid] then return nil end
|
||||||
|
|
||||||
|
local data = minetest.deserialize(storage:get_string("cart-"..uuid))
|
||||||
|
if not data then
|
||||||
|
cart_data_fail_cache[uuid] = true
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
cart_data[uuid] = data
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
local function save_cart_data(uuid)
|
||||||
|
if not cart_data[uuid] then return end
|
||||||
|
storage:set_string("cart-"..uuid,minetest.serialize(cart_data[uuid]))
|
||||||
|
end
|
||||||
|
local function destroy_cart_data(uuid)
|
||||||
|
storage:set_string("cart-"..uuid,"")
|
||||||
|
cart_data[uuid] = nil
|
||||||
|
cart_data_fail_cache[uuid] = true
|
||||||
|
end
|
||||||
|
|
||||||
local function detach_minecart(self)
|
local function detach_minecart(self)
|
||||||
local staticdata = self._staticdata
|
local staticdata = self._staticdata
|
||||||
|
|
||||||
@ -72,13 +99,68 @@ local function handle_cart_enter_exit(self, pos, next_dir, event)
|
|||||||
local hook = self["_mcl_minecarts_"..event]
|
local hook = self["_mcl_minecarts_"..event]
|
||||||
if hook then hook(self, pos) end
|
if hook then hook(self, pos) end
|
||||||
end
|
end
|
||||||
|
local function set_metadata_cart_status(pos, uuid, state)
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
|
||||||
|
carts[uuid] = state
|
||||||
|
meta:set_string("_mcl_minecarts_carts", minetest.serialize(carts))
|
||||||
|
end
|
||||||
local function handle_cart_enter(self, pos, next_dir)
|
local function handle_cart_enter(self, pos, next_dir)
|
||||||
|
--print("entering "..tostring(pos))
|
||||||
|
set_metadata_cart_status(pos, self._staticdata.uuid, 1)
|
||||||
handle_cart_enter_exit(self, pos, next_dir, "on_enter" )
|
handle_cart_enter_exit(self, pos, next_dir, "on_enter" )
|
||||||
end
|
end
|
||||||
local function handle_cart_leave(self, pos, next_dir)
|
local function handle_cart_leave(self, pos, next_dir)
|
||||||
|
--print("leaving "..tostring(pos))
|
||||||
|
set_metadata_cart_status(pos, self._staticdata.uuid, nil)
|
||||||
handle_cart_enter_exit(self, pos, next_dir, "on_leave" )
|
handle_cart_enter_exit(self, pos, next_dir, "on_leave" )
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function handle_cart_collision(cart1, pos, next_dir)
|
||||||
|
local meta = minetest.get_meta(pos)
|
||||||
|
local carts = minetest.deserialize(meta:get_string("_mcl_minecarts_carts")) or {}
|
||||||
|
local cart_uuid = nil
|
||||||
|
local dirty = false
|
||||||
|
for uuid,v in pairs(carts) do
|
||||||
|
-- Clean up dead carts
|
||||||
|
if not get_cart_data(uuid) then
|
||||||
|
carts[uuid] = nil
|
||||||
|
dirty = true
|
||||||
|
uuid = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if uuid and uuid ~= cart1._staticdata.uuid then cart_uuid = uuid end
|
||||||
|
end
|
||||||
|
if dirty then
|
||||||
|
meta:set_string("_mcl_minecarts_carts",minetest.serialize(carts))
|
||||||
|
end
|
||||||
|
|
||||||
|
local meta = minetest.get_meta(vector.add(pos,next_dir))
|
||||||
|
if not cart_uuid then return end
|
||||||
|
minetest.log("action","cart #"..cart1._staticdata.uuid.." collided with cart #"..cart_uuid.." at "..tostring(pos))
|
||||||
|
|
||||||
|
local cart2_aoid = mcl_util.get_active_object_id_from_uuid(cart_uuid)
|
||||||
|
local cart2 = minetest.luaentities[cart2_aoid]
|
||||||
|
if not cart2 then return end
|
||||||
|
|
||||||
|
local cart1_staticdata = cart1._staticdata
|
||||||
|
local cart2_staticdata = cart2._staticdata
|
||||||
|
|
||||||
|
local u1 = cart1_staticdata.velocity
|
||||||
|
local u2 = cart2_staticdata.velocity
|
||||||
|
local m1 = cart1_staticdata.mass
|
||||||
|
local m2 = cart2_staticdata.mass
|
||||||
|
|
||||||
|
-- Calculate new velocities according to https://en.wikipedia.org/wiki/Elastic_collision#One-dimensional_Newtonian
|
||||||
|
local c1 = m1 + m2
|
||||||
|
local d = m1 - m2
|
||||||
|
local v1 = ( d * u1 + 2 * m2 * u2 ) / c1
|
||||||
|
local v2 = ( 2 * m1 * u1 + d * u2 ) / c1
|
||||||
|
|
||||||
|
cart1_staticdata.velocity = v1
|
||||||
|
cart2_staticdata.velocity = v2
|
||||||
|
end
|
||||||
|
|
||||||
local function handle_cart_node_watches(self, dtime)
|
local function handle_cart_node_watches(self, dtime)
|
||||||
local staticdata = self._staticdata
|
local staticdata = self._staticdata
|
||||||
local watches = staticdata.node_watches or {}
|
local watches = staticdata.node_watches or {}
|
||||||
@ -246,7 +328,7 @@ local function do_movement_step(self, dtime)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if DEBUG and ( v_0 > 0 or a ~= 0 ) then
|
if DEBUG and ( v_0 > 0 or a ~= 0 ) then
|
||||||
print( " cart #"..tostring(staticdata.cart_id)..
|
print( " cart "..tostring(staticdata.uuid)..
|
||||||
": a="..tostring(a)..
|
": a="..tostring(a)..
|
||||||
",v_0="..tostring(v_0)..
|
",v_0="..tostring(v_0)..
|
||||||
",x_0="..tostring(x_0)..
|
",x_0="..tostring(x_0)..
|
||||||
@ -305,7 +387,7 @@ local function do_movement_step(self, dtime)
|
|||||||
staticdata.distance = x_1
|
staticdata.distance = x_1
|
||||||
|
|
||||||
if DEBUG and (1==0) and ( v_0 > 0 or a ~= 0 ) then
|
if DEBUG and (1==0) and ( v_0 > 0 or a ~= 0 ) then
|
||||||
print( "- cart #"..tostring(staticdata.cart_id)..
|
print( "- cart #"..tostring(staticdata.uuid)..
|
||||||
": a="..tostring(a)..
|
": a="..tostring(a)..
|
||||||
",v_0="..tostring(v_0)..
|
",v_0="..tostring(v_0)..
|
||||||
",v_1="..tostring(v_1)..
|
",v_1="..tostring(v_1)..
|
||||||
@ -337,6 +419,9 @@ local function do_movement_step(self, dtime)
|
|||||||
print( "Changing direction from "..tostring(staticdata.dir).." to "..tostring(next_dir))
|
print( "Changing direction from "..tostring(staticdata.dir).." to "..tostring(next_dir))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Handle cart collisions
|
||||||
|
handle_cart_collision(self, pos, next_dir)
|
||||||
|
|
||||||
-- Leave the old node
|
-- Leave the old node
|
||||||
handle_cart_leave(self, old_pos, next_dir )
|
handle_cart_leave(self, old_pos, next_dir )
|
||||||
|
|
||||||
@ -376,7 +461,7 @@ local function do_movement_step(self, dtime)
|
|||||||
|
|
||||||
-- Debug reporting
|
-- Debug reporting
|
||||||
if DEBUG and ( v_0 > 0 or v_1 > 0 ) then
|
if DEBUG and ( v_0 > 0 or v_1 > 0 ) then
|
||||||
print( " cart #"..tostring(staticdata.cart_id)..
|
print( " cart #"..tostring(staticdata.uuid)..
|
||||||
": a="..tostring(a)..
|
": a="..tostring(a)..
|
||||||
",v_0="..tostring(v_0)..
|
",v_0="..tostring(v_0)..
|
||||||
",v_1="..tostring(v_1)..
|
",v_1="..tostring(v_1)..
|
||||||
@ -615,7 +700,7 @@ local function make_staticdata( railtype, connected_at, dir )
|
|||||||
distance = 0,
|
distance = 0,
|
||||||
velocity = 0,
|
velocity = 0,
|
||||||
dir = vector.new(dir),
|
dir = vector.new(dir),
|
||||||
cart_id = math.random(1,1000000000),
|
mass = 1,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -644,24 +729,40 @@ local DEFAULT_CART_DEF = {
|
|||||||
_staticdata = nil,
|
_staticdata = nil,
|
||||||
}
|
}
|
||||||
function DEFAULT_CART_DEF:on_activate(staticdata, dtime_s)
|
function DEFAULT_CART_DEF:on_activate(staticdata, dtime_s)
|
||||||
|
-- Transfer older data
|
||||||
|
local data = minetest.deserialize(staticdata) or {}
|
||||||
|
if not data.uuid then
|
||||||
|
data.uuid = mcl_util.get_uuid(self.object)
|
||||||
|
end
|
||||||
|
local cd = get_cart_data(data.uuid)
|
||||||
|
if not cd then
|
||||||
|
cart_data[data.uuid] = data
|
||||||
|
cart_data_fail_cache[data.uuid] = nil
|
||||||
|
save_cart_data(data.uuid)
|
||||||
|
end
|
||||||
|
|
||||||
-- Initialize
|
-- Initialize
|
||||||
local data = minetest.deserialize(staticdata)
|
|
||||||
if type(data) == "table" then
|
if type(data) == "table" then
|
||||||
-- Migrate old data
|
-- Migrate old data
|
||||||
if data._railtype then
|
if data._railtype then
|
||||||
data.railtype = data._railtype
|
data.railtype = data._railtype
|
||||||
data._railtype = nil
|
data._railtype = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Fix up types
|
-- Fix up types
|
||||||
data.dir = vector.new(data.dir)
|
data.dir = vector.new(data.dir)
|
||||||
|
|
||||||
|
-- Fix mass
|
||||||
|
data.mass = data.mass or 1
|
||||||
|
|
||||||
-- Make sure all carts have an ID to isolate them
|
-- Make sure all carts have an ID to isolate them
|
||||||
data.cart_id = staticdata.cart_id or math.random(1,1000000000)
|
self._uuid = data.uuid
|
||||||
|
data.uuid = mcl_util.get_uuid(self.object)
|
||||||
|
|
||||||
self._staticdata = data
|
self._staticdata = data
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Activate cart if on activator rail
|
-- Activate cart if on powered activator rail
|
||||||
if self.on_activate_by_rail then
|
if self.on_activate_by_rail then
|
||||||
local pos = self.object:get_pos()
|
local pos = self.object:get_pos()
|
||||||
local node = minetest.get_node(vector.floor(pos))
|
local node = minetest.get_node(vector.floor(pos))
|
||||||
@ -670,6 +771,11 @@ function DEFAULT_CART_DEF:on_activate(staticdata, dtime_s)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
function DEFAULT_CART_DEF:get_staticdata()
|
||||||
|
save_cart_data(self._staticdata.uuid)
|
||||||
|
return minetest.serialize({uuid = self._staticdata.uuid})
|
||||||
|
end
|
||||||
|
|
||||||
function DEFAULT_CART_DEF:add_node_watch(pos)
|
function DEFAULT_CART_DEF:add_node_watch(pos)
|
||||||
local staticdata = self._staticdata
|
local staticdata = self._staticdata
|
||||||
local watches = staticdata.node_watches or {}
|
local watches = staticdata.node_watches or {}
|
||||||
@ -693,9 +799,6 @@ function DEFAULT_CART_DEF:remove_node_watch(pos)
|
|||||||
end
|
end
|
||||||
staticdata.node_watches = new_watches
|
staticdata.node_watches = new_watches
|
||||||
end
|
end
|
||||||
function DEFAULT_CART_DEF:get_staticdata()
|
|
||||||
return minetest.serialize(self._staticdata or {})
|
|
||||||
end
|
|
||||||
function DEFAULT_CART_DEF:on_step(dtime)
|
function DEFAULT_CART_DEF:on_step(dtime)
|
||||||
local staticdata = self._staticdata
|
local staticdata = self._staticdata
|
||||||
if not staticdata then
|
if not staticdata then
|
||||||
@ -765,11 +868,14 @@ function DEFAULT_CART_DEF:on_step(dtime)
|
|||||||
local r = 0.6
|
local r = 0.6
|
||||||
for _, node_pos in pairs({{r, 0}, {0, r}, {-r, 0}, {0, -r}}) do
|
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
|
if minetest.get_node(vector.offset(pos, node_pos[1], 0, node_pos[2])).name == "mcl_core:cactus" then
|
||||||
|
self:on_death()
|
||||||
|
--[[
|
||||||
detach_driver(self)
|
detach_driver(self)
|
||||||
local drop = self.drop
|
local drop = self.drop
|
||||||
for d = 1, #drop do
|
for d = 1, #drop do
|
||||||
minetest.add_item(pos, drop[d])
|
minetest.add_item(pos, drop[d])
|
||||||
end
|
end
|
||||||
|
]]
|
||||||
self.object:remove()
|
self.object:remove()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -777,6 +883,8 @@ function DEFAULT_CART_DEF:on_step(dtime)
|
|||||||
end
|
end
|
||||||
function DEFAULT_CART_DEF:on_death(killer)
|
function DEFAULT_CART_DEF:on_death(killer)
|
||||||
local staticdata = self._staticdata
|
local staticdata = self._staticdata
|
||||||
|
minetest.log("action", "cart #"..staticdata.uuid.." was killed")
|
||||||
|
|
||||||
detach_driver(self)
|
detach_driver(self)
|
||||||
|
|
||||||
-- Detach passenger
|
-- Detach passenger
|
||||||
@ -792,6 +900,9 @@ function DEFAULT_CART_DEF:on_death(killer)
|
|||||||
mcl_log("TODO: handle detatched minecart death")
|
mcl_log("TODO: handle detatched minecart death")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Remove data
|
||||||
|
destroy_cart_data(staticdata.uuid)
|
||||||
|
|
||||||
-- Drop items
|
-- Drop items
|
||||||
local drop = self.drop
|
local drop = self.drop
|
||||||
if not killer or not minetest.is_creative_enabled(killer:get_player_name()) then
|
if not killer or not minetest.is_creative_enabled(killer:get_player_name()) then
|
||||||
@ -838,7 +949,12 @@ function mcl_minecarts.place_minecart(itemstack, pointed_thing, placer)
|
|||||||
-- Update static data
|
-- Update static data
|
||||||
local le = cart:get_luaentity()
|
local le = cart:get_luaentity()
|
||||||
if le then
|
if le then
|
||||||
le._staticdata = make_staticdata( railtype, railpos, cart_dir )
|
local uuid = mcl_util.get_uuid(cart)
|
||||||
|
data = make_staticdata( railtype, railpos, cart_dir )
|
||||||
|
data.uuid = uuid
|
||||||
|
cart_data[uuid] = data
|
||||||
|
le._staticdata = data
|
||||||
|
save_cart_data(le._staticdata.uuid)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Call placer
|
-- Call placer
|
||||||
@ -1386,3 +1502,8 @@ if minetest.get_modpath("mcl_wip") then
|
|||||||
mcl_wip.register_wip_item("mcl_minecarts:furnace_minecart")
|
mcl_wip.register_wip_item("mcl_minecarts:furnace_minecart")
|
||||||
mcl_wip.register_wip_item("mcl_minecarts:command_block_minecart")
|
mcl_wip.register_wip_item("mcl_minecarts:command_block_minecart")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
minetest.register_globalstep(function(dtime)
|
||||||
|
-- TODO: handle periodically updating out-of-range carts
|
||||||
|
end)
|
||||||
|
|
||||||
|
@ -177,7 +177,7 @@ table_merge(SLOPED_RAIL_DEF,{
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
local function register_rail_v2(itemstring, ndef)
|
function mod.register_rail(itemstring, ndef)
|
||||||
assert(ndef.tiles)
|
assert(ndef.tiles)
|
||||||
|
|
||||||
-- Extract out the craft recipe
|
-- Extract out the craft recipe
|
||||||
@ -188,15 +188,14 @@ local function register_rail_v2(itemstring, ndef)
|
|||||||
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
|
if not ndef.inventory_image then ndef.inventory_image = ndef.tiles[1] end
|
||||||
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
|
if not ndef.wield_image then ndef.wield_image = ndef.tiles[1] end
|
||||||
|
|
||||||
--print("registering rail "..itemstring.." with definition: "..dump(ndef))
|
print("registering rail "..itemstring.." with definition: "..dump(ndef))
|
||||||
|
|
||||||
-- Make registrations
|
-- Make registrations
|
||||||
minetest.register_node(itemstring, ndef)
|
minetest.register_node(itemstring, ndef)
|
||||||
if craft then minetest.register_craft(craft) end
|
if craft then minetest.register_craft(craft) end
|
||||||
end
|
end
|
||||||
mod.register_rail = register_rail_v2
|
|
||||||
|
|
||||||
local function register_straight_rail(base_name, tiles, def)
|
function mod.register_straight_rail(base_name, tiles, def)
|
||||||
def = def or {}
|
def = def or {}
|
||||||
local base_def = table.copy(BASE_DEF)
|
local base_def = table.copy(BASE_DEF)
|
||||||
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
||||||
@ -235,9 +234,8 @@ local function register_straight_rail(base_name, tiles, def)
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
mod.register_straight_rail = register_straight_rail
|
|
||||||
|
|
||||||
local function register_curves_rail(base_name, tiles, def)
|
function mod.register_curves_rail(base_name, tiles, def)
|
||||||
def = def or {}
|
def = def or {}
|
||||||
local base_def = table.copy(BASE_DEF)
|
local base_def = table.copy(BASE_DEF)
|
||||||
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
local sloped_def = table.copy(SLOPED_RAIL_DEF)
|
||||||
@ -333,9 +331,8 @@ local function register_curves_rail(base_name, tiles, def)
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
mod.register_curves_rail = register_curves_rail
|
|
||||||
|
|
||||||
local function register_rail_sloped(itemstring, def)
|
function mod.register_rail_sloped(itemstring, def)
|
||||||
assert(def.tiles)
|
assert(def.tiles)
|
||||||
|
|
||||||
-- Build the node definition
|
-- Build the node definition
|
||||||
@ -351,7 +348,6 @@ local function register_rail_sloped(itemstring, def)
|
|||||||
-- Make registrations
|
-- Make registrations
|
||||||
minetest.register_node(itemstring, ndef)
|
minetest.register_node(itemstring, ndef)
|
||||||
end
|
end
|
||||||
mod.register_rail_sloped = register_rail_sloped
|
|
||||||
|
|
||||||
-- Redstone rules
|
-- Redstone rules
|
||||||
local rail_rules_long =
|
local rail_rules_long =
|
||||||
|
Loading…
Reference in New Issue
Block a user