How should I go about organising a family system?

I’m trying to think of an efficient way to create a family system. My current layout would be:

Player1 invites Player2 to join their family
If Player2 accepts, then they join Player1’s family (this would create a folder under a Families folder like so)

Families
    Player1
        Player2

And anybody else who gets invited to join Player1’s family would be added on like so

Families
    Player1
        Player2
        Player3

So Player1 would be like the ‘owner’ of the family. So if they left the game, the family would be destroyed.

I’m not a huge fan of this though, but I can’t think of an efficient way to store the families. I don’t just want to do it as a numerical thing

Families
    Family1
        Player1
        Player2
    Family2
        Player3

But I feel that may be the only way to do it? That’d then beg the question of what happens if you don’t want people in your family anymore, you’d have to leave the whole family, instead of just kicking one member out

2 Likes

What exactly do you mean by family? It seems a little more context is needed.

2 Likes

Why not? The one you’re least comfortable with is really the better option here and it seems like it completely covers your use case and issues as well. The only problem is you aren’t comfortable with it and it’s not apparent why.

If you want to go with option two, again not sure why you don’t want a numerical system, but you can instead just name the folder differently like “Player’s Family”. You could then allow them to change the family name and thus the folder, or don’t, doesn’t really matter. Clearing out the family is as simple as providing an option to bulk kick instead of kick individually - you don’t need to think very hard about that one.

1 Like

I think the third option, creating a family class that has the owner determined by seniority, is the best way to do it. It seems pretty easy to organize through an OOP framework. As a bonus, it gives you more freedom to do other things with it later, such as if you want to have co-ownership.

Reason why I was fearful of doing the second option was like so:
If Player1 creates the family, and then the folder is stored as

Families
    Player1 Family
        Player1
        Player2

Then what happens if Player1 leaves? Ye I can remove them from the family folder, but the folder would still be named as the Player1’s family. Player2 wouldn’t have access to control it, so it’d make sense to just completely destroy the folder when Player1 leaves. But I feel that can be a problem if you had like 5 players all in a family, then the owner leaves and now none of them are in a family and would have to go about making another one

I’ve already kinda started a setup using OOP based structure, like so

--[[
	// NinjoOnline \\
	
	Family
--]]

local Family = {}
Family.__index = Family

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local Families = ReplicatedStorage:WaitForChild('Families')

-- Create / destroy the family folder (owner)
local function CreateFamilyFolder(owner)
	local FamilyFolder = Families:FindFirstChild(owner.Name)
	if FamilyFolder then return end
	
	FamilyFolder = Instance.new('Folder')
	FamilyFolder.Name = owner.Name
	FamilyFolder.Parent = Families
end

local function DestroyFamilyFolder(owner)
	local FamilyFolder = Families:FindFirstChild(owner.Name)
	if not FamilyFolder then return end
	
	FamilyFolder:Destroy()
end

-- Add / remove family members
local function AddMemberFolder(familyData)
	for _, member in pairs(familyData.Members) do
		if member ~= familyData.Owner then
			local FamilyFolder = Families:FindFirstChild(familyData.Owner.Name)
			if not FamilyFolder then return end
			
			if not FamilyFolder:FindFirstChild(member.Name) then
				local NewMember = Instance.new('StringValue')
				NewMember.Name = member.Name
				NewMember.Value = 'Member'
				NewMember.Parent = FamilyFolder
			end
		end
	end
end

local function RemoveMemberFolder(owner, member)
	local FamilyFolder = Families:FindFirstChild(owner.Name)
	if not FamilyFolder then return end
	
	local MemberFolder = FamilyFolder:FindFirstChild(member.Name)
	if not MemberFolder then return end
	
	MemberFolder:Destroy()
	
	if #FamilyFolder:GetChildren() == 0 then
		-- Family has no members, destroy the folder
		DestroyFamilyFolder(owner)
	end
end

-- Add family member
function Family:AddMember(member)
	table.insert(self.Members, member)
	
	AddMemberFolder(self)
end

-- Remove famiyl member
function Family:RemoveMember(member)
	if member == self.Owner then
		DestroyFamilyFolder(self.Owner)
	else
		RemoveMemberFolder(self.Owner, member)
	end
	
	-- Remove member from family
	local MemberIndex = table.find(self.Members, member)
	if MemberIndex then
		table.remove(self.Members, MemberIndex)
	end
	
	if #self.Members <= 1 then
		-- Only the owner is a member, remove family
		self = nil
	end
end

-- New family constructor
function Family.new(owner)
	local NewFamily = {}
	setmetatable(NewFamily, Family)
	
	NewFamily.Owner = owner
	NewFamily.Members = {owner}
	
	CreateFamilyFolder(owner)
	
	return NewFamily
end

return Family
2 Likes

So is the problem succession then? There are many ways you can work around this, up to adding metadata to the folder (a separate folder with informational values) or handle that in your code by determining the order of who joined the family and thus who should be passed control if the owner happens to leave it. Seems like mostly trivial stuff with a bit of work to do.

1 Like

Is there any easy way to then check what family a player is in, other than having to do a for loop within a for loop?

for _, familyFolder in pairs(Families:GetChildren()) do
    for _, player in pairs(familyFolder:GetChildren()) do
        if player.Name == LocalPlayer.Name then
            print(player.Name .. " is in " .. familyFolder.Name)
        end
    end
end

I don’t know OOP, but here’s a simple function that does what you’re looking for. You can convert it to OOP if needed. By the looks of it, you just need to use GetDescendants().

function GetFamily(Player) -- "Player" is the player instance
	for i,v in pairs(Families:GetDescendants()) do -- Assuming "Families" is defined
		if v.Name == Player.Name and v:IsA('StringValue') then -- Checking to make sure the object found is the correct type of object
			print(v.Name..' is in '..v.Parent.Name..'\'s Family') -- I'm assuming the parent of the Value is the owner of the Family's name.
		end
	end
end

Looks good to me so far! I’m not sure I’d do anything substantially different at this stage.

1 Like

Meta should also be controlling this. You need outside values that should be set to identify what family a player belongs to, which you can then reference in other code. For example, an ObjectValue can point directly to the family folder so you can access it by value.

local familyFolder = PlayerDataLocation.Family.Value

Or you can also use a StringValue to identify the family. This is probably less appropriate an option if you include renaming since you’d also have to update these values if the family name changes. There’d be more manual management involved than using a direct reference.

local familyName = PlayerDataLocation.Family.Value
local familyFolder = Families:FindFirstChild(familyName)

My personal preference would be using CollectionService to tag people. No folders or anything, other than for metadata that I might want to include. Even successors and owners can be managed through tags, though you’ll still need to loop - which would be fairly negligible. For this though, you would have to track the name of the family and probably have players assign an unchanging name upon creation.

for _, familyMember in ipairs(CollectionService:GetTagged("Epic Family")) do
    local isOwner = CollectionService:HasTag(familyMember, "Owner")
    local isSuccessor = CollectionService:HasTag(familyMember, "Successor")
end

These are all good problems you need to design your systems around. Keeping a storage of player session data somewhere and tracking their family in an actual script or ValueObject will get rid of the need of having to looping over the families. Including meta in your folder will help you identify information better so you can work with families.

On a separate wing, you can reduce that for loop assuming a player can only be part of a single family. This is a really ugly trick making lots of assumptions though and I don’t recommend it at all.

-- First check if a player belongs to any family
local membership = Families:FindFirstChild(player.Name, true)
-- Identify the family by finding an ancestor folder if membership exists
local family = membership and membership:FindFirstAncestorOfClass("Folder")

-- Membership exists as does the family: proceed
if membership and family then
    local familyName = family.Name
end
3 Likes