will_b_gaming Posted March 13, 2025 Share Posted March 13, 2025 I am trying to make a mod that displays the collection of everyone's inventories as a hud element for each player. This means when one player picks up/drops an item, everyone's huds must update to the new list. I have a function that accurately collects this data, but I am having trouble getting it to update for clients. I tried: for _, player in ipairs(GLOBAL.AllPlayers) do if player.headwidget then player.headwidget.text:SetString(resources) end However, "player.headwidget" (defined in AddPlayerPostInit) is nil for clients, even though they can see a string of test text above their head. I've attached the mod file. I would really appreciate any help on this. modmain.lua Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/ Share on other sites More sharing options...
Baguettes Posted March 13, 2025 Share Posted March 13, 2025 I suggest reading into how netvars work. Those help a lot with how you can convey info from server to client. 1 Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1806801 Share on other sites More sharing options...
will_b_gaming Posted March 14, 2025 Author Share Posted March 14, 2025 18 hours ago, Baguettes said: I suggest reading into how netvars work. Those help a lot with how you can convey info from server to client. I'm not sure if this post has enough information. The mod it is referencing no longer exists, and there's clearly more code that is necessary for me to see. There is also a major difference in that I am trying to send the same information to each client, which doesn't involve keeping track of each client's stats. Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1807047 Share on other sites More sharing options...
oregu Posted March 15, 2025 Share Posted March 15, 2025 (edited) On 3/13/2025 at 10:48 PM, will_b_gaming said: I'm not sure if this post has enough information. The mod it is referencing no longer exists, and there's clearly more code that is necessary for me to see. There is also a major difference in that I am trying to send the same information to each client, which doesn't involve keeping track of each client's stats. Unfortunately The Hunt Gamemode doesnt work anymore with current version so you'd need to downpatch to see it in action. I know that large scale mods like Island Adventures or Uncompromising mod probably use net_vars so you could reference those, but I'd recommend something smaller if you're starting out understanding them. You could safely reference this mod, because it uses net_vars and works with current version if you need to make new net_vars, but my example will use existing net events instead. https://steamcommunity.com/sharedfiles/filedetails/?id=786566397 I wrote a script that makes a network listen for player inventory updates and display it. Please let me know if you encounter issues or notice any mistakes I made because this is my first time doing something like this. I tried to only get the bare-minimum of information because too much information can cause the server to lag, as you can guess. It works for worlds with caves and no caves. I also notice the event count goes up a lot more serverside than clientside, not sure why that is, but you could probably optimize your way out of that if you let servers only account for dirty changes. Spoiler Inventory is a component that all players have by default that broadcast their inventory updates privately, but you can have serverside things listen to these events. Snippet from inventory_classified.lua: Spoiler local Inventory = Class(function(self, inst) self.inst = inst self.opentask = nil if TheWorld.ismastersim then if inst:HasTag("player") then self.classified = SpawnPrefab("inventory_classified") self.classified.entity:SetParent(inst.entity) self.opentask = inst:DoStaticTaskInTime(0, OpenInventory, self) --Server intercepts messages and forwards to clients via classified net vars inst:ListenForEvent("newactiveitem", function(inst, data) self.classified:SetActiveItem(data.item) end) inst:ListenForEvent("itemget", function(inst, data) self.classified:SetSlotItem(data.slot, data.item, data.src_pos) end) inst:ListenForEvent("itemlose", function(inst, data) self.classified:SetSlotItem(data.slot) end) inst:ListenForEvent("equip", function(inst, data) self.classified:SetSlotEquip(data.eslot, data.item) end) inst:ListenForEvent("unequip", function(inst, data) self.classified:SetSlotEquip(data.eslot) end) end elseif self.classified == nil and inst.inventory_classified ~= nil then self.classified = inst.inventory_classified inst.inventory_classified.OnRemoveEntity = nil inst.inventory_classified = nil self:AttachClassified(self.classified) end end) modmain.lua Spoiler -- *** env _G *** -- -- const _G = GLOBAL unpack = _G.unpack TheNet = _G.TheNet modstamp = "[".._G.KnownModIndex:GetModInfo(modname).name.."] " --fn runscript = function(name) modimport("scripts/"..name) end print_old = print print = function(a, ...) print_old(modstamp..tostring(a), ...) end _G.d_getitems = function(inst) local inst = inst or _G.ThePlayer or _G.ConsoleCommandPlayer() local inventory = inst.components.inventory if not inventory then return end local result = "Nothing" local count = 0 for i = 1, inventory.maxslots do local item = inventory.itemslots[i] if item then _G.dumptable(item) local stackable = item.components.stackable local finiteuses = item.components.finiteuses count = count + 1 local name = "["..i.."] "..item:GetDisplayName() if count == 1 then result = name else result = result..", "..name end if stackable then result = result.." ("..stackable.stacksize.."/"..stackable.maxsize..")" end if finiteuses then result = result.." ("..string.format("%d",(finiteuses.current/finiteuses.total)*100).."%)" end end end return inst:GetDisplayName().." has: "..result end local function NetHandler(inst) if TheNet:GetServerGameMode() ~= "survival" then print("Unexpected results will come from enabling this with another gamemode, be warned.") end inst:AddComponent"globalinventory" end AddPrefabPostInit("forest_network", NetHandler) AddPrefabPostInit("cave_network", NetHandler) scripts/components/globalinventory.lua Spoiler local Text = require "widgets/text" local _GlobalInventory local function GetPlayerIndex(inst) for i,player in ipairs(AllPlayers) do if player == inst then return i end end return 0 end local function AddString(stack, str) -- something something garbage collection. a stack is {""} or larger table.insert(stack, str) -- push into the the stack for i = #stack - 1, 1, -1 do if string.len(stack[i]) > string.len(stack[i+1]) then break end stack[i] = stack[i]..table.remove(stack) end end -- table.concat(stack) local function OnPrefabDirty(inst) local self = inst.components.globalinventory self.last_prefab = self.net_last_prefab:value() end local function OnInventoryDirty(inst) local self = inst.components.globalinventory self.count = self.count + 1 self.last_inventory = self.net_last_inventory:value() self.inventory_widget:SetString(self:ConstructData()) end local function _OnServer(self) self._onitemupdate = function(inst, data) --c_announce(tostring(self.count+1)..", "..tostring(inst)..", "..tostring(data.item)) self.count = self.count + 1 local item = data.item local prefab = item and item.prefab or "nil" self:SetLastPrefab(prefab) local stackable = item and item.components.stackable local quantity = stackable and stackable.stacksize or 0 local finiteuses = item and item.components.finiteuses local percent = finiteuses and math.ceil((finiteuses.current/finiteuses.total)*100) or 0 self:SetLastInventory({ GetPlayerIndex(inst), data.slot or 0, data.eslot or 0, quantity, percent, }) end local protocols = { "newactiveitem", -- data.slot "itemget", -- data.slot, data.item, data.src_pos "itemlose", -- data.slot "equip", -- data.eslot, data.item "unequip", -- data.eslot } self.inst:ListenForEvent("ms_playerjoined", function(inst, player) for _,prot in pairs(protocols) do _GlobalInventory.inst:ListenForEvent(prot, self._onitemupdate, player) end end, TheWorld) self.inst:ListenForEvent("ms_playerleft", function(inst, player) for _,prot in pairs(protocols) do _GlobalInventory.inst:RemoveEventCallback(prot, self._onitemupdate, player) end end, TheWorld) end local GlobalInventory = Class(function(self, inst) _GlobalInventory = self self.inst = inst self.count = 0 -- just for visuals self.last_prefab = "" self.net_last_prefab = net_string(self.inst.GUID, "globinv_prefab", "globinv_prefabdirty") self.last_inventory = -- don't know if im overcomplicating it. bytes only go up to 256 { 0, -- playerindex 0, -- slot 0, -- eslot 0, -- quantity 0, -- percent } self.net_last_inventory = net_bytearray(self.inst.GUID, "globinv_inventory", "globinv_inventorydirty") if TheWorld.ismastersim then _OnServer(self) else self:Client() end self.inst:StartUpdatingComponent(self) end) function GlobalInventory:SetLastPrefab(prefab) self.last_prefab = prefab self.net_last_prefab:set(prefab) end function GlobalInventory:SetLastInventory(inventory) local net_inventory = {} for _,v in ipairs(inventory) do local num = tonumber(v) if type(num) == "number" then if num > 256 then num = 256 end else num = 0 end table.insert(net_inventory, num) end self.last_inventory = net_inventory self.net_last_inventory:set(net_inventory) end function GlobalInventory:Client() self.inst:ListenForEvent("globinv_prefabdirty", OnPrefabDirty) self.inst:ListenForEvent("globinv_inventorydirty", OnInventoryDirty) end function GlobalInventory:ConstructData() local str = {""} AddString(str, "("..self.count..")") AddString(str, self.last_prefab.." {") for _,data in pairs(self.last_inventory) do AddString(str, data..", ") end AddString(str, "}") return table.concat(str) end function GlobalInventory:ServerOnUpdate(dt) -- Server Only if #AllPlayers == 0 then return end if self.last_prefab and self.last_inventory and self.inventory_widget then self.inventory_widget:SetString(self:ConstructData()) end end function GlobalInventory:OnUpdate(dt) -- Client/Server if ThePlayer and not self.inventory_widget then -- Clientside UI self.inventory_widget = ThePlayer.HUD.overlayroot:AddChild(Text(UIFONT, 30)) self.inventory_widget:SetVAnchor(ANCHOR_TOP) self.inventory_widget:SetHAnchor(ANCHOR_MIDDLE) self.inventory_widget:SetPosition(0, -45) self.inventory_widget:SetString(self:ConstructData()) end if TheWorld.ismastersim then self:ServerOnUpdate(dt) end end return GlobalInventory Right, those are live changes but you need to get all inventory information and send it to the player each time a player joins in. Well, d_getitems is a good starting point for that. I have to go but maybe I can work on this more later. Edited March 15, 2025 by oregu Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1807339 Share on other sites More sharing options...
will_b_gaming Posted March 15, 2025 Author Share Posted March 15, 2025 12 hours ago, oregu said: Unfortunately The Hunt Gamemode doesnt work anymore with current version so you'd need to downpatch to see it in action. I know that large scale mods like Island Adventures or Uncompromising mod probably use net_vars so you could reference those, but I'd recommend something smaller if you're starting out understanding them. You could safely reference this mod, because it uses net_vars and works with current version if you need to make new net_vars, but my example will use existing net events instead. https://steamcommunity.com/sharedfiles/filedetails/?id=786566397 I wrote a script that makes a network listen for player inventory updates and display it. Please let me know if you encounter issues or notice any mistakes I made because this is my first time doing something like this. I tried to only get the bare-minimum of information because too much information can cause the server to lag, as you can guess. It works for worlds with caves and no caves. I also notice the event count goes up a lot more serverside than clientside, not sure why that is, but you could probably optimize your way out of that if you let servers only account for dirty changes. Hide contents Inventory is a component that all players have by default that broadcast their inventory updates privately, but you can have serverside things listen to these events. Snippet from inventory_classified.lua: Reveal hidden contents local Inventory = Class(function(self, inst) self.inst = inst self.opentask = nil if TheWorld.ismastersim then if inst:HasTag("player") then self.classified = SpawnPrefab("inventory_classified") self.classified.entity:SetParent(inst.entity) self.opentask = inst:DoStaticTaskInTime(0, OpenInventory, self) --Server intercepts messages and forwards to clients via classified net vars inst:ListenForEvent("newactiveitem", function(inst, data) self.classified:SetActiveItem(data.item) end) inst:ListenForEvent("itemget", function(inst, data) self.classified:SetSlotItem(data.slot, data.item, data.src_pos) end) inst:ListenForEvent("itemlose", function(inst, data) self.classified:SetSlotItem(data.slot) end) inst:ListenForEvent("equip", function(inst, data) self.classified:SetSlotEquip(data.eslot, data.item) end) inst:ListenForEvent("unequip", function(inst, data) self.classified:SetSlotEquip(data.eslot) end) end elseif self.classified == nil and inst.inventory_classified ~= nil then self.classified = inst.inventory_classified inst.inventory_classified.OnRemoveEntity = nil inst.inventory_classified = nil self:AttachClassified(self.classified) end end) modmain.lua Hide contents -- *** env _G *** -- -- const _G = GLOBAL unpack = _G.unpack TheNet = _G.TheNet modstamp = "[".._G.KnownModIndex:GetModInfo(modname).name.."] " --fn runscript = function(name) modimport("scripts/"..name) end print_old = print print = function(a, ...) print_old(modstamp..tostring(a), ...) end _G.d_getitems = function(inst) local inst = inst or _G.ThePlayer or _G.ConsoleCommandPlayer() local inventory = inst.components.inventory if not inventory then return end local result = "Nothing" local count = 0 for i = 1, inventory.maxslots do local item = inventory.itemslots[i] if item then _G.dumptable(item) local stackable = item.components.stackable local finiteuses = item.components.finiteuses count = count + 1 local name = "["..i.."] "..item:GetDisplayName() if count == 1 then result = name else result = result..", "..name end if stackable then result = result.." ("..stackable.stacksize.."/"..stackable.maxsize..")" end if finiteuses then result = result.." ("..string.format("%d",(finiteuses.current/finiteuses.total)*100).."%)" end end end return inst:GetDisplayName().." has: "..result end local function NetHandler(inst) if TheNet:GetServerGameMode() ~= "survival" then print("Unexpected results will come from enabling this with another gamemode, be warned.") end inst:AddComponent"globalinventory" end AddPrefabPostInit("forest_network", NetHandler) AddPrefabPostInit("cave_network", NetHandler) scripts/components/globalinventory.lua Hide contents local Text = require "widgets/text" local _GlobalInventory local function GetPlayerIndex(inst) for i,player in ipairs(AllPlayers) do if player == inst then return i end end return 0 end local function AddString(stack, str) -- something something garbage collection. a stack is {""} or larger table.insert(stack, str) -- push into the the stack for i = #stack - 1, 1, -1 do if string.len(stack[i]) > string.len(stack[i+1]) then break end stack[i] = stack[i]..table.remove(stack) end end -- table.concat(stack) local function OnPrefabDirty(inst) local self = inst.components.globalinventory self.last_prefab = self.net_last_prefab:value() end local function OnInventoryDirty(inst) local self = inst.components.globalinventory self.count = self.count + 1 self.last_inventory = self.net_last_inventory:value() self.inventory_widget:SetString(self:ConstructData()) end local function _OnServer(self) self._onitemupdate = function(inst, data) --c_announce(tostring(self.count+1)..", "..tostring(inst)..", "..tostring(data.item)) self.count = self.count + 1 local item = data.item local prefab = item and item.prefab or "nil" self:SetLastPrefab(prefab) local stackable = item and item.components.stackable local quantity = stackable and stackable.stacksize or 0 local finiteuses = item and item.components.finiteuses local percent = finiteuses and math.ceil((finiteuses.current/finiteuses.total)*100) or 0 self:SetLastInventory({ GetPlayerIndex(inst), data.slot or 0, data.eslot or 0, quantity, percent, }) end local protocols = { "newactiveitem", -- data.slot "itemget", -- data.slot, data.item, data.src_pos "itemlose", -- data.slot "equip", -- data.eslot, data.item "unequip", -- data.eslot } self.inst:ListenForEvent("ms_playerjoined", function(inst, player) for _,prot in pairs(protocols) do _GlobalInventory.inst:ListenForEvent(prot, self._onitemupdate, player) end end, TheWorld) self.inst:ListenForEvent("ms_playerleft", function(inst, player) for _,prot in pairs(protocols) do _GlobalInventory.inst:RemoveEventCallback(prot, self._onitemupdate, player) end end, TheWorld) end local GlobalInventory = Class(function(self, inst) _GlobalInventory = self self.inst = inst self.count = 0 -- just for visuals self.last_prefab = "" self.net_last_prefab = net_string(self.inst.GUID, "globinv_prefab", "globinv_prefabdirty") self.last_inventory = -- don't know if im overcomplicating it. bytes only go up to 256 { 0, -- playerindex 0, -- slot 0, -- eslot 0, -- quantity 0, -- percent } self.net_last_inventory = net_bytearray(self.inst.GUID, "globinv_inventory", "globinv_inventorydirty") if TheWorld.ismastersim then _OnServer(self) else self:Client() end self.inst:StartUpdatingComponent(self) end) function GlobalInventory:SetLastPrefab(prefab) self.last_prefab = prefab self.net_last_prefab:set(prefab) end function GlobalInventory:SetLastInventory(inventory) local net_inventory = {} for _,v in ipairs(inventory) do local num = tonumber(v) if type(num) == "number" then if num > 256 then num = 256 end else num = 0 end table.insert(net_inventory, num) end self.last_inventory = net_inventory self.net_last_inventory:set(net_inventory) end function GlobalInventory:Client() self.inst:ListenForEvent("globinv_prefabdirty", OnPrefabDirty) self.inst:ListenForEvent("globinv_inventorydirty", OnInventoryDirty) end function GlobalInventory:ConstructData() local str = {""} AddString(str, "("..self.count..")") AddString(str, self.last_prefab.." {") for _,data in pairs(self.last_inventory) do AddString(str, data..", ") end AddString(str, "}") return table.concat(str) end function GlobalInventory:ServerOnUpdate(dt) -- Server Only if #AllPlayers == 0 then return end if self.last_prefab and self.last_inventory and self.inventory_widget then self.inventory_widget:SetString(self:ConstructData()) end end function GlobalInventory:OnUpdate(dt) -- Client/Server if ThePlayer and not self.inventory_widget then -- Clientside UI self.inventory_widget = ThePlayer.HUD.overlayroot:AddChild(Text(UIFONT, 30)) self.inventory_widget:SetVAnchor(ANCHOR_TOP) self.inventory_widget:SetHAnchor(ANCHOR_MIDDLE) self.inventory_widget:SetPosition(0, -45) self.inventory_widget:SetString(self:ConstructData()) end if TheWorld.ismastersim then self:ServerOnUpdate(dt) end end return GlobalInventory Right, those are live changes but you need to get all inventory information and send it to the player each time a player joins in. Well, d_getitems is a good starting point for that. I have to go but maybe I can work on this more later. It looks like d_getitems doesn't work for clients. It prints a blank line when running on ThePlayer or AllPlayers[1]/AllPlayers[2] etc. The text at the top of the screen also doesn't update for the client whenever the host picks something up. Also, why is print getting reassigned? Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1807462 Share on other sites More sharing options...
oregu Posted March 15, 2025 Share Posted March 15, 2025 6 minutes ago, will_b_gaming said: It looks like d_getitems doesn't work for clients. It prints a blank line when running on ThePlayer or AllPlayers[1]/AllPlayers[2] etc. The text at the top of the screen also doesn't update for the client whenever the host picks something up. Also, why is print getting reassigned? You would have to send this locally: GLOBAL.TheNet:SendRemoteExecute("c_announce(d_getitems())")) or create a ClientRPC that does print(d_getitems()) if you want to print it to console. I made print different in the mod environment and stamped it with the mod name to make debugging easier. You don't have to include that change. While messing around I did notice issues when it comes to quickdropping and auto-equipped items. It only worked well when you use the mouse to move items. I have to figure out a fix for that. Spoiler function GlobalInventory:Client() self.syncprefabinventory = function(inst) self.count = self.count + 1 self.inventory_widget:SetString(self:ConstructData()) self.inst:RemoveEventCallback("globinv_inventorydirty", self.syncprefabinventory) self.inst:RemoveEventCallback("globinv_prefabdirty", self.syncprefabinventory) end self.inst:ListenForEvent("globinv_prefabdirty", OnPrefabDirty) self.inst:ListenForEvent("globinv_inventorydirty", OnInventoryDirty) end local function OnPrefabDirty(inst) local self = inst.components.globalinventory self.last_prefab = self.net_last_prefab:value() self.inst:ListenForEvent("globinv_inventorydirty", self.syncprefabinventory) end local function OnInventoryDirty(inst) local self = inst.components.globalinventory self.last_inventory = self.net_last_inventory:value() self.inst:ListenForEvent("globinv_prefabdirty", self.syncprefabinventory) end I don't know if this is the correct way to sync events, but I tried it. Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1807466 Share on other sites More sharing options...
will_b_gaming Posted March 15, 2025 Author Share Posted March 15, 2025 I'm having a lot of trouble understanding what's going on in globalinventory.lua. Could you make a simpler version that just returns something like the host's prefab name, and preferably put comments on important bits? I want to understand how your version works so I can get better at modding. Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1807469 Share on other sites More sharing options...
oregu Posted March 15, 2025 Share Posted March 15, 2025 14 minutes ago, will_b_gaming said: I'm having a lot of trouble understanding what's going on in globalinventory.lua. Could you make a simpler version that just returns something like the host's prefab name, and preferably put comments on important bits? I want to understand how your version works so I can get better at modding. I could try, yeah. It will be a bit, though. Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1807471 Share on other sites More sharing options...
oregu Posted March 17, 2025 Share Posted March 17, 2025 (edited) modmain.lua Spoiler GLOBAL.STRINGS.NETCOMPONENT_WARN = "Unexpected results will come from enabling {component} with a different gamemode than {gamemode}. Be warned" local function AddNetComponent(component, gamemode) local function NetComponentHandler(inst) if TheNet:GetServerGameMode() ~= gamemode then -- Create a warning if the gamemode doesn't match the one it was designed for print(GLOBAL.subfmt(GLOBAL.STRINGS.NETCOMPONENT_WARN, {component = component, gamemode = gamemode})) end inst:AddComponent(component) end AddPrefabPostInit("forest_network", NetComponentHandler) -- Must be added to both cave and forest network, or whatever network prefabs your gamemode uses. AddPrefabPostInit("cave_network", NetComponentHandler) end AddNetComponent("displayhostname", "survival") --AddNetComponent("globalinventory", "survival") scripts/components/displayhostname.lua Spoiler local Text = require "widgets/text" -- Our widget used for our UI local _DisplayHostName -- pre-defining the component name, it will be defined later. fns with _ before it are server fns, but this is the whole component STRINGS.DISPLAYHOSTNAME = -- We need STRINGS for our UI { NONE = "Nobody", HOSTNAME = "Host: {name}", -- we will use subfmt for the things in brackets {} } local function OnHostNameDirty(inst) -- Client: When the server signals us that the host name has updated, we will do the following _DisplayHostName.host_name = _DisplayHostName.net_host_name:value() -- Update our clientside host_name to the Server's net_string _DisplayHostName:UpdateUI() -- Updating UI. This will not be run because the server knows the host before we join in. end local function OnPlayerActivated(world, inst) -- If you have a Clientside UI, you could probably initialize it here print("PLAYERACTIVATED", world, inst) -- This will be printed in your client_log.txt. Notice that this prints twice if you're on the character select screen, without an inst or world if inst:IsValid() then print("I am valid!") _DisplayHostName:UpdateUI() -- Updating UI for the first time end end local function _SetHostName(self, val) -- Server self.host_name = val -- We must set the server's local hostname as well so the server's widget can update self.net_host_name:set(val) -- We will now officially set the Host name and trigger a chain of changes on the network end local function _OnPlayerJoined(world, inst) c_announce(tostring(world)..", "..tostring(inst)) -- Server if TheNet:GetIsHosting(inst.Network) then -- Players will have a Network (same as their own TheNet) for the Server that's available when they join _SetHostName(_DisplayHostName, tostring(inst:GetDisplayName()).." ("..tostring(inst.prefab)..")") -- Using tostring because it's a good idea to ensure everything you send to the net is accurate because the server will stop if it recieves a bad input end end local function _OnServer(self) -- Made it a local function so it's private. I don't think this is neccessary, but this is more of an indication that this is classified info. Everything in this fn is defined later -- ms_ usually indicates server-side events self.inst:ListenForEvent("ms_playerjoined", _OnPlayerJoined, TheWorld) -- Whether you put TheWorld/inst in the third arg determines where this component will listen, whether Client or on the Server -- You may need to use RemoveEventCallback, passing in the same arguments if you need to remove the listener. end local DisplayHostName = Class(function(self, inst) _DisplayHostName = self -- our net component is now available anywhere in the file self.inst = inst -- setting up local and net variables that both the server and client uses self.host_name = "" -- Initializing strings as empty to ensure we initialized data correctly -- First arg is the GUID this net_var resides on, basically an address for the variables -- The second arg ("displayhost_hostname") is the Server Event that gets pushed when the variable changes -- The third arg ("displayhost_hostnamedirty") is the Client Event that gets pushed when the variable changes. This is commonly referred to as dirty because it means we are unsynced with the server and have to listen to this event to have our data "clean" again self.net_host_name = net_string(self.inst.GUID, "displayhost_hostname", "displayhost_hostnamedirty") self.inst:ListenForEvent("playeractivated", OnPlayerActivated, TheWorld) -- Server listens for when player activates into a world, and makes the client do something -- Both the player (Client) and Server will read this component, we want it to do different things based on whether we're the Client or Server if TheWorld.ismastersim then -- if we are the Server, use the server fn _SetHostName(self, STRINGS.DISPLAYHOSTNAME.NONE) -- Set our host name to our default value _OnServer(self) else -- If we are the Client, use the client fn self:Client() end self.inst:StartUpdatingComponent(self) -- The net component will now do an OnUpdate that both client and server reads end) function DisplayHostName:Client() -- What the component does when we are a client self.inst:ListenForEvent("displayhost_hostnamedirty", OnHostNameDirty) -- The client will listen for the event that happens when the net_var changes end function DisplayHostName:ConstructHostNameData() -- Server/Client return subfmt(STRINGS.DISPLAYHOSTNAME.HOSTNAME, -- return a formatted string with our data { name = self.host_name, }) end function DisplayHostName:UpdateUI() -- Server/Client if not self.host_widget then return end self.host_widget:SetString(self:ConstructHostNameData()) end function DisplayHostName:ServerOnUpdate(dt) -- Server Only if #AllPlayers == 0 then return end -- Don't do anything from this point on if there's no players. There's no point. if self.host_name and self.host_widget then -- If we have data and we have a widget, then update it. We will only have this happen if we are both ThePlayer and Server, which can happen when you are on single shards. You will have more accurate data in this situation. self:UpdateUI() -- Created a general fn for updating UI because it's so small end end function DisplayHostName:OnUpdate(dt) -- Client/Server if ThePlayer and not self.host_widget then -- Clientside UI. Right when ThePlayer exists, create the UI. ThePlayer will not exist yet if you're loading in or on the character select self.host_widget = ThePlayer.HUD.overlayroot:AddChild(Text(UIFONT, 40)) -- Initializing a new text widget self.host_widget:SetVAnchor(ANCHOR_TOP) self.host_widget:SetHAnchor(ANCHOR_MIDDLE) self.host_widget:SetPosition(0, -100) end if TheWorld.ismastersim then -- If we are the server, use this fn self:ServerOnUpdate(dt) end end return DisplayHostName -- net component fn Notice how when you're in single shard worlds, there's no host. That's because the Server is the host instead, therefore nobody is hosting. (If you do TheNet:GetIsHosting() you get false.). If you want to interpret as someone's network being the server as hosting then you can do if TheNet:GetIsHosting(inst.Network) or TheNet:GetIsServer(inst.Network) Edited March 17, 2025 by oregu Link to comment https://forums.kleientertainment.com/forums/topic/164837-how-do-i-send-data-from-server-to-client/#findComment-1807789 Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now