What PlayerManager does is simplify Player join / leave and Character spawn / despawn events and provide more control over what happens when with additional bells and whistles such as ragdoll, corpse, unified character mass, sanitizing accessories, etc
-- TODO:
local CONFIGURATION = script:GetAttributes()
local Players = game:GetService("Players")
Players.CharacterAutoLoads = CONFIGURATION.CharacterAutoLoads
Players.RespawnTime = CONFIGURATION.RespawnTime
local StarterPlayer = game:GetService("StarterPlayer")
StarterPlayer.LoadCharacterAppearance = CONFIGURATION.LoadCharacterAppearance
local HttpService = game:GetService("HttpService")
local BLACKLIST: { [string]: boolean } = HttpService:JSONDecode(CONFIGURATION.BLACKLIST)
local CHARACTERS_FOLDER = Instance.new("Folder")
CHARACTERS_FOLDER.Name = "Characters"
CHARACTERS_FOLDER.Parent = workspace
local ragdollUtil = require(script.ragdollUtil) :: (Humanoid) -> ()
local Debris = game:GetService("Debris")
-- Execution order
-- PlayerAdded > PlayerJoined
-- PlayerAdded is when you initialize the player, for example getting sessiondata
-- PlayerJoined is after initialization
-- Execution order
-- PlayerRemoving > PlayerDestroyed
-- PlayerRemoving is when you deinitialize the player, for example saving sessiondata
-- PlayerDestroyed is after deinitialization
local PlayerManager = { PlayerAdded = {}, PlayerJoined = {}, PlayerRemoving = {}, PlayerDestroyed = {} }
-- Execution order
-- CharacterLoaded > CharacterAdded > CharacterSpawned
-- CharacterLoaded is when the Character is created
-- CharacterAdded is when the Player.Character is set to Character
-- CharacterSpawned is when the Character.Parent is set to workspace
-- CharacterRemoving > CharacterDestroyed
-- Died
local CharacterManager = PlayerManager
CharacterManager.Characters = {}
function CharacterManager.handleOnCharacterLoaded(player: Player, callback: (character: Model) -> ())
local CharacterLoaded = CharacterManager.Characters[player].CharacterLoaded
table.insert(CharacterLoaded, callback)
end
function CharacterManager.handleOnCharacterAdded(player: Player, callback: (character: Model) -> ())
local CharacterAdded = CharacterManager.Characters[player].CharacterAdded
table.insert(CharacterAdded, callback)
end
function CharacterManager.handleOnCharacterSpawned(player: Player, callback: (character: Model) -> ())
local CharacterSpawned = CharacterManager.Characters[player].CharacterSpawned
table.insert(CharacterSpawned, callback)
if player.Character then
task.spawn(callback, player.Character)
end
end
function CharacterManager.handleOnCharacterDied(player: Player, callback: (character: Model) -> ())
local Died = CharacterManager.Characters[player].Died
table.insert(Died, callback)
end
function CharacterManager.handleOnCharacterRemoving(player: Player, callback: (character: Model) -> ())
local CharacterRemoving = CharacterManager.Characters[player].CharacterRemoving
table.insert(CharacterRemoving, callback)
end
function CharacterManager.handleOnCharacterDestroyed(player: Player, callback: (character: Model) -> ())
local CharacterDestroyed = CharacterManager.Characters[player].CharacterDestroyed
table.insert(CharacterDestroyed, callback)
end
local function setDensity(Size: Vector3): number
local volume: number = Size.X * Size.Y * Size.Z
return 5 / volume
end
function CharacterManager._loadCharacter(player: Player)
local userid: number = Players:GetUserIdFromNameAsync(player.Name)
-- 215718515 // https://www.roblox.com/catalog/215718515/Fiery-Horns-of-the-Netherworld
-- 89171071 // https://www.roblox.com/catalog/89171071/Boss-White-Hat
-- 305888394 // https://www.roblox.com/catalog/305888394/Brighteyes-Witches-Brew-Hat
-- local humanoidDescription: HumanoidDescription = Players:GetHumanoidDescriptionFromUserId(userid)
-- humanoidDescription.HatAccessory ..= ",215718515 ,89171071, 305888394"
-- local character = Players:CreateHumanoidModelFromDescription(humanoidDescription, Enum.HumanoidRigType.R15)
local character = Players:CreateHumanoidModelFromUserId(userid)
character.Name = player.Name
character.ModelStreamingMode = Enum.ModelStreamingMode.PersistentPerPlayer
character:AddPersistentPlayer(player)
do
local humanoid: Humanoid = character.Humanoid
humanoid.BreakJointsOnDeath = CONFIGURATION.BreakJointsOnDeath
humanoid.RequiresNeck = CONFIGURATION.RequiresNeck
humanoid.AutomaticScalingEnabled = CONFIGURATION.AutomaticScalingEnabled
humanoid:SetStateEnabled(Enum.HumanoidStateType.Dead, CONFIGURATION.DeathEnabled)
local rootpart = humanoid.RootPart
rootpart.RootPriority = 127
if CONFIGURATION.SetDensity then
rootpart.CustomPhysicalProperties = PhysicalProperties.new(setDensity(rootpart.Size), 0.3, 0.5)
end
for _, descendant: Instance in character:GetDescendants() do
if descendant:IsA("BasePart") then
local basepart: BasePart = descendant
basepart.Material = Enum.Material.SmoothPlastic
basepart.CastShadow = false
basepart.Massless = true
basepart.CanTouch = false
basepart.CanQuery = false
end
end
if CONFIGURATION.SanitizeAccessories then
for _, accessory: Accessory in humanoid:GetAccessories() do
for _, descendant: Instance in accessory:GetDescendants() do
if BLACKLIST[descendant.ClassName] then
descendant:Destroy()
end
end
end
end
if CONFIGURATION.RagdollEnabled then
ragdollUtil(humanoid)
end
end
local CharacterLoaded = CharacterManager.Characters[player].CharacterLoaded
for _, callback: (character: Model) -> () in ipairs(CharacterLoaded) do
callback(character)
end
player.Character = character
end
function CharacterManager._spawnCharacter(player: Player)
local character = player.Character
character.Parent = CHARACTERS_FOLDER
local humanoid = character.Humanoid :: Humanoid
local rootpart = humanoid.RootPart
rootpart:SetNetworkOwner(player)
if not CONFIGURATION.LayeredClothingEnabled then
local humanoidDescription: HumanoidDescription = humanoid:GetAppliedDescription()
humanoidDescription:SetAccessories({}, false)
humanoid:ApplyDescriptionReset(humanoidDescription)
end
local CharacterSpawned = CharacterManager.Characters[player].CharacterSpawned
for _, callback: (character: Model) -> () in ipairs(CharacterSpawned) do
task.spawn(callback, character)
end
end
Players.PlayerAdded:Connect(function(addedPlayer: Player)
for _, callback in ipairs(PlayerManager.PlayerAdded) do
callback(addedPlayer)
end
for _, callback in ipairs(PlayerManager.PlayerJoined) do
task.spawn(callback, addedPlayer)
end
end)
function PlayerManager.handleOnPlayerAdded(callback: (Player) -> ())
table.insert(PlayerManager.PlayerAdded, callback)
for _, player: Player in ipairs(Players:GetPlayers()) do
task.spawn(callback, player)
end
end
PlayerManager.handleOnPlayerAdded(function(addedPlayer: Player)
CharacterManager.Characters[addedPlayer] = {
CharacterLoaded = {},
CharacterAdded = {},
CharacterSpawned = {},
Died = {},
CharacterRemoving = {},
CharacterDestroyed = {},
}
end)
function PlayerManager.handleOnPlayerJoined(callback: (Player) -> ())
table.insert(PlayerManager.PlayerJoined, callback)
for _, player: Player in ipairs(Players:GetPlayers()) do
task.spawn(callback, player)
end
end
PlayerManager.handleOnPlayerJoined(function(joiningPlayer: Player)
local player = joiningPlayer
do
player.CharacterAdded:Connect(function(addedCharacter: Model)
local character = addedCharacter
if CharacterManager.Characters[player] then
local CharacterAdded = CharacterManager.Characters[player].CharacterAdded
for _, callback in ipairs(CharacterAdded) do
task.spawn(callback, character)
end
end
character.Destroying:Once(function()
local CharacterDestroyed = CharacterManager.Characters[player].CharacterDestroyed
for _, callback in ipairs(CharacterDestroyed) do
task.defer(callback, character)
end
end)
CharacterManager._spawnCharacter(player)
end)
end
CharacterManager.handleOnCharacterSpawned(player, function(spawnedCharacter)
local humanoid = spawnedCharacter.Humanoid :: Humanoid
humanoid.Died:Once(function()
local Died = CharacterManager.Characters[player].Died
for _, callback in ipairs(Died) do
task.defer(callback, humanoid)
end
end)
end)
CharacterManager.handleOnCharacterDied(player, function()
if CONFIGURATION.CorpseEnabled then
local corpse = player.Character:Clone()
player.Character = nil
corpse.Parent = workspace
local humanoid: Humanoid = corpse.Humanoid
humanoid:ChangeState(Enum.HumanoidStateType.Dead)
Debris:AddItem(corpse, CONFIGURATION.CorpseTime)
end
task.delay(CONFIGURATION.RespawnTime, function()
player.Character = nil
CharacterManager._loadCharacter(player)
end)
end)
do
player.CharacterRemoving:Connect(function(removingCharacter)
local character = removingCharacter
local humanoid = character.Humanoid :: Humanoid
if CONFIGURATION.KillOnDespawned then
humanoid:SetStateEnabled(Enum.HumanoidStateType.Dead, true)
humanoid:ChangeState(Enum.HumanoidStateType.Dead)
end
local CharacterRemoving = CharacterManager.Characters[player].CharacterRemoving
for _, callback in ipairs(CharacterRemoving) do
task.defer(callback, character)
end
end)
end
do
player.Destroying:Once(function()
for _, callback in ipairs(PlayerManager.PlayerDestroyed) do
task.defer(callback, player)
end
CharacterManager.Characters[player] = nil
end)
end
CharacterManager._loadCharacter(player)
end)
Players.PlayerRemoving:Connect(function(removingPlayer: Player)
for _, callback in ipairs(PlayerManager.PlayerRemoving) do
task.spawn(callback, removingPlayer)
end
end)
function PlayerManager.handleOnPlayerRemoving(callback: (Player) -> ())
table.insert(PlayerManager.PlayerRemoving, callback)
end
function PlayerManager.handleOnPlayerDestroyed(callback: (Player) -> ())
table.insert(PlayerManager.PlayerDestroyed, callback)
end
if CONFIGURATION.TestEnabled then
PlayerManager.handleOnPlayerAdded(function()
print("handleOnPlayerAdded", tick())
end)
PlayerManager.handleOnPlayerJoined(function(player: Player)
print("handleOnPlayerJoined", tick())
CharacterManager.handleOnCharacterLoaded(player, function()
print("handleOnCharacterLoaded", tick())
end)
CharacterManager.handleOnCharacterAdded(player, function()
print("handleCharacterAdded", tick())
end)
CharacterManager.handleOnCharacterSpawned(player, function()
print("handleOnCharacterSpawned", tick())
end)
CharacterManager.handleOnCharacterRemoving(player, function()
print("handleOnCharacterRemoving", tick())
end)
CharacterManager.handleOnCharacterDestroyed(player, function()
print("handleOnCharacterDestroyed", tick())
end)
end)
PlayerManager.handleOnPlayerRemoving(function()
print("handleOnPlayerRemoving", tick())
end)
PlayerManager.handleOnPlayerDestroyed(function()
print("handleOnPlayerDestroyed", tick())
end)
end
return PlayerManager