From b8152537048f8dac5c76cc9271c646b219da214d Mon Sep 17 00:00:00 2001 From: teknomunk Date: Sat, 6 Apr 2024 13:40:40 +0000 Subject: [PATCH] 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 --- mods/CORE/mcl_util/init.lua | 46 +++++++++ mods/ENTITIES/mcl_minecarts/carts.lua | 143 ++++++++++++++++++++++++-- mods/ENTITIES/mcl_minecarts/rails.lua | 14 +-- 3 files changed, 183 insertions(+), 20 deletions(-) diff --git a/mods/CORE/mcl_util/init.lua b/mods/CORE/mcl_util/init.lua index 28f5503d1..114816ef9 100644 --- a/mods/CORE/mcl_util/init.lua +++ b/mods/CORE/mcl_util/init.lua @@ -1179,3 +1179,49 @@ function mcl_util.table_keys(t) return keys 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 + diff --git a/mods/ENTITIES/mcl_minecarts/carts.lua b/mods/ENTITIES/mcl_minecarts/carts.lua index f72a33830..7d673e638 100644 --- a/mods/ENTITIES/mcl_minecarts/carts.lua +++ b/mods/ENTITIES/mcl_minecarts/carts.lua @@ -1,5 +1,6 @@ local modname = minetest.get_current_modname() local mod = mcl_minecarts +local storage = minetest.get_mod_storage() local S = minetest.get_translator(modname) 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 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 staticdata = self._staticdata @@ -72,13 +99,68 @@ local function handle_cart_enter_exit(self, pos, next_dir, event) local hook = self["_mcl_minecarts_"..event] if hook then hook(self, pos) 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) + --print("entering "..tostring(pos)) + set_metadata_cart_status(pos, self._staticdata.uuid, 1) handle_cart_enter_exit(self, pos, next_dir, "on_enter" ) end 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" ) 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 staticdata = self._staticdata local watches = staticdata.node_watches or {} @@ -246,7 +328,7 @@ local function do_movement_step(self, dtime) end if DEBUG and ( v_0 > 0 or a ~= 0 ) then - print( " cart #"..tostring(staticdata.cart_id).. + print( " cart "..tostring(staticdata.uuid).. ": a="..tostring(a).. ",v_0="..tostring(v_0).. ",x_0="..tostring(x_0).. @@ -305,7 +387,7 @@ local function do_movement_step(self, dtime) staticdata.distance = x_1 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).. ",v_0="..tostring(v_0).. ",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)) end + -- Handle cart collisions + handle_cart_collision(self, pos, next_dir) + -- Leave the old node handle_cart_leave(self, old_pos, next_dir ) @@ -376,7 +461,7 @@ local function do_movement_step(self, dtime) -- Debug reporting 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).. ",v_0="..tostring(v_0).. ",v_1="..tostring(v_1).. @@ -615,7 +700,7 @@ local function make_staticdata( railtype, connected_at, dir ) distance = 0, velocity = 0, dir = vector.new(dir), - cart_id = math.random(1,1000000000), + mass = 1, } end @@ -644,24 +729,40 @@ local DEFAULT_CART_DEF = { _staticdata = nil, } 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 - local data = minetest.deserialize(staticdata) if type(data) == "table" then -- Migrate old data if data._railtype then data.railtype = data._railtype data._railtype = nil end + -- Fix up types data.dir = vector.new(data.dir) + -- Fix mass + data.mass = data.mass or 1 + -- 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 end - -- Activate cart if on activator rail + -- Activate cart if on powered activator rail if self.on_activate_by_rail then local pos = self.object:get_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 +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) local staticdata = self._staticdata local watches = staticdata.node_watches or {} @@ -693,9 +799,6 @@ function DEFAULT_CART_DEF:remove_node_watch(pos) end staticdata.node_watches = new_watches end -function DEFAULT_CART_DEF:get_staticdata() - return minetest.serialize(self._staticdata or {}) -end function DEFAULT_CART_DEF:on_step(dtime) local staticdata = self._staticdata if not staticdata then @@ -765,11 +868,14 @@ function DEFAULT_CART_DEF:on_step(dtime) 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 + self:on_death() + --[[ detach_driver(self) local drop = self.drop for d = 1, #drop do minetest.add_item(pos, drop[d]) end + ]] self.object:remove() return end @@ -777,6 +883,8 @@ function DEFAULT_CART_DEF:on_step(dtime) end function DEFAULT_CART_DEF:on_death(killer) local staticdata = self._staticdata + minetest.log("action", "cart #"..staticdata.uuid.." was killed") + detach_driver(self) -- Detach passenger @@ -792,6 +900,9 @@ function DEFAULT_CART_DEF:on_death(killer) mcl_log("TODO: handle detatched minecart death") end + -- Remove data + destroy_cart_data(staticdata.uuid) + -- Drop items local drop = self.drop 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 local le = cart:get_luaentity() 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 -- 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:command_block_minecart") end + +minetest.register_globalstep(function(dtime) + -- TODO: handle periodically updating out-of-range carts +end) + diff --git a/mods/ENTITIES/mcl_minecarts/rails.lua b/mods/ENTITIES/mcl_minecarts/rails.lua index 17dc19e67..d0a2eaa4a 100644 --- a/mods/ENTITIES/mcl_minecarts/rails.lua +++ b/mods/ENTITIES/mcl_minecarts/rails.lua @@ -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) -- 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.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 minetest.register_node(itemstring, ndef) if craft then minetest.register_craft(craft) 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 {} local base_def = table.copy(BASE_DEF) local sloped_def = table.copy(SLOPED_RAIL_DEF) @@ -235,9 +234,8 @@ local function register_straight_rail(base_name, tiles, def) }, })) 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 {} local base_def = table.copy(BASE_DEF) local sloped_def = table.copy(SLOPED_RAIL_DEF) @@ -333,9 +331,8 @@ local function register_curves_rail(base_name, tiles, def) }, })) 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) -- Build the node definition @@ -351,7 +348,6 @@ local function register_rail_sloped(itemstring, def) -- Make registrations minetest.register_node(itemstring, ndef) end -mod.register_rail_sloped = register_rail_sloped -- Redstone rules local rail_rules_long =