This is a sub-page of Roblox (Engine).
DebugDraw#
Found in content\avatar\scripts\RobloxCharacterPackages-0.0.1.rbxm under RobloxCharacterPackages.RobloxCharacterPackages.CharacterController.Dev.DebugDraw.
local srcPath = game.ReplicatedStorage.RobloxCharacterPackages.CharacterController
local Util = require(srcPath.Utils.Util)
local DebugDraw = {}
-- Forward declare private functions
local _drawDashedLine
local _initSeries
local _getSeriesDrawContext
local _getGenericDrawContext
DebugDraw.config = {
-- Default color for a plot, if not passed via plotPoint()
defaultPlotColor = Color3.fromRGB(255, 255, 255),
-- Background color for the graph
plotBGColor = Color3.fromRGB(0, 0, 0),
-- Graph position relative to player
graphPosition = Vector3.new(-30, 0, -20),
-- Graph width/height
graphDimensions = Vector3.new(60, 10, 0),
-- The number of plots to fit inside the fixed graphDimensions. More plots
-- make the graph draw slower. Graph resets after filling up.
maxPlots = 1200,
-- Margins between plots and edge of graph
margin = Vector3.new(0, .5, 0),
-- Draw a dotted line at zero
drawZeroLine = true,
-- Force a min/max, else auto-calculate the current running min/max
fixedMin = nil,
fixedMan = nil,
}
-- Nested data structure storing state for each series. See _initSeries().
DebugDraw.series = {}
--[[
USAGE:
DebugDraw.config.drawZeroLine = false
DebugDraw.plotPoint("PosX", humanoid.RootPart.Position.X, Color3.fromRGB(255, 0, 0))
DebugDraw.plotPoint("PosY", humanoid.RootPart.Position.Y, Color3.fromRGB(0, 255, 0))
DebugDraw.plotPoint("PosZ", humanoid.RootPart.Position.Z, Color3.fromRGB(0, 120, 255))
--]]
function DebugDraw.plotPoint(key: string, value: number, color: Color3?)
local graphDimensions = DebugDraw.config.graphDimensions
local lowerLeft = Vector3.zero - (graphDimensions / 2) + DebugDraw.config.margin
local upperRight = Vector3.zero + (graphDimensions / 2) - DebugDraw.config.margin
local drawContext = _initSeries(key, color).drawContext
if drawContext == nil then
return
end
-- Start over if we've hit the graph width
if (#DebugDraw.series[key].values > DebugDraw.config.maxPlots) then
DebugDraw.series[key].values = {}
end
drawContext:Clear()
-- Initialize min/max values
if DebugDraw.series[key].minValue == nil then
DebugDraw.series[key].minValue = value
end
if DebugDraw.series[key].maxValue == nil then
DebugDraw.series[key].maxValue = value
end
-- Track min/max values to auto-scale the graph, or just use the min/max
-- passed in via config.
if (DebugDraw.config.minValue == nil) then
if value < DebugDraw.series[key].minValue then
DebugDraw.series[key].minValue = value
end
else
DebugDraw.series[key].minValue = DebugDraw.config.minValue
end
if (DebugDraw.config.maxValue == nil) then
if value > DebugDraw.series[key].maxValue then
DebugDraw.series[key].maxValue = value
end
else
DebugDraw.series[key].maxValue = DebugDraw.config.maxValue
end
-- Add this value to the list of all values
-- XXX: This method assumes a constant deltaTime. Could store pairs of
-- gameTime/value, but iterating over lua tables like this was ugly.
table.insert(DebugDraw.series[key].values, value)
-- Draw zero line
if DebugDraw.config.drawZeroLine then
local remappedZero = Util.remapClamped(
0,
DebugDraw.series[key].minValue,
DebugDraw.series[key].maxValue,
lowerLeft.Y,
upperRight.Y
)
local zeroInRange = remappedZero > DebugDraw.series[key].minValue and
remappedZero < DebugDraw.series[key].maxValue
if zeroInRange then
_drawDashedLine(
drawContext,
Vector3.new(lowerLeft.X, remappedZero, 0),
Vector3.new(upperRight.X, remappedZero, 0)
)
end
end
-- Don't draw anything until we have at least 2 plots
if (#DebugDraw.series[key].values < 2) then
return
end
-- Plot all values
local lastValue = nil
local lastTime = nil
for i, val in ipairs(DebugDraw.series[key].values) do
-- Start plotting from the second index
if i < 2 then
continue
end
-- Remap values to min/max of graph
local remappedValue = Util.remapClamped(
val,
DebugDraw.series[key].minValue,
DebugDraw.series[key].maxValue,
lowerLeft.Y,
upperRight.Y)
-- Remap time to min/max of graph
local remappedTime = Util.remapClamped(
i - 1,
1,
DebugDraw.config.maxPlots,
lowerLeft.X,
upperRight.X)
-- Handle first time we plot
if lastValue == nil then
lastValue = remappedValue
end
if lastTime == nil then
lastTime = remappedTime
end
-- Draw a line between this point and the last point
local lastPoint: Vector3 = Vector3.new(lastTime, lastValue, 0)
local curPoint: Vector3 = Vector3.new(remappedTime, remappedValue, 0)
drawContext:AddLine(
lastPoint,
curPoint
)
-- Store last point for next iteration
lastValue = remappedValue
lastTime = remappedTime
end
end
function DebugDraw.drawLine(point1: Vector3, point2: Vector3, color: Color3?)
local debugDraw = _getGenericDrawContext()
color = color or Color3.fromRGB(255, 255, 255)
debugDraw.Color3 = color
debugDraw:AddLine(point1, point2)
return debugDraw
end
function DebugDraw.drawRay(origin: Vector3, ray: Vector3, color: Color3?)
local debugDraw = _getGenericDrawContext()
color = color or Color3.fromRGB(255, 255, 255)
debugDraw.Color3 = color
debugDraw:AddLine(origin, origin + ray)
return debugDraw
end
function DebugDraw.clear()
local debugDraw = _getGenericDrawContext()
debugDraw:Clear()
end
_drawDashedLine = function(debugDraw, point1: Vector3, point2: Vector3)
local length = (point2 - point1).Magnitude
local step = .25 / length
local dashRatio = .5
for t = 0, 1 - step, step do
debugDraw:AddLine(
point1:Lerp(point2, t),
point1:Lerp(point2, t + (step * dashRatio))
)
end
end
_initSeries = function(key: string, color: Color3?)
color = color or DebugDraw.config.defaultPlotColor
if DebugDraw.series[key] ~= nil then
-- Cached series
return DebugDraw.series[key]
else
-- Construct new series
DebugDraw.series[key] = {
values = {},
minValue = nil,
maxValue = nil,
}
DebugDraw.series[key].drawContext = _getSeriesDrawContext(key)
DebugDraw.series[key].drawContext.Color3 = color
return DebugDraw.series[key]
end
end
_getSeriesDrawContext = function(key: string)
if DebugDraw.series[key].drawContext ~= nil then
return DebugDraw.series[key].drawContext
end
-- Create plot frame
local plotFrame = Instance.new("Part")
plotFrame.Parent = workspace
plotFrame.Name = "debugPlotFrame_"..key
plotFrame.Anchored = true
plotFrame.CanCollide = false
plotFrame.CanQuery = false
plotFrame.CanTouch = false
plotFrame.Size = DebugDraw.config.graphDimensions
plotFrame.Color = DebugDraw.config.plotBGColor
local playerPos = game.Players.LocalPlayer.Character.HumanoidRootPart.Position
plotFrame.Position = playerPos + DebugDraw.config.graphPosition +
(DebugDraw.config.graphDimensions / 2)
-- Create drawing context
local drawContext = Instance.new("WireframeHandleAdornment")
drawContext.Parent = plotFrame
drawContext.Name = "debugDraw_"..key
drawContext.Adornee = plotFrame
-- Cache the drawing context
DebugDraw.series[key].drawContext = drawContext
return drawContext
end
_getGenericDrawContext = function()
if DebugDraw.genericDrawContext ~= nil then
return DebugDraw.genericDrawContext
end
-- Create drawing context
local drawContext = Instance.new("WireframeHandleAdornment")
local owner = Instance.new("Part")
owner.Parent = workspace
owner.Name = "debugDraw_genericOwner"
owner.Anchored = true
owner.CanCollide = false
owner.CanQuery = false
owner.CanTouch = false
drawContext.Parent = owner
drawContext.Name = "debugDraw_genericDrawContext"
drawContext.Adornee = owner
-- Cache the drawing context
DebugDraw.genericDrawContext = drawContext
return drawContext
end
return DebugDraw
DebugVisual#
Found in content\avatar\scripts\RobloxCharacterPackages-0.0.1.rbxm under RobloxCharacterPackages.RobloxCharacterPackages.CharacterController.Utils.DebugVisual.
local DebugVisual = {}
local DEBUG_VISUAL = false
local RED = Color3.new(1,0,0)
local GREEN = Color3.new(0,1,0)
local WHITE = Color3.new(1,1,1)
local MAX_POOL_SIZE = 500
DebugVisual.debugLHAPool = {}
DebugVisual.nextLHAIndex = 1
local MAX_POOL_SIZE_SPHERE = 10
DebugVisual.debugSpherePool = {}
DebugVisual.nextSphereIndex = 1
-- Line
function DebugVisual:DrawLine(origin : Vector3, dir : Vector3, color : Color3)
if not DEBUG_VISUAL then
return
end
local lha = self:GetNextDebugLHA()
lha.Color3 = color
lha.Length = dir.Magnitude
lha.CFrame = CFrame.new(origin, origin + dir)
lha.Visible = true
end
function DebugVisual:ClearRays()
for i, lha in ipairs(self.debugLHAPool) do
lha.Visible = false
end
self.nextLHAIndex = 1
end
function DebugVisual:GetNextDebugLHA()
if self.nextLHAIndex > MAX_POOL_SIZE then
self.nextLHAIndex = 1
end
local lha = self.debugLHAPool[self.nextLHAIndex]
if not lha then
lha = Instance.new("LineHandleAdornment")
lha.Name = "LHA_" .. tostring(#self.debugLHAPool+1)
lha.Color3 = Color3.new(1,1,1)
lha.Thickness = 4
lha.ZIndex = 0
lha.Adornee = game.Workspace.Terrain
lha.Parent = game.Workspace.Terrain
lha.Transparency = 0.5
table.insert(self.debugLHAPool,lha)
end
self.nextLHAIndex = self.nextLHAIndex + 1
return lha
end
-- Sphere
function DebugVisual:DrawSphere(origin : Vector3, radius : number, color : Color3)
if not DEBUG_VISUAL then
return
end
local d = radius * 2
local sOrigin = self:GetNextDebugSphere()
sOrigin.Color = color
sOrigin.Position = origin
sOrigin.Size = Vector3.new(d, d, d)
sOrigin.Transparency = 0.95
end
function DebugVisual:ClearSpheres()
for i, s in ipairs(self.debugSpherePool) do
s.Transparency = 1
end
self.nextSphereIndex = 1
end
function DebugVisual:GetNextDebugSphere()
if self.nextSphereIndex > MAX_POOL_SIZE_SPHERE then
self.nextSphereIndex = 1
end
local s = self.debugSpherePool[self.nextSphereIndex]
if not s then
s = Instance.new("Part")
s.Shape = "Ball"
s.Material = Enum.Material.SmoothPlastic
s.CollisionGroup = "PosedCloneGhostGroup"
s.Anchored = true
s.Name = "Sphere_" .. tostring(#self.debugSpherePool+1)
s.Color = Color3.new(1, 1, 1)
s.Size = Vector3.new(1, 1, 1)
s.Position = Vector3.new()
s.Parent = game.Workspace.Terrain
s.Transparency = 0.9
table.insert(self.debugSpherePool, s)
end
self.nextSphereIndex = self.nextSphereIndex + 1
return s
end
-- Ray cast visual with hit normal
function DebugVisual:DrawRayCast(origin : Vector3, dir : Vector3, result : RaycastResult)
if not DEBUG_VISUAL then
return
end
local color = if result then GREEN else WHITE
self:DrawLine(origin, dir, color)
if result then
self:DrawLine(result.Position, result.Normal, RED)
end
end
-- Sphere cast visual with hit normal
function DebugVisual:DrawSphereCast(origin : Vector3, dir : Vector3, radius : number, result : RaycastResult)
if not DEBUG_VISUAL then
return
end
local color = if result then GREEN else WHITE
self:DrawSphere(origin, radius, color)
self:DrawSphere(origin + dir, radius, color)
if result then
self:DrawLine(result.Position, result.Normal, RED)
end
end
-- Self cleanup for debug visuals (refreshes 1/10th of the visual per heartbeat)
if DEBUG_VISUAL then
game:GetService("RunService").Heartbeat:Connect(function(dt)
for i = 1, math.floor(MAX_POOL_SIZE / 10) do
DebugVisual:DrawLine(Vector3.new(), Vector3.new(), Color3.new(1, 1, 1))
end
for i = 1, math.floor(MAX_POOL_SIZE_SPHERE / 10) do
DebugVisual:DrawSphere(Vector3.new(), 1, Color3.new(1, 1, 1))
end
end)
end
return DebugVisual
EmulatedRootMotionClip#
Found in content\avatar\scripts\RobloxCharacterPackages-0.0.1.rbxm under RobloxCharacterPackages.RobloxCharacterPackages.CharacterController.Utils.EmulatedRootMotionClip. This script is listed to only be used for testing.
-- THIS SHOULD BE USED FOR TESTING
-- If something needs RM, just export it with the animation
local Layer = require(game.ReplicatedStorage.RobloxCharacterPackages.MotionCompositor.Base.Layer)
-- Emulates linear constant root motion when defined in config
local EmulatedRootMotionClip = Layer:extend("EmulatedRootMotionClip")
EmulatedRootMotionClip.defaultProps = {
speed = 0,
dt = 0,
}
function EmulatedRootMotionClip:update(dt)
for _, layer in self.children do
layer:update(dt)
end
end
function EmulatedRootMotionClip:getDuration(): number
if #self.children == 1 then
return self.children[1]:getDuration()
end
return 0
end
function EmulatedRootMotionClip:updateToPhase(phase: number)
if #self.children == 1 then
self.children[1]:updateToPhase(phase)
end
end
function EmulatedRootMotionClip:evaluate(mask)
local pose = if #self.children == 1
then self.children[1]:evaluate(mask)
else mask:CreateEmptyPose()
-- The dir vector is just taken as character space forward hence (0, 0, -1)
pose:SetTranslation("RootMotionDelta", Vector3.new(0, 0, -1) * self.props.speed * self.props.dt)
return pose
end
return EmulatedRootMotionClip
SensorUtil#
Found in content\avatar\scripts\RobloxCharacterPackages-0.0.1.rbxm under RobloxCharacterPackages.RobloxCharacterPackages.CharacterController.Utils.SensorUtil. It's possible that only the debug parts are actually unused. So far no uses of this module have been found.
local SensorUtil = {}
local DEBUG_VISUAL = false
local UP = Vector3.new(0, 1, 0)
local DOWN = Vector3.new(0, -1, 0)
local FORWARD = Vector3.new(0, 0, -1)
-- Distribute points evenly using spiral sunflower pattern
local goldenAngle = math.pi * (3 - math.sqrt(5))
local pi2 = 2 * math.pi
local Players = game:GetService("Players")
local Utils = game.ReplicatedStorage.RobloxCharacterPackages.CharacterController.Utils
local TemporalCache = require(Utils.TemporalCache)
local DebugVisual = require(Utils.DebugVisual)
SensorUtil.scanCache = {}
-- TODO: keep the results for a forward cast globally for all abilities to use??
local function orthogonal(v : Vector3)
local dir = v.Unit
if dir.x ~= 0 or dir.y ~= 0 then
return Vector3.new(-dir.y, dir.x, 0).Unit
else
return Vector3.new(0, -dir.z, dir.y).Unit
end
end
local function randFloat(min, max)
local range = max - min
local randomValue = math.random() * range + min
return randomValue
end
function SensorUtil:Raycast(origin, dir : Vector3, raycastParams : RaycastParams)
if typeof(origin) == "CFrame" then
origin = origin.Position
end
local result = nil
if raycastParams then
result = game.Workspace:Raycast(origin, dir, raycastParams)
else
result = game.Workspace:Raycast(origin, dir)
end
DebugVisual:DrawRayCast(origin, dir, result)
return result
end
-- Based on a microprofiling, Spherecast cost is roughly a handful of Raycasts (~ 4:1 ratio)
-- May vary based on the casting radius.
-- Spherecast would likely be preferred with simple linear area scans where we mostly care if something is hit
function SensorUtil:SphereScan(scanParams)
local radius = scanParams.radius or 0.5
local result = game.Workspace:Spherecast(scanParams.origin, radius, scanParams.dir, scanParams.raycastParams)
DebugVisual:DrawSphereCast(scanParams.origin, scanParams.dir, radius, result)
if not result then
return {}
end
-- TODO: investigate adding a temporal version of spherecast with option where filtered hit ancestors get added to the raycastparam instance filters
-- This is so the scan can eventually hit objects that may be obscured by other instances which the filter function wants to ignore
local filterFn = scanParams.filter
if not filterFn then
return {result}
end
if filterFn(result) then
return {result}
end
return {}
end
-- Cast a approximate cylindrical volume from a circular plane
-- Distribute rays in sunflower seed pattern and locally randomize a shift based on the average point density
-- Pattern rotation is modded over time to try and guarantee some level of uniform sampling
-- Spread can be adjusted for casting in a cone shape
function SensorUtil:RaycastScan(scanParams)
-- These are the minimal required params
local origin = scanParams.origin
local dir = scanParams.dir
-- raycastParams can be nil but not filtering is not recommended
local raycastParams = scanParams.raycastParams
-- Default cast is basically a triangle cast of a 1 unit wide cylindrical volume
local radius = scanParams.radius or 0.5
local rayCount = scanParams.rayCount or 3
local spread = scanParams.spread or 1
if typeof(origin) == "CFrame" then
origin = origin.Position
end
local results = {}
if not origin then
warn("RaycastScan missing parameter: origin")
return results
end
if not dir then
warn("RaycastScan missing parameter: dir")
return results
end
-- Find two perpendicular unit vectors on the orthogonal plane
local perp1 = orthogonal(dir)
local perp2 = dir.Unit:Cross(perp1).Unit
local timeOffset = tick() % pi2
local ptRange = math.sqrt((math.pi * radius * radius) / rayCount) / 2
local xOffset = randFloat(-ptRange, ptRange)
local yOffset = randFloat(-ptRange, ptRange)
local centerCast = origin + dir
local insertFn = function (result)
if result then
table.insert(results, result)
end
end
local filterFn = scanParams.filter
if filterFn then
insertFn = function (result)
if result and filterFn(result) then
table.insert(results, result)
end
end
end
for i = 1, rayCount do
local angle = goldenAngle * i + timeOffset
local r = math.sqrt(i / rayCount) * radius
local x = r * math.cos(angle) + xOffset
local y = r * math.sin(angle) + yOffset
local result = nil
local offset = x * perp1 + y * perp2
local castSource = origin + offset
if spread == 1.0 then
result = self:Raycast(castSource, dir, raycastParams)
else
local dirSpread = (centerCast + offset * spread) - castSource
result = self:Raycast(castSource, dirSpread, raycastParams)
end
insertFn(result)
end
return results
end
-- Similar to RaycastScan but uses a unique scanID to cache results for expireTime seconds
-- Offers a tradeoff between data staleness and performance (more comprehensive scan over time)
-- A particular instance will be able to have it's own cache with different sub caches
function SensorUtil:RaycastScanTemporal(scanParams, instance : Instance, scanID : string?, expireTime : number?)
if not instance then
warn("RaycastScanTemporal requires a instance.")
return
end
scanID = scanID or "DEFAULT"
expireTime = expireTime or 0.2
if not SensorUtil.scanCache[instance] then
SensorUtil.scanCache[instance] = {}
end
local cacheInstance = SensorUtil.scanCache[instance]
if not cacheInstance[scanID] then
cacheInstance[scanID] = TemporalCache:new(expireTime)
end
local cache = cacheInstance[scanID]
local results = self:RaycastScan(scanParams)
if results then
cache:insert(results)
end
return cache:get()
end
-- Specific folder for posed clones
local workspace = game:GetService("Workspace")
local posedClonesFolder = workspace:FindFirstChild("posedClones")
if not posedClonesFolder then
posedClonesFolder = Instance.new("Folder")
posedClonesFolder.Name = "posedClones"
posedClonesFolder.Parent = workspace
end
-- Returns a clone character that can be posed using the given animation (useful for pose prediction)
-- The hrp is returned to allow positioning, and animTrack allows selecting specific moment within the animation
function SensorUtil:posedClone(character:Model, animationAssetID:number)
-- generate a unique identifier for the clone (used for tracking and clean up)
local player = Players:GetPlayerFromCharacter(character)
local UID = "Player-" .. player.UserId .. ":Animation-" .. tostring(animationAssetID)
local oldClone = posedClonesFolder:FindFirstChild(UID)
if oldClone then
oldClone:Destroy()
end
-- Only archivable instances can be cloned
character.Archivable = true
local clonedCharacter:Model = character:Clone()
character.Archivable = false
clonedCharacter.Name = UID
-- TODO: only temporarily instantiate the clone to collect info, destroy afterwards
-- Remove unecessary instances in clone
for _, d in ipairs(clonedCharacter:GetChildren()) do
-- Remove all unecessary instances
if not (d:IsA("BasePart") or d:IsA("Humanoid")) then
d:Destroy()
end
end
for _, d in ipairs(clonedCharacter:GetDescendants()) do
if d:IsA("Decal") then
d:Destroy()
end
-- Move all parts into non-colliding group
if d:IsA("BasePart") then
d.CollisionGroup = "PosedCloneGhostGroup"
d.Transparency = math.max(d.Transparency, DEBUG_VISUAL and 0.5 or 1.0)
end
-- Reset all motors
if d:IsA("Motor6D") then
d.Transform = CFrame.new()
end
end
-- add to workspace after removing scripts so they don't excute
-- this should be done prior to animator stuff since animator needs to be part of the workspace before loading
clonedCharacter.Parent = posedClonesFolder
local animator = clonedCharacter:FindFirstChild("Animator", true)
if not animator then
animator = Instance.new("Animator")
animator.Parent = clonedCharacter:FindFirstChildOfClass("Humanoid")
end
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://" .. tostring(animationAssetID)
-- The caller can control animTrack position in case they need a specific keyframe
local animTrack = animator:LoadAnimation(animation)
animTrack:Play()
animTrack:AdjustSpeed(0)
-- The caller can move the posed clone to the desired moment in animation
local hrp = clonedCharacter:WaitForChild("HumanoidRootPart")
hrp.Anchored = true
return clonedCharacter, hrp, animTrack
end
function SensorUtil:AveragePosition(hits)
local n = #hits
if n == 0 then
return nil
end
local totalPosition = Vector3.new(0, 0, 0)
for _, hit in ipairs(hits) do
totalPosition = totalPosition + hit.Position
end
return totalPosition / n
end
return SensorUtil
CharacterStateMachineCommon#
Found in content\avatar\scripts\RobloxCharacterPackages-0.0.1.rbxm under RobloxCharacterPackages.RobloxCharacterPackages.CharacterController.StateMachine.CharacterStateMachineCommon. This is not the entire script, this is only the DEBUG code.
local DEBUG = false
if DEBUG then
print("CCSM Created", CCSM, CCSM.definition)
end
ServerMonitor#
Found in content\avatar\scripts\RobloxCharacterPackages-0.0.1.rbxm under RobloxCharacterPackages.RobloxCharacterPackages.CharacterController.StateMachine.ServerMonitor. This is not the entire script, this only contains parts with debug code.
local debug = false
function ServerMonitor:onServerHumanoidRemoteEventServer(
player: Player,
eventType: string,
data: any
)
if player == nil or player.Character == nil then
return
end
if eventType == "ChangeStateSM" then
-- changes to character state
local changeStateData: commonTypes.ChangeStateSMRecord = data
commonFuncs.SetState(changeStateData.character, changeStateData.newState)
local humanoid = changeStateData.character:FindFirstChildOfClass("Humanoid")
if changeStateData.newState == "Dead" then
if
self.trackedOwnershipList[changeStateData.character] == player
and humanoid
and humanoid.BreakJointsOnDeath
then
changeStateData.character:BreakJoints()
end
end
elseif eventType == "ChangeCollisionSettings" then
local settingsData: commonTypes.ChangeCollisionSettingsRecord = data
if settingsData ~= nil and settingsData.character ~= nil then
for k, v in pairs(settingsData.parts) do
local part: Part? = settingsData.character:FindFirstChild(k)
if part ~= nil then
part.CanCollide = v
end
end
end
elseif eventType == "RegisterHumanoidSM" then
if debug then
print("Server: New Humanoid", data)
end
self.trackedOwnershipList[data.characterModel] = player
elseif eventType == "ContextDataHumanoidSM" then
-- Received SM Context Information From Client
if debug then
print("Server: Received Context Data", data)
end
local contextXfer: commonTypes.ContextTransferDataType = data
-- check to see if we're expecting a transfer
local transferRecord: commonTypes.OwnerTransferDataType =
self.humanoidUploadTransferInProgress[contextXfer.character]
if transferRecord ~= nil then
-- Check to see if we're expecting it from this Client
if transferRecord.oldOwner == contextXfer.oldOwner then
self.humanoidUploadTransferInProgress[contextXfer.character] = nil
commonFuncs.SetState(contextXfer.character, contextXfer.currentState)
if transferRecord.newOwner == nil then
-- server owned
local humanoid: Humanoid? =
contextXfer.character:FindFirstChildOfClass("Humanoid")
if humanoid ~= nil and humanoid.Parent ~= nil then
local localHumanoidStateMachineEventInstance: Instance =
game.ServerScriptService
:WaitForChild("StateMachineSystemServer")
:WaitForChild("LocalHumanoidStateMachineEvent")
local localHumanoidStateMachineEvent: BindableEvent = nil
if localHumanoidStateMachineEventInstance:IsA("BindableEvent") then
localHumanoidStateMachineEvent = localHumanoidStateMachineEventInstance
end
if localHumanoidStateMachineEvent == nil then
print(
"Error !! Expecting LocalHumanoidStateMachineEvent under the character model"
)
end
local record: commonTypes.RunningStateMachineRecord? =
commonFuncs.ContinueStateMachine(
contextXfer.character,
humanoid,
self.ccsmDefinition,
self.localEvent,
contextXfer.currentState,
contextXfer.context
)
self.runningStateMachines[contextXfer.character] = record
end
self.trackedOwnershipList[contextXfer.character] = "server"
else
-- new client owner, send it to them
local newOwner = transferRecord.newOwner
local newTransferRecord: commonTypes.OwnerContextTransferDataType = {
oldOwner = transferRecord.oldOwner,
newOwner = newOwner,
currentState = contextXfer.currentState,
context = contextXfer.context,
}
self.humanoidStartupTransferInProgress[contextXfer.character] =
newTransferRecord
self.trackedOwnershipList[contextXfer.character] = newOwner
humanoidStateMachineUpdateEvent:FireClient(
newOwner,
"ContextDataHumanoidSM",
contextXfer
)
end
else
warn("Unexpected context recieved from", contextXfer.oldOwner)
end
end
elseif eventType == "ConfirmStartHumanoidSM" then
if data ~= nil then
local character: Model = data
local startupRecord: commonTypes.OwnerContextTransferDataType? =
self.humanoidStartupTransferInProgress[character]
if startupRecord == nil then
-- error
if debug then
warn(
"Startup confirmation from unexpected client",
character,
self.humanoidStartupTransferInProgress
)
end
else
-- check to verify that this client is supposed to own this
if startupRecord.newOwner == player then
-- clear record
self.humanoidStartupTransferInProgress[character] = nil
self.trackedOwnershipList[character] = player
else
warn(
"Unexpected client attempting to start up state machine",
player,
character
)
end
end
end
end
end
2013 Playerlist#
Downloaded at assetdelivery.roblox.com, found on the creator store at create.roblox.com. This is not the entire script, this only contains parts with debug code. Every line starting with a # isn't apart of the script and is added to provide more info on the code. 
# Line 321 to 326, only the debugprint() function is, well, debug.
function WaitForChild(parent,child)
while not parent:FindFirstChild(child) do
wait() debugprint(" child "..parent.Name.." waiting for "..child)
end
return parent[child]
end
# Line 896 to 935.
local debugFrame = Obj.Create"Frame"
{
Name = 'debugframe',
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(0, 150, 0, 800),--0.99000001
BackgroundTransparency = 1,
}
local debugplayers = Obj.Create"TextLabel"
{
BackgroundTransparency = .8,
Position = UDim2.new(0, 0, .01, 0),
Size = UDim2.new(1,0,.5,0),
Parent = debugFrame,
Font = 'ArialBold',
Text = '--',
FontSize='Size14',
TextWrapped=true,
TextColor3 = Color3.new(1,1,1),
TextStrokeColor3 = Color3.new(0,0,0),
TextStrokeTransparency = 0,
TextXAlignment = 'Right',
TextYAlignment = 'Center',
}
local debugOutput = Obj.Create"TextLabel"
{
BackgroundTransparency = .8,
Position = UDim2.new(0, 0, .5, 0),
Size = UDim2.new(1,0,.5,0),
Parent = debugFrame,
Font = 'ArialBold',
Text = '--',
FontSize='Size14',
TextWrapped=true,
TextColor3 = Color3.new(1,1,1),
TextStrokeColor3 = Color3.new(0,0,0),
TextStrokeTransparency = 0,
TextXAlignment = 'Right',
TextYAlignment = 'Center',
}
# Line 938 to 947.
--[[
simple function to toggle the display of debug output
--]]
local DebugPrintEnabled=true
function debugprint(str)
--print(str)
if DebugPrintEnabled then
debugOutput.Text=str
end
end
# Line 1145 to 1179, only the debug() function is, debug.
function WaitForClick(frameParent,polledFunction,exitFunction)
if WaitForClickLock then return end
WaitForClickLock=true
local upHappened=false
local connection, connection2
connection=BigButton.MouseButton1Up:connect(function(nx,ny)
exitFunction(nx,ny)
BigButton.Visible=false
connection:disconnect()
if connection2 then
connection2:disconnect()
end
--debugprint('mouse up!')
end)
connection2=BigButton.MouseMoved:connect( function(nx,ny)
polledFunction(nx,ny)
end)
--debugprint('waiting for click!')
BigButton.Visible=true
BigButton.Active=true
BigButton.Parent=frameParent
frameParent.AncestryChanged:connect(function(child,nparent)
if child == frameParent and nparent ==nil then
exitFunction(nx,ny)
BigButton.Visible=false
connection:disconnect()
connection2:disconnect()
debugprint("forced out of wait for click")
end
end)
WaitForClickLock=false
end
# Line 1230 to 1234, only debugprint() is debug.
function OnPrivilegeLevelSelect(player,nlevel,BanPlayerButton,VisitorButton,MemberButton,AdminButton)
debugprint('setting privilege level')
SetPrivilegeRank(player,nlevel)
HighlightMyRank(player,BanPlayerButton,VisitorButton,MemberButton,AdminButton)
end
# Line 1259 to 1280, only debugprint() is debug.
--------------------------
-- Report abuse handling
--------------------------
--[[
does final reporting of abuse on selected player, calls closeAbuseDialog
--]]
function OnSubmitAbuse()
if SubmitReportButton.Active then
if AbuseName and SelectedPlayer then
AbuseSettingsFrame.Visible = false
game.Players:ReportAbuse(SelectedPlayer, AbuseName, AbuseDescriptionBox.Text)
if AbuseName == "Rude or Mean Behavior" or AbuseName == "False Reporting Me" then
CalmingAbuseBox.Parent = ReportAbuseShield
else
debugprint('opening abuse box')
NormalAbuseBox.Parent = ReportAbuseShield
end
else
CloseAbuseDialog()
end
end
end
# Line 1282 to 1294, only debugfunction() is debug.
--[[
opens the abuse dialog, initialises text to display selectedplayer
--]]
function OpenAbuseDialog()
debugprint('adding report dialog')
AbusePlayerLabel.Text = SelectedPlayer.Name
--AbuseDescriptionBox.Text = ""
PopUpPanel:TweenPosition(UDim2.new(1,0,0,0), "Out", "Linear", BASE_TWEEN,true)
AbuseDescriptionBox=OriginalAbuseDescriptionBox:Clone()
AbuseDescriptionBox.Parent = AbuseDescriptionWrapper
ReportAbuseShield.Parent = ScreenGui
ClosePopUpPanel()
end
# Line 1440 to 1452, only debugprint() is debug. Function does continue after this point, it doesn't end here.
--[[
Called when stat is added
if playerEntry is localplayer, will add to score names and re-sort the stats, and resize the width of the leaderboard
for all players, will add a listener for if this stat changes
if stat is a string value, crashes the leaderboard
Note:change crash to a 'tabify' leaderboard later
@Args:
nchild new child value to leaderstats
playerEntry entry this stat was added to
--]]
function StatAdded(nchild,playerEntry)
-- dont re - add a leaderstat I alreday have
while AddingStatLock do debugprint('in stat added function lock') wait(1/30) end
# Line 1502 to 1512, only debugprint() is debug. Function does continue after this point, it doesn't end here.
--[[
Called when stat is removed from player
for all players, destroys the stat frame associated with this value,
then calls statchanged(to resize frame)
if playerEntry==localplayer, will remove from scorenames
@Args:
nchild ___value to be removed
playerEntry entry of player value is being removed from
--]]
function StatRemoved(nchild,playerEntry)
while AddingStatLock do debugprint('In Adding Stat Lock1') wait(1/30) end
AddingStatLock = true
if playerEntry['Frame']:FindFirstChild(nchild.Name) then
debugprint('Destroyed frame!')
playerEntry['Frame'][nchild.Name].Parent = nil
end
# Line 1578 to 1582, only debugprint() is debug.
if scoreval['Name'] == ScoreNames[1]['Name'] then
debugprint('changing score')
entry['Score'] = GetScoreValue(thisScore)
if entry['Player'] == LocalPlayer then HeaderScore.Text = tostring(GetScoreValue(thisScore)) end
end
# Line 1612 to 1667, only debugprint() is debug.
function RecreateScoreColumns(ptable)
while AddingStatLock do debugprint ('In Adding Stat Lock2') wait(1/30) end
AddingStatLock = true
local Xoffset=5--15 --current offset from Right
local maxXOffset=Xoffset
local MaxSizeColumn=0 --max size for this column
-- foreach known leaderstat
for j = #ScoreNames, 1,-1 do
local scoreval = ScoreNames[j]
MaxSizeColumn=0
-- for each entry in this player table
for i,entry in ipairs(ptable) do
local panel = entry['Frame']
local tplayer = entry['Player']
-- if this panel does not have an element named after this stat
if not panel:FindFirstChild(scoreval['Name']) then
-- make an entry for this object
local nentry = MakeScoreEntry(entry,scoreval,panel)
if nentry then
debugprint('adding '..nentry.Name..' to '..entry['Player'].Name )
nentry.Parent = panel
-- add score to team
if entry['MyTeam'] and entry['MyTeam'] ~= NeutralTeam and not entry['MyTeam']['Frame']:FindFirstChild(scoreval['Name']) then
local ntitle = nentry:Clone()
--ntitle.TextXAlignment = 'Right'
ntitle.Parent = entry['MyTeam']['Frame']
end
end
end
scoreval['XOffset']=Xoffset
if panel:FindFirstChild(scoreval['Name']) then
MaxSizeColumn=math.max(MaxSizeColumn,panel[scoreval['Name']].TextBounds.X)
end
end
if AreNamesExpanded.Value then
MaxSizeColumn=math.max(MaxSizeColumn,StatTitles[scoreval['Name'] ].TextBounds.X)
StatTitles[scoreval['Name'] ]:TweenPosition(UDim2.new(RightEdgeSpace,-Xoffset,0,0),'Out','Linear',BASE_TWEEN,true)
else
StatTitles[scoreval['Name'] ]:TweenPosition(UDim2.new((.4+((.6/#ScoreNames)*(j-1)))-1,0,0,0),'Out','Linear',BASE_TWEEN,true)
end
scoreval['ColumnSize']=MaxSizeColumn
Xoffset= Xoffset+SpacingPerStat+MaxSizeColumn
maxXOffset=math.max(Xoffset,maxXOffset)
end
NormalBounds = UDim2.new(0, BaseScreenXSize+maxXOffset-SpacingPerStat,0,800)
NormalPosition = UDim2.new(1 , -NormalBounds.X.Offset, NormalPosition.Y.Scale, 0)
UpdateHeaderNameSize()
UpdateMaximize()
AddingStatLock = false
end
# Line 1929 to 2012, only debugprint() is debug.
function RecreateScoreColumns(ptable)
while AddingStatLock do debugprint ('In Adding Stat Lock2') wait(1/30) end
AddingStatLock = true
local Xoffset=5--15 --current offset from Right
local maxXOffset=Xoffset
local MaxSizeColumn=0 --max size for this column
-- foreach known leaderstat
for j = #ScoreNames, 1,-1 do
local scoreval = ScoreNames[j]
MaxSizeColumn=0
-- for each entry in this player table
for i,entry in ipairs(ptable) do
local panel = entry['Frame']
local tplayer = entry['Player']
-- if this panel does not have an element named after this stat
if not panel:FindFirstChild(scoreval['Name']) then
-- make an entry for this object
local nentry = MakeScoreEntry(entry,scoreval,panel)
if nentry then
debugprint('adding '..nentry.Name..' to '..entry['Player'].Name )
nentry.Parent = panel
-- add score to team
if entry['MyTeam'] and entry['MyTeam'] ~= NeutralTeam and not entry['MyTeam']['Frame']:FindFirstChild(scoreval['Name']) then
local ntitle = nentry:Clone()
--ntitle.TextXAlignment = 'Right'
ntitle.Parent = entry['MyTeam']['Frame']
end
end
end
scoreval['XOffset']=Xoffset
if panel:FindFirstChild(scoreval['Name']) then
MaxSizeColumn=math.max(MaxSizeColumn,panel[scoreval['Name']].TextBounds.X)
end
end
if AreNamesExpanded.Value then
MaxSizeColumn=math.max(MaxSizeColumn,StatTitles[scoreval['Name'] ].TextBounds.X)
StatTitles[scoreval['Name'] ]:TweenPosition(UDim2.new(RightEdgeSpace,-Xoffset,0,0),'Out','Linear',BASE_TWEEN,true)
else
StatTitles[scoreval['Name'] ]:TweenPosition(UDim2.new((.4+((.6/#ScoreNames)*(j-1)))-1,0,0,0),'Out','Linear',BASE_TWEEN,true)
end
scoreval['ColumnSize']=MaxSizeColumn
Xoffset= Xoffset+SpacingPerStat+MaxSizeColumn
maxXOffset=math.max(Xoffset,maxXOffset)
end
NormalBounds = UDim2.new(0, BaseScreenXSize+maxXOffset-SpacingPerStat,0,800)
NormalPosition = UDim2.new(1 , -NormalBounds.X.Offset, NormalPosition.Y.Scale, 0)
UpdateHeaderNameSize()
UpdateMaximize()
AddingStatLock = false
end
# Line 2153 to 2159, only debugprint() is debug.
--]]
function LeaderstatsRemoved(oldLeaderstats,playerEntry)
while AddingFrameLock do debugprint('waiting to insert '..playerEntry['Player'].Name) wait(1/30) end
AddingFrameLock = true
RemoveAllStats(playerEntry)
AddingFrameLock = false
end
# Line 2176 to 2187, only debugprint() is debug. Function does continue after this point, it doesn't end here.
function InitMovingPanel( entry, player)
PopUpClipFrame.Parent= ScreenGui
if PopUpPanel then
PopUpPanel:Destroy()
end
PopUpPanel= PopUpPanelTemplate:Clone()
PopUpPanel.Parent= PopUpClipFrame
local nextIndex = 2
local friendStatus = GetFriendStatus(player)
debugprint (tostring(friendStatus))
# Line 2300 to 2308, only debugprint() is debug. Function does continue after this point, it doesn't end here.
--[[
this one's a doozie, happens when a player is added to the game
inits their player frame and player entry, assigns them to a team if possible,
and hooks up their leaderstats
@Args:
nplayer new player object to insert
--]]
function InsertPlayerFrame(nplayer)
while AddingFrameLock do debugprint('waiting to insert '..nplayer.Name) wait(1/30) end
# Line 2403 to 2411, only debugprint() is debug.
nplayer.ChildAdded:connect(function(nchild)
if nchild.Name == 'leaderstats' then
while AddingFrameLock do debugprint('in adding leaderstats lock') wait(1/30) end
if not nplayer:FindFirstChild("leaderstats") then return end
AddingFrameLock = true
LeaderstatsAdded(nentry)
AddingFrameLock = false
end
end)
# Line 2429 to 2434, only debugprint() is debug. Function does continue after this point, it doesn't end here.
--[[
Note:major optimization can be done here
removes this player's frame if it exists, calls base update
--]]
function RemovePlayerFrame(tplayer)
while AddingFrameLock do debugprint('in removing player frame lock') wait(1/30) end
# Line 2618 to 2689, only debugprint() is debug.
function SetPlayerToTeam(entry)
FindRemovePlayerFromTeam(entry)
-- check to see if team exists, if it does add to that team
local setToTeam = false
for i,tframe in ipairs(TeamFrames) do
-- add my entry on the new team
if tframe['MyTeam'].TeamColor == entry['Player'].TeamColor then
AddPlayerToTeam(tframe,entry)
setToTeam = true
end
end
-- if player was set to an invalid team, then set it back to neutral
if not setToTeam and #(game.Teams:GetTeams())>0 then
debugprint(entry['Player'].Name..'could not find team')
entry['MyTeam']=nil
if not NeutralTeam then
AddNeutralTeam()
else AddPlayerToTeam(NeutralTeam,entry) end
end
end
--[[
Note:another big one, consiter breaking up
called when any children of player changes
handles 'Neutral', teamColor, Name and MembershipType changes
@Args
entry Player entry changed
property name of property changed
--]]
function PlayerChanged(entry, property)
while PlayerChangedLock do
debugprint('in playerchanged lock')
wait(1/30)
end
PlayerChangedLock=true
if property == 'Neutral' then
-- if player changing to neutral
if entry['Player'].Neutral and #(game.Teams:GetTeams())>0 then
debugprint(entry['Player'].Name..'setting to neutral')
FindRemovePlayerFromTeam(entry)
entry['MyTeam']=nil
if not NeutralTeam then
debugprint(entry['Player'].Name..'creating neutral team')
AddNeutralTeam()
else
debugprint(entry['Player'].Name..'adding to neutral team')
AddPlayerToTeam(NeutralTeam,entry)
end
elseif #(game.Teams:GetTeams())>0 then -- else player switching to a team, or a weird edgecase
debugprint(entry['Player'].Name..'has been set non-neutral')
SetPlayerToTeam(entry)
end
BaseUpdate()
elseif property == 'TeamColor' and not entry['Player'].Neutral and entry['Player'] ~= entry['MyTeam'] then
debugprint(entry['Player'].Name..'setting to new team')
SetPlayerToTeam(entry)
BaseUpdate()
elseif property == 'Name' or property == 'MembershipType' then
entry['Frame']:FindFirstChild('BCLabel').Image = getMembershipTypeIcon(entry['Player'].MembershipType,entry['Player'].Name)
entry['Frame'].Name = entry['Player'].Name
entry['Frame'].TitleFrame.Title.Text = entry['Player'].Name
if(entry['Frame'].BCLabel.Image ~= '') then
entry['Frame'].TitleFrame.Title.Position=UDim2.new(.01, 30, .1, 0)
end
if entry['Player'] == LocalPlayer then
entry['Frame'].TitleFrame.DropShadow.Text= entry['Player'].Name
ChangeHeaderName(entry['Player'].Name)
end
BaseUpdate()
end
PlayerChangedLock=false
end
# Line 2691 to 2709, only debugprint() is debug.
function OnFriendshipChanged(player,friendStatus)
Delay(.5,function()
debugprint('friend status changed for:'..player.Name .." ".. tostring(friendStatus) .. " vs " .. tostring(GetFriendStatus(player)) )
for _, entry in ipairs(PlayerFrames) do
if entry['Player']==player then
local nicon = getFriendStatusIcon(friendStatus)
if nicon == '' and entry['Frame'].FriendLabel.Image ~= '' then
entry['Frame'].TitleFrame.Title.Position=entry['Frame'].TitleFrame.Title.Position-UDim2.new(0,17,0,0)
elseif nicon ~= '' and entry['Frame'].FriendLabel.Image == '' then
entry['Frame'].TitleFrame.Title.Position=entry['Frame'].TitleFrame.Title.Position+UDim2.new(0,17,0,0)
debugprint('confirmed status:'..player.Name)
end
entry['Frame'].FriendLabel.Image = nicon
return
end
end
end)
end
# Line 2713 to 2718, only debugprint() is debug. Function does continue after this point, it doesn't end here.
function OnFriendshipChanged(player,friendStatus)
Delay(.5,function()
debugprint('friend status changed for:'..player.Name .." ".. tostring(friendStatus) .. " vs " .. tostring(GetFriendStatus(player)) )
for _, entry in ipairs(PlayerFrames) do
if entry['Player']==player then
local nicon = getFriendStatusIcon(friendStatus)
if nicon == '' and entry['Frame'].FriendLabel.Image ~= '' then
entry['Frame'].TitleFrame.Title.Position=entry['Frame'].TitleFrame.Title.Position-UDim2.new(0,17,0,0)
elseif nicon ~= '' and entry['Frame'].FriendLabel.Image == '' then
entry['Frame'].TitleFrame.Title.Position=entry['Frame'].TitleFrame.Title.Position+UDim2.new(0,17,0,0)
debugprint('confirmed status:'..player.Name)
end
entry['Frame'].FriendLabel.Image = nicon
return
end
end
end)
end
# Line 2752 to 2759, only debugprint() is debug.
function RemoveNeutralTeam()
while NeutralTeamLock do debugprint('in neutral team lock') wait() end
NeutralTeamLock = true
NeutralTeam['Frame']:Destroy()
NeutralTeam=nil
RemoveMiddleBGFrame()
NeutralTeamLock = false
end
# Line 2816 to 2824, only debugprint() is debug. Function does continue after this point, it doesn't end here.
--[[
creates team entry and frame for this team, sets up listeners for this team
adds any players intended for this team,Creates neutral team if this is the first team added
Note:might be best to break this into multiple functions to simplify
@Args:
nteam new team object added
--]]
function InsertTeamFrame(nteam)
while AddingFrameLock do debugprint('in adding team frame lock') wait(1/30) end
# Line 2876 to 2907, only debugprint() is debug.
--[[
removes team from team list
@Args:
nteam Teamobject to remove
--]]
function RemoveTeamFrame(nteam)
while AddingFrameLock do debugprint('in removing team frame lock') wait(1/30) end
AddingFrameLock = true
if IsMinimized.Value then
end
local myEntry
for i,key in ipairs(TeamFrames) do
if nteam == key['MyTeam'] then
myEntry = key
key['Frame']:Destroy()
table.remove(TeamFrames,i)
end
end
if #TeamFrames==0 then
debugprint('removeteamframe, remove neutral')
if NeutralTeam then
RemoveNeutralTeam()
end
end
for i,key in ipairs(myEntry['MyPlayers']) do
RemovePlayerFromTeam(myEntry,i)
PlayerChanged(key, 'TeamColor')
end
RemoveMiddleBGFrame()
BaseUpdate()
AddingFrameLock = false
end
# Line 2916 to 2959, only debugprint() is debug. Function does continue after this point, it doesn't end here.
---------------------------------
--[[
called when ANYTHING changes the state of the playerlist
re-sorts everything,assures correct positions of all elements
--]]
function BaseUpdate()
while BaseUpdateLock do debugprint('in baseupdate lock') wait(1/30) end
BaseUpdateLock = true
--print ('baseupdate')
UpdateStatNames()
if #TeamFrames == 0 and not NeutralTeam then
PlayerListModeUpdate()
else
TeamListModeUpdate()
end
for i,key in ipairs(MiddleFrames) do
if key.Parent ~= nil then
key.Position = UDim2.new(.5,0,((#MiddleFrames - (i)) * key.Size.Y.Scale),0)
end
end
if not IsMinimized.Value and #MiddleFrames>DefaultEntriesOnScreen then
UpdateScrollPosition()
end
UpdateMinimize()
UpdateScrollBarSize()
UpdateScrollPosition()
UpdateScrollBarVisibility()
--debugprint('EndBaseUpdate')
BaseUpdateLock = false
end
--[[
code for attaching tab key to maximizing player list
--]]
game.GuiService:AddKey("\t")
local LastTabTime = time()
game.GuiService.KeyPressed:connect(
function(key)
if key == "\t" then
debugprint('caught tab key')
# Line 2983 to 2989, only debugPlayerAdd() is debug.
function PlayersChildAdded(tplayer)
if tplayer:IsA('Player') then
Spawn(function() debugPlayerAdd(tplayer) end)
else
BlowThisPopsicleStand()
end
end
# Line 3013 to 3018.
function PlayersChildAdded(tplayer)
if tplayer:IsA('Player') then
Spawn(function() debugPlayerAdd(tplayer) end)
else
BlowThisPopsicleStand()
end
end
# Line 3025 to 3027, only debugPlayerAdd() is debug.
while not game:GetService('Teams') do wait(1/30) debugprint('Waiting For Teams') end
for _,i in pairs(game.Teams:GetTeams()) do TeamAdded(i) end
for _,i in pairs(Players:GetPlayers()) do Spawn(function() debugPlayerAdd(i) end) end
# Line 3043 to 3060, yes, this is the code from the picture! All of this is pure debug heaven!
----------------------------
-- Running Logic
----------------------------
--debug stuffs, will only run for 'newplayerlistisbad'
if LocalPlayer.Name == 'newplayerlistisbad' or LocalPlayer.Name == 'imtotallyadmin' then
debugFrame.Parent = ScreenGui
Spawn(function()
while true do
local str_players=''
for _,i in pairs(game.Players:GetPlayers()) do
str_players= str_players .." " .. i.Name
end
debugplayers.Text=str_players
wait(.5)
end
end)
end