Jump to content

Recommended Posts

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

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.

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 by oregu
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?

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.

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.

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.

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 by oregu

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...