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:
teknomunk 2024-04-06 13:40:40 +00:00
parent 2247d19dea
commit 6736d80b0c
3 changed files with 183 additions and 20 deletions

View File

@ -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

View File

@ -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)

View File

@ -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 =