Description
Anime Girl Painter An auto painter for Anime Girl Paint by Numbers. It paints your whole picture for you, tile by tile, in whatever order you pick. It has three pages. How it works Every tile in the game stores which color it needs ( N ), its real color ( C ) and whether it's painted ( D ). The painter finds the unpainted tiles, selects the right color through the game's own color-select remote, and teleports your character onto each tile. The game paints a tile when you stand on it, so no remotes are exploited and nothing is skipped. Tiles are only painted with the matching color, so it switches colors on its own. How to use it Run the script and the GUI opens. Drag it by the title bar. On Painter , press Start painting and press Stop to pause. Use the Speed slider to go faster or slower. If tiles get missed at top speed, turn it down a little. Press X to close it. This stops everything and removes the GUI. Features Painter Progress bar: shows tiles painted and the current color. Group: paint color by color, or the whole picture in one pass. Whole picture is slower because it switches colors constantly. Pattern: Snake: row by row, left to right then right to left. Center-out: starts in the middle and works outward. Random: a random order. Live Live picture: a minimap of your picture. Unpainted tiles are faded and each tile fills with its real color as it's painted. Session: tiles painted, tiles left, cash earned, cash per minute and an estimated time to finish. Reset restarts the counters. Tools Color progress: one bar per color in its real color, filling as that color is completed. Top-down view: a camera looking straight down at the whole picture. Follow cam: a top-down camera that tracks your character while it paints. Focus selected color: unpainted tiles of the selected color show their real color and the rest stay grey. Finish chime: plays a sound when the picture is complete. Color order: picks which color goes next. Options are Numeric, Most tiles first, Fewest tiles first, Light to dark and Dark to light. Notes It works on whatever picture you have loaded. When you switch pictures, the color count, minimap, chart and session stats update on their own. It only affects your own client and your own plot. Roblox may handle fast teleporting differently over time. Slowing the speed slider is the first thing to try if painting acts up.
Script source Luau
-- [[ BloxScripts Risk Notice ]]
-- This script has not been verified by BloxScripts.fun. Deal with caution.
--
-- Stay safe:
-- • Never log in on unofficial Roblox sites or lookalike domains.
-- • Real Roblox links use roblox.com (check the .com ending).
-- • Treat fake Roblox login / "claim reward" pages as phishing.
-- [[ End BloxScripts Risk Notice ]]
local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local SoundService = game:GetService("SoundService")
local plr = Players.LocalPlayer
local cam = workspace.CurrentCamera
local remotes = game.ReplicatedStorage.Remotes
if getgenv().PaintCleanup then pcall(getgenv().PaintCleanup) end
if getgenv().PaintGui then pcall(function() getgenv().PaintGui:Destroy() end) end
local PINK = Color3.fromRGB(205, 150, 168)
local PINK_HOVER = Color3.fromRGB(218, 166, 183)
local BG = Color3.fromRGB(24, 24, 28)
local PANEL = Color3.fromRGB(29, 29, 34)
local CARD = Color3.fromRGB(37, 37, 43)
local TEXT = Color3.fromRGB(214, 212, 216)
local DIM = Color3.fromRGB(125, 124, 134)
local LINE = Color3.fromRGB(55, 55, 63)
local DARK_TEXT = Color3.fromRGB(32, 26, 30)
local cfg = {
running = false,
delay = 0.03,
group = "Color",
pattern = "Snake",
order = "Numeric",
camMode = "off",
focus = false,
chime = true,
}
local conns = {}
local oldColors = {}
local gui = Instance.new("ScreenGui")
gui.Name = "PaintCS"
gui.ResetOnSpawn = false
gui.IgnoreGuiInset = true
gui.Parent = (gethui and gethui()) or game:GetService("CoreGui")
getgenv().PaintGui = gui
local function new(class, props, parent)
local obj = Instance.new(class)
for k, v in pairs(props) do obj[k] = v end
obj.Parent = parent
return obj
end
local function corner(obj, r)
new("UICorner", {CornerRadius = UDim.new(0, r or 6)}, obj)
end
local function outline(obj, color)
return new("UIStroke", {
Color = color or LINE,
Thickness = 1,
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
}, obj)
end
local function listen(signal, fn)
local c = signal:Connect(fn)
table.insert(conns, c)
return c
end
local main = new("Frame", {
Size = UDim2.fromOffset(540, 340),
Position = UDim2.new(0.5, -270, 0.5, -170),
BackgroundColor3 = BG,
BorderSizePixel = 0,
Active = true,
ClipsDescendants = true,
}, gui)
corner(main, 8)
outline(main)
local header = new("Frame", {Size = UDim2.new(1, 0, 0, 36), BackgroundColor3 = PANEL, BorderSizePixel = 0}, main)
new("TextLabel", {
Size = UDim2.new(1, -50, 1, 0),
Position = UDim2.fromOffset(14, 0),
BackgroundTransparency = 1,
Text = "ANIME GIRL PAINT BY NUMBERS",
TextColor3 = TEXT,
Font = Enum.Font.GothamBold,
TextSize = 13,
TextXAlignment = Enum.TextXAlignment.Left,
}, header)
new("TextLabel", {
Size = UDim2.fromOffset(120, 36),
Position = UDim2.new(1, -190, 0, 0),
BackgroundTransparency = 1,
Text = "Auto Painter",
TextColor3 = PINK,
Font = Enum.Font.Gotham,
TextSize = 12,
TextXAlignment = Enum.TextXAlignment.Right,
}, header)
new("Frame", {Size = UDim2.new(1, 0, 0, 1), Position = UDim2.new(0, 0, 1, -1), BackgroundColor3 = LINE, BorderSizePixel = 0}, header)
local closeBtn = new("TextButton", {
Size = UDim2.fromOffset(36, 36),
Position = UDim2.new(1, -36, 0, 0),
BackgroundTransparency = 1,
Text = "X",
TextColor3 = DIM,
Font = Enum.Font.GothamBold,
TextSize = 13,
AutoButtonColor = false,
}, header)
do
local dragging, startPos, startFrame
header.InputBegan:Connect(function(i)
if i.UserInputType == Enum.UserInputType.MouseButton1 then
dragging, startPos, startFrame = true, i.Position, main.Position
end
end)
listen(UIS.InputChanged, function(i)
if dragging and i.UserInputType == Enum.UserInputType.MouseMovement then
local d = i.Position - startPos
main.Position = UDim2.new(startFrame.X.Scale, startFrame.X.Offset + d.X, startFrame.Y.Scale, startFrame.Y.Offset + d.Y)
end
end)
listen(UIS.InputEnded, function(i)
if i.UserInputType == Enum.UserInputType.MouseButton1 then dragging = false end
end)
end
local sidebar = new("Frame", {Size = UDim2.new(0, 116, 1, -36), Position = UDim2.fromOffset(0, 36), BackgroundColor3 = PANEL, BorderSizePixel = 0}, main)
new("Frame", {Size = UDim2.new(0, 1, 1, 0), Position = UDim2.new(1, -1, 0, 0), BackgroundColor3 = LINE, BorderSizePixel = 0}, sidebar)
local body = new("Frame", {Size = UDim2.new(1, -116, 1, -36), Position = UDim2.fromOffset(116, 36), BackgroundTransparency = 1}, main)
local pages, tabs = {}, {}
local function addPage(name)
local idx = #sidebar:GetChildren()
local first = next(pages) == nil
local page = new("Frame", {
Size = UDim2.new(1, -24, 1, -24),
Position = UDim2.fromOffset(12, 12),
BackgroundTransparency = 1,
Visible = first,
}, body)
local tab = new("TextButton", {
Size = UDim2.new(1, -16, 0, 32),
Position = UDim2.fromOffset(8, 10 + (idx - 1) * 38),
BackgroundColor3 = first and CARD or PANEL,
BorderSizePixel = 0,
Text = name,
TextColor3 = first and PINK or DIM,
Font = Enum.Font.GothamMedium,
TextSize = 12,
AutoButtonColor = false,
}, sidebar)
corner(tab)
pages[name], tabs[name] = page, tab
tab.MouseButton1Click:Connect(function()
for n, p in pairs(pages) do
p.Visible = n == name
tabs[n].BackgroundColor3 = n == name and CARD or PANEL
tabs[n].TextColor3 = n == name and PINK or DIM
end
end)
return page
end
local painterPage = addPage("Painter")
local livePage = addPage("Live")
local toolsPage = addPage("Tools")
local W = 392
local function card(parent, title, x, y, w, h)
local c = new("Frame", {Size = UDim2.fromOffset(w, h), Position = UDim2.fromOffset(x, y), BackgroundColor3 = CARD, BorderSizePixel = 0}, parent)
corner(c, 8)
outline(c)
new("TextLabel", {
Size = UDim2.new(1, -20, 0, 18),
Position = UDim2.fromOffset(12, 6),
BackgroundTransparency = 1,
Text = title:upper(),
TextColor3 = DIM,
Font = Enum.Font.GothamBold,
TextSize = 10,
TextXAlignment = Enum.TextXAlignment.Left,
}, c)
return c
end
local function text(parent, str, x, y, w, color, align, size)
return new("TextLabel", {
Size = UDim2.fromOffset(w, 16),
Position = UDim2.fromOffset(x, y),
BackgroundTransparency = 1,
Text = str,
TextColor3 = color or TEXT,
Font = Enum.Font.Gotham,
TextSize = size or 12,
TextXAlignment = align or Enum.TextXAlignment.Left,
TextTruncate = Enum.TextTruncate.AtEnd,
}, parent)
end
local function button(parent, str, x, y, w, h, accent)
local base = accent and PINK or PANEL
local hover = accent and PINK_HOVER or Color3.fromRGB(40, 40, 47)
local b = new("TextButton", {
Size = UDim2.fromOffset(w, h or 28),
Position = UDim2.fromOffset(x, y),
BackgroundColor3 = base,
BorderSizePixel = 0,
Text = str,
TextColor3 = accent and DARK_TEXT or TEXT,
Font = Enum.Font.GothamMedium,
TextSize = 12,
AutoButtonColor = false,
TextTruncate = Enum.TextTruncate.AtEnd,
}, parent)
corner(b)
outline(b, accent and PINK or LINE)
b.MouseEnter:Connect(function() b.BackgroundColor3 = hover end)
b.MouseLeave:Connect(function() b.BackgroundColor3 = base end)
return b
end
local function setActive(b, on, str)
b.Text = str
b.TextColor3 = on and PINK or TEXT
b:FindFirstChildOfClass("UIStroke").Color = on and PINK or LINE
end
local function slider(parent, x, y, w, start, onChange)
local track = new("Frame", {Size = UDim2.fromOffset(w, 6), Position = UDim2.fromOffset(x, y), BackgroundColor3 = PANEL, BorderSizePixel = 0, Active = true}, parent)
corner(track, 3)
local fill = new("Frame", {Size = UDim2.fromScale(start, 1), BackgroundColor3 = PINK, BorderSizePixel = 0}, track)
corner(fill, 3)
local held = false
local function update(px)
local a = math.clamp((px - track.AbsolutePosition.X) / track.AbsoluteSize.X, 0, 1)
fill.Size = UDim2.fromScale(a, 1)
onChange(a)
end
track.InputBegan:Connect(function(i)
if i.UserInputType == Enum.UserInputType.MouseButton1 then
held = true
update(i.Position.X)
end
end)
listen(UIS.InputChanged, function(i)
if held and i.UserInputType == Enum.UserInputType.MouseMovement then update(i.Position.X) end
end)
listen(UIS.InputEnded, function(i)
if i.UserInputType == Enum.UserInputType.MouseButton1 then held = false end
end)
onChange(start)
end
local function getPicture()
local plot = workspace.Map.PlotModels:FindFirstChild(plr.Name)
return plot and plot:FindFirstChild("ActivePicture")
end
local function unpainted()
local list = {}
local pic = getPicture()
if not pic then return list end
for _, t in ipairs(pic:GetChildren()) do
if t:GetAttribute("N") and not t:GetAttribute("D") then
table.insert(list, t)
end
end
return list
end
local function brightness(c)
return c.R * 0.3 + c.G * 0.6 + c.B * 0.1
end
-- painter page
local progCard = card(painterPage, "Progress", 0, 0, W, 58)
local status = text(progCard, "Idle", 12, 26, W - 24)
local barBack = new("Frame", {Size = UDim2.fromOffset(W - 24, 6), Position = UDim2.fromOffset(12, 46), BackgroundColor3 = PANEL, BorderSizePixel = 0}, progCard)
corner(barBack, 3)
local bar = new("Frame", {Size = UDim2.fromScale(0, 1), BackgroundColor3 = PINK, BorderSizePixel = 0}, barBack)
corner(bar, 3)
local ctrlCard = card(painterPage, "Controls", 0, 66, W, 108)
local startBtn = button(ctrlCard, "Start painting", 12, 26, W - 24, 34, true)
local groupBtn = button(ctrlCard, "", 12, 70, (W - 32) / 2, 28)
local patternBtn = button(ctrlCard, "", 20 + (W - 32) / 2, 70, (W - 32) / 2, 28)
local speedCard = card(painterPage, "Speed", 0, 182, W, 58)
local speedText = text(speedCard, "", 12, 6, W - 24, PINK, Enum.TextXAlignment.Right, 11)
slider(speedCard, 12, 36, W - 24, 0.85, function(a)
cfg.delay = 0.25 * (1 - a) + 0.005
speedText.Text = string.format("%.0f tiles/s", 1 / (cfg.delay + 0.02))
end)
local function refreshLabels()
groupBtn.Text = cfg.group == "Color" and "Group: by color" or "Group: whole picture"
patternBtn.Text = "Pattern: " .. cfg.pattern
end
refreshLabels()
local function sortTiles(list)
if cfg.pattern == "Snake" then
table.sort(list, function(a, b)
local ra = math.floor(a.Position.Z / 1.5 + 0.5)
local rb = math.floor(b.Position.Z / 1.5 + 0.5)
if ra ~= rb then return ra < rb end
if ra % 2 == 0 then return a.Position.X < b.Position.X end
return a.Position.X > b.Position.X
end)
elseif cfg.pattern == "Center-out" then
local sum, n = Vector3.zero, 0
for _, t in ipairs(getPicture():GetChildren()) do
if t:IsA("BasePart") then
sum += t.Position
n += 1
end
end
local mid = sum / math.max(n, 1)
local function dist(t)
return Vector2.new(t.Position.X - mid.X, t.Position.Z - mid.Z).Magnitude
end
table.sort(list, function(a, b) return dist(a) < dist(b) end)
else
for i = #list, 2, -1 do
local j = math.random(i)
list[i], list[j] = list[j], list[i]
end
end
end
local function nextColor(list)
local info = {}
for _, t in ipairs(list) do
local n = t:GetAttribute("N")
if not info[n] then
local c = t:GetAttribute("C")
info[n] = {n = n, count = 0, light = typeof(c) == "Color3" and brightness(c) or 0.5}
end
info[n].count += 1
end
local best
for _, v in pairs(info) do
if not best then
best = v
else
local o = cfg.order
if (o == "Numeric" and v.n < best.n)
or (o == "Most tiles first" and v.count > best.count)
or (o == "Fewest tiles first" and v.count < best.count)
or (o == "Light to dark" and v.light > best.light)
or (o == "Dark to light" and v.light < best.light) then
best = v
end
end
end
return best.n
end
local function walkTo(tile)
local root = plr.Character and plr.Character:FindFirstChild("HumanoidRootPart")
if not root then
task.wait(0.5)
return
end
root.CFrame = CFrame.new(tile.Position + Vector3.new(0, 3, 0))
local t0 = tick()
while cfg.running and not tile:GetAttribute("D") and tick() - t0 < cfg.delay + 0.3 do
task.wait()
end
if cfg.delay > 0.03 then task.wait(cfg.delay - 0.03) end
end
local function selectColor(n, wait)
if plr:GetAttribute("SelectedColorNumber") ~= n then
remotes.SelectNumber:FireServer(n)
task.wait(wait)
end
end
local function playChime()
if not cfg.chime then return end
local s = Instance.new("Sound")
s.SoundId = "rbxasset://sounds/electronicpingshort.wav"
s.Parent = SoundService
s:Play()
game:GetService("Debris"):AddItem(s, 3)
end
local function paintLoop()
local function finish(msg)
cfg.running = false
startBtn.Text = "Start painting"
status.Text = msg
end
local total = #unpainted()
if not getPicture() then return finish("No picture found") end
if total == 0 then return finish("Nothing left to paint") end
local function showProgress(left)
bar.Size = UDim2.fromScale(math.clamp((total - left) / total, 0, 1), 1)
status.Text = string.format("Painting %d / %d (color %s)", total - left, total, tostring(plr:GetAttribute("SelectedColorNumber")))
end
while cfg.running do
local left = unpainted()
if #left == 0 then break end
showProgress(#left)
local batch = left
if cfg.group == "Color" then
local pick = nextColor(left)
selectColor(pick, 0.25)
batch = {}
for _, t in ipairs(left) do
if t:GetAttribute("N") == pick then table.insert(batch, t) end
end
end
sortTiles(batch)
for i, tile in ipairs(batch) do
if not cfg.running then break end
if not tile:GetAttribute("D") then
selectColor(tile:GetAttribute("N"), 0.12)
walkTo(tile)
end
if i % 5 == 0 then showProgress(#left - i) end
end
end
local rest = #unpainted()
finish(rest == 0 and "Done" or (rest .. " tiles left"))
bar.Size = UDim2.fromScale(math.clamp((total - rest) / total, 0, 1), 1)
if rest == 0 then playChime() end
end
startBtn.MouseButton1Click:Connect(function()
if cfg.running then
cfg.running = false
return
end
cfg.running = true
startBtn.Text = "Stop"
task.spawn(paintLoop)
end)
groupBtn.MouseButton1Click:Connect(function()
cfg.group = cfg.group == "Color" and "Whole" or "Color"
refreshLabels()
end)
patternBtn.MouseButton1Click:Connect(function()
cfg.pattern = ({Snake = "Center-out", ["Center-out"] = "Random", Random = "Snake"})[cfg.pattern]
refreshLabels()
end)
-- live page
local mapCard = card(livePage, "Live picture", 0, 0, 186, 196)
local canvas = new("Frame", {Size = UDim2.fromOffset(162, 162), Position = UDim2.fromOffset(12, 26), BackgroundColor3 = PANEL, BorderSizePixel = 0, ClipsDescendants = true}, mapCard)
corner(canvas, 4)
local mapTiles, mapPicture = {}, nil
local function buildMap()
for _, t in ipairs(mapTiles) do t.frame:Destroy() end
table.clear(mapTiles)
local pic = getPicture()
mapPicture = pic
if not pic then return end
local parts = {}
local x0, z0, x1, z1 = math.huge, math.huge, -math.huge, -math.huge
for _, p in ipairs(pic:GetChildren()) do
if p:IsA("BasePart") and p:GetAttribute("N") then
table.insert(parts, p)
x0, x1 = math.min(x0, p.Position.X), math.max(x1, p.Position.X)
z0, z1 = math.min(z0, p.Position.Z), math.max(z1, p.Position.Z)
end
end
if #parts == 0 then return end
local cols = math.floor((x1 - x0) / 1.5 + 0.5) + 1
local rows = math.floor((z1 - z0) / 1.5 + 0.5) + 1
local cell = 162 / math.max(cols, rows)
local padX, padZ = (162 - cols * cell) / 2, (162 - rows * cell) / 2
for i, p in ipairs(parts) do
local gx = cols - 1 - math.floor((p.Position.X - x0) / 1.5 + 0.5)
local gz = rows - 1 - math.floor((p.Position.Z - z0) / 1.5 + 0.5)
local f = new("Frame", {
Size = UDim2.fromOffset(math.ceil(cell), math.ceil(cell)),
Position = UDim2.fromOffset(padX + gx * cell, padZ + gz * cell),
BorderSizePixel = 0,
}, canvas)
table.insert(mapTiles, {part = p, frame = f, color = p:GetAttribute("C")})
if i % 600 == 0 then task.wait() end
end
end
task.spawn(buildMap)
task.spawn(function()
while gui.Parent do
if getPicture() ~= mapPicture then buildMap() end
for i, t in ipairs(mapTiles) do
local done = t.part:GetAttribute("D") and true or false
if done ~= t.done then
t.done = done
local c = typeof(t.color) == "Color3" and t.color or Color3.new(0.5, 0.5, 0.5)
t.frame.BackgroundColor3 = done and c or c:Lerp(PANEL, 0.82)
end
if i % 1500 == 0 then task.wait() end
end
task.wait(0.5)
end
end)
local statCard = card(livePage, "Session", 194, 0, 198, 196)
local rows = {}
for i, name in ipairs({"Tiles painted", "Tiles left", "Cash earned", "Cash / min", "Time to finish"}) do
text(statCard, name, 12, 28 + (i - 1) * 24, 100, DIM, nil, 11)
rows[i] = text(statCard, "-", 100, 28 + (i - 1) * 24, 86, TEXT, Enum.TextXAlignment.Right)
end
local resetBtn = button(statCard, "Reset", 12, 158, 174, 26)
local session
local function resetSession()
session = {t = tick(), cash = plr:GetAttribute("CurrencyCash") or 0, left = #unpainted()}
end
resetSession()
resetBtn.MouseButton1Click:Connect(resetSession)
local function formatTime(sec)
if sec ~= sec or sec == math.huge or sec > 86400 then return "-" end
sec = math.floor(sec)
if sec >= 3600 then return string.format("%dh %dm", sec // 3600, (sec % 3600) // 60) end
return string.format("%dm %ds", sec // 60, sec % 60)
end
task.spawn(function()
while gui.Parent do
local left = #unpainted()
local mins = math.max((tick() - session.t) / 60, 1e-3)
local done = math.max(session.left - left, 0)
local cash = (plr:GetAttribute("CurrencyCash") or 0) - session.cash
rows[1].Text = tostring(done)
rows[2].Text = tostring(left)
rows[3].Text = tostring(cash)
rows[4].Text = string.format("%.0f", cash / mins)
rows[5].Text = done > 0 and formatTime(left / (done / (mins * 60))) or "-"
task.wait(1)
end
end)
-- tools page
local chartCard = card(toolsPage, "Color progress", 0, 0, W, 104)
local chart = new("Frame", {Size = UDim2.fromOffset(W - 24, 62), Position = UDim2.fromOffset(12, 26), BackgroundTransparency = 1}, chartCard)
text(chartCard, "One bar per color. The filled part is what you've painted.", 12, 88, W - 24, DIM, nil, 11)
local bars, barsPicture = {}, nil
local function pickerColors()
local out = {}
local picker = plr.PlayerGui:FindFirstChild("ColorPickerGui")
local holder = picker and picker:FindFirstChild("Main", true)
if holder then
for _, d in ipairs(holder:GetChildren()) do
local n = tonumber(d.Name)
if n and d:IsA("GuiObject") then out[n] = d.BackgroundColor3 end
end
end
return out
end
local function buildChart()
for _, b in pairs(bars) do b.back:Destroy() end
table.clear(bars)
local pic = getPicture()
barsPicture = pic
if not pic then return end
local fromGame, colors = pickerColors(), {}
for _, t in ipairs(pic:GetChildren()) do
local n = t:GetAttribute("N")
if n and not colors[n] then colors[n] = fromGame[n] or t:GetAttribute("C") end
end
local nums = {}
for n in pairs(colors) do table.insert(nums, n) end
table.sort(nums)
local gap = 3
local w = ((W - 24) - gap * (#nums - 1)) / #nums
for i, n in ipairs(nums) do
local c = typeof(colors[n]) == "Color3" and colors[n] or Color3.new(0.5, 0.5, 0.5)
local back = new("Frame", {Size = UDim2.fromOffset(w, 62), Position = UDim2.fromOffset((i - 1) * (w + gap), 0), BackgroundColor3 = PANEL, BorderSizePixel = 0}, chart)
corner(back, 3)
local fill = new("Frame", {AnchorPoint = Vector2.new(0, 1), Size = UDim2.new(1, 0, 0, 0), Position = UDim2.fromScale(0, 1), BackgroundColor3 = c, BorderSizePixel = 0}, back)
corner(fill, 3)
bars[n] = {back = back, fill = fill}
end
end
buildChart()
task.spawn(function()
while gui.Parent do
if getPicture() ~= barsPicture then buildChart() end
local pic = getPicture()
if pic then
local total, done = {}, {}
for _, t in ipairs(pic:GetChildren()) do
local n = t:GetAttribute("N")
if n then
total[n] = (total[n] or 0) + 1
if t:GetAttribute("D") then done[n] = (done[n] or 0) + 1 end
end
end
for n, b in pairs(bars) do
local frac = (done[n] or 0) / math.max(total[n] or 1, 1)
b.fill.Size = UDim2.new(1, 0, 0, math.floor(62 * frac))
end
end
task.wait(0.8)
end
end)
local toolCard = card(toolsPage, "Tools", 0, 112, W, 168)
local half = (W - 32) / 2
local topBtn = button(toolCard, "Top-down view", 12, 26, half, 28)
local followBtn = button(toolCard, "Follow cam", 20 + half, 26, half, 28)
local focusBtn = button(toolCard, "Focus selected color", 12, 62, half, 28)
local chimeBtn = button(toolCard, "Finish chime", 20 + half, 62, half, 28)
local orderBtn = button(toolCard, "", 12, 98, W - 24, 28)
text(toolCard, "Focus shows the real color on tiles you still have to paint. Order picks which color goes next.", 12, 134, W - 24, DIM, nil, 10)
setActive(chimeBtn, true, "Finish chime")
orderBtn.Text = "Color order: " .. cfg.order
local orders = {"Numeric", "Most tiles first", "Fewest tiles first", "Light to dark", "Dark to light"}
orderBtn.MouseButton1Click:Connect(function()
for i, o in ipairs(orders) do
if o == cfg.order then
cfg.order = orders[i % #orders + 1]
break
end
end
orderBtn.Text = "Color order: " .. cfg.order
end)
chimeBtn.MouseButton1Click:Connect(function()
cfg.chime = not cfg.chime
setActive(chimeBtn, cfg.chime, "Finish chime")
end)
local function pictureBounds()
local pic = getPicture()
if not pic then return end
local x0, z0, x1, z1, y = math.huge, math.huge, -math.huge, -math.huge, 33
for _, p in ipairs(pic:GetChildren()) do
if p:IsA("BasePart") then
x0, x1 = math.min(x0, p.Position.X), math.max(x1, p.Position.X)
z0, z1 = math.min(z0, p.Position.Z), math.max(z1, p.Position.Z)
y = p.Position.Y
end
end
return Vector3.new((x0 + x1) / 2, y, (z0 + z1) / 2), math.max(x1 - x0, z1 - z0)
end
local camLoop
local function camOff()
if camLoop then
camLoop:Disconnect()
camLoop = nil
end
if cfg.camMode ~= "off" then
cam.CameraType = Enum.CameraType.Custom
local hum = plr.Character and plr.Character:FindFirstChildOfClass("Humanoid")
if hum then cam.CameraSubject = hum end
end
cfg.camMode = "off"
setActive(topBtn, false, "Top-down view")
setActive(followBtn, false, "Follow cam")
end
local function camOn(mode)
if camLoop then camLoop:Disconnect() end
cfg.camMode = mode
setActive(topBtn, mode == "top", "Top-down view")
setActive(followBtn, mode == "follow", "Follow cam")
cam.CameraType = Enum.CameraType.Scriptable
if mode == "top" then
local mid, size = pictureBounds()
if not mid then return camOff() end
local height = size * 0.85 + 10
camLoop = RunService.RenderStepped:Connect(function()
cam.CFrame = CFrame.lookAt(mid + Vector3.new(0, height, -0.01), mid)
end)
else
local pos
camLoop = RunService.RenderStepped:Connect(function()
local root = plr.Character and plr.Character:FindFirstChild("HumanoidRootPart")
if not root then return end
pos = pos and pos:Lerp(root.Position, 0.15) or root.Position
cam.CFrame = CFrame.lookAt(pos + Vector3.new(0, 45, -0.01), pos)
end)
end
end
topBtn.MouseButton1Click:Connect(function()
if cfg.camMode == "top" then camOff() else camOn("top") end
end)
followBtn.MouseButton1Click:Connect(function()
if cfg.camMode == "follow" then camOff() else camOn("follow") end
end)
local function resetTiles()
for part, color in pairs(oldColors) do
if part.Parent and not part:GetAttribute("D") then part.Color = color end
end
table.clear(oldColors)
end
task.spawn(function()
while gui.Parent do
local pic = getPicture()
if cfg.focus and pic then
local sel = plr:GetAttribute("SelectedColorNumber")
for _, t in ipairs(pic:GetChildren()) do
local n = t:GetAttribute("N")
if n and not t:GetAttribute("D") then
if n == sel then
local c = t:GetAttribute("C")
if not oldColors[t] and typeof(c) == "Color3" then
oldColors[t] = t.Color
t.Color = c
end
elseif oldColors[t] then
t.Color = oldColors[t]
oldColors[t] = nil
end
end
end
end
task.wait(0.75)
end
end)
focusBtn.MouseButton1Click:Connect(function()
cfg.focus = not cfg.focus
if not cfg.focus then resetTiles() end
setActive(focusBtn, cfg.focus, "Focus selected color")
end)
listen(plr:GetAttributeChangedSignal("LoadedPicture"), function()
task.wait(1)
if gui.Parent then resetSession() end
end)
local function cleanup()
cfg.running, cfg.focus = false, false
resetTiles()
camOff()
for _, c in ipairs(conns) do pcall(function() c:Disconnect() end) end
getgenv().PaintGui, getgenv().PaintCleanup = nil, nil
end
getgenv().PaintCleanup = cleanup
closeBtn.MouseEnter:Connect(function() closeBtn.TextColor3 = Color3.fromRGB(200, 100, 100) end)
closeBtn.MouseLeave:Connect(function() closeBtn.TextColor3 = DIM end)
closeBtn.MouseButton1Click:Connect(function()
cleanup()
gui:Destroy()
end)Review the source before using it. Executions count successful copies, capped at 10 per user and network per script in 24 hours.


Your community, in one place