It’s real basic I know and it doesn’t consider / cover much. Plus I haven’t tested it out. What do you guys think? Hm well I think it looks ok. Any suggestions or mistakes I made?
Here’s the uh code:
local DataStoreService = game:GetService("DataStoreService")
-- You could integrate this with your existing data store if you have one
local DataStore = DataStoreService:GetDataStore("DataStore")
local Tracker = {}
Tracker.__index = Tracker
Tracker.Index = {}
-- Globals
-- Just in case they leave / disconnect, we'll log their UserIds
local ListOfPlayers = {}
local MAX_STRIKES = 5
-- Utility Functions
local function yieldCallback(waitTime: number, callback: any)
if type(callback) ~= "function" then return end
task.wait(waitTime)
callback()
waitTime = nil
end
-- Class Functions
function Tracker.new(Player: Player)
DataStore:UpdateAsync(Player.UserId, function(oldValue)
-- Check to see if the player is banned
if not oldValue then
return {["IsBanned"] = false}
end
if oldValue.IsBanned == true then Player:Kick("You're banned. That's it.") end
end)
local self = setmetatable({}, Tracker)
self.__index = Tracker
self.Player = Player
self.LoggedName = Player.Name
self.Strikes = 0
self.PositionHistory = {}
self.States = {}
Tracker.Index[Player.Name] = self
-- If a change occurs, we'll always kinda will have the updated changes
Tracker.Index[Player.Name].__index = self
ListOfPlayers[Player.Name] = Player.UserId
return self
end
function Tracker:GetPlayerTracker(playerName: string)
return Tracker.Index[playerName]
end
function Tracker:RemoveTracker(playerName: string)
if Tracker.Index[playerName] then
Tracker.Index[playerName].__index, Tracker.Index[playerName], playerName = nil, nil, nil
end
end
-- Object Instance Functions
function Tracker:BanPlayer()
if not self.Player and self.LoggedName and ListOfPlayers[self.LoggedName] then
local Player = ListOfPlayers[self.LoggedName]
DataStore:UpdateAsync(Player.UserId, function(oldValue)
return {["IsBanned"] = true}
end)
end
end
function Tracker:AddStrike()
if not self.Strikes then return end
self.Strikes += 1
warn(`Strike given to {self.Player.Name}`)
warn(`Strike total is at {self.Strikes}`)
if self.Strikes == MAX_STRIKES then self.Player:Kick("You've been flagged by our system too many times. This is not a ban message.") end
end
function Tracker:LogPlayerState()
if not self.Player or not self.PositionHistory or not self.States then return end
if self.Player.Character.Humanoid:GetState() == nil then
warn(`Mismatched player states for {self.Player.Name}`)
return self:AddStrike()
end
local State = self.Player.Character.Humanoid:GetState()
table.insert(self.PositionHistory, self.Player.Character.HumanoidRootPart.CFrame)
self.States[State] = State
State = nil
end
function Tracker:RollbackPosition()
if not self.Player or not self.PositionHistory or not self.PositionHistory[#self.PositionHistory] then return end
self.Player.Character.HumanoidRootPart.CFrame = self.PositionHistory[#self.PositionHistory]
end
function Tracker:PositionSanityCheck()
if not self.PositionHistory or not self.PositionHistory[#self.PositionHistory] or not self.PositionHistory[#self.PositionHistory - 1] then return end
local CurrentPosition = self.PositionHistory[#self.PositionHistory]
local PreviousPosition = self.PositionHistory[#self.PositionHistory - 1]
local Distance = (CurrentPosition.Position - PreviousPosition.Position).Magnitude
if (self.Player.Character.Humanoid.WalkSpeed - (Distance / 5)) > 2 then return self:AddStrike() end
CurrentPosition, PreviousPosition, Distance = nil, nil, nil
end
function Tracker:IsFlying()
if not self.Player then return end
if self.Player.Character.Humanoid.FloorMaterial == Enum.Material.Air then
return true
else return false
end
end
function Tracker:IsNoClipping()
if not self.Player then return end
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Include
Params.FilterDescendantsInstances = self.Player.Character:GetChildren()
-- Cast a ray in the look direction of the player multiplied by 2 (so 2 studs forward)
local Cast1 = workspace:Raycast(self.Player.Character.HumanoidRootPart.Position, self.Player.Character.HumanoidRootPart.CFrame.LookVector * 2, Params)
-- Cast a ray in the opposite of the look direction of the player multiplied by 2 (so 2 studs backwards)
local Cast2 = workspace:Raycast(self.Player.Character.HumanoidRootPart.Position, -self.Player.Character.HumanoidRootPart.CFrame.LookVector * 2, Params)
if Cast1 and Cast2 then
warn(`{self.Player.Name} is possibly no-clipping (could be innocent no-clipping)`)
Cast1, Cast2, Params = nil, nil, nil
return self:AddStrike()
end
end
return Tracker