From abccd212fd2ae9ef04d3e540553dc05966140d8c Mon Sep 17 00:00:00 2001 From: Kabby <1925023+tehkab@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:02:54 -0400 Subject: [PATCH 1/4] Move some player ghost code, add caching of ghost entity id and store ghost cosmetics in PlayerList entry --- .../core/files/scripts/util/player_ghosts.lua | 231 ++++++++++++++++++ noita_mod/core/files/scripts/utils.lua | 109 +-------- noita_mod/core/files/ws/events.lua | 7 +- 3 files changed, 240 insertions(+), 107 deletions(-) create mode 100644 noita_mod/core/files/scripts/util/player_ghosts.lua diff --git a/noita_mod/core/files/scripts/util/player_ghosts.lua b/noita_mod/core/files/scripts/util/player_ghosts.lua new file mode 100644 index 0000000..8271815 --- /dev/null +++ b/noita_mod/core/files/scripts/util/player_ghosts.lua @@ -0,0 +1,231 @@ +--move player ghost utility code into this file! +function SpawnPlayerGhosts(player_list) + for userId, player in pairs(player_list) do + local ghost = SpawnPlayerGhost(player, userId) + + --this only happens if SpawnPlayerGhost for some reason returns nil + --most likely reason for this is because noita emotes overwrote the function + --see the interoperability notice on SpawnPlayerGhost + if ghost == nil then + --this is in a weird spot on purpose, see interop notice on SpawnPlayerGhost + --invalidate stored ghost entity (it will be recached) + player.ghostEntityId = nil + --apply cosmetics, finding ghost in process + SetPlayerGhostCosmetics(userId, nil) + end + end +end + +--interoperability notice: noita emotes overwrites this function, so let infinitesunrise know if we change it +--might(?) still work when moved into this file and dofile() included from original utils.lua??? +function SpawnPlayerGhost(player, userId) + local ghost = EntityLoad("mods/noita-together/files/entities/ntplayer.xml", 0, 0) + AppendName(ghost, player.name) + local vars = EntityGetComponent(ghost, "VariableStorageComponent") + for _, var in pairs(vars) do + local name = ComponentGetValue2(var, "name") + if (name == "userId") then + ComponentSetValue2(var, "value_string", userId) + end + if (name == "inven") then + ComponentSetValue2(var, "value_string", json.encode(PlayerList[userId].inven)) + end + end + if (player.x ~= nil and player.y ~= nil) then + EntitySetTransform(ghost, player.x, player.y) + end + + --cache the ghost's entity id for this player + player.ghostEntityId = ghost + --apply cosmetics (if known) + SetPlayerGhostCosmetics(userId, ghost) + --return reference to created ghost + return ghost +end + +function DespawnPlayerGhosts() + local ghosts = EntityGetWithTag("nt_ghost") + for _, eid in pairs(ghosts) do + EntityKill(eid) + end + + --clear cached ghosts from all players + for userId, entry in pairs(PlayerList) do + --nt print_error("DespawnPlayerGhosts: invalidating cached ghost for userId " .. userId) + entry.ghostEntityId = nil + end +end + +function DespawnPlayerGhost(userId) + local ghosts = EntityGetWithTag("nt_ghost") + for _, ghost in pairs(ghosts) do + local vars = EntityGetComponent(ghost, "VariableStorageComponent") + for _, var in pairs(vars) do + local name = ComponentGetValue2(var, "name") + if (name == "userId") then + local id = ComponentGetValue2(var, "value_string") + if (id == userId) then EntityKill(ghost) end + end + end + end +end + +function TeleportPlayerGhost(data) + local ghosts = EntityGetWithTag("nt_ghost") + + for _, ghost in pairs(ghosts) do + local id_comp = get_variable_storage_component(ghost, "userId") + local userId = ComponentGetValue2(id_comp, "value_string") + if (userId == data.userId) then + EntitySetTransform(ghost, data.x, data.y) + break + end + end +end + +function MovePlayerGhost(data) + local ghosts = EntityGetWithTag("nt_ghost") + + for _, ghost in pairs(ghosts) do + local id_comp = get_variable_storage_component(ghost, "userId") + local userId = ComponentGetValue2(id_comp, "value_string") + if (userId == data.userId) then + local dest = get_variable_storage_component(ghost, "dest") + + ComponentSetValue2(dest, "value_string", data.jank) + break + end + end +end + +function UpdatePlayerGhost(data) + local ghosts = EntityGetWithTag("nt_ghost") + local stuff = {} + local inven = "," + for i, wand in ipairs(data.inven) do + inven = inven .. "," .. tostring(wand.stats.inven_slot) .. "," .. wand.stats.sprite .. "," + end + for _, ghost in pairs(ghosts) do + local id_comp = get_variable_storage_component(ghost, "userId") + local userId = ComponentGetValue2(id_comp, "value_string") + if (userId == data.userId) then + local dest = get_variable_storage_component(ghost, "inven") + ComponentSetValue2(dest, "value_string", inven) + break + end + end +end + +--PLAYER GHOST COSMETICS +function GetPlayerCosmeticFlags() + local data = {} + if HasFlagPersistent( "secret_amulet" ) then + table.insert(data, "player_amulet") + end + if HasFlagPersistent( "secret_amulet_gem" ) then + table.insert(data, "player_amulet_gem") + end + if HasFlagPersistent( "secret_hat" ) then + table.insert(data, "player_hat2") + end + return data +end + +function StorePlayerGhostCosmetic(data, refresh) + if PlayerList[data.userId] ~= nil then + local cosmeticFlags = {} + + --nt print_error("StorePlayerGhostCosmetic: store " .. ((data and #(data.flags)) or "(nil?)") .. " cosmetic flags for userId " .. data.userId) + if data.flags and #(data.flags) > 0 then + for _, flag in pairs(data.flags) do + cosmeticFlags[#cosmeticFlags+1] = flag + --nt print_error(" + " .. flag) + end + end + + PlayerList[data.userId].cosmeticFlags = cosmeticFlags + + if refresh then + SetPlayerGhostCosmetics(data.userId) + end + else + --nt print_error("StorePlayerGhostCosmetic: invalid userId " .. data.userId) + end +end + +--utility function to get the ghost entity for a particular player, tries cached value then checks all ghosts +function GetPlayerGhost(userId) + --store/fetch player ghost's entity from its PlayerList object + local eid = PlayerList[userId].ghostEntityId + if eid ~= 0 and EntityHasTag(eid, "nt_ghost") then + local id_comp = get_variable_storage_component(ghost, "userId") + local entityUserId = ComponentGetValue2(id_comp, "value_string") + + if entityUserId == userId then + --nt print_error("GetPlayerGhost: use cached ghost " .. eid .. " for userId " .. userId) + return eid + end + end + + --ghostEntityId was not the ghost, need to check all ghosts + local ghosts = EntityGetWithTag("nt_ghost") + + for _, ghost in pairs(ghosts) do + local id_comp = get_variable_storage_component(ghost, "userId") + local entityUserId = ComponentGetValue2(id_comp, "value_string") + + if entityUserId == userId then + --cache this value for later calls + PlayerList[userId].ghostEntityId = ghost + --nt print_error("GetPlayerGhost: caching ghost " .. ghost .. " for userId " .. userId) + return ghost + end + end + + --failed to find + --nt print_error("GetPlayerGhost failed to find for userId " .. userId) + return nil +end + +--set cosmetics on a player's ghost +--userId is the player userid, non-nil +--ghost is the ghost entity, if nil try to find it +function SetPlayerGhostCosmetics(userId, ghost) + --nt print_error("SetPlayerGhostCosmetics: ghost " .. (ghost or "(nil)") .. ", userId " .. userId) + + --get player ghost entity + if not ghost then + ghost = GetPlayerGhost(userId) + + if not ghost then + --nt print_error("SetPlayerGhostCosmetics: failed to find player's ghost???") + --should we print a real error? + return + end + end + + --TODO do we need to be able to CLEAR these flags ever? + for _, flag in pairs(PlayerList[userId].cosmeticFlags) do + EntitySetComponentsWithTagEnabled(ghost, flag, true) + end +end + +--not used? +function SetAllPlayerGhostCosmetics() + --nt print_error("SetAllPlayerGhostCosmetics") + local ghosts = EntityGetWithTag("nt_ghost") + + for _, ghost in pairs(ghosts) do + local id_comp = get_variable_storage_component(ghost, "userId") + local userId = ComponentGetValue2(id_comp, "value_string") + local playerEntry = PlayerList[userId] + if playerEntry then + for __, flag in pairs(playerEntry.cosmeticFlags or {}) do + EntitySetComponentsWithTagEnabled( ghost, flag, true ) + end + --cache the ghost id for this player for later use + playerEntry.ghostEntityId = ghost + break + end + end +end \ No newline at end of file diff --git a/noita_mod/core/files/scripts/utils.lua b/noita_mod/core/files/scripts/utils.lua index bf0d36c..81539dc 100644 --- a/noita_mod/core/files/scripts/utils.lua +++ b/noita_mod/core/files/scripts/utils.lua @@ -5,6 +5,9 @@ if not async then dofile("mods/noita-together/files/scripts/coroutines.lua") end +--util includes +dofile("mods/noita-together/files/scripts/util/player_ghosts.lua") + function GetPlayer() local player = EntityGetWithTag("player_unit") or nil if player ~= nil then @@ -217,112 +220,6 @@ function PlayerOrbPickup(id, userId) ) end -function SpawnPlayerGhosts(player_list) - for userId, player in pairs(player_list) do - SpawnPlayerGhost(player, userId) - end -end - -function SpawnPlayerGhost(player, userId) - local ghost = EntityLoad("mods/noita-together/files/entities/ntplayer.xml", 0, 0) - AppendName(ghost, player.name) - local vars = EntityGetComponent(ghost, "VariableStorageComponent") - for _, var in pairs(vars) do - local name = ComponentGetValue2(var, "name") - if (name == "userId") then - ComponentSetValue2(var, "value_string", userId) - end - if (name == "inven") then - ComponentSetValue2(var, "value_string", json.encode(PlayerList[userId].inven)) - end - end - if (player.x ~= nil and player.y ~= nil) then - EntitySetTransform(ghost, player.x, player.y) - end -end - -function DespawnPlayerGhosts() - local ghosts = EntityGetWithTag("nt_ghost") - for _, eid in pairs(ghosts) do - EntityKill(eid) - end -end - -function DespawnPlayerGhost(userId) - local ghosts = EntityGetWithTag("nt_ghost") - for _, ghost in pairs(ghosts) do - local vars = EntityGetComponent(ghost, "VariableStorageComponent") - for _, var in pairs(vars) do - local name = ComponentGetValue2(var, "name") - if (name == "userId") then - local id = ComponentGetValue2(var, "value_string") - if (id == userId) then EntityKill(ghost) end - end - end - end -end - -function TeleportPlayerGhost(data) - local ghosts = EntityGetWithTag("nt_ghost") - - for _, ghost in pairs(ghosts) do - local id_comp = get_variable_storage_component(ghost, "userId") - local userId = ComponentGetValue2(id_comp, "value_string") - if (userId == data.userId) then - EntitySetTransform(ghost, data.x, data.y) - break - end - end -end - -function MovePlayerGhost(data) - local ghosts = EntityGetWithTag("nt_ghost") - - for _, ghost in pairs(ghosts) do - local id_comp = get_variable_storage_component(ghost, "userId") - local userId = ComponentGetValue2(id_comp, "value_string") - if (userId == data.userId) then - local dest = get_variable_storage_component(ghost, "dest") - - ComponentSetValue2(dest, "value_string", data.jank) - break - end - end -end - -function UpdatePlayerGhost(data) - local ghosts = EntityGetWithTag("nt_ghost") - local stuff = {} - local inven = "," - for i, wand in ipairs(data.inven) do - inven = inven .. "," .. tostring(wand.stats.inven_slot) .. "," .. wand.stats.sprite .. "," - end - for _, ghost in pairs(ghosts) do - local id_comp = get_variable_storage_component(ghost, "userId") - local userId = ComponentGetValue2(id_comp, "value_string") - if (userId == data.userId) then - local dest = get_variable_storage_component(ghost, "inven") - ComponentSetValue2(dest, "value_string", inven) - break - end - end -end - -function UpdatePlayerGhostCosmetic(data) - local ghosts = EntityGetWithTag("nt_ghost") - - for _, ghost in pairs(ghosts) do - local id_comp = get_variable_storage_component(ghost, "userId") - local userId = ComponentGetValue2(id_comp, "value_string") - if (userId == data.userId) then - for __, flag in pairs(data.flags) do - EntitySetComponentsWithTagEnabled( ghost, flag, true ) - end - break - end - end -end - function AppendName(entity, name) local text = { string = name, diff --git a/noita_mod/core/files/ws/events.lua b/noita_mod/core/files/ws/events.lua index 06495a7..a8cfa07 100644 --- a/noita_mod/core/files/ws/events.lua +++ b/noita_mod/core/files/ws/events.lua @@ -1,5 +1,6 @@ dofile( "data/scripts/perks/perk.lua" ) dofile( "mods/noita-together/files/scripts/hourglass_events.lua") +dofile( "mods/noita-together/files/scripts/util/player_ghost_cosmetics.lua") customEvents = { PlayerPOI = function(data) @@ -82,7 +83,7 @@ customEvents = { UpdatePlayerGhost(data) end, PlayerCosmeticFlags = function(data) - UpdatePlayerGhostCosmetic(data) + StorePlayerGhostCosmetic(data, true) end, SecretHourglass = HandleHourglassEvent } @@ -192,6 +193,10 @@ wsEvents = { location = "Mountain", sampo = false, inven = {}, + --cached entity ID for player ghost - check before any use! + ghostEntityId = 0, + --player cosmetic (crown,amulet,?) tracking + cosmeticFlags = {}, --reasonable start for health check values HealthCheck = { lastPosUpdate = GameGetFrameNum() } } From e1b80fef4c761683a5dcb906357e5859af74d141 Mon Sep 17 00:00:00 2001 From: Kabby <1925023+tehkab@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:04:03 -0400 Subject: [PATCH 2/4] add restoring player ghost wand inventory --- .../core/files/scripts/util/player_ghosts.lua | 102 ++++++++++-------- noita_mod/core/files/ws/events.lua | 5 +- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/noita_mod/core/files/scripts/util/player_ghosts.lua b/noita_mod/core/files/scripts/util/player_ghosts.lua index 8271815..cdabfaf 100644 --- a/noita_mod/core/files/scripts/util/player_ghosts.lua +++ b/noita_mod/core/files/scripts/util/player_ghosts.lua @@ -12,6 +12,8 @@ function SpawnPlayerGhosts(player_list) player.ghostEntityId = nil --apply cosmetics, finding ghost in process SetPlayerGhostCosmetics(userId, nil) + --refresh inventory + SetPlayerGhostInventory(userId, nil) end end end @@ -39,6 +41,8 @@ function SpawnPlayerGhost(player, userId) player.ghostEntityId = ghost --apply cosmetics (if known) SetPlayerGhostCosmetics(userId, ghost) + --refresh inventory + SetPlayerGhostInventory(userId, nil) --return reference to created ghost return ghost end @@ -98,22 +102,64 @@ function MovePlayerGhost(data) end end -function UpdatePlayerGhost(data) - local ghosts = EntityGetWithTag("nt_ghost") - local stuff = {} - local inven = "," - for i, wand in ipairs(data.inven) do - inven = inven .. "," .. tostring(wand.stats.inven_slot) .. "," .. wand.stats.sprite .. "," +--utility function to get the ghost entity for a particular player, tries cached value then checks all ghosts +function GetPlayerGhost(userId) + --store/fetch player ghost's entity from its PlayerList object + local eid = PlayerList[userId].ghostEntityId + if eid ~= 0 and EntityHasTag(eid, "nt_ghost") then + local id_comp = get_variable_storage_component(ghost, "userId") + local entityUserId = ComponentGetValue2(id_comp, "value_string") + + if entityUserId == userId then + --nt print_error("GetPlayerGhost: use cached ghost " .. eid .. " for userId " .. userId) + return eid + end end + + --ghostEntityId was not the ghost, need to check all ghosts + local ghosts = EntityGetWithTag("nt_ghost") + for _, ghost in pairs(ghosts) do local id_comp = get_variable_storage_component(ghost, "userId") - local userId = ComponentGetValue2(id_comp, "value_string") - if (userId == data.userId) then - local dest = get_variable_storage_component(ghost, "inven") - ComponentSetValue2(dest, "value_string", inven) - break + local entityUserId = ComponentGetValue2(id_comp, "value_string") + + if entityUserId == userId then + --cache this value for later calls + PlayerList[userId].ghostEntityId = ghost + --nt print_error("GetPlayerGhost: caching ghost " .. ghost .. " for userId " .. userId) + return ghost + end + end + + --failed to find + --nt print_error("GetPlayerGhost failed to find for userId " .. userId) + return nil +end + +--set inventory on a player's ghost +--userId is the player userid, non-nil +--ghost is the ghost entity, if nil try to find it +function SetPlayerGhostInventory(userId, ghost) + --nt print_error("SetPlayerGhostInventory: ghost " .. (ghost or "(nil)") .. ", userId " .. userId) + + --get player ghost entity + if not ghost then + ghost = GetPlayerGhost(userId) + + if not ghost then + --nt print_error("SetPlayerGhostInventory: failed to find player's ghost???") + --should we print a real error? + return end end + + local inven = "," + for i, wand in ipairs(PlayerList[userId].inven) do + inven = inven .. "," .. tostring(wand.stats.inven_slot) .. "," .. wand.stats.sprite .. "," + end + + local inventoryVSComp = get_variable_storage_component(ghost, "inven") + ComponentSetValue2(inventoryVSComp, "value_string", inven) end --PLAYER GHOST COSMETICS @@ -153,40 +199,6 @@ function StorePlayerGhostCosmetic(data, refresh) end end ---utility function to get the ghost entity for a particular player, tries cached value then checks all ghosts -function GetPlayerGhost(userId) - --store/fetch player ghost's entity from its PlayerList object - local eid = PlayerList[userId].ghostEntityId - if eid ~= 0 and EntityHasTag(eid, "nt_ghost") then - local id_comp = get_variable_storage_component(ghost, "userId") - local entityUserId = ComponentGetValue2(id_comp, "value_string") - - if entityUserId == userId then - --nt print_error("GetPlayerGhost: use cached ghost " .. eid .. " for userId " .. userId) - return eid - end - end - - --ghostEntityId was not the ghost, need to check all ghosts - local ghosts = EntityGetWithTag("nt_ghost") - - for _, ghost in pairs(ghosts) do - local id_comp = get_variable_storage_component(ghost, "userId") - local entityUserId = ComponentGetValue2(id_comp, "value_string") - - if entityUserId == userId then - --cache this value for later calls - PlayerList[userId].ghostEntityId = ghost - --nt print_error("GetPlayerGhost: caching ghost " .. ghost .. " for userId " .. userId) - return ghost - end - end - - --failed to find - --nt print_error("GetPlayerGhost failed to find for userId " .. userId) - return nil -end - --set cosmetics on a player's ghost --userId is the player userid, non-nil --ghost is the ghost entity, if nil try to find it diff --git a/noita_mod/core/files/ws/events.lua b/noita_mod/core/files/ws/events.lua index a8cfa07..4d4e824 100644 --- a/noita_mod/core/files/ws/events.lua +++ b/noita_mod/core/files/ws/events.lua @@ -1,6 +1,6 @@ dofile( "data/scripts/perks/perk.lua" ) dofile( "mods/noita-together/files/scripts/hourglass_events.lua") -dofile( "mods/noita-together/files/scripts/util/player_ghost_cosmetics.lua") +dofile( "mods/noita-together/files/scripts/util/player_ghosts.lua") customEvents = { PlayerPOI = function(data) @@ -80,7 +80,8 @@ customEvents = { local inven = jankson.decode(data.inven) PlayerList[data.userId].inven = inven data.inven = inven - UpdatePlayerGhost(data) + SetPlayerGhostInventory(data.userId) + --StorePlayerGhostInventory(data) end, PlayerCosmeticFlags = function(data) StorePlayerGhostCosmetic(data, true) From 9c421faa4749eb0a7d2fdc196a3d8aa3f80c1765 Mon Sep 17 00:00:00 2001 From: Kabby <1925023+tehkab@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:19:16 -0400 Subject: [PATCH 3/4] remove unused SetAllPlayerGhostCosmetics function --- .../core/files/scripts/util/player_ghosts.lua | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/noita_mod/core/files/scripts/util/player_ghosts.lua b/noita_mod/core/files/scripts/util/player_ghosts.lua index cdabfaf..860ebb5 100644 --- a/noita_mod/core/files/scripts/util/player_ghosts.lua +++ b/noita_mod/core/files/scripts/util/player_ghosts.lua @@ -220,24 +220,4 @@ function SetPlayerGhostCosmetics(userId, ghost) for _, flag in pairs(PlayerList[userId].cosmeticFlags) do EntitySetComponentsWithTagEnabled(ghost, flag, true) end -end - ---not used? -function SetAllPlayerGhostCosmetics() - --nt print_error("SetAllPlayerGhostCosmetics") - local ghosts = EntityGetWithTag("nt_ghost") - - for _, ghost in pairs(ghosts) do - local id_comp = get_variable_storage_component(ghost, "userId") - local userId = ComponentGetValue2(id_comp, "value_string") - local playerEntry = PlayerList[userId] - if playerEntry then - for __, flag in pairs(playerEntry.cosmeticFlags or {}) do - EntitySetComponentsWithTagEnabled( ghost, flag, true ) - end - --cache the ghost id for this player for later use - playerEntry.ghostEntityId = ghost - break - end - end end \ No newline at end of file From 652093d7a295ada86df5e65db4bacde40489cf71 Mon Sep 17 00:00:00 2001 From: Kabby <1925023+tehkab@users.noreply.github.com> Date: Sat, 23 Sep 2023 23:15:26 -0400 Subject: [PATCH 4/4] fix retrieving cached ghost entity not working --- noita_mod/core/files/scripts/util/player_ghosts.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/noita_mod/core/files/scripts/util/player_ghosts.lua b/noita_mod/core/files/scripts/util/player_ghosts.lua index 860ebb5..b6bc555 100644 --- a/noita_mod/core/files/scripts/util/player_ghosts.lua +++ b/noita_mod/core/files/scripts/util/player_ghosts.lua @@ -42,7 +42,7 @@ function SpawnPlayerGhost(player, userId) --apply cosmetics (if known) SetPlayerGhostCosmetics(userId, ghost) --refresh inventory - SetPlayerGhostInventory(userId, nil) + SetPlayerGhostInventory(userId, ghost) --return reference to created ghost return ghost end @@ -105,9 +105,9 @@ end --utility function to get the ghost entity for a particular player, tries cached value then checks all ghosts function GetPlayerGhost(userId) --store/fetch player ghost's entity from its PlayerList object - local eid = PlayerList[userId].ghostEntityId + local eid = PlayerList[userId].ghostEntityId or 0 if eid ~= 0 and EntityHasTag(eid, "nt_ghost") then - local id_comp = get_variable_storage_component(ghost, "userId") + local id_comp = get_variable_storage_component(eid, "userId") local entityUserId = ComponentGetValue2(id_comp, "value_string") if entityUserId == userId then