Requesting thoughts and help on methods of handling player "states" or "tags"

Hey everyone, I have a question for anyone who knows a thing or two about scripting. I need a really fast, quick, neat, and efficient way of handling player states or tags.

To be more specific, I am talking about states or tags that get added to a player if they are doing something or being affected by something.

For example, if a player is blocking, they could get tagged with “Blocking” so that you can check if the player is blocking or not from another script.

I handled this specifically with tables and modules. This worked, for the most part, until I found out it wasn’t possible to get these tags within the client even after using invoke server methods.

I personally had the idea of creating a Configuration for each player when they join which could store attributes that I could handle with a ModuleScript, but I am unsure if this method is fast, quick, neat, and efficient, as knowing Roblox, almost anything is a memory leak.

If anyone has any ideas of how to handle a state or tag system, or if my Configuration idea will work perfectly fine, please let me know.

----------------------------------------------------

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TeleportService = game:GetService("TeleportService")
local Debris = game:GetService("Debris")

----------------------------------------------------

local Modules = ReplicatedStorage.Modules
local Assets = ReplicatedStorage.Assets

----------------------------------------------------

local Services = require(Modules.Services)

----------------------------------------------------

local DataService = Services:GetService("DataService")
local Network = Services:GetService("Network")

----------------------------------------------------

local Shutdown = require(script.Shutdown)

----------------------------------------------------

local PlayersLoaded = {}
local DataLoaded = {}
local PlayerLoader = {}

----------------------------------------------------

PlayerLoader.CanLoad = function(Player)
	if PlayersLoaded[Player] then
		return
	end
	
	return true
end

PlayerLoader.GetPlayerData = function(Player)
	if DataLoaded[Player] then
		return
	end
	
	return DataService:GetPlayerData(Player)
end

PlayerLoader.CharacterLoaded = function(Player)
	local PlayerData = DataService:GetPlayerData(Player)
	
	local Character = Player.Character or Player.CharacterAdded:Wait()
	local Head = Character.Head
end

PlayerLoader.Load = function(Player)
	if not PlayerLoader.CanLoad(Player) then
		return
	end
	
	local PlayerData = DataService:GetPlayerData(Player)
	
	PlayerLoader.CharacterLoaded(Player)
	
	Player.CharacterAdded:Connect(function()
		PlayerLoader.CharacterLoaded(Player)
	end)
	
	local Cache_Folder = Instance.new("Folder")
	Cache_Folder.Parent = workspace.Cache
	Cache_Folder.Name = Player.Name
	
	local States_Folder = Instance.new("Folder")
	States_Folder.Parent = Assets.States
	States_Folder.Name = Player.Name
end

PlayerLoader.Disconnect = function(Player)
	PlayersLoaded[Player] = nil
	DataLoaded[Player] = nil
	
	local Cache = workspace.Cache:FindFirstChild(Player.Name)
	local States = Assets.States:FindFirstChild(Player.Name)
	
	if not Cache then
		return
	end
	
	if not States then
		return
	end
	
	Debris:AddItem(Cache, 0)
	Debris:AddItem(States, 0)
end

Players.PlayerRemoving:Connect(PlayerLoader.Disconnect)

return PlayerLoader

I don’t get what you mean by not being able to get these tags from the client, because it is possible.

In your case, it seems the cleanest and easiest way is to use CollectionService and/or attributes, both of which replicate to the clients.

You can use methods such as HasTag, AddTag, RemoveTag, SetAttribute, and GetAttribute.

I had this SAME exact issue bro, I figured out a way in which you can do it. Let me share.
You have 2 module scripts.
Framework and Manager
Framework contains the actual Statuses and Manager just retrieves status or adds and removes characters. Here’s mine.

FRAMEWORK

local CombatFramework = {}
CombatFramework.__index = CombatFramework;

function CombatFramework.new(Character)
	local self = setmetatable({}, CombatFramework)
	self.StatusList = {
		["Blocking"] = false,
		["Stunned"] = false,
		["IFrames"] = false,
		["Misc"] = false,
		["InAction"] = false,
		["Parrying"] = false,
		["AirCombo"] = false,
		["SpecialAction"] = ""
	}
	self.CombatData = {
		["Weapon"] = "Fists",
		["DMGMultiplier"] = 1
	}

	return self
end


return CombatFramework

Store whatever values, combat data, whatever you need in there

Secondly you put a module script inside of that module.
MANAGER

function characterStatusManager.new(character)
	local self = setmetatable({}, characterStatusManager)
	self.Character = character
	return self
end

function characterStatusManager:AddCharacter(character)
	local player = PS:GetPlayerFromCharacter(character)
	if player ~= nil then
	local playerStatus = framework.new(character)
	Characters[player.UserId] = playerStatus
	laststun[player.UserId] = os.clock()
	aircombo[player.UserId] = os.clock()
	else
	local playerStatus = framework.new(character)
	Characters[character] = playerStatus
	laststun[character] = os.clock()
	aircombo[character] = os.clock()
	end
end

function characterStatusManager:RemoveCharacter(character)
	local player = PS:GetPlayerFromCharacter(character)
	if player ~= nil then
		Characters[player.UserId] = nil
	else
		Characters[character] = nil
	end
end

function characterStatusManager:RetriveStatus(character)
	local player = PS:GetPlayerFromCharacter(character)
	if player ~= nil then
		return Characters[player.UserId].StatusList
	else
		return Characters[character].StatusList
	end
end

It is VERY messy, but you should be able to get the jist of it. I’m using this for both NPC’s and players so it may be a little funky. But a little cleaning up should help.

In the state in which you wanna call something. Just require the MANAGER script. And call on the StatusList or Whatever you need to call on. Put that in a RemoteFunction InvokeServer thing and it should work. Works for me at least.

I think this is for sure a cool method, although, the way it’s set up, you would need to create a new Manager instance in each script where you want to get a player’s tags which isn’t something I want to be doing especially when I need to be getting the tags from the same player in many different scripts all at the same time.

The module I was using before is different from the one attached now, since, the module attached isn’t handling tags but rather an early draft of the Configurations method I mentioned. Whenever I tried to print out the large table that handled each player’s tags from the Tags module, it would return an empty table. Also, when I used InvokeServer, the table wouldn’t update when data changed. I’m considering adding a DataChangedEvent, but I’m not sure if it’s even worth it overall. On top of that, everything I’ve read on this forum advises against using Attributes and CollectionService, suggesting I use a table method instead. So, I’m really confused about which approach to take.

What you could do instead is run a heartbeat like this.

RunService.RenderStepped:Connect(function()
	local blah = RFunc:InvokeServer()
	task.wait()
end)

it whatever script you put it in it’ll have the players status at ALL time EVERY moment.
For server scripts on the otherhand you can simply just call on the module.

But at the end of the day it is personal preference. I just prefer to use this cuz my monkey brain can understand it.