Party System - OOP Party System

I have noticed before that there are not many party systems open to the public. So, I decided to make a party system that is object oriented. You are going to need a bit of lua knowledge to understand how to use this module, but I will give a few examples.
A few things to note:

  • I do not have that many error checks, you can add them yourself if you wish to.
  • When adding a member to the party, you are going to have to have a check for if it’s invite only or not, I do not check when calling addMember()
  • It also creates physical information in a folder named “PhysicalParties” in ReplicatedStorage as you can see in the screenshot below. There are attributes that are apart of the Player. “Leader”, “CanKick”, and “CanInvite”
  • This works best when combined with remotes obviously so you can tell who called the functions and such obviously. I created a basic remote example for sending and accepting invites. You would also probably want to add something to it like adding a GUI that when you accept an invite, the remote to join your current invite fires. You could attach the GUI popup part to onInviteSent

Model:
OOP Party System - Roblox

I am very open to suggestions of stuff to add, or how to improve it. Please feel free to give me feedback :slight_smile:

image

local Party = {}
Party.__index = Party

local Players = game:GetService("Players")

local Parties = {};
local PhysicalParty
local defaultMaxMembers = 4
local inviteCooldown = 20

local PartyRemote = script:WaitForChild("Party")

--[[
	local PartyModule = require(game.ReplicatedStorage.PartySystem.Parties) -->> Referring to this module
	
	PartyModule:getParty(Player) -->> Requires a player argument. Will return the player's current party (even if they are not the leader), if they are in one.
	PartyModule:onInviteSent(function(Invite, PartyLeader, invitedPlayer)
		-- MUST BE USED BEFORE ANY PARTIES ARE CREATED
		-->> Invite is the physical invite stored in PhysicalParty.Invites
		-->> PartyLeader is the player instance of the party leader of which party the player was invited to
		-->> invitedPlayer is the player instance of the player who was invited
	
		print(Invite)
		print(PartyLeader)
		print(invitedPlayer)
	end)

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

	local Party = Party.new(Leader) -->> Requires a player argument. Player argument will become the leader.
	Party:disband()
	Party:transferOwnership(Player) -->> Requires a Player argument
	Party:invitePlayer(Player) -->> Requires a Player argument
	Party:addMember(Player) -->> Requires a Player argument
	Party:removeMember(Player) -->> Requires a Player argument
	Party:blacklistPlayer(Player) -->> Requires a Player argument
	Party:unblacklistPlayer(Player) -->> Requires a Player argument
	Party:setMaxMembers(MaxMembers) -->> Requires a number argument. The default max members is 4, and can be change in the module
	Party:setPermission(Member, Permission, Bool) -->> Requires a player argument, string argument, and bool argument.
	Party:toggleInviteOnly(Toggle) -->> Requires a bool argument.
	Party:getMembers() -->> Returns an array of all the current party members
	Party:isMember(Player) -->> Requires a Player argument. Returns a bool (true/false)
	Party:isLeader(Player) -->> Requires a Player argument. Returns a bool (true/false)
	Party:isInviteOnly() -->> Returns a bool (true/false)
	Party:hasPermission(Player, Permission) -->> Requires a Player argument and a string for the permission. Returns a bool (true/false)
	Party:teleportMembers(123456789, {ReservedServerCode = "CodeHere", JobId = "1234567890abc", ShouldReserveServer = true}) -->> Requires a PlaceId. Anything inside the table is optional.
]]--

local function createNewInstance(InstanceType, Parent, Name, Value)
	local newInstance

	local Success, err = pcall(function()
		newInstance = Instance.new(InstanceType)
		newInstance.Parent = Parent
		newInstance.Name = Name

		if Value then
			newInstance.Value = Value
		end

		return newInstance
	end)

	if not Success then
		warn("Error creating instance: " ..err)
	else
		return newInstance
	end
end

--// Party module functions \\--

-->> Return what party a player is in, if any

function Party:getParty(Player: Player)
	if Parties[Player] then
		return Parties[Player]
	end

	for PartyLeader,Party in pairs (Parties) do
		for i,Member in pairs (Party.Members) do
			if Member == Player then
				return Party
			end
		end
	end

	return "NoParty"
end

--// Party object function --\\

-->> Create a new party
function Party.new(Leader: Player)
	if Parties[Leader] then
		warn(("%s is already a party leader!"):format(Leader.Name))

		return Parties[Leader]
	end

	setmetatable({}, Party)

	Party.Leader = Leader;
	Party.MaxMembers = defaultMaxMembers;
	Party.CurrentMemberCount = 0;
	Party.InviteOnly = false;
	Party.Members = {};
	Party.Invites = {};
	Party.Blacklist = {};

	PhysicalParty = script.Parent.PhysicalParties.PartyTemplate:Clone()
	Party:addMember(Leader)

	PhysicalParty.Name = Leader.Name
	PhysicalParty.Parent = script.Parent.PhysicalParties
	PhysicalParty.Members[Leader.Name]:SetAttribute("Leader", true)
	PhysicalParty.Members[Leader.Name]:SetAttribute("CanKick", true)
	PhysicalParty.Members[Leader.Name]:SetAttribute("CanInvite", true)

	Parties[Leader] = Party
	
	print(Parties)
	
	return Party
end

-->> Disband a party/delete a party
function Party:disband()
	if not Parties[self.Leader] then
		warn("Party does not exist!")
		return
	end

	Parties[self.Leader] = nil
	PhysicalParty:Destroy()
end

-->> Transfer party ownership to another party member
function Party:transferOwnership(Member: Player)
	if not table.find(self.Members, Member) then
		print(("%s not in the party."):format(Member.Name))
		return "NotInParty"
	elseif self.Leader == Member then
		warn("Cannot transfer ownership to current leader.")
		return
	end

	PhysicalParty.Name = Member.Name

	PhysicalParty.Members[self.Leader.Name]:SetAttribute("Leader", false)
	PhysicalParty.Members[Member.Name]:SetAttribute("Leader", true)
	self.Leader = Member
end

-->> Invites a player to the party
function Party:invitePlayer(Player: Player)
	if self.Invites[Player] then
		warn(("%s already has an invite. Wait before sending another."):format(Player.Name))
		return
	end

	if self.Members[Player] then
		warn(("%s is already a party member."):format(Player.Name))
		return
	end

	self.Invites[Player] = self
	createNewInstance("ObjectValue", PhysicalParty.Invites, Player.Name, Player)

	spawn(function()
		task.wait(inviteCooldown)
		self.Invites[Player] = nil
		
		if PhysicalParty.Invites:FindFirstChild(Player.Name) then
			PhysicalParty.Invites[Player.Name]:Destroy()
		end
	end)
end

-->> Adds a member to the party
function Party:addMember(Member: Player)
	if self.CurrentMemberCount + 1 > self.MaxMembers then
		warn(("[%s:%s] Cannot add %s to party. Too many members!"):format(Member.Name, self.CurrentMemberCount, self.MaxMembers))
		return
	elseif table.find(self.Members, Member) then
		warn(("%s is already in the party."):format(Member.Name))
		return
	elseif table.find(self.Blacklist, Member) then
		warn(("%s is blacklisted from the party!"):format(Member.Name))
		return
	end

	table.insert(self.Members, Member)
	self.CurrentMemberCount += 1

	local newPhysicalMember = createNewInstance("ObjectValue", PhysicalParty.Members, Member.Name, Member)

	newPhysicalMember:SetAttribute("Leader", false)
	newPhysicalMember:SetAttribute("CanKick", false)
	newPhysicalMember:SetAttribute("CanInvite", false)
end

-->> Removes a member from the party
function Party:removeMember(Member: Player)
	if not table.find(self.Members, Member) then
		warn(("%s not in the party."):format(Member.Name))
		return
	elseif self.Leader == Member then
		warn("You cannot kick the leader from the party.")
		return
	end

	table.remove(self.Members, table.find(self.Members, Member))
	PhysicalParty.Members[Member.Name]:Destroy()
end

-->> Blacklist a player from a party
function Party:blacklistPlayer(Player: Player)
	if table.find(self.Blacklist, Player) then
		warn(("%s is already blacklisted."):format(Player.Name))
		return
	elseif self.Leader == Player then
		warn("The party leader cannot be blacklisted.")
		return
	end

	table.insert(self.Blacklist, Player)
	local newPhysicalBlacklist = createNewInstance("ObjectValue", PhysicalParty.Blacklist, Player.Name, Player)

	if self:isMember(Player) then
		self:removeMember(Player)
	end
end

-->> Unblacklist a player from a party
function Party:unblacklistPlayer(Player: Player)
	if not table.find(self.Blacklist, Player) then
		warn(("%s is not blacklisted from the party"):format(Player.Name))
		return
	end

	table.remove(self.Blacklist, table.find(self.Blacklist, Player))

	if PhysicalParty.Blacklist:FindFirstChild(Player.Name) then
		PhysicalParty.Blacklist[Player.Name]:Destroy()
	end
end

-->> Set the number of members allowed in a party
function Party:setMaxMembers(MaxMembers: number)
	self.MaxMembers = MaxMembers
end

-->> Set a player's permission
function Party:setPermission(Member, Permission, Toggle)
	if not table.find(self.Members, Member) then
		warn(("%s is not in the party"):format(Member.Name))
		return
	elseif self.Leader == Member then
		warn("Cannot set leaders permissions.")
		return
	elseif Permission == "Leader" then
		warn(("You cannot set the %s permission. Use transferOwnership instead."):format("Leader"))
		return
	end

	PhysicalParty.Members[Member.Name]:SetAttribute(Permission, Toggle)
end

function Party:toggleInviteOnly(Toggle: boolean)
	if Toggle == true then
		self.InviteOnly = true
		PhysicalParty.InviteOnly.Value = true
	elseif Toggle == false then
		self.InviteOnly = false
		PhysicalParty.InviteOnly.Value = false
	end
end

-->> Return the members of a party
function Party:getMembers()
	return self.Members
end

-->> Return if a player is a member of a party
function Party:isMember(Player: Player)
	if not PhysicalParty.Members:FindFirstChild(Player.Name) then
		warn(Player.Name.. " is not a member of the party.")
		return
	end

	if table.find(self.Members, Player) then
		return true
	end

	return false
end

function Party:isLeader(Player: Player)
	if not PhysicalParty.Members:FindFirstChild(Player.Name) then
		warn(Player.Name.. " is not a member of the party.")
		return
	end

	if PhysicalParty.Members[Player.Name]:GetAttribute("Leader") == true then
		return true
	end

	return false
end

-->> Return if a party is invite only
function Party:isInviteOnly()
	if self.InviteOnly then
		return true
	end

	return false
end

-->> Check if player has a permission for a party
function Party:hasPermission(Member, Permission: string)
	if not PhysicalParty.Members[Member.Name]:GetAttribute(Permission) then
		print(("'%s' is an invalid permission."):format(Permission))
		return
	end

	if PhysicalParty.Members[Member.Name]:GetAttribute(Permission) == true then
		return true
	end

	return false
end

function Party:teleportMembers(PlaceId, OptionalData)
	local TeleportOptions = Instance.new("TeleportOptions")
	
	if OptionalData.ReservedServerCode then
		TeleportOptions.ReservedServerAccessCode = OptionalData.ReservedServerCode
	end
	
	if OptionalData.JobId then
		TeleportOptions.ServerInstanceId = OptionalData.JobId
	end
	
	if OptionalData.ShouldReserveServer then
		OptionalData.ShouldReserveServer = OptionalData.ShouldReserveServer
	end

	game:GetService("TeleportService"):TeleportAsync(PlaceId, self.Members, TeleportOptions)
end

function Party:onInviteSent(Callback)
	script.Parent.PhysicalParties.ChildAdded:Connect(function(newParty)
		local Tries
		local PartyLeader
		local invitedPlayer

		newParty.Invites.ChildAdded:Connect(function(newInvite)
			Tries = 0
			
			repeat
				task.wait(.25)
				Tries += 1
			until newInvite.Value ~= nil or Tries >= 3
			
			if Tries >= 3 then
				warn("An error has occured while attempting to run a callback when an invite was sent.")
				return
			end
			
			if newInvite.Parent.Parent ~= nil then
				if not game.Players:FindFirstChild(newInvite.Parent.Parent.Name) then
					print(newInvite.Parent.Parent.Name)
					warn("An error has occured!")
					return
				end

				PartyLeader = game.Players:FindFirstChild(newInvite.Parent.Parent.Name)
				invitedPlayer = game.Players:FindFirstChild(newInvite.Name)
			end

			Callback(newInvite, PartyLeader, invitedPlayer)
		end)
	end)
end

--// Handle remote
local function getPlayerParty(Player)
	for i,Party in pairs (script.Parent.PhysicalParties:GetChildren()) do
		if Party.Members:FindFirstChild(Player.Name) then
			return Party.Members:FindFirstChild(Player.Name)
		end
	end

	return false
end

local function playerHasInvite(Player)
	for i,Party in pairs (script.Parent.PhysicalParties:GetChildren()) do
		for i,Invite in pairs (Party.Invites:GetChildren()) do
			if Invite.Name == Player.Name then
				local PartyOwner = Players[Party.Name]
				return Parties[PartyOwner]
			end
		end
	end

	return false
end

local function removePlayerInvites(Player)
	for i,Party in pairs (script.Parent.PhysicalParties:GetChildren()) do
		for i,Invite in pairs (Party.Invites:GetChildren()) do
			if Invite.Name == Player.Name then
				Invite:Destroy()
			end
		end
	end

	for i,Party in pairs (Parties) do
		if Party.Invites[Player] then
			Party.Invites[Player] = nil
		end
	end
end

local function handleRemoteRequest(Player, Request, Data)
	if Request == "InvitePlayer" then
		if getPlayerParty(Player) and getPlayerParty(Player):GetAttribute("CanInvite") == true then
			Party:invitePlayer(Data.InvitedPlayer)
		end
	elseif Request == "AcceptInvite" then
		if playerHasInvite(Player) then
			playerHasInvite(Player):addMember(Player)
			removePlayerInvites(Player)
		end
	end
end

PartyRemote.OnServerEvent:Connect(handleRemoteRequest)

return Party

PartyModule Example usages:

local PartyModule = require(game.ReplicatedStorage.Parties) -->> Referring to this module

PartyModule:getParty(Player) -->> Requires a player argument. Will return the player's current party (even if they are not the leader), if they are in one.
PartyModule:onInviteSent(function(Invite, PartyLeader, invitedPlayer)
        -- MUST BE USED BFORE ANY PARTIES ARE CREATED
		-->> Invite is the physical invite stored in PhysicalParty.Invites
		-->> PartyLeader is the player instance of the party leader of which party the player was invited to
		-->> invitedPlayer is the player instance of the player who was invited
	
		print(Invite)
		print(PartyLeader)
		print(invitedPlayer)
	end)

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

local newParty = PartyModule.new(game.Players:WaitForChild("bellaouzo")) -->> Requires a player argument. Player argument will become the leader.
newParty:disband()
newParty:transferOwnership(game.Players:WaitForChild("bellaouzo")) -->> Requires a Player argument
newParty:invitePlayer(Player) -->> Requires a Player argument
newParty:addMember(game.Players:WaitForChild("bellaouzo")) -->> Requires a Player argument
newParty:removeMember(game.Players:WaitForChild("bellaouzo")) -->> Requires a Player argument
newParty:blacklistPlayer(game.Players:WaitForChild("bellaouzo")) -->> Requires a Player argument
newParty:unblacklistPlayer(game.Players:WaitForChild("bellaouzo")) -->> Requires a Player argument
newParty:setMaxMembers(6) -->> Requires a number argument. The default max members is 4, and can be change in the module
newParty:setPermission(game.Players:WaitForChild("bellaouzo"), "CanKick", true) -->> Requires a player argument, string argument and bool argument.
newParty:toggleInviteOnly(true) -->> Requires a bool argument.
newParty:getMembers() -->> Returns an array of all the current party members
newParty:isMember(game.Players:WaitForChild("bellaouzo")) -->> Requires a Player argument
newParty:isInviteOnly() -->> Returns a bool (true or false)
newParty:hasPermission(game.Players:WaitForChild("bellaouzo"), "CanKick") -->> Requires a Player argument and a string for the permission.
newParty:teleportMembers(123456789, {ReservedServerCode = "CodeHere", JobId = "1234567890abc", ShouldReserveServer = true}) -->> Requires a PlaceId. Anything inside the table is optional.

--[[
Permissions:
Leader
CanKick
CanInvite
--]]

(Local Script Example) Remote Examples Usages:

local PartyRemote = PartyModule.Party

PartyRemote:FireServer("InvitePlayer", {InvitedPlayer = game.Players:WaitForChild("Player2")}) -->> Would send an invite to Player2 if the person who called the remote can send invites
PartyRemote:FireServer("AcceptInvite") -->> Would accept a party invite if the person who called the remote has a pending invite
42 Likes

Seems like an interesting system, is there a documentation or setup video?

2 Likes

it might be worth adding an option to teleport the party too using teleportservice. I can see this being handy for games that might teleport a party across places in a universe; dungeons for example

3 Likes

That would be great! I was working on a project a while back and thus has kinda been out the the back burner that needed a system like that, and I’ve been thinking of starting it again from scratch now that I have more experience and this feature would be a great help!

2 Likes

I’ll add it in a few hours or so, thanks for the idea!

I haven’t really gotten that far, the setup it pretty simple, should just be able to move the module to ReplicatedStorage, require it and then call the functions and such. I can go over how to use it and such more in depth privately on something like dizzcord if needed.

Added party.disband() - I had this before, just forgot to document it
Added Party:teleportMembers(PlaceId, ReservedServerCode) – ReservedServerCode is optional

1 Like
  • party.disband() → party:disband()
  • Fixed max members bug
  • Made it so you cannot set the Leader property with :setPermission (use :transferOwnership if you need a new leader)
  • If someone already leads a party and you attempt to make a new one for them with Party.new() without disbanding it, it returns their current party instead of just erroring
1 Like

Is there a way to check if a certain user is the party leader?

Edit: You can do so with the hasPermission function. Turns out that Leader is also a permission, which is not listed in the “Example Usages” section. For example:

	local Party = PartyService:getParty(plr)
	
	if Party:hasPermission(plr, 'Leader') ~= true then
		print('Player is NOT Leader.')
	end
	

Although, I do have a question about the invite system. Why is there an ‘invite only’ toggle if the system doesn’t allow you to invite other players?

It’d be cool if the invite system was fully implemented, in which you could call a function to invite someone. Then there’d be an event which you could connect to and run a function every time an invite is made.

2 Likes

Sorry, didn’t get a notification for this reply for some reason. Yeah, Leader is an attribute/permission of the members. I can add a function that returns whether or not they are the leader. I’ll look at fully implementing the invite system soon, I think I had a reason that I couldn’t at the time, but I’ll take a look at it again. Thanks for the suggestions :slight_smile: I’ll get the leader thing added probably today.

1 Like
  • Added Party:isLeader(Player)
  • Added Party:invitePlayer(Player)
  • Added a remote with a couple possible calls. Check the examples for more info.
  • Add an error check that checks if the player is even apart of the party when using isMember and isLeader
2 Likes

Just added some stuff to do with what you were talking about, lmk what you think. Currently the InviteOnly thing doesn’t really do much, but you can handle sending invites and accepting them through a remote. Also let me know if you find anything I broke or something lol, took me a bit to figure out how to handle custom callback for when invites are sent.

2 Likes

That is literally amazing, thank you so much.

I saw that you’re referencing the PartySystem folder by using game.ReplicatedStorage.

I was thinking that both the script and PhysicalParties folder should be in the PartySystem folder, and then you can change game.ReplicatedStorage.PhysicalParties to be script.Parent.PhysicalParties, therefore if the game developers have a Modules folder, they could just drag the PartySystem folder into their Modules folder.

Kind of like this:
image

Also I saw that the onInviteSent function is execute with the party. I personally think it should be executed with the PartyModule instead.

1 Like
  • Switched the script from using game.ReplicatedStorage.PhysicalParties to script.Parent.PhysicalParties, so you can just drag and drop it and it will function. Thanks to qvgk (Roblox Profile) for the idea.
  • Changed onInviteSent to be handled by an actual module function instead of being attached to a specific party object. Keep in mind for it to work, you must call onInviteSent before any parties are created.
  • Changed how teleportMembers optional data is handled (See example in post). Also made it so the optional data actually works, apparently, I was never handling the reserved code stuff (Whoops lol)
1 Like

I tried running the onInviteSent event before party creation on player join, and that seems to make it so the party is not automatically created.

Even if I place the event before or after party creation, I’ll always get the error attempt to call missing method 'invitePlayer' of string as the getParty function always returns “No Party”.

At this moment, I automatically create a party for the player when they join. I have not tried using the event in a separate script yet.

Edit: I tried using it in a separate script and the issue still occurs.

This is actually really cool, good job on this!

1 Like

Seems like for some reason using setmetatable on the Parties table, makes the Parties table blank and unusable. I’ll probably end up removing onInviteSent till I see if I can figure out why, or find a workaround

2 Likes

Going to remove the onInviteSent for tonight but I think I’ll add it back tomorrow. Made a version that should work (really quick, will prob improve tomorrow lol), bases off the actual invite value that is made instead of the parties table. Replace it with the current in the module. It now returns the party owners instance as the second argument, but if you need their party you can just use Party:getParty()
Let me know if this works

function Party:onInviteSent(Callback)
	script.Parent.PhysicalParties.ChildAdded:Connect(function(newParty)
		local PartyLeader
		
		newParty.Invites.ChildAdded:Connect(function(newInvite)
			repeat task.wait() until newInvite.Value ~= nil
			if newInvite.Parent.Parent ~= nil then
				if not game.Players:FindFirstChild(newInvite.Parent.Parent.Name) then
					print(newInvite.Parent.Parent.Name)
					warn("An error has occured!")
					return
				end
				
				PartyLeader = game.Players:FindFirstChild(newInvite.Parent.Parent.Name)
			end
			
			Callback(newInvite, PartyLeader)
		end)
	end)
end
PartyModule:onInviteSent(function(invitedPlayer, partyLeader)
	print(invitedPlayer.Value) -->> invitedPlayer is an object value. The value of is the player instance of the person who was invited
    print(partyLeader) -->> Returns the player instance of the party leader
end)
3 Likes
  • Fixed a possible error caused by the remote not being loaded in time
  • Remade onInviteSent since the last method was causing an error

Happy new years :slight_smile:

2 Likes