noita_entangled_worlds/quant.ew/files/system/item_sync.lua

299 lines
9.5 KiB
Lua
Raw Normal View History

2024-07-14 12:19:26 +03:00
-- Synchronizes item pickup and item drop
2024-08-07 17:11:55 +03:00
local inventory_helper = dofile_once("mods/quant.ew/files/core/inventory_helper.lua")
local ctx = dofile_once("mods/quant.ew/files/core/ctx.lua")
local net = dofile_once("mods/quant.ew/files/core/net.lua")
2024-05-13 13:07:28 +03:00
2024-06-20 15:53:01 +03:00
dofile_once("data/scripts/lib/coroutines.lua")
local rpc = net.new_rpc_namespace()
2024-05-13 13:07:28 +03:00
local item_sync = {}
local pending_remove = {}
local pickup_handlers = {}
2024-05-13 16:19:28 +03:00
function item_sync.ensure_notify_component(ent)
local notify = EntityGetFirstComponentIncludingDisabled(ent, "LuaComponent", "ew_notify_component")
2024-05-13 13:07:28 +03:00
if notify == nil then
EntityAddComponent2(ent, "LuaComponent", {
_tags = "enabled_in_world,enabled_in_hand,enabled_in_inventory,ew_notify_component",
2024-08-07 17:21:25 +03:00
script_throw_item = "mods/quant.ew/files/resource/cbs/item_notify.lua",
script_item_picked_up = "mods/quant.ew/files/resource/cbs/item_notify.lua",
-- script_kick = "mods/quant.ew/files/resource/cbs/item_notify.lua",
2024-05-13 13:07:28 +03:00
})
end
2024-05-13 16:19:28 +03:00
end
local function mark_in_inventory(my_player)
local items = inventory_helper.get_all_inventory_items(my_player)
for _, ent in pairs(items) do
item_sync.ensure_notify_component(ent)
2024-05-13 13:07:28 +03:00
end
end
2024-05-13 15:18:52 +03:00
local function allocate_global_id()
local current = tonumber(GlobalsGetValue("ew_global_item_id", "1"))
GlobalsSetValue("ew_global_item_id", tostring(current+1))
2024-07-13 15:59:28 +03:00
return ctx.my_player.peer_id..":"..current
2024-05-13 15:18:52 +03:00
end
-- Try to guess if the item is in world.
2024-05-13 13:07:28 +03:00
local function is_item_on_ground(item)
return EntityGetComponent(item, "SimplePhysicsComponent") ~= nil or EntityGetComponent(item, "PhysicsBodyComponent") ~= nil or EntityGetComponent(item, "SpriteParticleEmitterComponent") ~= nil
2024-05-13 13:07:28 +03:00
end
2024-05-13 15:18:52 +03:00
function item_sync.get_global_item_id(item)
local gid = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
if gid == nil then
GamePrint("Item has no gid")
2024-07-13 15:59:28 +03:00
return "unknown"
2024-05-13 15:18:52 +03:00
end
2024-07-13 15:59:28 +03:00
local ret = ComponentGetValue2(gid, "value_string")
return ret or "unknown"
2024-05-13 15:18:52 +03:00
end
function item_sync.remove_item_with_id(gid)
table.insert(pending_remove, gid)
end
2024-07-18 15:03:07 +03:00
local find_by_gid_cache = {}
function item_sync.find_by_gid(gid)
2024-07-18 15:03:07 +03:00
if find_by_gid_cache[gid] ~= nil and EntityGetIsAlive(find_by_gid_cache[gid]) then
print("find_by_gid: found cached")
return find_by_gid_cache[gid]
end
print("find_by_gid: searching")
local global_items = EntityGetWithTag("ew_global_item")
for _, item in ipairs(global_items) do
local i_gid = item_sync.get_global_item_id(item)
2024-07-18 15:03:07 +03:00
find_by_gid_cache[i_gid] = item
if i_gid == gid then
return item
end
end
end
function item_sync.remove_item_with_id_now(gid)
2024-05-13 15:18:52 +03:00
local global_items = EntityGetWithTag("ew_global_item")
for _, item in ipairs(global_items) do
local i_gid = item_sync.get_global_item_id(item)
if i_gid == gid then
2024-05-13 15:18:52 +03:00
EntityKill(item)
end
end
end
2024-05-13 16:19:28 +03:00
function item_sync.host_localize_item(gid, peer_id)
2024-05-13 16:52:29 +03:00
if ctx.item_prevent_localize[gid] then
GamePrint("Item localize for "..gid.." prevented")
return
2024-05-13 16:52:29 +03:00
end
ctx.item_prevent_localize[gid] = true
if table.contains(pending_remove, gid) then
GamePrint("Item localize prevented, already taken")
return
end
local item_ent_id = item_sync.find_by_gid(gid)
if item_ent_id ~= nil then
for _, handler in ipairs(pickup_handlers) do
handler(item_ent_id)
end
end
2024-05-13 15:18:52 +03:00
if peer_id ~= ctx.my_id then
2024-05-13 16:19:28 +03:00
item_sync.remove_item_with_id(gid)
2024-05-13 15:18:52 +03:00
end
rpc.item_localize(peer_id, gid)
2024-05-13 15:18:52 +03:00
end
function item_sync.make_item_global(item, instant)
EntityAddTag(item, "ew_global_item")
2024-06-20 15:53:01 +03:00
async(function()
if not instant then
wait(1) -- Wait 1 frame so that game sets proper velocity.
end
if not EntityGetIsAlive(item) then
GamePrint("Thrown item vanished before we could send it")
return
end
item_sync.ensure_notify_component(item)
2024-08-20 09:42:47 -04:00
local gid_component = EntityGetFirstComponent(item, "VariableStorageComponent", "ew_global_item_id")
local gid
2024-07-13 15:59:28 +03:00
if gid_component == nil then
gid = allocate_global_id()
2024-06-20 15:53:01 +03:00
EntityAddComponent2(item, "VariableStorageComponent", {
_tags = "enabled_in_world,enabled_in_hand,enabled_in_inventory,ew_global_item_id",
2024-07-13 15:59:28 +03:00
value_string = gid,
2024-06-20 15:53:01 +03:00
})
2024-08-20 09:42:47 -04:00
else
gid = ComponentGetValue2(gid_component, "value_string")
2024-06-20 15:53:01 +03:00
end
--local vel = EntityGetFirstComponentIncludingDisabled(item, "VelocityComponent")
--if vel then
-- local vx, vy = ComponentGetValue2(vel, "mVelocity")
--end
2024-06-20 15:53:01 +03:00
local item_data = inventory_helper.serialize_single_item(item)
2024-07-13 15:59:28 +03:00
item_data.gid = gid
ctx.item_prevent_localize[gid] = false
rpc.item_globalize(item_data)
2024-06-20 15:53:01 +03:00
end)
2024-05-13 13:07:28 +03:00
end
local function get_global_ent(key)
local val = tonumber(GlobalsGetValue(key, "0"))
GlobalsSetValue(key, "0")
if val ~= 0 then
return val
end
end
2024-05-15 17:05:50 +03:00
local function remove_client_items_from_world()
if GameGetFrameNum() % 5 ~= 3 then
return
end
for _, item in ipairs(EntityGetWithTag("ew_client_item")) do
if is_item_on_ground(item) then
EntityKill(item)
end
end
end
2024-05-20 01:01:35 +03:00
function item_sync.on_world_update_host()
local my_player = ctx.my_player
2024-05-13 13:07:28 +03:00
if GameGetFrameNum() % 5 == 4 then
mark_in_inventory(my_player)
end
local thrown_item = get_global_ent("ew_thrown")
if thrown_item ~= nil then
item_sync.make_item_global(thrown_item)
2024-05-13 13:07:28 +03:00
end
2024-05-13 15:18:52 +03:00
local picked_item = get_global_ent("ew_picked")
if picked_item ~= nil and EntityHasTag(picked_item, "ew_global_item") then
local gid = item_sync.get_global_item_id(picked_item)
item_sync.host_localize_item(gid, ctx.my_id)
2024-05-13 16:19:28 +03:00
end
2024-05-15 17:05:50 +03:00
remove_client_items_from_world()
2024-05-13 16:19:28 +03:00
end
2024-05-20 01:01:35 +03:00
function item_sync.on_world_update_client()
local my_player = ctx.my_player
2024-05-13 16:52:29 +03:00
if GameGetFrameNum() % 5 == 4 then
mark_in_inventory(my_player)
end
local thrown_item = get_global_ent("ew_thrown")
2024-05-15 17:05:50 +03:00
if thrown_item ~= nil and not EntityHasTag(thrown_item, "ew_client_item") then
item_sync.make_item_global(thrown_item)
2024-05-13 16:52:29 +03:00
end
2024-05-13 16:19:28 +03:00
local picked_item = get_global_ent("ew_picked")
if picked_item ~= nil and EntityHasTag(picked_item, "ew_global_item") then
local gid = item_sync.get_global_item_id(picked_item)
rpc.item_localize_req(gid)
2024-05-13 15:18:52 +03:00
end
2024-05-15 17:05:50 +03:00
remove_client_items_from_world()
2024-05-13 13:07:28 +03:00
end
local function is_safe_to_remove()
return not ctx.is_wand_pickup
end
function item_sync.on_world_update()
-- TODO check that we not removing item we are going to pick now, instead of checking if picker gui is open.
if is_safe_to_remove() then
if #pending_remove > 0 then
local gid = table.remove(pending_remove)
item_sync.remove_item_with_id_now(gid)
end
end
end
2024-07-18 15:03:07 +03:00
function item_sync.on_should_send_updates()
if not ctx.is_host then
return
end
local global_items = EntityGetWithTag("ew_global_item")
local item_list = {}
for _, item in ipairs(global_items) do
if is_item_on_ground(item) then
local item_data = inventory_helper.serialize_single_item(item)
local gid = item_sync.get_global_item_id(item)
item_data.gid = gid
table.insert(item_list, item_data)
end
end
rpc.initial_items(item_list)
end
local function add_stuff_to_globalized_item(item, gid)
2024-07-18 21:32:52 +03:00
EntityAddTag(item, "ew_global_item")
item_sync.ensure_notify_component(item)
-- GamePrint("Got global item: "..item)
local gid_c = EntityGetFirstComponentIncludingDisabled(item, "VariableStorageComponent", "ew_global_item_id")
if gid_c == nil then
EntityAddComponent2(item, "VariableStorageComponent", {
_tags = "ew_global_item_id",
value_string = gid
})
else
ComponentSetValue2(gid_c, "value_string", gid)
end
ctx.item_prevent_localize[gid] = false
end
2024-07-18 15:03:07 +03:00
rpc.opts_reliable()
function rpc.initial_items(item_list)
2024-07-18 15:31:36 +03:00
-- Only run once ever, as it tends to duplicate items otherwise
if GameHasFlagRun("ew_initial_items") then
return
end
GameAddFlagRun("ew_initial_items")
2024-07-18 15:03:07 +03:00
for _, item_data in ipairs(item_list) do
local item = item_sync.find_by_gid(item_data.gid)
if item == nil then
local item_new = inventory_helper.deserialize_single_item(item_data)
add_stuff_to_globalized_item(item_new, item_data.gid)
2024-07-18 15:03:07 +03:00
end
end
end
rpc.opts_reliable()
function rpc.item_globalize(item_data)
if is_safe_to_remove() then
item_sync.remove_item_with_id_now(item_data.gid)
end
local item = inventory_helper.deserialize_single_item(item_data)
add_stuff_to_globalized_item(item, item_data.gid)
end
rpc.opts_reliable()
function rpc.item_localize(l_peer_id, item_id)
local item_ent_id = item_sync.find_by_gid(item_id)
if item_ent_id ~= nil then
for _, handler in ipairs(pickup_handlers) do
handler(item_ent_id)
end
end
if l_peer_id ~= ctx.my_id then
item_sync.remove_item_with_id(item_id)
end
end
rpc.opts_reliable()
function rpc.item_localize_req(gid)
if not ctx.is_host then
return
end
item_sync.host_localize_item(gid, ctx.rpc_peer_id)
end
2024-07-14 12:19:26 +03:00
ctx.cap.item_sync = {
globalize = item_sync.make_item_global,
register_pickup_handler = function(handler)
table.insert(pickup_handlers, handler)
end
2024-07-14 12:19:26 +03:00
}
return item_sync