A lot better than the old versions, but if it were up to me, I’d also include the fact that TeleportData is sent by the client, therefore letting the client edit it freely. I’ve seen many devs send player data through it, and wonder how exploiters have an infinite amount of currency.
There’s also the fact that humanoid animations currently replicate, allowing exploiters to play any animation they want; they are also able to spam the .AnimationPlayed connection, which, depending on the game, can cause lag or crash it.
While it is mentioned elsewhere, you should include the fact that InvokeClient can yield indefinitely if an exploiter returns task.wait(9e9) or similar. And, Roblox secrets should be included for security for api keys and the like. Maybe add a note that this isn’t to store values securely on the client.
On top of this, there are currently internal signals (I won’t say what) that allow exploiters to respawn faster than RespawnTime or stay dead longer than RespawnTIme. And I’m pretty sure there’s some method to gain ownership of accessories when the player dies, allowing for re-animation exploits.
Snippet from my old ac
function CharacterHandler:Add(Player, Character)
local Head = Character:FindFirstChild("Head")
local Humanoid, HRP = Character:FindFirstChild("Humanoid"), Character:FindFirstChild("HumanoidRootPart")
if not HRP or not Humanoid then
return
end
local Animator = Humanoid:FindFirstChild("Animator")
local Data = self.PlayerData[Player]
if self.LastDeaths[Player] and os.clock() - self.LastDeaths[Player] < (Players.RespawnTime - 0.5) then
return self:TakeAction(Data, "Fast Death", function()
Player:Kick("Fast Death")
end)
end
table.insert(Data.Connections, Player.CharacterAppearanceLoaded:Connect(function()
local Accessories = Humanoid:GetAccessories()
if #Accessories > 50 then
return self:TakeAction(Data, "Hat Crash", function()
Player:Kick("Hat Crash")
end)
end
for _, Accessory in Accessories do
if Accessory:FindFirstChildWhichIsA("Script", true) then
return self:TakeAction(Data, "Hat Backdoor", function()
Player:Kick("Hat Backdoor")
end)
end
for _, Vector in {"X", "Y", "Z"} do
local Handle = Accessory:FindFirstChild("Handle")
if Handle and Handle.Size[Vector] >= 5 then
Accessory:Destroy()
end
end
end
end))
table.insert(Data.Connections, Animator.AnimationPlayed:Connect(function(AnimationTrack)
Data.Tolerance.AnimationPlayed += 1
if os.clock() - Data.Timing.LastAnimation >= 1 then
if Data.Tolerance.AnimationPlayed >= 50 then
self:TakeAction(Data, "Animation Spam", function()
Player:Kick("Animation Spam")
end)
end
Data.Tolerance.AnimationPlayed = 0
Data.Timing.LastAnimation = os.clock()
end
local AnimationId = string.match(AnimationTrack.Animation.AnimationId, "%d+")
if not self.Whitelist[AnimationId] then
self:TakeAction(Data, "Animation", function()
Player:Kick("Animation")
end)
end
if AnimationTrack.Speed >= 10 then
self:TakeAction(Data, "Animation Speed", function()
self:BreakJoints(Data)
end)
end
end))
table.insert(Data.Connections, Humanoid.StateChanged:Connect(function(Old, New)
Data.Tolerance.StateChanges += 1
if os.clock() - Data.Timing.LastState >= 1 then
if Data.Tolerance.StateChanges >= 50 then
self:TakeAction(Data, "State Spam", function()
Player:Kick("State Spam")
end)
end
Data.Tolerance.StateChanges = 0
Data.Timing.LastState = os.clock()
end
if Data and Old == Enum.HumanoidStateType.Dead and New ~= Enum.HumanoidStateType.Dead then
self:TakeAction(Data, "God", function()
self:BreakJoints(Data)
end)
elseif Old ~= Enum.HumanoidStateType.Dead and New == Enum.HumanoidStateType.Dead then
if Data and Players.CharacterAutoLoads then
task.delay(Players.RespawnTime + 0.5, function()
if Player.Character == Character then
self:TakeAction(Data, "Respawn Time Exceeded", function()
Player:Kick("Respawn Time Exceeded")
end)
end
end)
end
end
end))
table.insert(Data.Connections, Humanoid.PlatformStanding:Connect(function()
self:TakeAction(Data, "Platform Standing", function()
Player:Kick("Humanoid Platform Standing")
end)
end))
table.insert(Data.Connections, Humanoid.AncestryChanged:Connect(function(_, Parent)
if game:IsAncestorOf(Character) then
if Character.PrimaryPart and Character:IsAncestorOf(Character.PrimaryPart) then
if not Parent or not game:IsAncestorOf(Humanoid) then
self:TakeAction(Data, "God", function()
self:BreakJoints(Data)
end)
end
end
end
end))
table.insert(Data.Connections, Character.ChildAdded:Connect(function(Child)
if Child:IsA("BasePart") then
Child.CollisionGroup = "Characters"
elseif Child:IsA("Tool") then
local Count = 0
for _, Tool in Character:GetChildren() do
if Tool:IsA("Tool") then
Count += 1
end
end
if Count > 1 then
self:TakeAction(Data, "Multiple Tools", function()
self:BreakJoints(Data)
end)
end
end
end))
table.insert(Data.Connections, Humanoid.Died:Connect(function()
task.delay(1, function()
if Player.Character == Character then
for _, Accessory in Humanoid:GetAccessories() do
Accessory:Destroy()
end
end
end)
for _, Descendant in Character:GetDescendants() do
if Descendant:IsA("BasePart") then
if Descendant:CanSetNetworkOwnership() then
Descendant:SetNetworkOwner(nil)
end
Descendant.AssemblyLinearVelocity = Vector3.zero
elseif Descendant:IsA("Script") then -- Health script, doesnt have a .died check so it will regen health with inf death making you unkillable
Descendant:Destroy()
end
end
self.LastDeaths[Player] = os.clock()
end))
end