Run it
Copy the script and paste it into your executor.
Players = :()
Workspace = :()
HttpService = :()
RunService = :()
Player = Players.LocalPlayer
ROOT =
CHECK_INTERVAL =
POSITION_TOLERANCE =
ROTATION_TOLERANCE = .()
Schematics = {}
Holograms = {}
HologramOffset = .zero
MoveHandles =
MoveAnchor =
MoveEnabled =
Checking =
LastCheck =
(playerName)
playerName = playerName Player.Name
blocksRoot = Workspace:()
blocksRoot
blocksRoot:(playerName)
(model)
model:()
model
part = model:(, )
part part:()
part
(playerName)
result = {}
folder = (playerName)
folder result
_, model (folder:())
part = (model)
part
.(result, {
model = model,
part = part
})
result
()
(writefile) ==
(readfile) ==
(listfiles) ==
()
(isfolder) ==
(makefolder) ==
(ROOT)
(ROOT)
(ROOT)
(name)
name = name:(, )
name = name:(, )
name = name:(, )
name
(name)
ROOT .. .. name ..
(cf)
x, y, z = cf:()
{x, y, z}
(value)
kind = (value)
kind == kind == kind == kind ==
{kind = kind, value = value}
kind ==
{kind = kind, value = {value.X, value.Y, value.Z}}
kind ==
{kind = kind, value = {value.X, value.Y}}
kind ==
{kind = kind, value = {value.R, value.G, value.B}}
kind ==
{kind = kind, value = {value:()}}
kind ==
{kind = kind, value = value.Number}
kind ==
{
kind = kind,
enumType = value.EnumType.Name,
value = value.Name
}
kind ==
{kind = kind, value = {value.Min, value.Max}}
kind ==
{kind = kind, value = {value.Scale, value.Offset}}
kind ==
{
kind = kind,
value = {
value.X.Scale, value.X.Offset,
value.Y.Scale, value.Y.Offset
}
}
kind ==
{
kind = kind,
value = {
value.Min.X, value.Min.Y,
value.Max.X, value.Max.Y
}
}
kind ==
{
kind = kind,
value = {
value.Density, value.Friction, value.Elasticity,
value.FrictionWeight, value.ElasticityWeight
}
}
(data)
(data) ~=
,
v = data.value
kind = data.kind
kind ==
,
kind == kind == kind ==
v,
kind ==
.(v[], v[], v[]),
kind ==
.(v[], v[]),
kind ==
.(v[], v[], v[]),
kind ==
.((v)),
kind ==
BrickColor.(v),
kind ==
enumType = [data.enumType]
enumType
enumType[v],
kind ==
NumberRange.(v[], v[]),
kind ==
.(v[], v[]),
kind ==
.(v[], v[], v[], v[]),
kind ==
Rect.(v[], v[], v[], v[]),
kind ==
PhysicalProperties.(v[], v[], v[], v[], v[]),
,
TEAM_ANCHORS = {
Magenta = {, -, , },
Yellow = {-, -, , },
Green = {-, -, , },
Blue = {, -, , },
Red = {, -, -, },
Black = {-, -, , },
White = {-, -, -, },
}
TEAM_NAME_ALIASES = {
Magenta = , Yellow = , Green = , Blue = ,
Red = , Black = , White = ,
}
(teamColor)
raw = (teamColor )
raw = raw:(, ):(, )
name (TEAM_ANCHORS)
raw:():(name:(), , ) name
lower = raw:()
lower:(, , )
lower:(, , ) lower:(, , )
lower:(, , )
lower:(, , )
lower:(, , )
lower:(, , )
lower:(, , )
TeamsService = :()
(targetPlayer)
targetPlayer
_, teamName ({, , , , , , })
team = TeamsService:(teamName)
leader = team team:()
leader leader:() leader.Value == targetPlayer.Name
team
targetPlayer.Team targetPlayer.Team:()
targetPlayer.Team
color = targetPlayer.TeamColor
_, team (TeamsService:())
team:() color team.TeamColor == color
team
(teamOrColor)
name
(teamOrColor) == teamOrColor:()
name = (teamOrColor.Name) (teamOrColor.TeamColor)
name = (teamOrColor)
a = name TEAM_ANCHORS[name]
a
.(a[], a[], a[]) * .(, .(a[]), )
()
(Player)
(modelData)
savedPivot = .((modelData.pivot))
modelData.coordinateSpace ==
team = ()
anchor = team (team)
anchor (anchor * savedPivot) + HologramOffset
()
savedPivot + HologramOffset
(part, pivot)
relative = pivot:(part.CFrame)
data = {
class = part.ClassName,
name = part.Name,
cf = {relative:()},
size = {part.Size.X, part.Size.Y, part.Size.Z},
color = {part.Color.R, part.Color.G, part.Color.B},
transparency = part.Transparency,
reflectance = part.Reflectance,
material = part.Material.Name,
anchored = part.Anchored,
canCollide = part.CanCollide,
shape = ,
meshId = ,
textureId = ,
properties = {}
}
part:()
data.shape = part.Shape.Name
part:()
data.meshId = part.MeshId
data.textureId = part.TextureID
(getproperties) ==
ok, properties = (getproperties, part)
ok (properties) ==
propertyName, value (properties)
encodedOk, encoded = (encodeProperty, value)
encodedOk encoded
data.properties[propertyName] = encoded
data
(root, instance)
segments = {}
current = instance
current current ~= root
parent = current.Parent
parent
sameNameIndex =
_, sibling (parent:())
sibling.Name == current.Name
sameNameIndex +=
sibling == current
.(segments, , {
name = current.Name,
class = current.ClassName,
index = sameNameIndex
})
current = parent
current ~= root
segments
(root, segments)
current = root
_, segment (segments {})
found, sameNameIndex = ,
_, child (current:())
child.Name == segment.name
sameNameIndex +=
sameNameIndex == (segment.index ) child.ClassName == segment.class
found = child
found
current = found
current
cachedBlockProperties
(model)
cachedBlockProperties
ok, module = (()
(:().Scripts.BlockProperties)
)
ok (module) ==
cachedBlockProperties = module
ok, definitions = (()
cachedBlockProperties[model.Name]
)
ok (definitions) == definitions
(model, definitions)
saved = {}
(definitions) ~= saved
propertyName, definition (definitions)
(definition) == (definition.GetState) ==
ok, state = (definition.GetState, model)
ok
encodedOk, encoded = (encodeProperty, state)
encodedOk encoded
saved[propertyName] = encoded
saved
PROPERTY_VALUE_ALIASES = {
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {},
[] = {}, [] = {},
[] = {}, [] = {},
[] = {}, [] = {},
}
(model, definitions)
values, allowedNames = {}, {}
(definitions) ~= values
propertyName, definition (definitions)
(definition) == (definition.GetState) ==
allowedNames[propertyName] =
_, alias (PROPERTY_VALUE_ALIASES[propertyName] {})
allowedNames[alias] =
_, obj (model:())
obj:() allowedNames[obj.Name]
path = (model, obj)
path
ok, encoded = (encodeProperty, obj.Value)
ok encoded
.(values, {
path = path,
name = obj.Name,
class = obj.ClassName,
value = encoded
})
values
(model, modelData)
model modelData (modelData.values) ~= ,
restored, skipped = ,
_, saved (modelData.values)
target = (model, saved.path)
target target:() target.ClassName == saved.class
decodedOk, value, valid = (()
v, canDecode = (saved.value)
v, canDecode
)
decodedOk valid
setOk = (() target.Value = value )
setOk restored += skipped +=
skipped +=
skipped +=
restored, skipped
(model)
tags = {}
_, tagName ({, , , })
ok, hasTag = (() model:(tagName) )
ok hasTag tags[#tags + ] = tagName
tags
(model)
result = {}
_, child (model:())
child.Name:(, ) == child.Name ~=
action = child:()
action action:() (action.Value) ==
keyCode
_, value (child:())
value:()
(value.Name == value.Name == value.Name == )
(value.Value) ==
keyCode = value.Value
result[action.Value] = {
bindName = child.Name,
keyCode = keyCode
}
result
(models)
idByModel = {}
index, entry (models)
idByModel[entry.model] = index
connections, seen = {}, {}
_, entry (models)
sourceModel = entry.model
_, object (sourceModel:())
object:() object.Attachment0 object.Attachment1
(attachment)
parent = attachment.Parent
candidate = parent parent.Parent
candidate candidate:() candidate
candidate candidate:()
a = (object.Attachment0)
b = (object.Attachment1)
aid, bid = idByModel[a], idByModel[b]
aid bid aid ~= bid
left, right = .(aid, bid), .(aid, bid)
key = (left) .. .. (right)
seen[key]
seen[key] =
actionSource, actionSourceId = a, aid
a:() b:()
actionSource, actionSourceId = b, bid
actionNames = {}
actionName ((actionSource) {})
actionNames[#actionNames + ] = actionName
.(actionNames)
connections[#connections + ] = {
a = aid,
b = bid,
actionSource = actionSourceId,
aTags = (a),
bTags = (b),
actions = actionNames
}
connections
(model, sourceAnchor)
worldPivot = model:()
pivot = sourceAnchor sourceAnchor:(worldPivot) worldPivot
parts = {}
_, obj (model:())
obj:()
.(parts, (obj, worldPivot))
mainPart = (model)
mainRelative =
mainPart
mainRelative = {
worldPivot:(mainPart.CFrame):()
}
propertyDefinitions = (model)
{
name = model.Name,
pivot = {pivot:()},
mainPart = mainRelative,
parts = parts,
bindTags = (model),
bindActions = (model),
blockProperties = (model, propertyDefinitions),
values = (model, propertyDefinitions)
}
(name, targetPlayerName)
()
()
()
()
name = (name)
name ==
()
targetPlayerName = targetPlayerName Player.Name
targetFolder = (targetPlayerName)
targetFolder
(, targetPlayerName)
models = (targetPlayerName)
#models ==
(, targetPlayerName)
Players = :()
sourcePlayer = Players:(targetPlayerName)
sourceTeam = (sourcePlayer)
sourceAnchor = sourceTeam (sourceTeam)
sourceAnchor
(, targetPlayerName, )
data = {
version = ,
coordinateSpace = sourceAnchor ,
sourceTeam = sourceTeam ((sourceTeam.Name) (sourceTeam.TeamColor)) ,
name = name,
sourcePlayer = targetPlayerName,
blocks = {}
}
index, entry (models)
blockData = (entry.model, sourceAnchor)
blockData.id = index
.(data.blocks, blockData)
data.bindConnections = (models)
_, blockData (data.blocks)
blockData.bindConnections = data.bindConnections
ok, encoded = (()
HttpService:(data)
)
ok
(, encoded)
saved, err = (()
((name), encoded)
)
saved
(, err)
(, name, , #data.blocks)
()
result = {}
()
result
()
result
ok, files = (()
(ROOT)
)
ok
result
_, path (files)
path:():()
readOk, content = (()
(path)
)
readOk
decodeOk, data = (()
HttpService:(content)
)
decodeOk
(data) ==
(data.blocks) ==
.(result, {
name = data.name path,
path = path,
data = data
})
.(result, (a, b)
a.name:() < b.name:()
)
result
(values)
.(
values[], values[], values[],
values[], values[], values[],
values[], values[], values[],
values[], values[], values[]
)
()
MoveAnchor MoveAnchor.Parent #Holograms ==
minV, maxV
_, item (Holograms)
_, entry (item.parts {})
part = entry.part
part part.Parent
half = part.Size *
cf = part.CFrame
sx = -, ,
sy = -, ,
sz = -, ,
corner = cf:(.(
half.X * sx, half.Y * sy, half.Z * sz
))
minV = minV .(
.(minV.X, corner.X),
.(minV.Y, corner.Y),
.(minV.Z, corner.Z)
) corner
maxV = maxV .(
.(maxV.X, corner.X),
.(maxV.Y, corner.Y),
.(maxV.Z, corner.Z)
) corner
minV maxV
size = maxV - minV
MoveAnchor.Size = .(
.(size.X, ),
.(size.Y, ),
.(size.Z, )
)
MoveAnchor.CFrame = .((minV + maxV) / )
(enabled)
MoveEnabled = enabled #Holograms >
MoveAnchor MoveAnchor:(); MoveAnchor =
MoveEnabled
folder = Workspace:() Workspace
MoveAnchor = .()
MoveAnchor.Name =
MoveAnchor.Size = .(,,)
MoveAnchor.Transparency =
MoveAnchor.Anchored =
MoveAnchor.CanCollide =
MoveAnchor.CanTouch =
MoveAnchor.CanQuery =
MoveAnchor.CFrame = .()
MoveAnchor.Parent = folder
()
MoveHandles
MoveHandles.Adornee = MoveAnchor
MoveHandles.Visible =
MoveHandles
MoveHandles.Adornee =
MoveHandles.Visible =
(delta)
delta.Magnitude <
HologramOffset += delta
_, item (Holograms)
item.pivot = item.pivot + delta
_, entry (item.parts {})
entry.part entry.part.Parent
entry.part.CFrame = entry.part.CFrame + delta
()
()
Checking =
MoveEnabled =
HologramOffset = .zero
MoveHandles MoveHandles.Adornee = ; MoveHandles.Visible =
MoveAnchor MoveAnchor:(); MoveAnchor =
_, item (Holograms)
item.model item.model.Parent
item.model:()
.(Holograms)
folder = Workspace:()
folder
_, obj (folder:())
obj:()
obj:()
()
folder = Workspace:()
folder
folder = .()
folder.Name =
folder.Parent = Workspace
folder
(data, parent, pivot)
class = data.class
part
class ==
(data.meshId) ==
data.meshId ~=
part = .()
(()
part.MeshId = data.meshId
part.TextureID = data.textureId
)
part = .()
data.shape
(()
part.Shape = .PartType[data.shape]
)
part.Name = data.name
properties = data.properties
(properties) ==
propertyName, encoded (properties)
protected = {
Parent = ,
Name = ,
= ,
Position = ,
Orientation = ,
Rotation = ,
PivotOffset = ,
AssemblyRootPart = ,
AssemblyCenterOfMass = ,
AssemblyLinearVelocity = ,
AssemblyAngularVelocity = ,
ReceiveAge = ,
NetworkOwnershipRule = ,
UniqueId = ,
SourceAssetId = ,
MeshContent = ,
TextureContent =
}
protected[propertyName]
decodedOk, value, valid = (()
v, canDecode = (encoded)
v, canDecode
)
decodedOk valid
(()
part[propertyName] = value
)
(()
part.Size = .(data.size[], data.size[], data.size[])
)
(()
part.Color = .(data.color[], data.color[], data.color[])
)
(()
part.Reflectance = data.reflectance
)
(()
part.Material = .Material[data.material]
)
part.Anchored =
part.CanCollide =
part.CanTouch =
part.CanQuery =
part.CastShadow =
(()
part.Transparency = .(data.transparency , )
)
part.CFrame = pivot * (data.cf)
part:(, )
part.Parent = parent
part
(adornee, modelName)
adornee
billboard = .()
billboard.Name =
billboard.Adornee = adornee
billboard.Size = .(, )
billboard.StudsOffset = .(, , )
billboard.AlwaysOnTop =
billboard.Enabled =
label = .()
label.Name =
label.Size = .(, )
label.BackgroundColor3 = .(, , )
label.BackgroundTransparency =
label.BorderSizePixel =
label.TextColor3 = .(, , )
label.TextStrokeTransparency =
label.TextWrapped =
label.TextXAlignment = .TextXAlignment.Left
label.TextYAlignment = .TextYAlignment.Center
label.Font = .Font.GothamSemibold
label.TextSize =
label.Text =
label.Parent = billboard
corner = .()
corner.CornerRadius = .(, )
corner.Parent = label
billboard.Parent = adornee
billboard, label
ShowMissingBlockMessages =
ShowAllHologramMessages =
ShowHolograms =
()
_, item (Holograms)
item.model item.model.Parent
_, descendant (item.model:())
descendant:()
descendant.LocalTransparencyModifier = ShowHolograms
item.errorBillboard item.errorBillboard.Parent
item.errorBillboard.Enabled = ShowAllHologramMessages ShowHolograms
item.errorMessage ~= item.errorMessage ~=
(item, message, enabled)
item.errorMessage = message
shouldShow = ShowAllHologramMessages ShowHolograms
enabled == message ~= message ~=
item.errorBillboard item.errorBillboard.Parent
item.errorBillboard.Enabled = shouldShow
item.errorLabel item.errorLabel.Parent
item.errorLabel.Text = message
(modelData, folder)
ghost = .()
ghost.Name = modelData.name
ghost:(, )
pivot = (modelData)
parts = {}
_, partData (modelData.parts)
ok, part = (()
(partData, ghost, pivot)
)
ok part
.(parts, {
part = part,
data = partData
})
mainPart =
modelData.mainPart
expectedCF = pivot * (modelData.mainPart)
bestDistance = .huge
_, entry (parts)
distance =
(entry.part.Position - expectedCF.Position).Magnitude
distance < bestDistance
bestDistance = distance
mainPart = entry.part
mainPart #parts >
mainPart = parts[].part
ghost.PrimaryPart = mainPart
errorBillboard, errorLabel = (mainPart, ghost.Name)
ghost.Parent = folder
.(Holograms, {
model = ghost,
pivot = pivot,
parts = parts,
data = modelData,
status = ,
errorBillboard = errorBillboard,
errorLabel = errorLabel,
errorMessage =
})
ghost
(blocks)
dataFolder = Player:()
dataFolder
, {}
required = {}
_, item (blocks {})
modelData = item item.data
name = modelData modelData.name
(name) == name ~=
required[name] = (required[name] ) +
missing = {}
name, needed (required)
value = dataFolder:(name)
value value:()
available = (value.Value)
available available < needed
.(missing, {
name = name,
needed = needed,
available = available,
short = needed - available
})
.(missing, (a, b) a.name < b.name )
#missing == , missing
(blocks)
enough, missing = (blocks)
enough
total = #blocks
(.(, total))
, .(, total)
()
_, item (missing)
(.(, item.name, item.needed, item.available, item.short))
summary = {}
_, item (missing)
.(summary, .(, item.name, item.short, item.needed, item.available))
, .. .(summary, )
(entry)
()
folder = ()
_, modelData (entry.data.blocks)
modelData.coordinateSpace = entry.data.coordinateSpace modelData.coordinateSpace
(modelData, folder)
Checking =
(Holograms)
(
,
entry.name,
,
#Holograms
)
(a, b)
.(.(
.(a - b),
.(a - b)
))
(a, b)
ax, ay, az = a:()
bx, by, bz = b:()
(ax, bx) <= ROTATION_TOLERANCE
(ay, by) <= ROTATION_TOLERANCE
(az, bz) <= ROTATION_TOLERANCE
SPATIAL_CELL_SIZE =
(position)
.(position.X / SPATIAL_CELL_SIZE),
.(position.Y / SPATIAL_CELL_SIZE),
.(position.Z / SPATIAL_CELL_SIZE)
(blocks)
index = {}
_, entry (blocks)
x, y, z = (entry.part.Position)
key = x .. .. y .. .. z
cell = index[key]
cell
cell = {}
index[key] = cell
.(cell, entry)
index
(position, spatialIndex)
cx, cy, cz = (position)
best, bestDistance = , POSITION_TOLERANCE
radius = .(POSITION_TOLERANCE / SPATIAL_CELL_SIZE) +
x = cx - radius, cx + radius
y = cy - radius, cy + radius
z = cz - radius, cz + radius
cell = spatialIndex[x .. .. y .. .. z]
cell
_, entry (cell)
distance = (entry.part.Position - position).Magnitude
distance <= bestDistance
best = entry
bestDistance = distance
best
(item, status)
item.status = status
status ==
(item, , )
status ==
(item, .. (item.data.name ), )
status ==
(item, item.errorMessage ~= item.errorMessage , )
status ==
(item, item.errorMessage ~= item.errorMessage , )
color
status ==
color = .(, , )
status ==
color = .(, , )
status ==
color = .(, , )
color = .(, , )
_, entry (item.parts)
part = entry.part
part part.Parent
part.Color = color
status ==
part.Transparency =
part.Transparency =
(force)
force ( Checking ShowAllHologramMessages)
blocks = ()
spatialIndex = (blocks)
_, item (Holograms)
expected = item.data
pivot = item.pivot
mainPosition = pivot.Position
expected.mainPart
mainPosition =
(pivot * (expected.mainPart)).Position
found = (mainPosition, spatialIndex)
found
item.errorMessage = .. (expected.name )
(item, )
found.model.Name ~= expected.name
item.errorMessage = .. (expected.name)
.. .. (found.model.Name)
(item, )
actual = found.part
messages = {}
expectedRotation = pivot
expected.mainPart
expectedRotation = pivot * (expected.mainPart)
(actual.CFrame, expectedRotation)
ex, ey, ez = expectedRotation:()
ax, ay, az = actual.CFrame:()
.(messages, .(
,
.(ex), .(ey), .(ez),
.(ax), .(ay), .(az)
))
#messages ==
item.errorMessage =
(item, )
item.errorMessage = .(messages, )
(item, )
refreshStatus
BuildRunning =
(character, backpack)
(backpack backpack:())
(character character:())
(modelData)
(modelData.parts) ~=
_, partData (modelData.parts)
partData.name == (partData.size) ==
x, y, z = (partData.size[]), (partData.size[]), (partData.size[])
x y z x > y > z >
.(x, y, z)
(blocksFolder, before, blockName, desiredCF, claimedModels, timeout)
blocksFolder
deadline = .() + (timeout )
best, bestDistance = , .huge
_, model (blocksFolder:())
before[model] claimedModels[model]
model:() model.Name == blockName
part = (model)
part
distance = (part.Position - desiredCF.Position).Magnitude
distance < bestDistance
best, bestDistance = model, distance
best
claimedModels[best] =
best
.()
.() >= deadline
(modelData)
(modelData.parts) ~=
_, partData (modelData.parts)
partData.name == (partData.color) ==
r, g, b = (partData.color[]), (partData.color[]), (partData.color[])
r g b
.(
.(r, , ),
.(g, , ),
.(b, , )
)
()
BuildRunning
()
loaded = Holograms
#loaded ==
()
enoughMaterials, inventoryMessage = (loaded)
enoughMaterials
()
( .. inventoryMessage)
character = Player.Character Player.CharacterAdded:()
humanoid = character:()
backpack = Player:()
tool = (backpack backpack:())
character:()
humanoid tool
()
scaleTool = (character, backpack)
scaleRemote = scaleTool scaleTool:()
scaleRemote scaleRemote:() scaleRemote =
paintTool = (backpack backpack:())
(character character:())
paintRemote = paintTool paintTool:()
paintRemote paintRemote:() paintRemote =
remote = tool:()
remote remote:()
()
replicated = :()
templates = replicated:()
dataFolder = Player:()
guiPlayer = Player:()
buildGui = guiPlayer guiPlayer:()
inventory = buildGui buildGui:()
more = inventory inventory:()
anchorObj = more more:()
anchorValue = anchorObj anchorObj:()
rotationObj = more (more:() more:())
rotationValue = rotationObj (rotationObj:() rotationObj:())
bindObj = more more:()
bindValue = bindObj bindObj:()
anchorValue anchorValue:() anchorValue.Value =
rotationValue rotationValue:() rotationValue.Value =
bindValue bindValue:() bindValue.Value =
templates dataFolder
()
resolvedTeam = ()
teamColor = resolvedTeam resolvedTeam.TeamColor Player.TeamColor
zone = Workspace:((teamColor) .. )
zone zone:()
()
target = zone
allBlocksRoot = Workspace:()
playerBlocks = allBlocksRoot allBlocksRoot:(Player.Name)
blocksFolder = playerBlocks
BuildRunning =
()
(() humanoid:(tool) )
.(()
placedCount, failedCount = ,
templateCache = {}
countValueCache = {}
secondPassQueue = {}
claimedModels = {}
placedModelsByIndex = {}
paintQueue = {}
scaleQueue = {}
PAINT_BATCH_SIZE =
SCALE_BATCH_SIZE =
PLACEMENTS_PER_BATCH =
BATCH_DELAY =
()
paintRemote #paintQueue ==
.(paintQueue)
queue = paintQueue
paintQueue = {}
startIndex = , #queue, PAINT_BATCH_SIZE
batch = {}
endIndex = .(startIndex + PAINT_BATCH_SIZE - , #queue)
i = startIndex, endIndex
batch[#batch + ] = queue[i]
okPaint, paintErr = (()
paintRemote:(batch)
)
okPaint
(, paintErr)
()
scaleRemote #scaleQueue ==
.(scaleQueue)
queue = scaleQueue
scaleQueue = {}
batchStart = , #queue, SCALE_BATCH_SIZE
batchEnd = .(batchStart + SCALE_BATCH_SIZE - , #queue)
total = batchEnd - batchStart +
finished =
i = batchStart, batchEnd
entry = queue[i]
.(()
okScale, scaleErr = (()
scaleRemote:(entry.model, entry.size, entry.cframe)
)
okScale
(, entry.blockName, scaleErr)
finished +=
)
finished < total BuildRunning
.()
(index, item)
BuildRunning
modelData = item.data
blockName = modelData.name
template = templateCache[blockName]
template ==
template = templates:(blockName)
templateCache[blockName] = template
countValue = countValueCache[blockName]
countValue ==
countValue = dataFolder:(blockName)
countValueCache[blockName] = countValue
template == template =
countValue == countValue =
template template:()
failedCount +=
(, blockName)
template.PrimaryPart
failedCount +=
(, blockName)
countValue countValue:() countValue.Value <=
failedCount +=
(, blockName)
desiredCF
okCF, resultCF = (()
pivot = (modelData)
modelData.mainPart
pivot * (modelData.mainPart)
pivot
)
okCF desiredCF = resultCF
desiredCF
failedCount +=
(, blockName)
relativeCF = zone.CFrame:(desiredCF)
anchor =
makeBinds =
args = {
blockName,
countValue.Value,
target,
relativeCF,
anchor,
desiredCF,
,
makeBinds
}
before = {}
blocksFolder
_, existing (blocksFolder:()) before[existing] =
okInvoke, result = (()
remote:((args))
)
newlyPlacedModel =
okInvoke result ~=
newlyPlacedModel = ((result) ==
(result:() result result:()))
newlyPlacedModel newlyPlacedModel.Parent ~= blocksFolder
newlyPlacedModel = (blocksFolder, before, blockName, desiredCF, claimedModels, )
newlyPlacedModel
claimedModels[newlyPlacedModel] =
okInvoke result ~=
placedCount +=
newlyPlacedModel newlyPlacedModel.Parent == blocksFolder
placedModelsByIndex[index] = newlyPlacedModel
propertyModel = newlyPlacedModel
propertyModel propertyModel.Parent == blocksFolder
restored, skipped = (propertyModel, modelData)
skipped >
(, blockName, , restored, , skipped)
savedSize = (modelData)
scaleRemote savedSize blocksFolder
placedModel = newlyPlacedModel
placedModel placedModel.Parent == blocksFolder
placedPrimary = (placedModel)
placedPrimary
.(scaleQueue, {model = placedModel, size = savedSize, cframe = desiredCF, blockName = blockName})
savedColor = (modelData)
placedModel = newlyPlacedModel
paintRemote savedColor blocksFolder
placedModel placedModel.Parent == blocksFolder
.(paintQueue, {placedModel, savedColor})
savedColor
(, blockName)
.(secondPassQueue, {
index = index,
args = args,
blockName = blockName,
modelData = modelData,
desiredCF = desiredCF,
blocksFolder = blocksFolder
})
failedCount +=
(, blockName, result)
batchStart = , #loaded, PLACEMENTS_PER_BATCH
BuildRunning
claimedModels = {}
batchEnd = .(batchStart + PLACEMENTS_PER_BATCH - , #loaded)
pendingCalls = batchEnd - batchStart +
finishedCalls =
index = batchStart, batchEnd
item = loaded[index]
.(()
ok, err = (processItem, index, item)
ok
failedCount +=
(, index, err)
finishedCalls +=
)
finishedCalls < pendingCalls BuildRunning
.()
()
()
(.(, batchEnd, #loaded, placedCount, failedCount))
batchEnd < #loaded .(BATCH_DELAY)
BuildRunning #secondPassQueue >
(.(, #secondPassQueue))
.()
remainingFailures =
retryIndex, pending (secondPassQueue)
BuildRunning
blocksFolder = pending.blocksFolder
before = {}
blocksFolder
_, existing (blocksFolder:())
before[existing] =
okRetry, retryResult = (()
remote:((pending.args))
)
placedModel = ((retryResult) ==
(retryResult:() retryResult retryResult:()))
placedModel blocksFolder
_, existing (blocksFolder:())
before[existing] existing:() (existing)
placedModel = existing
okRetry retryResult ~=
placedCount +=
placedModel placedModel.Parent == blocksFolder pending.index
placedModelsByIndex[pending.index] = placedModel
placedModel placedModel.Parent == blocksFolder
restored, skipped = (placedModel, pending.modelData)
skipped >
(, pending.blockName, , restored, , skipped)
savedSize = (pending.modelData)
scaleRemote savedSize placedModel placedModel.Parent == blocksFolder
.(scaleQueue, {model = placedModel, size = savedSize, cframe = pending.desiredCF, blockName = pending.blockName})
savedColor = (pending.modelData)
paintRemote savedColor placedModel placedModel.Parent == blocksFolder
.(paintQueue, {placedModel, savedColor})
#paintQueue >= PAINT_BATCH_SIZE ()
remainingFailures +=
(, pending.blockName, retryResult)
(.(,
retryIndex, #secondPassQueue, placedCount, remainingFailures))
()
()
failedCount = remainingFailures
()
savedConnections = loaded[] loaded[].data loaded[].data.bindConnections
(savedConnections) ~= #savedConnections ==
()
bindTool = (backpack backpack:())
(character character:())
bindRemote = bindTool bindTool:()
bindRemote bindRemote:()
()
restored, skipped = ,
_, edge (savedConnections)
modelA = placedModelsByIndex[edge.a]
modelB = placedModelsByIndex[edge.b]
modelA modelB modelA.Parent == blocksFolder modelB.Parent == blocksFolder
(model, tagName)
ok, result = (() model:(tagName) )
ok result
groupA, groupB
(modelA, ) ((modelB, ) (modelB, ))
groupA, groupB = {modelA}, {modelB}
(modelB, ) ((modelA, ) (modelA, ))
groupA, groupB = {modelB}, {modelA}
groupA groupB
actionMap, keyMap = {}, {}
_, source (groupA)
_, child (source:())
child.Name:(, ) == child.Name ~=
action = child:()
action action:() (action.Value) ==
actionMap[action.Value] = actionMap[action.Value] {}
.(actionMap[action.Value], child)
_, actionName (edge.actions {})
savedSourceData
_, hologramItem (loaded)
hologramItem.data hologramItem.data.id == (edge.actionSource edge.a)
savedSourceData = hologramItem.data
actions = savedSourceData savedSourceData.bindActions
actions actions[actionName] actions[actionName].keyCode
keyMap[actionName] = actions[actionName].keyCode
seatInput = (groupA[], ) (groupB[], )
okBind, bindErr = (()
bindRemote:(actionMap, groupB, keyMap, , seatInput)
)
okBind
restored +=
skipped +=
(, bindErr)
skipped +=
skipped +=
(, restored, , skipped)
()
()
.()
()
correct, missing, wrong = , ,
_, item (Holograms)
item.status ==
correct +=
item.status ==
missing +=
wrong +=
BuildRunning =
(.(, correct, missing, wrong))
)
gui = .()
gui.Name =
gui.ResetOnSpawn =
parentOk = (()
gui.Parent = :()
)
parentOk gui.Parent
gui.Parent = Player:()
main = .()
main.Name =
main.Size = .(, )
main.Position = .(, , , )
main.BackgroundColor3 = .(, , )
main.BorderSizePixel =
main.Active =
main.Draggable =
main.Parent = gui
corner = .()
corner.CornerRadius = .(, )
corner.Parent = main
title = .()
title.Size = .(, , , )
title.BackgroundColor3 = .(, , )
title.BorderSizePixel =
title.Text =
title.TextColor3 = .(, , )
title.Font = .Font.GothamBold
title.TextSize =
title.Parent = main
(parent, text, y, callback)
button = Instance.new("TextButton")
button.Size = UDim2.new(1, -20, 0, 34)
button.Position = UDim2.new(0, 10, 0, y)
button.BackgroundColor3 = Color3.fromRGB(50, 100, 175)
button.BorderSizePixel = 0
button.Text = text
button.TextColor3 = Color3.new(1, 1, 1)
button.Font = Enum.Font.GothamSemibold
button.TextSize = 13
button.Parent = parent
local c = Instance.new("UICorner")
c.CornerRadius = UDim.new(0, 6)
c.Parent = button
button.MouseButton1Click:Connect(callback)
return button
end
-- Roblox Handles: use the same axis-drag interaction as the Move tool.
MoveHandles = Instance.new("Handles")
MoveHandles.Name = "SchematicMoveHandles"
MoveHandles.Style = Enum.HandlesStyle.Movement
MoveHandles.Faces = Faces.new(Enum.NormalId.Right, Enum.NormalId.Left, Enum.NormalId.Top, Enum.NormalId.Bottom, Enum.NormalId.Front, Enum.NormalId.Back)
MoveHandles.Visible = false
MoveHandles.Parent = Player:WaitForChild("PlayerGui")
local lastDragDistance = 0
MoveHandles.MouseButton1Down:Connect(function()
lastDragDistance = 0
end)
MoveHandles.MouseButton1Up:Connect(function()
lastDragDistance = 0
end)
MoveHandles.MouseDrag:Connect(function(face, distance)
if not MoveEnabled or #Holograms == 0 then return end
local deltaDistance = distance - lastDragDistance
lastDragDistance = distance
local localNormal = Vector3.FromNormalId(face)
local delta = MoveAnchor and MoveAnchor.CFrame:VectorToWorldSpace(localNormal) * deltaDistance or localNormal * deltaDistance
applyHologramMove(delta)
end)
-- Select holograms in screen space instead of raycasting them. This lets ghost
-- parts keep CanQuery=false, so BABFT's own BuildingTool cannot snap/place blocks
-- onto the schematic merely because the user clicked it.
local Mouse = Player:GetMouse()
local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")
local camera = Workspace.CurrentCamera
local function pointInsideSchematicScreenBounds(mouseX, mouseY)
camera = Workspace.CurrentCamera
if not camera then return false end
for _, item in ipairs(Holograms) do
for _, entry in ipairs(item.parts or {}) do
local part = entry.part
if part and part.Parent and part:IsA("BasePart") then
local cf = part.CFrame
local half = part.Size * 0.5
local minX, minY = math.huge, math.huge
local maxX, maxY = -math.huge, -math.huge
local anyOnScreen = false
for sx = -1, 1, 2 do
for sy = -1, 1, 2 do
for sz = -1, 1, 2 do
local corner = cf:PointToWorldSpace(Vector3.new(
half.X * sx, half.Y * sy, half.Z * sz
))
local screen, visible = camera:WorldToScreenPoint(corner)
if screen.Z > 0 then
anyOnScreen = true
minX = math.min(minX, screen.X)
minY = math.min(minY, screen.Y)
maxX = math.max(maxX, screen.X)
maxY = math.max(maxY, screen.Y)
end
end
end
end
if anyOnScreen and mouseX >= minX - 3 and mouseX <= maxX + 3
and mouseY >= minY - 3 and mouseY <= maxY + 3 then
return true
end
end
end
end
return false
end
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
-- Ignore clicks on this script's own control panel.
local mousePos = UserInputService:GetMouseLocation()
local guiObjects = GuiService:GetGuiObjectsAtPosition(mousePos.X, mousePos.Y)
for _, object in ipairs(guiObjects) do
if object == gui or object:IsDescendantOf(gui) then
return
end
end
-- Mouse.X/Y match WorldToScreenPoint's screen coordinates.
local clickedSchematic = pointInsideSchematicScreenBounds(Mouse.X, Mouse.Y)
if clickedSchematic then
if not MoveEnabled then
setMoveMode(true)
else
updateMoveAnchor()
end
refreshStatus("Drag the arrows to move the hologram; click outside to hide them.")
elseif MoveEnabled then
setMoveMode(false)
refreshStatus("Hologram movement hidden")
end
end)
local function makeTextBox(parent, placeholder, y)
local box = Instance.new("TextBox")
box.Size = UDim2.new(1, -20, 0, 32)
box.Position = UDim2.new(0, 10, 0, y)
box.BackgroundColor3 = Color3.fromRGB(40, 44, 57)
box.BorderSizePixel = 0
box.PlaceholderText = placeholder
box.PlaceholderColor3 = Color3.fromRGB(170, 175, 190)
box.Text = ""
box.TextColor3 = Color3.new(1, 1, 1)
box.Font = Enum.Font.Gotham
box.TextSize = 13
box.ClearTextOnFocus = false
box.Parent = parent
local c = Instance.new("UICorner")
c.CornerRadius = UDim.new(0, 6)
c.Parent = box
return box
end
local nameBox = makeTextBox(main, "Schematic name...", 48)
local status = Instance.new("TextLabel")
status.Size = UDim2.new(1, -20, 0, 18)
status.Position = UDim2.new(0, 10, 1, -20)
status.BackgroundTransparency = 1
status.Text = "Ready"
status.TextColor3 = Color3.fromRGB(200, 205, 220)
status.Font = Enum.Font.Gotham
status.TextSize = 11
status.Parent = main
refreshStatus = function(text)
status.Text = text
end
local selectedSavePlayer = Player
local savePlayerButton
savePlayerButton = makeButton(main, "Save player: " .. Player.Name, 87, function()
local playerList = Players:GetPlayers()
table.sort(playerList, function(a, b) return a.Name:lower() < b.Name:lower() end)
if #playerList == 0 then return end
local currentIndex = 0
for i, candidate in ipairs(playerList) do
if candidate == selectedSavePlayer then
currentIndex = i
break
end
end
currentIndex = (currentIndex % #playerList) + 1
selectedSavePlayer = playerList[currentIndex]
savePlayerButton.Text = "Save player: " .. selectedSavePlayer.Name
refreshStatus("Source: " .. selectedSavePlayer.Name)
end)
makeButton(main, "Save Build", 127, function()
local name = nameBox.Text
if name == "" then
refreshStatus("Enter a name first!")
return
end
if saveSchematic(name, selectedSavePlayer.Name) then
refreshStatus("Saved: " .. safeFileName(name) .. " (" .. selectedSavePlayer.Name .. ")")
else
refreshStatus("Could not save schematic")
end
end)
makeButton(main, "Load Schematic", 167, function()
local old = main:FindFirstChild("SchematicList")
if old then
old:Destroy()
end
local list = Instance.new("Frame")
list.Name = "SchematicList"
list.Size = UDim2.fromOffset(280, 240)
list.Position = UDim2.new(1, 10, 0, 35)
list.BackgroundColor3 = Color3.fromRGB(28, 31, 43)
list.BorderSizePixel = 0
list.Parent = main
local c = Instance.new("UICorner")
c.CornerRadius = UDim.new(0, 8)
c.Parent = list
local heading = Instance.new("TextLabel")
heading.Size = UDim2.new(1, -40, 0, 30)
heading.Position = UDim2.new(0, 10, 0, 0)
heading.BackgroundTransparency = 1
heading.Text = "Available Schematics"
heading.TextColor3 = Color3.new(1, 1, 1)
heading.Font = Enum.Font.GothamBold
heading.TextSize = 13
heading.Parent = list
local close = Instance.new("TextButton")
close.Size = UDim2.fromOffset(28, 28)
close.Position = UDim2.new(1, -32, 0, 2)
close.BackgroundTransparency = 1
close.Text = "X"
close.TextColor3 = Color3.new(1, 1, 1)
close.Font = Enum.Font.GothamBold
close.TextSize = 14
close.Parent = list
close.MouseButton1Click:Connect(function()
list:Destroy()
end)
local scroll = Instance.new("ScrollingFrame")
scroll.Size = UDim2.new(1, -16, 1, -40)
scroll.Position = UDim2.new(0, 8, 0, 35)
scroll.BackgroundTransparency = 1
scroll.BorderSizePixel = 0
scroll.ScrollBarThickness = 5
scroll.CanvasSize = UDim2.new(0, 0, 0, 0)
scroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
scroll.Parent = list
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0, 5)
layout.Parent = scroll
local found = listSchematics()
if #found == 0 then
local empty = Instance.new("TextLabel")
empty.Size = UDim2.new(1, -8, 0, 50)
empty.BackgroundTransparency = 1
empty.Text = "No schematics found."
empty.TextColor3 = Color3.fromRGB(190, 195, 210)
empty.Font = Enum.Font.Gotham
empty.TextSize = 12
empty.Parent = scroll
end
for _, entry in ipairs(found) do
local row = Instance.new("Frame")
row.Size = UDim2.new(1, -8, 0, 36)
row.BackgroundTransparency = 1
row.Parent = scroll
local button = Instance.new("TextButton")
button.Size = UDim2.new(1, -40, 1, 0)
button.Position = UDim2.new(0, 0, 0, 0)
button.BackgroundColor3 = Color3.fromRGB(48, 80, 125)
button.BorderSizePixel = 0
button.Text = entry.name .. " (" .. #entry.data.blocks .. " blocks)"
button.TextColor3 = Color3.new(1, 1, 1)
button.Font = Enum.Font.Gotham
button.TextSize = 12
button.TextTruncate = Enum.TextTruncate.AtEnd
button.Parent = row
local bc = Instance.new("UICorner")
bc.CornerRadius = UDim.new(0, 5)
bc.Parent = button
button.MouseButton1Click:Connect(function()
loadSchematic(entry)
local enoughMaterials, inventoryMessage = showInventoryReport(Holograms)
refreshStatus(enoughMaterials and ("Loaded: " .. entry.name .. " | " .. inventoryMessage) or inventoryMessage)
list:Destroy()
end)
local deleteButton = Instance.new("TextButton")
deleteButton.Size = UDim2.new(0, 34, 1, 0)
deleteButton.Position = UDim2.new(1, -34, 0, 0)
deleteButton.BackgroundColor3 = Color3.fromRGB(170, 55, 55)
deleteButton.BorderSizePixel = 0
deleteButton.Text = "X"
deleteButton.TextColor3 = Color3.new(1, 1, 1)
deleteButton.Font = Enum.Font.GothamBold
deleteButton.TextSize = 14
deleteButton.Parent = row
local dc = Instance.new("UICorner")
dc.CornerRadius = UDim.new(0, 5)
dc.Parent = deleteButton
deleteButton.MouseButton1Click:Connect(function()
if type(delfile) ~= "function" then
refreshStatus("This executor does not support file deletion")
return
end
local ok, err = pcall(function()
delfile(entry.path)
end)
if ok then
row:Destroy()
refreshStatus("Schematic deleted: " .. entry.name)
else
refreshStatus("Delete failed: " .. tostring(err))
end
end)
end
end)
-- IMAGE IMPORT FILE BROWSER
-- Browse folders recursively by entering them; Back returns to the previous folder.
local IMAGE_DIRS = {
[[C:\Users\marco\AppData\Local\Xeno\workspace\Images]],
[[workspace\Images]],
[[Images]],
[[C:\Users\marco\AppData\Local\Xeno\workspace]]
}
local function imageExtension(path)
local lower = tostring(path):lower()
return lower:match("%.png$") or lower:match("%.jpe?g$") or lower:match("%.bmp$")
end
local function normalizePath(path)
path = tostring(path or ""):gsub("/", "\\")
while path:find("\\\\", 1, true) and not path:match("^\\\\") do
path = path:gsub("\\\\", "\\")
end
return path
end
local function joinPath(parent, child)
parent, child = normalizePath(parent), normalizePath(child)
if child:match("^%a:\\") or child:match("^\\\\") then return child end
if child:lower() == parent:lower() or child:sub(1, #parent + 1):lower() == (parent .. "\\"):lower() then
return child
end
-- Some executors return paths relative to their workspace, e.g. workspace\\Images\\x.png.
local childLower, parentLower = child:lower(), parent:lower()
local marker = parentLower:match("workspace\\(.+)$")
if marker and childLower:sub(1, #marker + 1) == (marker .. "\\") then
return normalizePath(parent:match("^(.*workspace)") .. "\\" .. child)
end
return parent:gsub("\\+$", "") .. "\\" .. child:gsub("^\\+", "")
end
local function pathIsFolder(path)
if type(isfolder) == "function" then
local ok, result = pcall(isfolder, path)
if ok and result == true then return true end
end
return false
end
local function parentPath(path)
path = normalizePath(path):gsub("\\+$", "")
local parent = path:match("^(.*)\\[^\\]+$")
if parent == "" then return nil end
if parent and parent:match("^%a:$") then parent = parent .. "\\" end
return parent
end
local function samePath(a, b)
a = normalizePath(a):gsub("\\+$", "")
b = normalizePath(b):gsub("\\+$", "")
return a:lower() == b:lower()
end
local function listDirectory(path)
if type(listfiles) ~= "function" then
return nil, "Xeno listfiles() is unavailable"
end
path = normalizePath(path):gsub("\\+$", "")
local attempts, attemptSeen = {}, {}
local function addAttempt(value)
value = normalizePath(value)
if value ~= "" and not attemptSeen[value:lower()] then
attemptSeen[value:lower()] = true
attempts[#attempts + 1] = value
end
end
addAttempt(path)
local afterWorkspace = path:match("[Ww]orkspace\\(.+)$")
if afterWorkspace then
addAttempt("workspace\\" .. afterWorkspace)
addAttempt(afterWorkspace)
end
-- Xeno may expose the same workspace through a relative Images path.
addAttempt("Images")
addAttempt("workspace\\Images")
local entries, usedPath
for _, candidate in ipairs(attempts) do
local ok, result = pcall(listfiles, candidate)
if ok and type(result) == "table" then
-- Prefer the first non-empty result, but keep empty result as fallback.
if #result > 0 then
entries, usedPath = result, candidate
break
elseif entries == nil then
entries, usedPath = result, candidate
end
end
end
if type(entries) ~= "table" then
return nil, "Cannot open folder: " .. tostring(path)
end
local function workspaceRelative(value)
value = normalizePath(value)
local rel = value:match("^[^:]*[Ww]orkspace\\(.+)$")
if rel then return rel end
rel = value:match("^workspace\\(.+)$")
if rel then return rel end
return nil
end
local currentRel = workspaceRelative(path)
local currentBase = path:match("([^\\]+)$") or path
local normalizedEntries, seen = {}, {}
for _, entry in ipairs(entries) do
if type(entry) == "string" then
local raw = normalizePath(entry)
local fullPath
if raw:match("^%a:\\") or raw:match("^\\\\") then
fullPath = raw
else
-- If listfiles returns a workspace-relative path, map it back to
-- the current absolute path only when it belongs to this directory.
local rel = workspaceRelative(raw)
if rel then
if currentRel then
local currentLower, relLower = currentRel:lower(), rel:lower()
if relLower:sub(1, #currentLower + 1) == currentLower .. "\\" then
fullPath = path .. "\\" .. rel:sub(#currentRel + 2)
end
elseif currentBase:lower() == "images" and rel:lower():sub(1, 7) == "images\\" then
fullPath = path .. "\\" .. rel:sub(8)
end
end
if not fullPath then
local rawLower, pathLower = raw:lower(), path:lower()
local pathTail = path:match("([^\\]+\\[^\\]+)$")
if rawLower:sub(1, #pathLower + 1) == pathLower .. "\\" then
fullPath = raw
elseif pathTail and rawLower:sub(1, #pathTail + 1) == pathTail:lower() .. "\\" then
fullPath = path .. "\\" .. raw:sub(#pathTail + 2)
elseif not raw:find("\\", 1, true) then
fullPath = path .. "\\" .. raw
end
end
end
if fullPath then
fullPath = normalizePath(fullPath)
local parent = parentPath(fullPath)
if parent and samePath(parent, path) and not seen[fullPath:lower()] then
seen[fullPath:lower()] = true
normalizedEntries[#normalizedEntries + 1] = fullPath
end
end
end
end
-- Some executor builds do not accept absolute paths in isfolder(). Try the
-- equivalent workspace-relative path too, so folders remain recognizable.
local function isDirectory(entryPath)
if pathIsFolder(entryPath) then return true end
if type(isfolder) == "function" then
local rel = workspaceRelative(entryPath)
if rel then
local candidates = {"workspace\\" .. rel, rel}
for _, candidate in ipairs(candidates) do
local ok, result = pcall(isfolder, candidate)
if ok and result == true then return true end
end
end
end
-- Folder names usually have no extension; use this only as fallback.
return not entryPath:match("%.[^\\]+$")
end
table.sort(normalizedEntries, function(a, b)
local af, bf = isDirectory(a), isDirectory(b)
if af ~= bf then return af end
return a:lower() < b:lower()
end)
return normalizedEntries
end
local function openImageImport()
local old = main:FindFirstChild("ImageImportWindow")
if old then old:Destroy() end
local popup = Instance.new("Frame")
popup.Name = "ImageImportWindow"
popup.Size = UDim2.fromOffset(390, 455)
popup.Position = UDim2.new(1, 10, 0, 35)
popup.BackgroundColor3 = Color3.fromRGB(28, 31, 43)
popup.BorderSizePixel = 0
popup.Parent = main
Instance.new("UICorner", popup).CornerRadius = UDim.new(0, 8)
local title = Instance.new("TextLabel")
title.Size = UDim2.new(1, -44, 0, 30)
title.Position = UDim2.fromOffset(10, 3)
title.BackgroundTransparency = 1
title.Text = "Import Image"
title.TextColor3 = Color3.new(1, 1, 1)
title.Font = Enum.Font.GothamBold
title.TextSize = 14
title.TextXAlignment = Enum.TextXAlignment.Left
title.Parent = popup
local close = Instance.new("TextButton")
close.Size = UDim2.fromOffset(28, 28)
close.Position = UDim2.new(1, -32, 0, 3)
close.BackgroundTransparency = 1
close.Text = "X"
close.TextColor3 = Color3.new(1, 1, 1)
close.Font = Enum.Font.GothamBold
close.TextSize = 14
close.Parent = popup
close.MouseButton1Click:Connect(function() popup:Destroy() end)
local folderLabel = Instance.new("TextLabel")
folderLabel.Size = UDim2.new(1, -20, 0, 44)
folderLabel.Position = UDim2.fromOffset(10, 30)
folderLabel.BackgroundTransparency = 1
folderLabel.Text = "PATH: (select a starting folder)"
folderLabel.TextColor3 = Color3.fromRGB(190, 200, 220)
folderLabel.Font = Enum.Font.Code
folderLabel.TextSize = 10
folderLabel.TextWrapped = true
folderLabel.TextTruncate = Enum.TextTruncate.None
folderLabel.TextXAlignment = Enum.TextXAlignment.Left
folderLabel.TextYAlignment = Enum.TextYAlignment.Center
folderLabel.Parent = popup
local search = Instance.new("TextBox")
search.Size = UDim2.new(1, -20, 0, 28)
search.Position = UDim2.fromOffset(10, 77)
search.BackgroundColor3 = Color3.fromRGB(20, 23, 32)
search.BorderSizePixel = 0
search.PlaceholderText = "Search image or folder..."
search.Text = ""
search.TextColor3 = Color3.new(1, 1, 1)
search.PlaceholderColor3 = Color3.fromRGB(150, 155, 170)
search.Font = Enum.Font.Gotham
search.TextSize = 11
search.ClearTextOnFocus = false
search.Parent = popup
Instance.new("UICorner", search).CornerRadius = UDim.new(0, 5)
local back = Instance.new("TextButton")
back.Size = UDim2.fromOffset(85, 26)
back.Position = UDim2.fromOffset(10, 110)
back.BackgroundColor3 = Color3.fromRGB(65, 70, 88)
back.BorderSizePixel = 0
back.Text = "< Back"
back.TextColor3 = Color3.new(1, 1, 1)
back.Font = Enum.Font.Gotham
back.TextSize = 11
back.Parent = popup
Instance.new("UICorner", back).CornerRadius = UDim.new(0, 5)
local refresh = Instance.new("TextButton")
refresh.Size = UDim2.fromOffset(85, 26)
refresh.Position = UDim2.fromOffset(101, 110)
refresh.BackgroundColor3 = Color3.fromRGB(48, 80, 125)
refresh.BorderSizePixel = 0
refresh.Text = "Refresh"
refresh.TextColor3 = Color3.new(1, 1, 1)
refresh.Font = Enum.Font.Gotham
refresh.TextSize = 11
refresh.Parent = popup
Instance.new("UICorner", refresh).CornerRadius = UDim.new(0, 5)
local scroll = Instance.new("ScrollingFrame")
scroll.Size = UDim2.new(1, -20, 0, 215)
scroll.Position = UDim2.fromOffset(10, 141)
scroll.BackgroundColor3 = Color3.fromRGB(20, 23, 32)
scroll.BorderSizePixel = 0
scroll.ScrollBarThickness = 5
scroll.CanvasSize = UDim2.new(0, 0, 0, 0)
scroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
scroll.Parent = popup
Instance.new("UICorner", scroll).CornerRadius = UDim.new(0, 5)
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0, 4)
layout.Parent = scroll
local scaleBox = Instance.new("TextBox")
scaleBox.Size = UDim2.fromOffset(88, 25)
scaleBox.Position = UDim2.fromOffset(10, 360)
scaleBox.BackgroundColor3 = Color3.fromRGB(40, 44, 57)
scaleBox.BorderSizePixel = 0
scaleBox.Text = "10"
scaleBox.PlaceholderText = "Studs"
scaleBox.TextColor3 = Color3.new(1, 1, 1)
scaleBox.Font = Enum.Font.Gotham
scaleBox.TextSize = 11
scaleBox.Parent = popup
Instance.new("UICorner", scaleBox).CornerRadius = UDim.new(0, 4)
local heightBox = Instance.new("TextBox")
heightBox.Size = UDim2.fromOffset(72, 25)
heightBox.Position = UDim2.fromOffset(104, 360)
heightBox.BackgroundColor3 = Color3.fromRGB(40, 44, 57)
heightBox.BorderSizePixel = 0
heightBox.Text = "0.2"
heightBox.PlaceholderText = "Size Y"
heightBox.TextColor3 = Color3.new(1, 1, 1)
heightBox.Font = Enum.Font.Gotham
heightBox.TextSize = 11
heightBox.Parent = popup
Instance.new("UICorner", heightBox).CornerRadius = UDim.new(0, 4)
local materialBox = Instance.new("TextBox")
materialBox.Size = UDim2.fromOffset(174, 25)
materialBox.Position = UDim2.fromOffset(184, 360)
materialBox.BackgroundColor3 = Color3.fromRGB(40, 44, 57)
materialBox.BorderSizePixel = 0
materialBox.Text = "PlasticBlock"
materialBox.PlaceholderText = "BABFT block name"
materialBox.TextColor3 = Color3.new(1, 1, 1)
materialBox.Font = Enum.Font.Gotham
materialBox.TextSize = 10
materialBox.Parent = popup
Instance.new("UICorner", materialBox).CornerRadius = UDim.new(0, 4)
local status = Instance.new("TextLabel")
status.Size = UDim2.new(1, -20, 0, 34)
status.Position = UDim2.fromOffset(10, 386)
status.BackgroundTransparency = 1
status.Text = "Choose an image file."
status.TextColor3 = Color3.fromRGB(220, 220, 230)
status.Font = Enum.Font.Gotham
status.TextSize = 10
status.TextWrapped = true
status.TextXAlignment = Enum.TextXAlignment.Left
status.Parent = popup
local import = Instance.new("TextButton")
import.Size = UDim2.new(1, -20, 0, 28)
import.Position = UDim2.fromOffset(10, 411)
import.BackgroundColor3 = Color3.fromRGB(35, 130, 85)
import.BorderSizePixel = 0
import.Text = "Import selected image"
import.TextColor3 = Color3.new(1, 1, 1)
import.Font = Enum.Font.GothamBold
import.TextSize = 11
import.Parent = popup
Instance.new("UICorner", import).CornerRadius = UDim.new(0, 5)
local currentPath, selectedPath
local history = {}
local function clearRows()
for _, child in ipairs(scroll:GetChildren()) do
if child ~= layout then child:Destroy() end
end
end
local render
local function enterFolder(path)
history[#history + 1] = currentPath
currentPath = path
selectedPath = nil
render()
end
render = function()
clearRows()
folderLabel.Text = "PATH: " .. normalizePath(currentPath or "(none)")
back.Active = #history > 0
back.AutoButtonColor = #history > 0
local entries, err = listDirectory(currentPath)
if not entries then
status.Text = err or "Could not read this folder."
return
end
local query = search.Text:lower()
local shown = 0
for _, path in ipairs(entries) do
if type(path) == "string" then
path = normalizePath(path)
local name = path:gsub("\\", "/"):match("([^/]+)$") or path
local folder = pathIsFolder(path) or (not path:match("%.[^\\]+$"))
local isImage = imageExtension(path)
if (folder or isImage) and (query == "" or name:lower():find(query, 1, true)) then
shown += 1
local item = Instance.new("TextButton")
item.Size = UDim2.new(1, -8, 0, 27)
item.BackgroundColor3 = (path == selectedPath) and Color3.fromRGB(65, 100, 145)
or (folder and Color3.fromRGB(28, 75, 43) or Color3.fromRGB(42, 47, 62))
item.BorderSizePixel = 0
item.Text = (folder and "[DIR] " or "[IMG] ") .. name
item.TextColor3 = folder and Color3.fromRGB(100, 255, 130) or Color3.fromRGB(240, 240, 245)
item.Font = Enum.Font.Gotham
item.TextSize = 11
item.TextXAlignment = Enum.TextXAlignment.Left
item.Parent = scroll
Instance.new("UICorner", item).CornerRadius = UDim.new(0, 4)
item.MouseButton1Click:Connect(function()
if folder then
enterFolder(path)
else
selectedPath = path
status.Text = "Selected: " .. path
render()
end
end)
end
end
end
if shown == 0 then
local empty = Instance.new("TextLabel")
empty.Size = UDim2.new(1, -8, 0, 34)
empty.BackgroundTransparency = 1
empty.Text = query ~= "" and "No matching image/folder." or "No image files or subfolders."
empty.TextColor3 = Color3.fromRGB(180, 190, 210)
empty.Font = Enum.Font.Gotham
empty.TextSize = 11
empty.Parent = scroll
end
status.Text = selectedPath and ("Selected: " .. normalizePath(selectedPath))
or (tostring(shown) .. " item(s) shown | folders are green.")
end
back.MouseButton1Click:Connect(function()
if #history > 0 then
currentPath = table.remove(history)
selectedPath = nil
render()
end
end)
refresh.MouseButton1Click:Connect(render)
search:GetPropertyChangedSignal("Text"):Connect(render)
local function startAt(index)
local path = IMAGE_DIRS[index]
if path then
local entries = listDirectory(path)
if entries ~= nil then
currentPath = path
render()
return true
end
end
return false
end
if not startAt(1) and not startAt(2) then
currentPath = IMAGE_DIRS[1]
status.Text = "Couldn't open Images. Check the Xeno workspace folder."
render()
end
-- Decode uncompressed 24/32-bit BMP directly. PNG/JPEG can be supplied by an
-- executor-provided decodeImagePixels(path) function returning {width,height,pixels}.
local function decodeBMP(path)
local ok, bytes = pcall(readfile, path)
if not ok or type(bytes) ~= "string" or #bytes < 54 then return nil, "Could not read BMP" end
local function u16(i) local a,b=string.byte(bytes,i,i+1); if not b then return 0 end; return a+b*256 end
local function u32(i) local a,b,c,d=string.byte(bytes,i,i+3); if not d then return 0 end; return a+b*256+c*65536+d*16777216 end
if string.sub(bytes,1,2) ~= "BM" then return nil, "Not a BMP file" end
local offset, dib = u32(11), u32(15)
local width, height, planes, bpp, compression = u32(19), u32(23), u16(27), u16(29), u32(31)
if dib < 40 or width < 1 or height < 1 or width > 512 or height > 512 then return nil, "BMP size/header unsupported (max 512x512)" end
if planes ~= 1 or (bpp ~= 24 and bpp ~= 32) or compression ~= 0 then return nil, "BMP must be uncompressed 24/32-bit" end
local stride = math.floor((width*bpp+31)/32)*4
local pixels = {}
for y=1,height do
pixels[y] = {}
local srcY = (height > 0) and (height-y) or (y-1)
for x=1,width do
local pos = offset + srcY*stride + (x-1)*(bpp/8) + 1
local b,g,r,a = string.byte(bytes,pos,pos+3)
if not r then return nil, "BMP pixel data is truncated" end
pixels[y][x] = {r,g,b,a or 255}
end
end
return {width=width,height=height,pixels=pixels}
end
local function decodeSelectedImage(path)
local ext = path:lower():match("%.([%w]+)$")
if ext == "bmp" then return decodeBMP(path) end
local env = (type(getgenv) == "function" and getgenv()) or _G
local decoder = rawget(env, "decodeImagePixels")
if type(decoder) == "function" then
local ok, result = pcall(decoder, path)
if ok and type(result) == "table" and tonumber(result.width) and tonumber(result.height) and type(result.pixels) == "table" then
return result
end
return nil, "decodeImagePixels(path) failed or returned an invalid pixel table"
end
return nil, "This executor has no PNG/JPG pixel decoder. Convert the image to uncompressed 24/32-bit BMP to import it."
end
local function importPixels(image)
local width, height = math.floor(tonumber(image.width) or 0), math.floor(tonumber(image.height) or 0)
if width < 1 or height < 1 or width*height > 20000 then return false, "Invalid image or over 20,000 pixels; resize it first." end
local studs = tonumber(scaleBox.Text)
local sizeY = tonumber(heightBox.Text)
local blockName = materialBox.Text:gsub("^%s+", ""):gsub("%s+$", "")
if not studs or studs <= 0 or not sizeY or sizeY <= 0 or blockName == "" then
return false, "Enter positive Scale/Size Y and a BABFT block name."
end
local templates = game:GetService("ReplicatedStorage"):FindFirstChild("BuildingParts")
if not templates or not templates:FindFirstChild(blockName) then return false, "BABFT block not found: " .. blockName end
local zone = Workspace:FindFirstChild(tostring((currentPlayerTeam() and currentPlayerTeam().TeamColor) or Player.TeamColor) .. "Zone")
if not zone or not zone:IsA("BasePart") then return false, "Could not find your team zone." end
-- Scale means studs per 100 source pixels: 200x100 at scale 10 becomes 20x10 studs.
local pixelSize = studs / 100
local px, pz = pixelSize, pixelSize
if px < 0.05 or sizeY < 0.05 then return false, "Block dimensions cannot be below 0.05 studs." end
local totalWidth, totalDepth = width * px, height * pz
local blocks = {}
local id = 0
-- Center image in front of the plot zone; one schematic block per non-transparent pixel.
local origin = zone.CFrame * CFrame.new(-totalWidth/2, 2 + sizeY/2, -totalDepth/2)
for y=1,height do
local row = image.pixels[y]
if type(row) == "table" then
for x=1,width do
local rgba = row[x]
if type(rgba) == "table" then
local r,g,b,a = tonumber(rgba[1]),tonumber(rgba[2]),tonumber(rgba[3]),tonumber(rgba[4]) or 255
if r and g and b and a > 8 then
id += 1
local cf = CFrame.new((x-0.5)*px, 0, (y-0.5)*pz)
local partData = {class="Part",name="PPart",cf={cf:GetComponents()},size={px,sizeY,pz},color={math.clamp(r/255,0,1),math.clamp(g/255,0,1),math.clamp(b/255,0,1)},transparency=0,reflectance=0,material="SmoothPlastic",anchored=true,canCollide=false,properties={}}
blocks[#blocks+1] = {id=id,name=blockName,pivot={origin:GetComponents()},mainPart={cf:GetComponents()},parts={partData},coordinateSpace="world"}
end
end
end
end
end
if #blocks == 0 then return false, "No visible pixels found in image." end
removeHolograms()
local folder = getHologramFolder()
for _, data in ipairs(blocks) do createHologram(data, folder) end
Checking = true
refreshStatus("Image loaded: " .. width .. "x" .. height .. " | " .. #blocks .. " pixels")
print("[Import Image] Loaded", #blocks, "pixel blocks from", width .. "x" .. height)
return true, #blocks
end
import.MouseButton1Click:Connect(function()
if not selectedPath then status.Text = "Select an image first."; return end
status.Text = "Reading image..."
local image, err = decodeSelectedImage(selectedPath)
if not image then
status.Text = err or "Could not decode image."
warn("[Import Image] " .. tostring(err))
return
end
local ok, result = importPixels(image)
if not ok then status.Text = tostring(result); warn("[Import Image] " .. tostring(result)); return end
status.Text = "Imported " .. tostring(result) .. " pixel blocks. Click BUILD SCHEMATIC to place them."
end)
end
local messagesButton
messagesButton = makeButton(main, "Floating Messages: OFF", 327, function()
ShowAllHologramMessages = not ShowAllHologramMessages
messagesButton.Text = "Floating Messages: " .. (ShowAllHologramMessages and "ON" or "OFF")
applyHologramVisibility()
if ShowAllHologramMessages then compareBlocks() end
end)
local hologramsButton
hologramsButton = makeButton(main, "Holograms: ON", 367, function()
ShowHolograms = not ShowHolograms
hologramsButton.Text = "Holograms: " .. (ShowHolograms and "ON" or "OFF")
applyHologramVisibility()
end)
-- Movement handles now appear automatically when the player clicks a schematic,
-- and disappear when they click anywhere outside the hologram.
makeButton(main, "BUILD SCHEMATIC", 287, function()
buildSchematic()
end)
makeButton(main, "Remove Holograms", 207, function()
removeHolograms()
refreshStatus("Holograms removed")
end)
makeButton(main, "Import Image...", 407, function()
openImageImport()
end)
makeButton(main, "Verify Now", 247, function()
compareBlocks(true)
local correct = 0
local missing = 0
local wrong = 0
for _, item in ipairs(Holograms) do
if item.status == "correct" then
correct = correct + 1
elseif item.status == "missing" then
missing = missing + 1
else
wrong = wrong + 1
end
end
refreshStatus(
"Correct: " .. correct
.. " | Missing: " .. missing
.. " | Errors: " .. wrong
)
end)
RunService.Heartbeat:Connect(function()
if Checking and os.clock() - LastCheck >= CHECK_INTERVAL then
LastCheck = os.clock()
compareBlocks()
end
end)
print("BABFT Schematics System initialized!")BABFT Schematics is a building utility for Build A Boat For Treasure that makes managing and reusing builds easier. Features: - Save and manage custom schematics. - Load previously saved schematics. - Build structures from saved schematic data. - Browse saved schematics through an integrated interface. - Select a schematic to view its name and details. - Organize and reuse builds more conveniently. Designed to make building workflows faster and easier.

Universal