Hi guys,
Not sure if this is the appropriate forum to post on.
I’m currently trying to make a door that only allows players of certain teams to pass through.
I was thinking of doing this via a table that stores the teams that are whitelisted to pass through. This is handled via a GUI that allows the player to select certain teams to be whitelisted and added to said table.
However, I can’t seem to find any way to actually add a table to an instance as its own property.
Would I have to make a class (OOP) specifically for this type of door? Otherwise, how could I go about this?
Use CollectionService (hereafter “TagService”, because that’s what it should have been called) and a TagService plugin to set a common tag on all doors that need this functionality, then use TagService:GetTagged and TagService:GetInstanceAddedSignal to call a setup function on all doors that exist at the start and that get added during gameplay. Here’s some code I adapted to what I think your situation is:
local Players = game:GetService("Players")
local TagS = game:GetService("CollectionService")
local ReplicatedStorage = game.ReplicatedStorage
local Libraries = ReplicatedStorage.Libraries
local Remotes = ReplicatedStorage.Remotes
local ServerStorage = game.ServerStorage
local Locals = ServerStorage.Locals --BindableEvents and BindableFunctions for inter-serverscript comms. Or use a ModuleScript and return an interface, up to you.
local Signal = require(Libraries.GoodSignal) --I love GoodSignal <3
local Trove = require(Libraries.Trove) --Actually, probably use a different state cleaning lib there are quite a few out there
local DOOR_TAG = "Door"
local DoorCreated = Signal.new()
local DoorRemoving = Signal.new()
local modelDoors = setmetatable({}, {__mode="k"}) --Don't prevent GC'ing models just for referencing door objects (the tables created in setupDoor)
local playerTroves = setmetatable({}, {__mode="kv"}) --Don't prevent GC'ing players or troves (might not be necessary as Player instances get Destroy'ed when they leave)
local function map(t, mapper) --Put in a functional programming / table util library, or find an existing one
local result = table.create(#t)
local i = 1
for _, v in t do
result[i] = mapper(v)
i += 1
end
return result
end
local function filter(t, pred) --Put in a functional programming / table util lib, or find an existing one
local result = {}
local i = 1
for _, v in t do
if pred(v) then
result[i] = v
i += 1
end
end
return result
end
local function setupDoor(model: Model)
--If you wanted to use OOP style, you'd call it newDoor and use metatables to enable dynamic dispatch for methods,
-- i.e. Door:SerializeForRemoteEvent()
local door = {
Model = model,
Owner = nil,
AllowedTeams = {},
}
modelDoors[model] = door
DoorCreated:Fire(door)
return door
end
local function cleanupDoor(door)
--Depends on your game's requirements.
DoorRemoving:Fire(door)
modelDoors[door.Model] = nil --Not necessary for GC, but necessary to prevent e.g. GetDoorFromModel from returning this door (can't expect GC to run immediately)
end
map(TagS:GetTagged(DOOR_TAG), setupDoor)
TagS:GetInstanceAddedSignaL(DOOR_TAG):Connect(setupDoor)
TagS:GetInstanceRemovedSignal(DOOR_TAG):Connect(cleanupDoor)
local function updateTeamCollisionGroups()
--Create a collision group per team, and ensure all players on each team have their character's
-- and the teams' doors' collision groups set accordingly.
end
local function updateDoorCollisionGroups(door)
--Set the door's collision group according to it's owner's team.
end
local function serializeDoorForRemoteEvent(door, forOwner)
return table.clone(door) --Manually populate a new table if the door object contains secrets that the owner or other players shouldn't know about
end
Players.PlayerAdded:Connect(function(player)
local T = Trove.new() --Gets cleaned on this player removing
playerTroves[player] = T --Gets GC'ed eventually because playerTroves is weak
ServerStorage.AwaitPlayerReady:Invoke(player)
T:Add(DoorCreated:Connect(function(door)
local serialized = serializeDoorForRemoteEvent(door, door.Owner == player)
Remotes.ToClient.OnDoorCreated:Fire(player, serialized)
end))
T:Add(DoorRemoving:Connect(function(door)
local serialized = serializeDoorForRemoteEvent(door, door.Owner == player)
Remotes.ToClient.OnDoorRemoving:Fire(player, serialized)
end))
T:Add(Players.PlayerRemoving:Connect(function(removingPlayer)
if removingPlayer ~= player then return end
T:Clean()
end))
end)
--Note: Only EVER have RemoteFunctions in ToServer, never in ToClient!!! Or use a client/server comms/replication library
Remotes.ToServer.RequestAllDoors.OnInvoke = function(player)
return map(modelDoors, function(door)
return serializeDoorForRemoteEvent(door, door.Owner == player)
end)
end
Remotes.ToServer.RequestOwnedDoors.OnInvoke = function(player: Player, owner: Player?)
assert(typeof(owner)=="Instance" and owner:IsA("Player")) --Or find a type checking library and ensure the correct types are passed
owner = owner or player
return map(
filter(
modelDoors,
function(door) return door.Owner == owner end
),
function(door) return serializeDoorForRemoteEvent(door, door.Owner == player) end
)
end
Locals.GetDoorFromModel.OnInvoke = function(instance)
return modelDoors[instance]
end
Thanks @ThanksRoBama, I’ll definitely give this a look. Right now I’ve just stuck with a simple system without whitelisting, but i’ll be sure to use your response when I get around to making it