Run it
Copy the script and paste it into your executor.
Players = :()
UIS = :()
Lighting = :()
SoundService = :()
ReplicatedStorage = :()
TextChatService = :()
player = Players.LocalPlayer
player
mouse = player:()
guiParent = player:()
oldSphere = guiParent:()
oldSphere oldSphere:()
oldGui = guiParent:()
oldGui oldGui:()
oldOutline = guiParent:()
oldOutline oldOutline:()
oldSound = SoundService:()
oldSound oldSound:()
GOLD = .(,,)
DARK = .(,,)
PANEL = .(,,)
SOFT = .(,,)
WHITE = .(,,)
QUIET = .(,,)
GREEN = .(,,)
AMBER = .(,,)
SONGS = {
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
, , , ,
}
FACES = {
.NormalId.Front, .NormalId.Back, .NormalId.Left,
.NormalId.Right, .NormalId.Top, .NormalId.Bottom,
}
SKY_FACES = {,,,,,}
songIds = {
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
[] = ,
}
songPitches = { [] = , [] = }
defaults = {
commandPrefix = ,
commandDelay = ,
commandCooldown = ,
f3xDelay = ,
decalCooldown = ,
batchCooldown = ,
realmPartDelay = ,
realmCooldown = ,
skyCooldown = ,
}
config = {}
key, value (defaults) config[key] = value
PREFIX_FILE=
(readfile)==
ok, stored=(() (PREFIX_FILE) )
ok (stored)==
candidate=stored:(,)
#candidate== candidate:()
config.commandPrefix=candidate
selectedSong =
selectedPart =
pickMode =
busy =
lastEdit =
faceIndex =
savedSkies =
localSky =
f3xSkyPart =
env =
()==
ok, value=()
ok (value)== env=value
mapSessionKey=(.PlaceId)....(.JobId)
realmRegistry=env.__RetroF3X_OwnRealmParts
(realmRegistry)~= realmRegistry.sessionKey~=mapSessionKey
realmRegistry={sessionKey=mapSessionKey, parts={}, origin=}
env.__RetroF3X_OwnRealmParts=realmRegistry
(realmRegistry.parts)~= realmRegistry.parts={}
realmParts = realmRegistry.parts
realmBusy =
realmAnimation = {active=,serial=,objects={},theme=}
startRealmAnimation
stopRealmAnimation
armyActions = {}
lastRealmResult = {ok=, detail=}
lastRealmBuild = -
lastSkyEdit = -
lastBatchEdit = -
rememberedLightingSkyState =
outlineSelection = .()
outlineSelection.Name =
outlineSelection.LineThickness =
outlineSelection.Color3 = GOLD
outlineSelection.SurfaceTransparency =
outlineSelection.Adornee =
outlineSelection.Parent = guiParent
(className, properties, parent)
object = .(className)
key, value (properties {}) object[key] = value
object.Parent = parent
object
(object, color, width)
(, {Color=color GOLD,Thickness=width },object)
(parent, label, x, y, width, height, onClick)
button = (, {
Position=.(x,y), Size=.(width,height),
Text=label, BackgroundColor3=DARK, BorderSizePixel=,
TextColor3=WHITE, TextSize=, TextWrapped=,
Font=.Font.GothamBold, AutoButtonColor=,
},parent)
(button)
onClick button.MouseButton1Click:(onClick)
button
(parent, placeholder, x, y, width, height)
box = (, {
Position=.(x,y), Size=.(width,height),
BackgroundColor3=PANEL, BorderSizePixel=,
PlaceholderText=placeholder, PlaceholderColor3=QUIET,
ClearTextOnFocus=, Text=, TextColor3=WHITE,
Font=.Font.Gotham, TextSize=,
},parent)
(box)
box
(parent, s, x, y, width, height, fontSize, color)
(, {
Position=.(x,y),Size=.(width,height),
BackgroundTransparency=,Text=s, TextColor3=color WHITE,
TextSize=fontSize ,Font=.Font.Gotham,
TextXAlignment=.TextXAlignment.Left, TextWrapped=,
}, parent)
(raw)
str = (raw ):(,)
str ==
digits = str:()
str:()
str:()
digits #digits > (digits) ==
digits
gui = (,{
Name=,ResetOnSpawn=,IgnoreGuiInset=,
DisplayOrder=,ZIndexBehavior=.ZIndexBehavior.Sibling,
},guiParent)
toggle = (gui,,,,,)
toggle.Position=.(,,,-)
WINDOW_W, WINDOW_H = ,
root = (,{
Name=,Size=.(WINDOW_W,WINDOW_H),
Position=.(,-WINDOW_W/,,-WINDOW_H/),
BackgroundColor3=DARK,BorderSizePixel=,Active=,
},gui)
(root,GOLD,)
fit = (, {Scale=}, root)
()
camera = .CurrentCamera
camera
viewport = camera.ViewportSize
fit.Scale = .(, .(,
.((viewport.X - )/WINDOW_W, (viewport.Y - )/WINDOW_H)))
()
.CurrentCamera
.CurrentCamera:():(updateFit)
bar = (,{
Size=.(,,,),BackgroundColor3=SOFT,BorderSizePixel=,
},root)
(bar,,,,,,,GOLD)
minimize=(bar,,,,,)
close=(bar,,,,,)
tabRow = (,{
Size=.(,-,,),Position=.(,),
BackgroundTransparency=,
},root)
content = (,{
Size=.(,-,,),Position=.(,),
BackgroundColor3=PANEL,BorderSizePixel=,
},root)
(content,GOLD,)
status = (root,..#realmParts..,,,,,,AMBER)
(s, good)
status.Parent
status.Text = (s)
status.TextColor3 = good GREEN AMBER
stopRealmAnimation=(silent)
realmAnimation.active=
realmAnimation.serial=realmAnimation.serial+
silent gui.Parent (,)
pages={}
tabButtons={}
(tab)
name, page (pages) page.Visible=(name==tab)
name, button (tabButtons)
button.BackgroundColor3=(name==tab) .(,,) DARK
button.TextColor3=(name==tab) GOLD WHITE
tabCount =
(name, caption)
index = tabCount
tabCount = tabCount +
tabButtons[name] = (tabRow,caption,index*,,,,() (name) )
tabButtons[name].TextSize =
name== name== name==
pages[name]=(,{
Size=.(,),BackgroundTransparency=,Visible=,
BorderSizePixel=,ScrollBarThickness=,ScrollBarImageColor3=GOLD,
CanvasSize=.(,name== (name== )),ScrollingDirection=.ScrollingDirection.Y,
},content)
pages[name]=(,{
Size=.(,),BackgroundTransparency=,Visible=,
},content)
pages[name]
musicPage=(,)
decalsPage=(,)
skyPage=(,)
realmPage=(,)
mapPage=(,)
scriptsPage=(,)
settingsPage=(,)
eventPage=(,)
grid = (,{
Size=.(,-,,),Position=.(,),
BackgroundTransparency=,
},musicPage)
(,{
CellSize=.(,-,,),
CellPadding=.(,),SortOrder=.SortOrder.LayoutOrder,
},grid)
songButtons = {}
presetCount =
_, id (songIds) id presetCount=presetCount+
(s)
(s:(,(a,b) a:()..b ))
nowPlaying = (musicPage,,,,,,,GOLD)
hint = (musicPage,..config.commandPrefix....config.commandPrefix....config.commandPrefix..,,,,,,WHITE)
requestCooldown =
commandBusy =
(command)
raw=(command )
raw:(,)== raw:(,)==
config.commandPrefix..raw:()
raw
(command)
command=(command)
hd = ReplicatedStorage:()
signals = hd hd:()
= signals signals:()
:()
ok, result = (()
:(command)
)
ok , (result)
result == ,
,
inputBar = TextChatService:()
channel = inputBar inputBar.TargetTextChannel
channel
channels = TextChatService:()
channel = channels channels:()
channel channel:()
ok, err = (() channel:(command) )
ok ,
, (err)
,
(index)
n = (songPitches[index])
n n >= n <= n
pitchBox
(songIndex)
id = songIds[songIndex]
id
hint.Text=
(..SONGS[songIndex]..)
commandBusy .() - requestCooldown < config.commandCooldown
()
requestCooldown=.()
commandBusy=
pitch = (songIndex)
hint.Text=..config.commandPrefix....id....config.commandPrefix....config.commandPrefix....(pitch)
(,)
.(()
steps = {
..id,
,
..(pitch),
}
errors={}
i,cmd (steps)
gui.Parent
ok, detail=(cmd)
ok .(errors, cmd....(detail):(,))
i<#steps .(config.commandDelay)
gui.Parent
#errors==
hint.Text=..(SONGS[songIndex])....(pitch)
(..config.commandPrefix....config.commandPrefix....config.commandPrefix..,)
hint.Text=..table.(errors,):(,)
()
commandBusy=
)
()
index,button (songButtons)
ready = songIds[index] ~=
selectedSong==index
button.BackgroundColor3=.(,,)
button.TextColor3=WHITE
button.BackgroundColor3=ready .(,,) .(,,)
button.TextColor3=ready WHITE .(,,)
button.Text=(SONGS[index])..(ready )
nowPlaying.Text=..(SONGS[selectedSong])
pitchBox pitchBox.Text=(songPitches[selectedSong] )
index, songName (SONGS)
button=(,{
LayoutOrder=index,Text=,TextSize=,
TextWrapped=,TextColor3=WHITE,Font=.Font.GothamBold,
BorderSizePixel=,BackgroundColor3=DARK,
AutoButtonColor=,
},grid)
(button, songIds[index] GOLD .(,,), )
.(songButtons,button)
button.MouseButton1Click:(()
selectedSong=index
()
(index)
)
playAgain=(musicPage,,,,,,()
(selectedSong)
)
(musicPage,,,,,,()
commandBusy .()-requestCooldown < config.commandCooldown
()
requestCooldown=.()
commandBusy=
.(()
ok,detail=()
gui.Parent
hint.Text=ok (..config.commandPrefix..) (..(detail):(,))
(ok (..(detail)),ok)
commandBusy=
)
)
customField = (musicPage,,,,,)
customField.Visible=
saveCustom
saveCustom=(musicPage,,,,,,()
id=(customField.Text)
id ()
songIds[selectedSong]=id
customField.Text=
()
button=songButtons[selectedSong]
button
_,outline (button:())
outline:() outline.Color=GOLD
(selectedSong)
)
saveCustom.Visible=
testMusicButton=(musicPage,..config.commandPrefix..,,,,,()
id=songIds[selectedSong]
id ()
commandBusy .()-requestCooldown < config.commandCooldown
()
requestCooldown=.()
commandBusy=
hint.Text=..config.commandPrefix....id..
(,)
.(()
ok,detail=(..id)
gui.Parent
ok
hint.Text=..config.commandPrefix....id....(detail)..
(,)
hint.Text=..(detail):(,)
()
commandBusy=
)
)
customOpen=
customButton
customButton=(musicPage,,,,,,()
customOpen= customOpen
customField.Visible=customOpen
saveCustom.Visible=customOpen
testMusicButton.Visible= customOpen
customButton.Text=customOpen
customOpen customField.Text=songIds[selectedSong]
)
(musicPage,,,,,,,GOLD)
pitchBox=(musicPage,,,,,)
pitchBox.Text=(songPitches[selectedSong] )
(musicPage,,,,,,()
entered=pitchBox.Text:(,)
entered==
songPitches[selectedSong]=
pitchBox.Text=
(..(SONGS[selectedSong])..,)
amount=(entered)
amount amount< amount>
()
songPitches[selectedSong]=amount
pitchBox.Text=(amount)
(..(amount)....(SONGS[selectedSong])..,)
)
footer=(musicPage,
(presetCount)..,
,,,,,QUIET)
()
(decalsPage,,,,,,,GOLD)
decalIdBox=(decalsPage,,,,,)
preview=(,{
Position=.(,),Size=.(,),
BackgroundColor3=DARK,BorderSizePixel=,
Image=,ScaleType=.ScaleType.Fit,
},decalsPage)
(preview)
(decalsPage,,,,,,()
id=(decalIdBox.Text)
id ()
preview.Image=..id
(,)
)
()
char=player.Character
bag=player:()
_,container ({char,bag})
container
_,tool (container:())
tool:()
sync=tool:()
endpoint=sync sync:()
endpoint endpoint:()
tool,endpoint
,
(decalsPage,,,,,,()
busy ()
decalId=(decalIdBox.Text)
decalId ()
_, endpoint=()
endpoint ()
busy=
(,)
.(()
ok,result=(()
endpoint:(,(decalId))
)
busy=
gui.Parent
imageId = ok (result)
imageId
decalIdBox.Text=imageId
preview.Image=..imageId
(..imageId..,)
()
)
)
partLabel=(decalsPage,,,,,,,WHITE)
pickButton
(part)
selectedPart=part
outlineSelection.Adornee=part
part
partLabel.Text=..part.Name
(..part.Name..,)
partLabel.Text=
()
pickButton=(decalsPage,,,,,,()
pickMode= pickMode
pickButton.Text=pickMode
(pickMode ,)
)
(decalsPage,,,,,,() () )
faceButton=(decalsPage,,,,,,()
faceIndex=(faceIndex % #FACES)+
face=FACES[faceIndex]
faceButton.Text=..string.(face.Name)
)
(decalsPage,,,,,,,WHITE)
(part)
model=part:()
model model:() ~=
(part,face)
_, child (part:())
child:() child.Face==face child
(parts,faces,imageId,endpoint)
content=..imageId
attempted=
_,part (parts)
part part:() part:() (part)
_,face (faces)
(part,face)
endpoint:(,{{
Part=part,Face=face,TextureType=
}})
.(config.f3xDelay)
endpoint:(,{{
Part=part,Face=face,TextureType=,Texture=content
}})
attempted=attempted+
.(config.f3xDelay)
.()
confirmed=
_,part (parts)
part part.Parent
_,face (faces)
decal=(part,face)
decal (decal.Texture):()==imageId
confirmed=confirmed+
attempted,confirmed
(parts,faces,imageId,endpoint,labelText)
busy=
(..labelText..,)
.(()
ok,attempted,confirmed=(()
(parts,faces,imageId,endpoint)
)
busy=
gui.Parent
ok
(..(attempted):(,))
confirmed==attempted attempted>
(..confirmed..,)
confirmed>
(..confirmed....attempted..)
()
)
(allFaces)
busy ()
.()-lastEdit < config.decalCooldown (..(config.decalCooldown)..)
id=(decalIdBox.Text)
id ()
part=selectedPart
part part:() part:() (part)
()
_, endpoint=()
endpoint
()
lastEdit=.()
({part},allFaces FACES {FACES[faceIndex]},id,endpoint,allFaces )
(decalsPage,,,,,,() () )
(decalsPage,,,,,,() () )
()
selectedPart selectedPart:() {}
p=selectedPart.Parent
container=(p (p:() p:()) p)
container container:() container=
container == container=
output={}
scope=container container:() {selectedPart}
_,item (scope)
item:() (item)
.(output,item)
#output>=
output
()
busy ()
.()-lastBatchEdit < config.batchCooldown (..(config.batchCooldown)..)
id=(decalIdBox.Text)
id ()
parts=()
#parts== ()
_, endpoint=()
endpoint ()
lastBatchEdit=.()
(parts,FACES,id,endpoint,..#parts..)
(decalsPage,,,,,,fillPickedBuild)
(decalsPage,,,,,,()
tool, endpoint=()
tool endpoint
(..tool.Name..,)
()
)
DECAL_PRESETS={
{name=, id=},
{name=, id=},
}
(decalsPage,,,,,,,GOLD)
index,preset (DECAL_PRESETS)
item=preset
(decalsPage,item.name....item.id,,+(index-)*,,,()
decalIdBox.Text=item.id
preview.Image=..item.id
selectedPart selectedPart:()
()
(item.name..,)
)
(decalsPage,,,,,,,QUIET)
(decalsPage,,,,,,,AMBER)
mapLimitBox = (decalsPage, , , , , )
mapLimitBox.Text =
mapFillState = { running=, cancel=, pending=, armedAt= }
mapProgress = (decalsPage, ,
, , , , , QUIET)
(decalsPage, ,
, , , , , GOLD)
(decalsPage, ,
, , , , , WHITE)
()
n = (mapLimitBox.Text)
n n~=n n==.huge n~=.(n) n< n>
()
n
(cap)
parts, total = {},
_, item (:())
item:() (item)
item:()
total = total +
#parts < cap parts[#parts+] = item
parts, total
(text)
gui.Parent
mapProgress.Text=text
(text, )
(parts, endpoint, imageId, total)
requests, errors, confirmed, attempted = , , ,
batchSize=
stopNow=
texture=..imageId
startIndex=,#parts,batchSize
mapFillState.cancel gui.Parent
lastIndex=.(#parts,startIndex+batchSize-)
_,face (FACES)
mapFillState.cancel gui.Parent
createBatch, syncBatch = {}, {}
i=startIndex,lastIndex
part=parts[i]
part part:() (part)
d=(part,face)
d
createBatch[#createBatch+] = { Part=part, Face=face, TextureType= }
d (d.Texture)~=texture
syncBatch[#syncBatch+] = {
Part=part, Face=face, TextureType=, Texture=texture
}
#createBatch>
ok=(() endpoint:(,createBatch) )
requests=requests+
ok errors=errors+
.(.(,config.f3xDelay))
#syncBatch>
ok=(() endpoint:(,syncBatch) )
requests=requests+
ok errors=errors+
attempted=attempted+#syncBatch
.(.(,config.f3xDelay))
errors>= stopNow=;
(..(lastIndex)....(#parts)..
..(requests)....(errors)..)
stopNow
.()
_,part (parts)
part part.Parent
_,face (FACES)
d=(part,face)
d (d.Texture):()==imageId confirmed=confirmed+
interrupted=mapFillState.cancel
mapFillState.running=
busy=
mapFillState.pending=
gui.Parent
summary=(interrupted stopNow )..
..confirmed....(#parts*)....
#parts....total..
mapProgress.Text=summary
(summary,confirmed>)
actionButton
()
mapFillState.pending=
actionButton actionButton.Parent actionButton.Text=
(decalsPage, ,,,,,()
mapFillState.running ()
limit=()
limit
parts,total=(limit)
mapProgress.Text=..total....#parts..
(mapProgress.Text,)
()
)
actionButton=(decalsPage,,,,,,()
busy mapFillState.running ()
.()-lastBatchEdit < config.batchCooldown
()
id=(decalIdBox.Text)
id ()
limit=()
limit
_,endpoint=()
endpoint ()
mapFillState.pending .()-mapFillState.armedAt>
mapFillState.pending=
mapFillState.armedAt=.()
actionButton.Text=
()
parts,total=(limit)
()
#parts== ()
lastBatchEdit=.()
mapFillState.running=
mapFillState.cancel=
busy=
(..#parts....total..)
.(()
ok,err=(() (parts,endpoint,id,total) )
ok
mapFillState.running=
mapFillState.pending=
busy=
(..(err):(,))
)
)
(decalsPage,,,,,,()
mapFillState.running
mapFillState.cancel=
()
()
()
)
(decalsPage,
..
,
,,,,,AMBER)
(skyPage,,,,,,,GOLD)
skyIdBox=(skyPage,,,,,)
DEFAULT_SKY_SCALE =
(skyPage,,,,,,,GOLD)
skyScaleBox=(skyPage,,,,,)
skyScaleBox.Text = (DEFAULT_SKY_SCALE)
()
n=(skyScaleBox.Text)
n n~=n n==.huge n==-.huge
,
n< n>
,
.(n)
(skyPage,,,,,,,AMBER)
()
id=(skyIdBox.Text)
id ()
savedSkies
savedSkies={}
_,item (Lighting:())
item:()
.(savedSkies,item:())
_,item (Lighting:())
item:() item:()
localSky=.()
localSky.Name=
_,prop (SKY_FACES) localSky[prop]=..id
localSky.Parent=Lighting
(,)
()
savedSkies ()
_,item (Lighting:())
item:() item:()
_,item (savedSkies) item.Parent=Lighting
savedSkies=
localSky=
(,)
(skyPage,,,,,,applyLocalSky)
(skyPage,,,,,,restoreSky)
()
busy realmBusy ()
.()-lastSkyEdit<config.skyCooldown (..(config.skyCooldown)..)
id=(skyIdBox.Text)
id ()
skyScale,scaleError=()
skyScale (scaleError)
f3xSkyPart f3xSkyPart.Parent
()
tool,endpoint=()
endpoint ()
character=player.Character
hrp=character character:()
hrp ()
busy=; lastSkyEdit=.()
(,)
.(()
created
ok,err=(()
created=endpoint:(,,.(hrp.Position),)
created created:()
()
f3xSkyPart=created
endpoint:(,{{Part=created,Anchored=}})
.(config.f3xDelay)
endpoint:(,{{Part=created,CanCollide=}})
.(config.f3xDelay)
endpoint:(,{created},)
.(config.f3xDelay)
endpoint:(,{{Part=created}})
.(config.f3xDelay)
endpoint:(,{{Part=created,
MeshId=,
TextureId=..id,
Scale=.(skyScale,skyScale,skyScale)}})
)
busy=
gui.Parent
ok
(..(skyScale)..,)
(..(err):(,))
)
()
part=f3xSkyPart
part part.Parent ()
busy ()
tool,endpoint=()
endpoint ()
busy=
.(()
ok,err=(() endpoint:(,{part}) )
busy=
ok f3xSkyPart=
gui.Parent (ok (..(err)),ok)
)
(skyPage,,,,,,createF3XSkyDome)
(skyPage,,,,,,removeF3XSkyDome)
()
busy realmBusy ()
part=f3xSkyPart
part part.Parent ()
.()-lastSkyEdit<config.skyCooldown
(..(config.skyCooldown)..)
scale,scaleError=()
scale (scaleError)
tool,endpoint=()
endpoint ()
busy=;lastSkyEdit=.()
.(()
ok,err=(()
endpoint:(,{{
Part=part,
Scale=.(scale,scale,scale),
}})
)
busy=
gui.Parent
(ok (..(scale)..)
(..(err):(,)),ok)
)
(skyPage,,,,,,resizeF3XSkyDome)
PIXEL_SKY_PRESETS = {
{name=, imageId=},
{name=, imageId=},
}
(skyPage,,,,,,,GOLD)
(preset)
busy realmBusy ()
skyIdBox.Text = preset.imageId
f3xSkyPart f3xSkyPart.Parent
(preset.name....preset.imageId..)
()
index,preset (PIXEL_SKY_PRESETS)
x=(index == )
(skyPage,preset.name....preset.imageId,x,,,,()
(preset)
)
(skyPage,,,,,,,QUIET)
(realmPage,,,,,,,GOLD)
(realmPage,,,,,,,WHITE)
realmInfo = (realmPage,,,,,,,GREEN)
realmOrigin = realmRegistry.origin
beaconPart =
selectedRealmSky =
(part)
(part)== part:() part:()
()
live={}
_, part (realmParts)
(part) .(live,part)
realmParts=live
realmRegistry.parts=live
#live== realmRegistry.origin=;realmRegistry.skyBase=;realmRegistry.skyTheme=
realmInfo.Text=..#live....(selectedRealmSky== )
live
()
tool,endpoint=()
endpoint tool,endpoint,
char=player.Character
bag=player:()
classic={}
_,container ({char,bag})
container
_,item (container:())
item:()
name=item.Name:()
name:() name:() name:()
name== name== name== name==
.(classic,item.Name)
#classic>
,,..table.(classic,)..
,,
(theme, detailed)
theme==
charcoal=.(,,)
wall=.(,,)
obsidian=.(,,)
rose=.(,,)
roseSoft=.(,,)
gold=.(,,)
pearl=.(,,)
ivory=.(,,)
accent=.(,,)
pieces={}
(x,y,z,sx,sy,sz,color,material,collide,animation,rx,ry,rz)
.(pieces,{
offset=.(x,y,z),size=.(sx,sy,sz),color=color,
material=material .Material.SmoothPlastic,collide=(collide~=),
animation=animation,
rotation=.(.(rx ),.(ry ),.(rz )),
})
( , , , ,,,charcoal,.Material.Slate)
( ,, , ,,,obsidian,.Material.Slate)
(-,, , ,,,wall)
( ,, , ,,,wall)
( ,,-, ,,,wall)
(-,,,,,,wall)
( ,,,,,,wall)
(,,,,,,wall)
(-,,,,,,rose,.Material.Neon,,)
( ,,,,,,rose,.Material.Neon,,)
( ,,,,,,gold,.Material.Neon,,)
( , , ,,,,roseSoft,.Material.Neon,)
( , ,-,,,,obsidian)
( , ,-,,,,gold)
( , ,-, ,,,charcoal)
(-, ,-, , ,,gold)
( , ,-, , ,,gold)
( ,,-, ,,,pearl,.Material.Neon,,)
(-,,-,,,,gold)
( ,,-,,,,gold)
( ,,-,,,,gold)
_,side ({-,})
(side*,,-, ,,,pearl,.Material.SmoothPlastic,,,,,side*)
(side*,,-, ,,,ivory,.Material.SmoothPlastic,,,,,side*)
(side*,,-, ,,,gold,.Material.Metal,,,,,side*)
_,x ({-,})
_,z ({-,})
(x,,z,,,,obsidian,.Material.Metal)
(x,,z,,,,gold,.Material.Neon,,)
(x,,z,,,,pearl,.Material.Neon,,)
(-,,,,,,rose,.Material.Neon,,)
( ,,,,,,rose,.Material.Neon,,)
(,,,,,,gold,.Material.Neon,,)
detailed
_,x ({-,-,,})
(x,,,,,,pearl,.Material.Neon,)
(,,,,,,gold,.Material.Metal)
(,,,,,,pearl,.Material.Neon,,)
pieces
tint=theme== .(,,) .(,,)
highlight=theme== .(,,) .(,,)
{
{offset=.(,,),size=.(,,),color=.(,,),material=.Material.Slate,collide=},
{offset=.(-,,-),size=.(,,),color=tint,material=.Material.Neon,collide=},
{offset=.(,,-),size=.(,,),color=tint,material=.Material.Neon,collide=},
{offset=.(-,,),size=.(,,),color=tint,material=.Material.Neon,collide=},
{offset=.(,,),size=.(,,),color=tint,material=.Material.Neon,collide=},
{offset=.(,,-),size=.(,,),color=highlight,material=.Material.Neon,collide=},
{offset=.(,,-),size=.(,,),color=highlight,material=.Material.Neon,collide=},
{offset=.(,,),size=.(,,),color=highlight,material=.Material.Neon,collide=},
}
(theme, quality)
specs={}
quality=.(.((quality) ),,)
C={
dark=.(,,),stone=.(,,),
grey=.(,,),rose=.(,,),
gold=.(,,),white=.(,,),
blue=.(,,),cyan=.(,,),
violet=.(,,),ice=.(,,),
ember=.(,,),red=.(,,),
green=.(,,),wood=.(,,),
}
M=.Material
(x,y,z,w,h,d,color,material,collide,rot)
#specs >=
specs[#specs+]={offset=.(x,y,z),size=.(w,h,d),
color=color C.white,material=material M.SmoothPlastic,
collide=collide~=,rotation=rot .(,.(rot),) }
(size,color)
(,,,size,,size,color C.dark,M.Slate)
(color,accent)
(,C.dark)
(-,,,,,,color,M.Slate)
(,,,,,,color,M.Slate)
(,,-,,,,color,M.Slate)
(-,,,,,,color,M.Slate)
(,,,,,,color,M.Slate)
(,,,,,,color,M.Slate)
(,,,,,,color,M.Slate)
(,,,,,,accent,M.Neon,)
(-,,,,,,accent,M.Neon,)
(,,,,,,accent,M.Neon,)
(,,,,,,accent,M.Neon,)
theme== theme==
accent=(theme== C.gold C.violet)
wall=(theme== C.wood C.stone)
secondary=(theme== C.white C.cyan)
(wall,accent)
add(0,3,-37,24,4,21,C.dark,M.Metal)
add(0,10,-43,14,15,3,accent,M.Metal)
add(0,12,-39,9,12,3,secondary,M.SmoothPlastic)
for _,side in ipairs({-1,1}) do
for i=-2,2 do
local z=i*19
add(side*43,16,z,4,32,4,secondary,M.Marble)
add(side*43,33,z,7,3,7,accent,M.Metal)
if quality>=2 then add(side*48,25,z,1,11,8,accent,M.Neon,false) end
end
add(side*19,20,-44,3,24,2,secondary,M.Metal,false)
add(side*27,22,-44,4,18,2,accent,M.Metal,false)
end
if quality>=2 then
for i=-3,3 do
add(i*13,2,0,2,.3,92,accent,M.Neon,false)
add(i*13,39,0,1,1,95,secondary,M.Metal,false)
end
end
if quality>=3 then
for i=-3,3 do
add(0,2,i*13,93,.3,1,secondary,M.Neon,false)
add(0,39,i*13,95,1,1,accent,M.Metal,false)
end
end
elseif theme=='NeonMegacity' then
platform(150,C.dark)
for _,side in ipairs({-1,1}) do
add(0,1.2,side*19,150,.3,3,C.cyan,M.Neon,false)
add(side*19,1.2,0,3,.3,150,C.rose,M.Neon,false)
end
for ix=-2,2 do for iz=-2,2 do
if ix~=0 and iz~=0 then
local x,z=ix*25,iz*25
local ht=22+((ix*7+iz*11+100)%4)*8
local col=((ix+iz)%2==0) and C.violet or C.blue
add(x,ht/2+2,z,15,ht,15,C.stone,M.Metal)
add(x,ht+3,z,16,2,16,col,M.Neon)
if quality>=2 then
for floor=1,math.floor(ht/8) do
add(x,3+floor*8,z+8,12,2,.3,col,M.Neon,false)
add(x+8,3+floor*8,z,.3,2,12,C.cyan,M.Neon,false)
end
end
if quality>=3 then
add(x,ht+6,z,2,5,2,C.gold,M.Neon)
end
end
end end
for i=-2,2 do
add(i*27,5,12,1,10,1,C.grey,M.Metal)
add(i*27,11,12,4,2,4,C.rose,M.Neon,false)
if quality>=2 then add(i*27,5,-12,1,10,1,C.grey,M.Metal) end
end
if quality>=2 then
add(0,14,0,130,2,6,C.blue,M.Glass,false)
add(0,15,0,130,.5,1,C.cyan,M.Neon,false)
end
elseif theme=='CrystalCavern' then
boxHall(C.stone,C.violet)
add(0,2,-10,35,.5,30,C.blue,M.Glass,false)
for i=1,22 do
if quality>=2 or i<=11 then
local x=((i*37)%94)-47
local z=((i*29)%94)-47
local h=6+(i%5)*4
add(x,1+h/2,z,3,h,3,(i%3==0) and C.cyan or C.violet,M.Neon,false,(i*13)%180)
if quality>=3 then
add(x+4,1+h/3,z+2,2,h*.65,2,C.ice,M.Glass,false,(i*23)%180)
end
end
end
for i=1,13 do
if quality>=2 or i<=7 then
local x=((i*31)%92)-46
local z=((i*17)%92)-46
local h=5+(i%4)*3
add(x,40-h/2,z,3,h,3,C.grey,M.Slate)
end
end
elseif theme=='SakuraSanctuary' then
platform(145,C.green)
add(0,1.2,0,16,.4,138,C.wood,M.WoodPlanks)
add(0,1.2,-35,62,.4,28,C.blue,M.Glass,false)
add(0,5,-48,32,8,15,C.wood,M.Wood)
add(0,12,-48,40,3,23,C.rose,M.SmoothPlastic)
add(0,16,-48,25,2,15,C.gold,M.Metal)
for i=1,10 do
if quality>=2 or i<=6 then
local side=(i%2==0) and 1 or -1
local z=-60+i*12
local x=side*(24+(i%3)*7)
add(x,8,z,3,15,3,C.wood,M.Wood)
for j=1,quality do
add(x+(j-2)*4,17+(j%2)*3,z,14,7,13,C.rose,M.SmoothPlastic,false)
end
add(side*11,5,z,2,9,2,C.gold,M.Metal)
add(side*11,10,z,4,4,4,C.rose,M.Neon,false)
end
end
for z=-35,35,17 do
add(-9,10,z,2,18,2,C.red,M.Wood)
add(9,10,z,2,18,2,C.red,M.Wood)
add(0,18,z,24,2,3,C.red,M.Wood)
add(0,21,z,28,2,3,C.wood,M.Wood)
end
elseif theme=='FrozenCitadel' then
platform(155,C.ice)
add(0,18,-50,102,35,8,C.grey,M.Ice)
for _,side in ipairs({-1,1}) do
add(side*50,18,0,8,35,110,C.grey,M.Ice)
add(side*29,18,50,42,35,8,C.grey,M.Ice)
for _,z in ipairs({-50,50}) do
add(side*50,24,z,15,48,15,C.blue,M.Ice)
add(side*50,50,z,18,5,18,C.ice,M.Neon)
end
end
add(0,37,50,25,5,8,C.ice,M.Ice)
add(0,2,-30,25,4,20,C.blue,M.Glass)
add(0,10,-36,13,13,4,C.ice,M.Neon)
for i=1,(quality==1 and 8 or quality==2 and 16 or 24) do
local x=((i*31)%122)-61
local z=((i*23)%95)-47
local h=5+(i%5)*2
add(x,2+h/2,z,2,h,2,C.white,M.Ice,false)
end
elseif theme=='InfernoForge' then
platform(145,C.dark)
for _,side in ipairs({-1,1}) do
add(side*48,13,0,9,26,114,C.wood,M.Slate)
add(side*21,1.2,0,12,.4,132,C.ember,M.Neon,false)
end
add(0,1.5,0,16,1,140,C.grey,M.Metal)
add(0,12,-43,36,23,19,C.wood,M.Brick)
add(0,14,-30,20,15,2,C.red,M.Neon,false)
add(0,24,-43,39,5,20,C.grey,M.Metal)
for i=-3,3 do
add(i*17,5,22,4,9,4,C.grey,M.Metal)
add(i*17,12,22,8,5,8,C.ember,M.Neon,false)
if quality>=2 then
add(i*17,17,22,3,9,3,C.wood,M.Metal)
add(i*17,23,22,5,3,5,C.gold,M.Neon)
end
end
if quality>=3 then
for i=-4,4 do
add(i*13,19,-10,3,3,3,C.gold,M.Neon)
add(i*13,30,-10,1,18,1,C.grey,M.Metal,false)
end
end
elseif theme=='CelestialObservatory' then
platform(145,C.dark)
add(0,2,0,84,1,84,C.blue,M.Metal)
add(0,3,0,68,.5,68,C.violet,M.Glass,false)
for i=1,16 do
if quality>=2 or i%2==0 then
local a=i*math.pi/8
local x,z=math.cos(a)*54,math.sin(a)*54
add(x,13,z,4,24,4,C.white,M.Marble)
add(x,26,z,7,3,7,C.gold,M.Neon,false)
if quality>=3 then
add(x*.8,29,z*.8,2,6,2,C.cyan,M.Neon,false)
end
end
end
add(0,11,0,20,18,20,C.white,M.Marble)
add(0,22,0,27,4,27,C.gold,M.Metal)
for i=1,(quality==1 and 8 or quality==2 and 16 or 26) do
local a=i*2*math.pi/26
local r=27+(i%3)*5
add(math.cos(a)*r,30+(i%5)*2,math.sin(a)*r,5,2,3,(i%2==0) and C.cyan or C.rose,M.Neon,false,i*14)
end
elseif theme=='EventHorizon' then
platform(150,C.dark)
add(0,2,0,45,2,45,C.violet,M.Neon,false)
add(0,5,0,30,6,30,C.dark,M.Slate)
add(0,28,0,34,34,34,C.dark,M.SmoothPlastic,false)
for i=1,(quality==1 and 12 or quality==2 and 24 or 36) do
local a=i*math.pi*2/(quality==1 and 12 or quality==2 and 24 or 36)
local r=40+(i%3)*6
add(math.cos(a)*r,17+(i%5)*2,math.sin(a)*r,8,2,2,(i%2==0) and C.rose or C.violet,M.Neon,false,i*360/36)
end
for i=1,8 do
local a=i*math.pi/4
add(math.cos(a)*58,18,math.sin(a)*58,5,36,5,C.stone,M.Metal)
add(math.cos(a)*58,38,math.sin(a)*58,7,4,7,C.cyan,M.Neon,false)
end
if quality>=3 then
for i=1,20 do
local a=i*math.pi/10
add(math.cos(a)*30,12+(i%6)*3,math.sin(a)*30,3,3,3,C.grey,M.Slate,false,i*13)
end
end
end
return specs
end
local function mapBlueprint(theme, quality)
quality=math.clamp(math.floor(tonumber(quality) or 2),1,3)
if theme=='PIXEL' then
local parts=realmSpecs('PIXEL',quality>=2)
if quality>=3 then
local accent=Color3.fromRGB(241,126,188)
local gold=Color3.fromRGB(235,179,67)
for i=-4,4 do
parts[#parts+1]={offset=Vector3.new(i*9,2,27),size=Vector3.new(2,.4,24),color=accent,material=Enum.Material.Neon,collide=false}
parts[#parts+1]={offset=Vector3.new(i*9,36,-2),size=Vector3.new(1,1,70),color=gold,material=Enum.Material.Metal,collide=false}
end
end
return parts
end
return expandedMapSpecs(theme,quality)
end
-- F3X supports bulk SyncResize, SyncColor, SyncMaterial and SyncCollision
-- updates for DISTINCT parts. Small chunks reduce request count without
-- overwhelming the server with a single very large remote invocation.
local function createRealm(theme, detailed, options)
options=options or {}
local replacing=options.replace==true
local quality=math.clamp(math.floor(tonumber(options.quality) or (detailed and 3 or 2)),1,3)
if busy or realmBusy then return say('Finish the current BTools operation first.') end
if os.clock()-lastRealmBuild<config.realmCooldown then return say('Realm cooldown: '..config.realmCooldown..' seconds.') end
if #liveRealmParts()>0 and not replacing then return say('Remove your current realm before building another.') end
local tool,endpoint,problem=detectBuildTool()
if not endpoint then return say(problem or 'No F3X tool with CreatePart found.') end
local char=player.Character
local hrp=char and char:FindFirstChild('HumanoidRootPart')
if not hrp then return say('Spawn your character before building.') end
local specs=mapBlueprint(theme, quality)
local look=hrp.CFrame.LookVector
local flat=Vector3.new(look.X,0,look.Z)
if flat.Magnitude<0.01 then flat=Vector3.new(0,0,-1) end
flat=flat.Unit
-- User approaches through the opening on the near (+Z) side of the hall.
local distance=theme=='PIXEL' and 59 or 82
local origin=hrp.Position+flat*distance+Vector3.new(0,-3,0)
if options.isolate then
-- An isolated building location prevents overlapping the game's original map.
-- The old world remains untouched; this is an alternative, not a wipe.
origin=origin+Vector3.new(0,400,0)
end
local base=CFrame.lookAt(origin,origin+flat)
local labelMode=options.label or (theme=='PIXEL' and (quality>=2 and 'Detailed Pixel Hall' or 'Pixel Hall') or theme)
stopRealmAnimation(true)
realmAnimation.objects={}
realmAnimation.theme=theme
realmBusy=true;lastRealmBuild=os.clock()
lastRealmResult={ok=false,detail='Building'}
say('Building '..labelMode..' with F3X ('..#specs..' pieces)...',true)
task.spawn(function()
local ok,err=pcall(function()
if replacing then
local previous=liveRealmParts()
if #previous>0 then
say('Removing '..#previous..' previous RetroF3X map parts...',true)
-- Removing hundreds of parts in one call can hit server limits.
-- Chunk only our own references; NEVER gather unrelated Workspace objects.
for first=1,#previous,16 do
local batch={}
for i=first,math.min(first+15,#previous) do
batch[#batch+1]=previous[i]
end
local response=endpoint:InvokeServer('Remove',batch)
if response==false then
error('F3X rejected cleanup batch starting at '..first)
end
task.wait(math.max(0.10,config.f3xDelay))
end
local started=os.clock()
while #liveRealmParts()>0 and os.clock()-started<3 do task.wait(.15) end
if #liveRealmParts()>0 then
error('Old RetroF3X map remains: '..#liveRealmParts()..' parts (F3X cleanup refused or delayed)')
end
end
end
realmOrigin=origin
realmRegistry.origin=origin
local resize,color,material,collision={},{},{},{}
for index,spec in ipairs(specs) do
if not gui.Parent then error('GUI closed during build') end
local cf=base*CFrame.new(spec.offset)*(spec.rotation or CFrame.new())
local part=endpoint:InvokeServer('CreatePart','Normal',cf,workspace)
if not validRealmPart(part) then
error('F3X CreatePart failed at piece '..index..'/'..#specs)
end
table.insert(realmParts,part)
-- Stock F3X CreatePart is already anchored; only request SyncAnchor
-- if this particular build tool actually returned an unanchored part.
if not part.Anchored then
local anchorOK=endpoint:InvokeServer('SyncAnchor',{{Part=part,Anchored=true}})
if anchorOK==false then error('F3X rejected anchor at piece '..index) end
end
table.insert(resize,{Part=part,CFrame=cf,Size=spec.size})
table.insert(color,{Part=part,Color=spec.color,UnionColoring=false})
table.insert(material,{Part=part,Material=spec.material})
if spec.collide==false then
table.insert(collision,{Part=part,CanCollide=false})
end
if spec.animation then
table.insert(realmAnimation.objects,{Part=part,CFrame=cf,Color=spec.color,
Kind=spec.animation,Phase=index*0.75})
end
if gui.Parent and (index%5==0 or index==#specs) then
say('Pixel Hall: '..index..'/'..#specs..' created — styling after build',true)
end
if index<#specs then task.wait(math.max(.05,config.realmPartDelay)) end
end
-- Batched changes for distinct parts: fewer server round trips.
local chunkSize=8
local passes={{'SyncResize',resize},{'SyncColor',color},
{'SyncMaterial',material},{'SyncCollision',collision}}
for _,pass in ipairs(passes) do
local action,changes=pass[1],pass[2]
for first=1,#changes,chunkSize do
if not gui.Parent then error('GUI closed during styling') end
local chunk={}
for i=first,math.min(first+chunkSize-1,#changes) do
chunk[#chunk+1]=changes[i]
end
if endpoint:InvokeServer(action,chunk)==false then
error('F3X rejected '..action)
end
task.wait(math.max(.05,config.f3xDelay))
end
end
end)
realmBusy=false
local count=#liveRealmParts()
lastRealmResult={ok=ok and count==#specs,
detail=ok and (count..'/'..#specs..' parts created') or tostring(err)}
if not gui.Parent then return end
if lastRealmResult.ok then
realmRegistry.skyBase=options.isolate and base or nil
realmRegistry.skyTheme=options.isolate and theme or nil
say(labelMode..' built ('..count..' F3X parts).'..(options.isolate and ' Press GO TO SKY MAP in MAPS.' or ''),true)
if theme=='PIXEL' then task.defer(function()
if startRealmAnimation then startRealmAnimation() end
end) end
else
say('Realm stopped ('..count..'/'..#specs..'): '..tostring(lastRealmResult.detail):sub(1,130))
end
end)
return true
end
-- Time-limited animation with BTools SyncMove / SyncColor, not local-only effects.
-- Keep the number and cadence of requests low to avoid spamming servers.
startRealmAnimation=function()
if busy or realmBusy then return say('Finish building before animating.') end
if #liveRealmParts()==0 or realmAnimation.theme~='PIXEL' then
return say('Build the Pixel Hall before starting its animation.')
end
if realmAnimation.active then return say('Pixel Hall animation is already running.',true) end
local _,endpoint,problem=detectBuildTool()
if not endpoint then return say(problem or 'F3X/BTools SyncAPI required for animation.') end
realmAnimation.active=true
realmAnimation.serial=realmAnimation.serial+1
local mySerial=realmAnimation.serial
say('Pixel Hall animation started: pulsing portal and hovering crystals (20 sec).',true)
task.spawn(function()
-- Up to 12 frames; two F3X calls per frame and a 1.6s delay.
for frame=1,12 do
if not gui.Parent or not realmAnimation.active or realmAnimation.serial~=mySerial then break end
local colors,moves={},{}
local light=(frame%2==0)
for _,item in ipairs(realmAnimation.objects) do
local p=item.Part
if validRealmPart(p) then
if item.Kind=='pulse' then
colors[#colors+1]={Part=p,
Color=light and Color3.fromRGB(255,215,236) or item.Color,
UnionColoring=false}
elseif item.Kind=='float' then
local offset=math.sin(frame*0.65+item.Phase)*1.5
moves[#moves+1]={Part=p,CFrame=item.CFrame*CFrame.new(0,offset,0)}
end
end
end
-- Chunking keeps each request small while never touching map parts.
local success,err=pcall(function()
for i=1,#colors,8 do
local chunk={}
for j=i,math.min(i+7,#colors) do chunk[#chunk+1]=colors[j] end
endpoint:InvokeServer('SyncColor',chunk)
end
for i=1,#moves,8 do
local chunk={}
for j=i,math.min(i+7,#moves) do chunk[#chunk+1]=moves[j] end
endpoint:InvokeServer('SyncMove',chunk)
end
end)
if not success then
realmAnimation.active=false
if gui.Parent then say('Animation stopped: '..tostring(err):sub(1,115)) end
break
end
task.wait(1.6)
end
if realmAnimation.serial==mySerial then
realmAnimation.active=false
if gui.Parent then say('Pixel Hall animation finished. Press REPLAY ANIMATION to repeat.',true) end
end
end)
end
local function removeRealm()
stopRealmAnimation(true)
if busy or realmBusy then return say('An F3X job is running.') end
local parts=liveRealmParts()
if #parts==0 then return say('No saved RetroF3X realm found in this server. Original game map was not created by this GUI.') end
local _,endpoint,problem=detectBuildTool()
if not endpoint then return say(problem or 'F3X required to remove your realm.') end
realmBusy=true
say('Removing '..#parts..' tracked RetroF3X parts (including earlier GUI runs)...',true)
task.spawn(function()
local ok,err=pcall(function()
for first=1,#parts,16 do
local batch={}
for i=first,math.min(first+15,#parts) do batch[#batch+1]=parts[i] end
local response=endpoint:InvokeServer('Remove',batch)
if response==false then error('F3X rejected removal at part '..first) end
task.wait(math.max(.10,config.f3xDelay))
end
end)
realmBusy=false
task.wait(.5)
local remaining=liveRealmParts()
if not gui.Parent then return end
if #remaining==0 then
realmOrigin=nil;realmRegistry.origin=nil
beaconPart=nil;realmAnimation.objects={};realmAnimation.theme=nil
end
say(ok and ('Remove sent. '..#remaining..' realm part(s) still present.') or ('Remove failed: '..tostring(err):sub(1,110)),ok)
end)
end
local function createBeacon()
if busy or realmBusy then return say('F3X still working.') end
if not realmOrigin or #liveRealmParts()==0 then return say('Build a realm before adding the beacon.') end
if validRealmPart(beaconPart) then return say('Beacon already exists.') end
local _,endpoint,problem=detectBuildTool()
if not endpoint then return say(problem or 'Equip F3X first.') end
realmBusy=true
task.spawn(function()
local ok,err=pcall(function()
local cf=CFrame.new(realmOrigin+Vector3.new(0,11,0))
local part=endpoint:InvokeServer('CreatePart','Normal',cf,workspace)
if not validRealmPart(part) then error('F3X denied beacon creation.') end
beaconPart=part;table.insert(realmParts,part)
endpoint:InvokeServer('SyncResize',{{Part=part,CFrame=cf,Size=Vector3.new(2,20,2)}})
task.wait(config.f3xDelay)
endpoint:InvokeServer('SyncAnchor',{{Part=part,Anchored=true}})
task.wait(config.f3xDelay)
endpoint:InvokeServer('SyncColor',{{Part=part,Color=Color3.fromRGB(69,255,249),UnionColoring=false}})
task.wait(config.f3xDelay)
endpoint:InvokeServer('SyncMaterial',{{Part=part,Material=Enum.Material.Neon}})
end)
realmBusy=false
if gui.Parent then liveRealmParts();say(ok and 'Beacon created using F3X.' or ('Beacon failed: '..tostring(err):sub(1,90)),ok) end
end)
end
textButton(realmPage,'BUILD PIXEL HALL',16,132,255,42,function() createRealm('PIXEL') end)
textButton(realmPage,'VOID REALM',288,132,231,42,function() createRealm('VOID') end)
textButton(realmPage,'GOLD REALM',536,132,257,42,function() createRealm('GOLD') end)
textButton(realmPage,'REMOVE REALM',16,185,248,39,removeRealm)
textButton(realmPage,'REPLAY ANIMATION',282,185,248,39,function() startRealmAnimation() end)
textButton(realmPage,'STOP ANIMATION',548,185,245,39,function() stopRealmAnimation(false) end)
label(realmPage,'BUILDER UTILITIES',16,237,700,24,15,GOLD)
textButton(realmPage,'CHECK BTOOLS',16,271,249,39,function()
local tool,endpoint,problem=detectBuildTool()
say(endpoint and ('Ready: '..tool.Name..' F3X SyncAPI') or (problem or 'No compatible BTools.'),endpoint~=nil)
end)
textButton(realmPage,'TEST ONE BLOCK',282,271,247,39,function()
if realmBusy or busy then return say('Finish the current build first.') end
local tool,endpoint,problem=detectBuildTool()
if not endpoint then return say(problem or 'F3X CreatePart unavailable.') end
local hrp=player.Character and player.Character:FindFirstChild('HumanoidRootPart')
if not hrp then return say('Your character has not spawned.') end
realmBusy=true
task.spawn(function()
local ok,result=pcall(function()
local cf=CFrame.new(hrp.Position+hrp.CFrame.LookVector*12+Vector3.new(0,-2,0))
local part=endpoint:InvokeServer('CreatePart','Normal',cf,workspace)
if not validRealmPart(part) then error('F3X did not return a BasePart') end
table.insert(realmParts,part)
endpoint:InvokeServer('SyncResize',{{Part=part,CFrame=cf,Size=Vector3.new(7,1,7)}})
endpoint:InvokeServer('SyncColor',{{Part=part,Color=Color3.fromRGB(241,126,188),UnionColoring=false}})
return part
end)
realmBusy=false
if gui.Parent then
say(ok and 'Test passed: rose block created; remove it before building.' or
('Build test failed: '..tostring(result):sub(1,130)),ok)
liveRealmParts()
end
end)
end)
textButton(realmPage,'ADD BEACON',548,271,245,39,createBeacon)
textButton(realmPage,'PIXEL SKY 1',16,330,246,36,function()
selectedRealmSky='129860680793705';skyIdBox.Text=selectedRealmSky
liveRealmParts();say('Pixel Sky 1 selected.',true)
end)
textButton(realmPage,'PIXEL SKY 2',279,330,246,36,function()
selectedRealmSky='135323759293167';skyIdBox.Text=selectedRealmSky
liveRealmParts();say('Pixel Sky 2 selected.',true)
end)
textButton(realmPage,'MORE DETAILS',543,330,250,36,function() createRealm('PIXEL',true) end)
label(realmPage,'Hall builds in front of you with a front door, four walls and roof. Animation uses small F3X batches; automatically stops.',16,380,790,44,12,QUIET)
do -- MAPS tab: F3X maps use precisely the same builder as REALMS.
label(mapPage,'F3X MAPS • SAME BTOOLS AS REALMS',16,10,773,31,19,GOLD)
local mapHintInfo=label(mapPage,'Pick a realm. Switching removes the last tracked RetroF3X build before creating the next.',16,43,773,33,14,WHITE)
local selectedMapKey='PixelHall'
local selectedMapQuality=2
local skyMapMode=true
local mapBusy=false
local mapButtons={}
local mapSelectionInfo=label(mapPage,'SELECTED: Pixel Hall',16,84,615,27,15,GOLD)
local mapListPanel=ui('ScrollingFrame',{
Position=UDim2.fromOffset(16,115),Size=UDim2.fromOffset(775,147),
BorderSizePixel=0,BackgroundColor3=DARK,ScrollBarThickness=7,
CanvasSize=UDim2.fromOffset(0,147),ScrollingDirection=Enum.ScrollingDirection.Y,
},mapPage)
stroke(mapListPanel,GOLD)
ui('UIListLayout',{
SortOrder=Enum.SortOrder.LayoutOrder,Padding=UDim.new(0,5),
},mapListPanel)
local availableMapList={
{key='PixelHall',theme='PIXEL',label='Pixel Hall',description='Avatar-themed enclosed throne hall'},
{key='VoidRealm',theme='VOID',label='Void Realm',description='Dark cosmic throne hall'},
{key='GoldRealm',theme='GOLD',label='Gold Realm',description='Royal golden throne hall'},
{key='NeonMegacity',theme='NeonMegacity',label='Neon Megacity',description='Streets and lit towers'},
{key='CrystalCavern',theme='CrystalCavern',label='Crystal Cavern',description='Enclosed cave and crystal fields'},
{key='SakuraSanctuary',theme='SakuraSanctuary',label='Sakura Sanctuary',description='Sakura garden and torii gates'},
{key='FrozenCitadel',theme='FrozenCitadel',label='Frozen Citadel',description='Snow and ice fortress'},
{key='InfernoForge',theme='InfernoForge',label='Inferno Forge',description='Lava foundry and furnaces'},
{key='CelestialObservatory',theme='CelestialObservatory',label='Celestial Observatory',description='Cosmic temple and star rings'},
{key='EventHorizon',theme='EventHorizon',label='Event Horizon',description='Black hole visual arena'},
}
local function repaintMapChoices()
for _,entry in ipairs(mapButtons) do
if entry.button and entry.button.Parent then
entry.button.BackgroundColor3=(selectedMapKey==entry.key) and Color3.fromRGB(104,65,91) or PANEL
end
end
end
local function drawMapChoices()
for _,entry in ipairs(mapButtons) do if entry.button then entry.button:Destroy() end end
mapButtons={}
for i,item in ipairs(availableMapList) do
local button=textButton(mapListPanel,item.label,8,(i-1)*39+7,746,34,function()
selectedMapKey=item.key
mapSelectionInfo.Text='SELECTED: '..item.label
mapHintInfo.Text=item.description
repaintMapChoices()
end)
button.LayoutOrder=i
button.TextSize=15
mapButtons[#mapButtons+1]={key=item.key,button=button}
end
mapListPanel.CanvasSize=UDim2.fromOffset(0,#availableMapList*39+16)
repaintMapChoices()
end
drawMapChoices()
label(mapPage,'REALM DETAIL:',16,272,151,25,14,WHITE)
local qualityButtons={}
for i,title in ipairs({'CLASSIC','DETAILED','ULTRA'}) do
local quality=i
local b=textButton(mapPage,title,160+(i-1)*151,267,141,31,function()
selectedMapQuality=quality
for level,button in ipairs(qualityButtons) do
button.BackgroundColor3=(level==quality) and Color3.fromRGB(110,67,94) or PANEL
end
say('Map detail set to '..title..'. More geometry requires more F3X calls.',true)
end)
qualityButtons[#qualityButtons+1]=b
b.TextSize=14
b.BackgroundColor3=(i==selectedMapQuality) and Color3.fromRGB(110,67,94) or PANEL
end
local mapState=label(mapPage,'F3X status: press CHECK BTOOLS to verify your tool.',16,305,589,24,13,AMBER)
local skyModeButton
skyModeButton=textButton(mapPage,'SKY BUILD: ON',622,300,167,30,function()
skyMapMode=not skyMapMode
skyModeButton.Text=skyMapMode and 'SKY BUILD: ON' or 'SKY BUILD: OFF'
say(skyMapMode and 'New map builds 400 studs above the current game. Use GO TO MAP afterward.'
or 'New map builds near your character. Existing game geometry stays.',true)
end)
local mapSafetyNote=label(mapPage,'SKY BUILD avoids overlap. Does not delete the original map.',16,334,590,32,12,QUIET)
textButton(mapPage,'GO TO SKY MAP',622,335,167,30,function()
local base=realmRegistry.skyBase
if typeof(base)~='CFrame' or #liveRealmParts()==0 then
return say('Build a SKY map first. Old maps or non-sky builds cannot be teleported to.')
end
local char=player.Character
local hrp=char and char:FindFirstChild('HumanoidRootPart')
if not hrp then return say('Character not ready.') end
local theme=realmRegistry.skyTheme
-- Safe destination on the front half of the build, away from central props.
local pos=(base*CFrame.new(0,9,theme=='PIXEL' and 28 or 59)).Position
local ok,err=pcall(function()
char:PivotTo(CFrame.new(pos))
end)
if ok then
say('Moved toward the sky realm. If you snap back, the game prevents client teleport.',true)
else
say('Could not move to sky realm: '..tostring(err):sub(1,90))
end
end)
local function beginMapBuild()
if mapBusy or busy or realmBusy then return say('Finish the current F3X job first.') end
local selected
for _,item in ipairs(availableMapList) do
if item.key==selectedMapKey then selected=item;break end
end
if not selected then return say('Select one of the built-in realms.') end
local tool,ep,problem=detectBuildTool()
if not ep then
mapState.Text='F3X status: tool not detected.'
return say(problem or 'Equip F3X/BTools before loading a map.')
end
mapState.Text='F3X status: building '..selected.label..'...'
local began=createRealm(selected.theme,selectedMapQuality>=2,
{replace=true,quality=selectedMapQuality,label=selected.label,isolate=skyMapMode})
if not began then
mapState.Text='F3X status: build was not started; check the GUI message.'
return
end
mapBusy=true
task.spawn(function()
while gui.Parent and realmBusy do task.wait(.3) end
mapBusy=false
if not gui.Parent then return end
mapState.Text='F3X status: '..tostring(lastRealmResult.detail):sub(1,100)
end)
end
local function removeBuiltMap()
if mapBusy or busy or realmBusy then return say('Wait for the active operation to finish.') end
if #liveRealmParts()==0 then return say('No tracked RetroF3X map. The original game map is not managed by this script.') end
mapBusy=true
mapState.Text='F3X status: removing the current GUI-built map...'
removeRealm()
task.spawn(function()
while gui.Parent and realmBusy do task.wait(.3) end
mapBusy=false
if gui.Parent then mapState.Text='F3X status: '..#liveRealmParts()..' GUI-built parts remain.' end
end)
end
textButton(mapPage,'CHECK BTOOLS',620,79,169,31,function()
local tool,endpoint,problem=detectBuildTool()
mapState.Text=endpoint and ('F3X status: '..tool.Name..' ready') or ('F3X status: '..tostring(problem):sub(1,110))
say(endpoint and ('F3X ready: '..tool.Name) or problem,endpoint~=nil)
end)
textButton(mapPage,'BUILD / SWITCH MAP',16,376,376,41,beginMapBuild)
textButton(mapPage,'REMOVE RETROF3X MAP',412,376,378,41,removeBuiltMap)
label(mapPage,'Only tracked RetroF3X builds are removed. SKY BUILD puts new maps 400 studs above the old map.',16,421,779,15,11,QUIET)
end -- MAPS tab local scope
-- SCRIPTS TAB: Small HD Admin fun/effects shortcuts.
-- These are documented HD Admin commands, not claimed to be verified V8 features.
-- All commands are submitted as the current player, with normal rank checks.
label(scriptsPage,'SCRIPT SHORTCUTS • HD ADMIN',16,10,600,29,19,GOLD)
label(scriptsPage,'Click to send a real HD Admin command. Requires rank/permission in the game.',16,39,772,27,13,AMBER)
label(scriptsPage,'LIGHTING / WORLD',16,73,520,24,15,GOLD)
local scriptInfo=label(scriptsPage,'Ready — choose a command below.',16,390,777,20,12,QUIET)
local function sendScriptShortcut(title, commands)
if commandBusy or os.clock()-requestCooldown < config.commandCooldown then
return say('HD Admin busy/cooling down. Wait before sending another script.')
end
commandBusy=true
requestCooldown=os.clock()
scriptInfo.Text='Running '..title..': '..table.concat((function() local formatted={} for _,cmd in ipairs(commands) do formatted[#formatted+1]=withHDAdminPrefix(cmd) end return formatted end)(),' → ')
say('Sending '..title..' through HD Admin. Server permission required...',true)
task.spawn(function()
local errors={}
for index,command in ipairs(commands) do
if not gui.Parent then break end
local ok,detail=submitHDAdminCommand(command)
if not ok then table.insert(errors, tostring(detail):sub(1,74)) end
if index<#commands then task.wait(config.commandDelay) end
end
commandBusy=false
if not gui.Parent then return end
if #errors==0 then
scriptInfo.Text=title..': commands sent. Effect depends on HD Admin rank/game settings.'
say(title..' submitted. Successful transmission is not proof the server applied it.',true)
else
scriptInfo.Text=title..': '..table.concat(errors,' | '):sub(1,190)
say('Some HD Admin commands were rejected or could not be sent.')
end
end)
end
local function quick(name,buttonText,x,y,commands)
local b=textButton(scriptsPage,buttonText,x,y,188,41,function() sendScriptShortcut(name,commands) end)
b.TextSize=14
return b
end
quick('Disco','DISCO '..config.commandPrefix..'disco',16,102,{';disco'})
quick('Stop Disco','STOP DISCO',216,102,{';undisco'})
quick('Night','MIDNIGHT',416,102,{';time 0'})
quick('Day','DAYTIME',616,102,{';time 14'})
quick('Foggy','FOG ON',16,152,{';fog 100'})
quick('Clear Fog','FOG OFF',216,152,{';fog 100000'})
quick('Give BTools','BTOOLS (ME)',416,152,{';buildingtools me'})
quick('HD Command List','OPEN '..config.commandPrefix..'cmds',616,152,{';cmds'})
label(scriptsPage,'CHARACTER EFFECTS / COMBOS',16,203,655,24,15,GOLD)
quick('Fire','FIRE (ME)',16,233,{';fire me'})
quick('Extinguish','REMOVE FIRE',216,233,{';unfire me'})
quick('Sparkles','SPARKLES (ME)',416,233,{';sparkles me'})
quick('Remove Sparkles','STOP SPARKLES',616,233,{';unsparkles me'})
quick('Smoke','SMOKE (ME)',16,283,{';smoke me'})
quick('Remove Smoke','STOP SMOKE',216,283,{';unsmoke me'})
quick('Party Mode','PARTY COMBO',416,283,{';disco',';sparkles me'})
quick('Calm Mode','CALM COMBO',616,283,{';undisco',';unsparkles me',';unfire me',';unsmoke me'})
label(scriptsPage,'Combos use Settings → HD command delay. Events are under EVENTS tab.',16,406,773,20,12,QUIET)
do
-- PLAYER KICK CONTROLS (SCRIPTS tab)
-- Both tools submit regular HD Admin commands. They cannot grant the user kick privileges.
-- All names are exact Roblox usernames (never display names). Both features start OFF.
-- Cooldown is shared so Loopkick and Barrier Kick do not emit bursts together.
local kickStore='RetroF3X_Kick_Preferences.json'
local kickJSON=game:GetService('HttpService')
local kickSharedLast=-1000
local kickSharedBusy=false
local KICK_GAP=2.0
local barrierEnabled=false
local barrierRadius=30
local barrierShowSphere=false
local barrierSphere=nil
local barrierInside={}
local barrierQueue={}
local barrierSent=0
local barrierToggle
local loopkickEnabled=false
local loopkickNames={}
local loopkickQueue={}
local loopkickQueued={}
local loopkickSent=0
local loopkickToggle
local barrierReason='Barrier Kick'
local loopkickReason='Loopkick'
local function cleanReason(raw, defaultReason)
local result=tostring(raw or ''):gsub('[%c;]',' '):gsub('%s+',' ')
result=result:match('^%s*(.-)%s*$'):sub(1,80)
return result~='' and result or defaultReason
end
local function validUsername(value)
if type(value)~='string' then return false end
if #value<3 or #value>20 then return false end
return value:match('^[%w_]+$')~=nil
end
local function canonicalUsername(raw)
local v=tostring(raw or ''):gsub('^@','')
if validUsername(v) then return v:lower() end
return nil
end
local function formatLoopkickNames()
local names={}
for _,name in pairs(loopkickNames) do names[#names+1]=name end
table.sort(names,function(a,b) return a:lower()<b:lower() end)
return table.concat(names,'\n')
end
local function numLoopkickNames()
local count=0
for _ in pairs(loopkickNames) do count=count+1 end
return count
end
-- Read the optional settings file but do not auto-enable either kick feature.
if type(readfile)=='function' then
local ok,raw=pcall(function() return readfile(kickStore) end)
if ok and type(raw)=='string' then
local decodedOk,saved=pcall(function() return kickJSON:JSONDecode(raw) end)
if decodedOk and type(saved)=='table' then
barrierReason=cleanReason(saved.barrierReason,'Barrier Kick')
loopkickReason=cleanReason(saved.loopkickReason,'Loopkick')
if type(saved.barrierRadius)=='number' and saved.barrierRadius >=5 and saved.barrierRadius<=250 then
barrierRadius=math.floor(saved.barrierRadius*10+0.5)/10
end
if type(saved.barrierShowSphere)=='boolean' then
barrierShowSphere=saved.barrierShowSphere
end
if type(saved.names)=='table' then
for _,name in ipairs(saved.names) do
local canon=canonicalUsername(name)
if canon and numLoopkickNames()<50 and canon~=player.Name:lower() then
loopkickNames[canon]=tostring(name):gsub('^@','')
end
end
end
end
end
end
local function saveKickPreferences()
if type(writefile)~='function' then return false,'No local file access; saved for this session only.' end
local names={}
for _,name in pairs(loopkickNames) do names[#names+1]=name end
table.sort(names)
local ok,err=pcall(function()
writefile(kickStore,kickJSON:JSONEncode({version=1,names=names,
barrierReason=barrierReason,loopkickReason=loopkickReason,
barrierRadius=barrierRadius,barrierShowSphere=barrierShowSphere}))
end)
return ok,ok and 'Saved for future sessions.' or tostring(err)
end
local function submitKickFor(username,reason)
if not validUsername(username) or username:lower()==player.Name:lower() then
return false,'Invalid or self username'
end
if not gui.Parent then return false,'GUI was closed' end
local cmd=';kick '..username..' '..cleanReason(reason,'Kick')
local called,ok,detail=pcall(submitHDAdminCommand,cmd)
if not called then return false,tostring(ok) end
return ok,ok and ('Submitted: '..cmd) or tostring(detail)
end
-- BARRIER KICK: configurable proximity + reason.
label(scriptsPage,'BARRIER KICK • PROXIMITY',16,446,650,30,19,GOLD)
label(scriptsPage,'Detect nearby players and send one kick request when they enter your radius.',16,475,776,28,13,AMBER)
label(scriptsPage,'RADIUS (5–250 STUDS) • Preview uses this value',16,506,420,22,14,WHITE)
local barrierRadiusBox=textbox(scriptsPage,'Radius in studs',16,533,190,35)
barrierRadiusBox.Text=tostring(barrierRadius)
local barrierStatus=label(scriptsPage,'Barrier Kick OFF • 0 requests',275,657,520,40,13,QUIET)
local function setBarrierStatus(msg,good)
if barrierStatus.Parent then
barrierStatus.Text=msg
barrierStatus.TextColor3=good==nil and QUIET or (good and GREEN or AMBER)
end
end
label(scriptsPage,'KICK REASON (SHOWN TO PLAYER)',16,585,430,22,14,WHITE)
local barrierReasonBox=textbox(scriptsPage,'Reason for proximity kick',16,612,780,36)
barrierReasonBox.Text=barrierReason
-- Local visual guide ONLY: does not modify map parts, affect movement, or replicate.
-- The visible sphere follows HumanoidRootPart through Adornee (no constant CFrame updates).
local function updateBarrierSphere()
if not barrierShowSphere or not gui.Parent then
if barrierSphere then barrierSphere:Destroy(); barrierSphere=nil end
return
end
local rootPart=player.Character and player.Character:FindFirstChild('HumanoidRootPart')
if not (barrierSphere and barrierSphere.Parent) then
barrierSphere=Instance.new('SphereHandleAdornment')
barrierSphere.Name='RetroF3X_BarrierRadiusSphere'
barrierSphere.AlwaysOnTop=false
barrierSphere.Color3=GOLD
barrierSphere.Transparency=0.84
barrierSphere.Parent=guiParent
end
barrierSphere.Radius=barrierRadius
barrierSphere.Adornee=rootPart
end
local barrierPreviewToggle
local function setPreviewButtonText()
if barrierPreviewToggle then
barrierPreviewToggle.Text=barrierShowSphere and 'HIDE RADIUS SPHERE' or 'SHOW RADIUS SPHERE'
barrierPreviewToggle.BackgroundColor3=barrierShowSphere and Color3.fromRGB(98,77,32) or DARK
end
end
local function tryUpdateBarrierRadius(showError)
local radius=tonumber(barrierRadiusBox.Text)
if not radius or radius~=radius or radius<5 or radius>250 then
if showError then say('Radius preview: enter a value from 5 to 250 studs.') end
return false
end
radius=math.floor(radius*10+0.5)/10
if radius ~= barrierRadius then
barrierRadius=radius
-- Treat range edits as a new entry boundary when barrier is already active.
table.clear(barrierInside)
table.clear(barrierQueue)
end
updateBarrierSphere()
return true
end
barrierPreviewToggle=textButton(scriptsPage,'SHOW RADIUS SPHERE',16,660,245,37,function()
if not tryUpdateBarrierRadius(true) then return end
barrierShowSphere=not barrierShowSphere
setPreviewButtonText()
updateBarrierSphere()
saveKickPreferences() -- Optional: executor may not expose writefile.
say(barrierShowSphere and ('Barrier preview ON, '..barrierRadius..' studs (local only).')
or 'Barrier preview hidden.',true)
end)
barrierPreviewToggle.TextSize=12
-- Bind textbox edits to the live radius while both barrier and preview remain active.
barrierRadiusBox.FocusLost:Connect(function()
if tryUpdateBarrierRadius(true) then
barrierRadiusBox.Text=tostring(barrierRadius)
saveKickPreferences()
end
end)
player.CharacterAdded:Connect(function()
if not gui.Parent then return end
task.defer(function()
if gui.Parent then updateBarrierSphere() end
end)
end)
-- Extra cleanup when the GUI is removed through a script reload or close button.
gui.Destroying:Connect(function()
if barrierSphere then barrierSphere:Destroy(); barrierSphere=nil end
end)
setPreviewButtonText()
updateBarrierSphere()
local function stopBarrier()
barrierEnabled=false
table.clear(barrierInside)
table.clear(barrierQueue)
barrierToggle.Text='ENABLE BARRIER KICK'
barrierToggle.BackgroundColor3=DARK
setBarrierStatus('Barrier Kick OFF • '..barrierSent..' requests')
end
barrierToggle=textButton(scriptsPage,'ENABLE BARRIER KICK',228,532,263,40,function()
if barrierEnabled then return stopBarrier() end
if not tryUpdateBarrierRadius(true) then return end
barrierReason=cleanReason(barrierReasonBox.Text,'Barrier Kick')
barrierReasonBox.Text=barrierReason
barrierRadiusBox.Text=tostring(barrierRadius)
barrierSent=0
table.clear(barrierInside)
table.clear(barrierQueue)
barrierEnabled=true
barrierToggle.Text='DISABLE BARRIER KICK'
barrierToggle.BackgroundColor3=Color3.fromRGB(100,63,24)
setBarrierStatus('Barrier ON • '..barrierRadius..' studs • scanning nearby players',true)
say('Barrier Kick enabled. Server must permit '..config.commandPrefix..'kick.',true)
end)
local barrierStop=textButton(scriptsPage,'STOP + CLEAR QUEUE',508,532,288,40,stopBarrier)
barrierStop.TextSize=13
label(scriptsPage,'Reasons cannot contain semicolons/newlines. At least 2s between kick requests.',16,703,775,30,12,QUIET)
task.spawn(function()
while gui.Parent do
task.wait(0.75)
if barrierShowSphere then updateBarrierSphere() end
if barrierEnabled then
local myRoot=player.Character and player.Character:FindFirstChild('HumanoidRootPart')
if myRoot and myRoot:IsA('BasePart') then
local insideNow={}
for _,other in ipairs(Players:GetPlayers()) do
if other~=player then
local theirRoot=other.Character and other.Character:FindFirstChild('HumanoidRootPart')
if theirRoot and theirRoot:IsA('BasePart') then
if (myRoot.Position-theirRoot.Position).Magnitude<=barrierRadius then
insideNow[other.UserId]=true
if not barrierInside[other.UserId] then
barrierInside[other.UserId]=true
if #barrierQueue<20 then table.insert(barrierQueue,other.UserId) end
end
end
end
end
end
for userId in pairs(barrierInside) do
if not insideNow[userId] then barrierInside[userId]=nil end
end
if #barrierQueue>0 and not kickSharedBusy and not commandBusy and
os.clock()-kickSharedLast>=math.max(KICK_GAP,config.commandDelay) then
local userId=table.remove(barrierQueue,1)
local other=Players:GetPlayerByUserId(userId)
local theirRoot=other and other.Character and other.Character:FindFirstChild('HumanoidRootPart')
if other and other~=player and theirRoot and theirRoot:IsA('BasePart') and
(myRoot.Position-theirRoot.Position).Magnitude<=barrierRadius then
kickSharedBusy=true
kickSharedLast=os.clock()
barrierReason=cleanReason(barrierReasonBox.Text,'Barrier Kick')
local ok,detail=submitKickFor(other.Name,barrierReason)
barrierSent=barrierSent+1
setBarrierStatus((ok and 'Sent: ' or 'Failed: ')..tostring(detail):sub(1,105)..
' • requests '..barrierSent,ok)
kickSharedBusy=false
end
end
else
setBarrierStatus('Barrier ON — waiting for your character...',false)
end
end
end
end)
-- LOOPKICK: saved username list. Only targets a joining username, not similarly named players.
label(scriptsPage,'LOOPKICK • USERNAME LIST',16,746,660,30,19,GOLD)
label(scriptsPage,'Put one username per line (commas also work), then SAVE LIST. Display names do not count.',16,776,785,31,13,AMBER)
local loopkickEditor=textbox(scriptsPage,'ExamplePlayer\nAnotherPlayer',16,814,780,122)
loopkickEditor.MultiLine=true
loopkickEditor.TextYAlignment=Enum.TextYAlignment.Top
loopkickEditor.TextXAlignment=Enum.TextXAlignment.Left
loopkickEditor.TextWrapped=true
loopkickEditor.Text=formatLoopkickNames()
local loopkickListStatus=label(scriptsPage,'Saved usernames: '..numLoopkickNames()..' / 50',16,1077,780,30,13,QUIET)
local loopkickStatus=label(scriptsPage,'Loopkick OFF • 0 requests',16,1136,775,65,14,QUIET)
local function setLoopkickStatus(msg,good)
if loopkickStatus.Parent then
loopkickStatus.Text=msg
loopkickStatus.TextColor3=good==nil and QUIET or (good and GREEN or AMBER)
end
end
local function parseLoopkickList(raw)
local added={}
local ignored={}
for token in tostring(raw or ''):gmatch('[^,%s]+') do
local canon=canonicalUsername(token)
if canon and canon~=player.Name:lower() then
added[canon]=token:gsub('^@','')
else
ignored[#ignored+1]=token:sub(1,24)
end
end
local count=0
for _ in pairs(added) do count=count+1 end
if count>50 then return nil,'Maximum 50 usernames.' end
if #ignored>0 then return nil,'Invalid username(s): '..table.concat(ignored,', '):sub(1,115) end
return added
end
label(scriptsPage,'LOOPKICK REASON (SHOWN TO PLAYER)',16,1007,525,22,14,WHITE)
local loopkickReasonBox=textbox(scriptsPage,'Reason for list kick',16,1033,780,36)
loopkickReasonBox.Text=loopkickReason
local function saveLoopkickForm()
local updated,errorText=parseLoopkickList(loopkickEditor.Text)
if not updated then return false,errorText end
loopkickNames=updated
barrierReason=cleanReason(barrierReasonBox.Text,'Barrier Kick')
loopkickReason=cleanReason(loopkickReasonBox.Text,'Loopkick')
barrierReasonBox.Text=barrierReason
loopkickReasonBox.Text=loopkickReason
loopkickEditor.Text=formatLoopkickNames()
loopkickListStatus.Text='Saved usernames: '..numLoopkickNames()..' / 50'
local saved,detail=saveKickPreferences()
setLoopkickStatus('List and reasons updated. '..tostring(detail):sub(1,120),saved)
return true,detail
end
local loopkickSave=textButton(scriptsPage,'SAVE LIST + REASONS',16,951,240,42,function()
local ok,err=saveLoopkickForm()
if not ok then setLoopkickStatus(err,false) end
end)
loopkickSave.TextSize=13
local function stopLoopkick()
loopkickEnabled=false
table.clear(loopkickQueue)
table.clear(loopkickQueued)
loopkickToggle.Text='ENABLE LOOPKICK'
loopkickToggle.BackgroundColor3=DARK
setLoopkickStatus('Loopkick OFF • '..loopkickSent..' requests sent')
end
loopkickToggle=textButton(scriptsPage,'ENABLE LOOPKICK',270,951,255,42,function()
if loopkickEnabled then return stopLoopkick() end
local ok,err=saveLoopkickForm()
if not ok then return setLoopkickStatus(err,false) end
if numLoopkickNames()==0 then return setLoopkickStatus('Add at least one username to the list.',false) end
loopkickEnabled=true
loopkickSent=0
loopkickToggle.Text='DISABLE LOOPKICK'
loopkickToggle.BackgroundColor3=Color3.fromRGB(100,63,24)
setLoopkickStatus('Loopkick ON • watching for listed usernames to join',true)
end)
local loopkickStop=textButton(scriptsPage,'STOP + CLEAR QUEUE',539,951,257,42,stopLoopkick)
loopkickStop.TextSize=13
local function queueLoopkick(other)
if not gui.Parent or not loopkickEnabled or not other or other==player then return end
local key=other.Name:lower()
if not loopkickNames[key] or loopkickQueued[other.UserId] then return end
if #loopkickQueue>=50 then return setLoopkickStatus('Loopkick queue full (50).',false) end
loopkickQueued[other.UserId]=true
loopkickQueue[#loopkickQueue+1]={userId=other.UserId,username=other.Name}
setLoopkickStatus('Detected '..other.Name..' joining • queued '..#loopkickQueue..' kick request(s)',true)
end
Players.PlayerAdded:Connect(function(joiningPlayer)
if gui.Parent then queueLoopkick(joiningPlayer) end
end)
-- Optional manual pass for players already in this server; intentionally not automatic on enable.
local loopkickNow=textButton(scriptsPage,'PROCESS LISTED PLAYERS ALREADY HERE',16,1209,384,35,function()
if not loopkickEnabled then return setLoopkickStatus('Enable Loopkick first.',false) end
for _,other in ipairs(Players:GetPlayers()) do queueLoopkick(other) end
end)
loopkickNow.TextSize=11
label(scriptsPage,'Only actual usernames are matched. List is stored locally if available.',415,1207,380,44,12,QUIET)
task.spawn(function()
while gui.Parent do
task.wait(0.35)
if loopkickEnabled and #loopkickQueue>0 and not kickSharedBusy and not commandBusy and
os.clock()-kickSharedLast>=math.max(KICK_GAP,config.commandDelay) then
local record=table.remove(loopkickQueue,1)
loopkickQueued[record.userId]=nil
local current=Players:GetPlayerByUserId(record.userId)
-- If they've left, changed identities, or been removed from the list, discard the request.
if current and current~=player and current.Name==record.username and
loopkickNames[current.Name:lower()] then
kickSharedBusy=true
kickSharedLast=os.clock()
loopkickReason=cleanReason(loopkickReasonBox.Text,'Loopkick')
local ok,detail=submitKickFor(current.Name,loopkickReason)
loopkickSent=loopkickSent+1
setLoopkickStatus((ok and 'Sent: ' or 'Failed: ')..tostring(detail):sub(1,105)..
' • requests '..loopkickSent,ok)
kickSharedBusy=false
end
end
end
end)
end -- scope kick-only locals, preserving Luau/Lua 200-local limit
-- F3X CLONE ARMY v2: 24 soldiers, smoother stepped server-side movement,
-- optional leg gait, squad formations and controllable follow/guard/orbit modes.
-- Movement requests are throttled. No scripts are installed in the soldiers.
do
local MAX_SOLDIERS=24
local army={}
local armyBusy=false
local armyFollow=false
local armyOrbit=false
local armyStop=false
local armyMode='grid'
local orbitAngle=0
local moveSerial=0
local tickInterval=.70
local armySpeed=8
local armyInfo=label(scriptsPage,'CLONE ARMY V2 • WALKING FORMATIONS',16,1267,765,29,19,GOLD)
label(scriptsPage,'F3X mannequin soldiers move in small steps. High counts use slower updates to control lag.',16,1299,775,36,13,WHITE)
label(scriptsPage,'SOLDIERS (1–24)',16,1338,186,21,13,GOLD)
local armyCountBox=textbox(scriptsPage,'8',16,1363,170,34)
armyCountBox.Text='8'
label(scriptsPage,'MARCH DISTANCE (2–80)',202,1338,258,21,13,GOLD)
local armyDistanceBox=textbox(scriptsPage,'15',202,1363,191,34)
armyDistanceBox.Text='15'
label(scriptsPage,'WALK SPEED (3–16)',408,1338,184,21,13,GOLD)
local armySpeedBox=textbox(scriptsPage,'8',408,1363,140,34)
armySpeedBox.Text='8'
local armyState=label(scriptsPage,'Army: 0 | Idle',566,1353,236,52,13,GREEN)
local followButton
local orbitButton
local function state(msg,ok)
if not armyState.Parent then return end
armyState.Text=tostring(msg)
armyState.TextColor3=(ok==false) and AMBER or GREEN
say('Clone Army: '..tostring(msg),ok~=false)
end
local function validModel(model)
return typeof(model)=='Instance' and model:IsA('Model') and model:IsDescendantOf(workspace)
end
local function currentArmy()
local valid={}
for _,record in ipairs(army) do
if validModel(record.model) then valid[#valid+1]=record end
end
army=valid
return army
end
local function endpointForArmy()
local tool,endpoint,problem=detectBuildTool()
if not endpoint then return nil,problem or 'Equip F3X tools first.' end
return endpoint
end
local function call(endpoint,action,...)
local ok,result,a,b=pcall(function(...) return endpoint:InvokeServer(action,...) end,...)
if not ok then error('F3X '..action..': '..tostring(result),2) end
if result==false then error('F3X rejected '..action,2) end
return result,a,b
end
local function getRoot()
local char=player.Character
local hrp=char and char:FindFirstChild('HumanoidRootPart')
if not hrp then error('Character not ready') end
return hrp
end
local function soldierParts(model)
local parts={}
for _,child in ipairs(model:GetDescendants()) do
if child:IsA('BasePart') then parts[#parts+1]=child end
end
if #parts<6 then error('Soldier model is incomplete (check cloning).') end
return parts
end
local function trackSoldier(model,base)
local record={model=model,root=base,parts=soldierParts(model),rel={},legs={},phase=0}
for _,part in ipairs(record.parts) do
local relative=base:ToObjectSpace(part.CFrame)
record.rel[part]=relative
-- Soldier legs occupy the bottom 2.3 studs of the seven-part model.
local pos=relative.Position
if pos.Y<2.4 and math.abs(pos.X)<1.4 then
record.legs[part]=(pos.X<0 and 1 or -1)
end
end
return record
end
local function getStreamingClone(streamId)
if type(streamId)~='number' then return nil end
local deadline=os.clock()+2.2
repeat
for _,desc in ipairs(workspace:GetDescendants()) do
if desc:IsA('Model') and desc:GetAttribute('BTStreamingCloneID')==streamId then
return desc
end
end
task.wait(.12)
until os.clock()>deadline or not gui.Parent
return nil
end
local function updateModeButtons()
if followButton and followButton.Parent then
followButton.Text=armyFollow and 'FOLLOW ME: ON' or 'FOLLOW ME: OFF'
end
if orbitButton and orbitButton.Parent then
orbitButton.Text=armyOrbit and 'ORBIT ME: ON' or 'ORBIT ME: OFF'
end
end
local function stopModes()
armyFollow=false
armyOrbit=false
moveSerial=moveSerial+1
updateModeButtons()
end
local function configuredSpeed()
local speed=tonumber(armySpeedBox.Text)
if not speed or speed~=speed or speed<3 or speed>16 then
return nil,'Walk speed must be between 3 and 16.'
end
return speed
end
local function spawn(count)
if armyBusy or realmBusy or busy then return false,'Another F3X build is running.' end
if #currentArmy()>0 then return false,'Remove the existing army first.' end
if type(count)~='number' or count<1 or count>MAX_SOLDIERS or count%1~=0 then
return false,'Soldier count must be 1 to '..MAX_SOLDIERS..'.'
end
local endpoint,problem=endpointForArmy()
if not endpoint then return false,problem end
stopModes();armyStop=false;armyBusy=true
local made={}
local ok,err=pcall(function()
local base=getRoot().CFrame*CFrame.new(0,-3,-12)
local specs={
{Vector3.new(0,5.4,0),Vector3.new(2,2,2),Color3.fromRGB(245,221,148)},
{Vector3.new(0,3.5,0),Vector3.new(2.5,2.6,1.4),Color3.fromRGB(45,45,56)},
{Vector3.new(-1.85,3.5,0),Vector3.new(1,2.6,1.3),Color3.fromRGB(241,201,120)},
{Vector3.new(1.85,3.5,0),Vector3.new(1,2.6,1.3),Color3.fromRGB(241,201,120)},
{Vector3.new(-.72,1.1,0),Vector3.new(1.05,2.1,1.3),Color3.fromRGB(185,128,169)},
{Vector3.new(.72,1.1,0),Vector3.new(1.05,2.1,1.3),Color3.fromRGB(185,128,169)},
{Vector3.new(0,5.45,-1.03),Vector3.new(1.5,.45,.16),Color3.fromRGB(250,193,228)},
}
local parts,resize,colours,collisions={},{},{},{}
state('Building the seven-piece soldier template...')
for _,spec in ipairs(specs) do
if armyStop or not gui.Parent then error('Build cancelled') end
local cf=base*CFrame.new(spec[1])
local part=call(endpoint,'CreatePart','Normal',cf,workspace)
if not validRealmPart(part) then error('F3X CreatePart did not return a replicated part.') end
made[#made+1]=part;parts[#parts+1]=part
resize[#resize+1]={Part=part,CFrame=cf,Size=spec[2]}
colours[#colours+1]={Part=part,Color=spec[3],UnionColoring=false}
collisions[#collisions+1]={Part=part,CanCollide=false}
task.wait(math.max(config.f3xDelay,.15))
end
call(endpoint,'SyncResize',resize);task.wait(config.f3xDelay)
call(endpoint,'SyncColor',colours);task.wait(config.f3xDelay)
call(endpoint,'SyncCollision',collisions);task.wait(config.f3xDelay)
local model=call(endpoint,'CreateGroup','Model',workspace,parts)
if not validModel(model) then error('F3X CreateGroup failed.') end
made[#made+1]=model
call(endpoint,'SetName',{model},'RetroF3X_Army_Soldier')
army[#army+1]=trackSoldier(model,base)
for i=2,count do
if armyStop or not gui.Parent then error('Build cancelled') end
state('Cloning soldier '..i..'/'..count..' ...')
local clones,streamId=call(endpoint,'Clone',{model},workspace)
local clone=(type(clones)=='table' and clones[1]) or getStreamingClone(streamId)
if not validModel(clone) then error('F3X Clone failed / streaming clone was not found.') end
made[#made+1]=clone
army[#army+1]=trackSoldier(clone,base)
task.wait(math.max(config.f3xDelay,.17))
end
end)
if not ok then
pcall(function()
local objects={}
for _,obj in ipairs(made) do
if typeof(obj)=='Instance' and obj:IsDescendantOf(workspace) and obj.Parent==workspace then
objects[#objects+1]=obj
end
end
if #objects>0 then call(endpoint,'Remove',objects) end
end)
army={};armyBusy=false
return false,tostring(err):sub(1,200)
end
armyBusy=false
return true,'Created '..#army..' soldiers. Forming a squad...'
end
local function targetFor(record,index,count,mode,hrp,angle)
local spacing=4.35
local origin=hrp.CFrame
if mode=='line' then
return origin*CFrame.new((index-(count+1)/2)*spacing,-3,10)
elseif mode=='v' then
local lane=math.ceil((index-1)/2)
local side=(index%2==0 and 1 or -1)
if index==1 then return origin*CFrame.new(0,-3,10) end
return origin*CFrame.new(side*lane*spacing,-3,10+lane*3.8)
elseif mode=='circle' or mode=='orbit' then
local radius=math.max(11,count*1.1)
local theta=2*math.pi*(index-1)/count+(angle or 0)
local offset=Vector3.new(math.cos(theta)*radius,-3,math.sin(theta)*radius)
local pos=hrp.Position+offset
local look=Vector3.new(hrp.Position.X,pos.Y,hrp.Position.Z)
return CFrame.lookAt(pos,look)
elseif mode=='shield' then
local cols=math.min(count,8)
local row=math.floor((index-1)/cols)
local col=(index-1)%cols
return origin*CFrame.new((col-(cols-1)/2)*3.5,-3,-10-row*3.8)
elseif mode=='grid' or mode=='follow' then
local cols=math.min(6,math.ceil(math.sqrt(count)))
local row=math.floor((index-1)/cols)
local col=(index-1)%cols
return origin*CFrame.new((col-(cols-1)/2)*spacing,-3,9+row*spacing)
end
error('Unknown army formation: '..tostring(mode))
end
-- Move each model's actual F3X parts by short steps. The gait alternates
-- leg angles within the SAME SyncMove update (no extra gait remotes).
local function advance(endpoint,targets,seconds,motion)
local soldiers=currentArmy()
if #soldiers==0 then return true,0 end
local changes={}
local positions={}
local farthest=0
for i,record in ipairs(soldiers) do
local target=targets[i]
if target then
local root=record.root
local delta=target.Position-root.Position
local distance=delta.Magnitude
farthest=math.max(farthest,distance)
local maxStep=math.max(.3,armySpeed*seconds)
local position=(distance>maxStep) and (root.Position+delta.Unit*maxStep) or target.Position
-- Ease the turn toward the next goal instead of instantly rotating.
local desired
local levelDelta=Vector3.new(target.Position.X-position.X,0,target.Position.Z-position.Z)
if distance>.65 and levelDelta.Magnitude>.05 then
desired=CFrame.lookAt(position,position+levelDelta)
else
desired=CFrame.new(position)*target.Rotation
end
local rotate=CFrame.new(position)*(root.Rotation:Lerp(desired.Rotation,math.min(1,seconds*4)))
positions[i]=rotate
local moving=motion and distance>.4
local bob=moving and .12*math.abs(math.sin(record.phase)) or 0
local base=rotate*CFrame.new(0,bob,0)
for _,part in ipairs(record.parts) do
if part.Parent then
local relative=record.rel[part]
local leg=record.legs[part]
if leg and moving then
relative=relative*CFrame.Angles(math.sin(record.phase)*leg*.24,0,0)
end
changes[#changes+1]={Part=part,CFrame=base*relative}
end
end
if moving then record.phase=record.phase+1.15 end
end
end
-- Split large armies into bounded batches, preserving one root update
-- per complete server step; requests still honor the timing setting.
local maxBatch=84
for from=1,#changes,maxBatch do
local subset={}
for k=from,math.min(from+maxBatch-1,#changes) do
subset[#subset+1]=changes[k]
end
call(endpoint,'SyncMove',subset)
if from+maxBatch<=#changes then task.wait(math.max(config.f3xDelay,.15)) end
end
for i,record in ipairs(soldiers) do
if positions[i] then record.root=positions[i] end
end
return farthest<1, farthest
end
local function movementTime()
local count=#currentArmy()
return (count<=8 and .60) or (count<=16 and .85) or 1.10
end
local function moveFormation(mode)
if armyBusy or realmBusy or busy then return false,'F3X is busy.' end
if #currentArmy()==0 then return false,'Spawn the army first.' end
local speed,problem=configuredSpeed()
if not speed then return false,problem end
local endpoint,err=endpointForArmy()
if not endpoint then return false,err end
stopModes();armyStop=false;armyBusy=true;armySpeed=speed;armyMode=mode
local ok,detail=pcall(function()
local started=os.clock()
while not armyStop and gui.Parent do
local hrp=getRoot()
local targets={}
for i,record in ipairs(currentArmy()) do
targets[i]=targetFor(record,i,#army,mode,hrp)
end
local dt=movementTime()
local arrived=advance(endpoint,targets,dt,true)
if arrived then break end
if os.clock()-started>40 then error('Formation took too long; try fewer soldiers.') end
task.wait(dt)
end
end)
armyBusy=false
if not ok then return false,tostring(detail):sub(1,180) end
return true,armyStop and 'Formation halted.' or ('Army formed '..mode..' ('..#army..' soldiers).')
end
local function march(distance)
if armyBusy or realmBusy or busy then return false,'F3X is busy.' end
local speed,problem=configuredSpeed()
if not speed then return false,problem end
local d=tonumber(distance)
if not d or d~=d or d<2 or d>80 then return false,'March distance must be 2–80 studs.' end
if #currentArmy()==0 then return false,'Spawn the army first.' end
local endpoint,err=endpointForArmy()
if not endpoint then return false,err end
stopModes();armyStop=false;armyBusy=true;armySpeed=speed
local ok,detail=pcall(function()
local forward=getRoot().CFrame.LookVector
local targets={}
for i,record in ipairs(currentArmy()) do
targets[i]=record.root+(forward*d)
end
local started=os.clock()
while gui.Parent and not armyStop do
local arrived=advance(endpoint,targets,movementTime(),true)
if arrived then break end
if os.clock()-started>45 then error('March timed out. Reduce distance or count.') end
task.wait(movementTime())
end
end)
armyBusy=false
if not ok then return false,tostring(detail):sub(1,180) end
return true,armyStop and 'March halted.' or ('Marched '..d..' studs with walking steps.')
end
local function setMode(kind)
if #currentArmy()==0 then return false,'Spawn the army first.' end
local speed,problem=configuredSpeed()
if not speed then return false,problem end
if armyBusy then return false,'Finish or HALT the previous formation first.' end
local endpoint,err=endpointForArmy()
if not endpoint then return false,err end
armySpeed=speed;armyStop=false
local enabled=(kind=='follow' and not armyFollow) or (kind=='orbit' and not armyOrbit)
stopModes()
if kind=='follow' then armyFollow=enabled;armyMode='grid' end
if kind=='orbit' then armyOrbit=enabled;orbitAngle=0 end
updateModeButtons()
return true, enabled and (kind=='follow' and 'Smooth follow ON' or 'Orbit ON') or 'Automatic movement stopped.'
end
local function halt()
stopModes()
armyStop=true
return true,'Halt requested; any in-flight BTools move may finish first.'
end
local function removeArmy()
if armyBusy then return false,'HALT and let the current move finish first.' end
halt()
local soldiers=currentArmy()
if #soldiers==0 then return false,'No army to remove.' end
local endpoint,problem=endpointForArmy()
if not endpoint then return false,problem end
local models={}
for _,record in ipairs(soldiers) do models[#models+1]=record.model end
local ok,err=pcall(function() call(endpoint,'Remove',models) end)
if not ok then return false,tostring(err):sub(1,180) end
army={}
return true,'Removed '..#models..' army soldiers.'
end
armyActions.spawn=function(count)
local ok,msg=spawn(count)
if not ok then return false,msg end
local formed,detail=moveFormation('grid')
if not formed then return false,'Soldiers created; formation could not complete: '..tostring(detail) end
return true,'Spawned '..#army..' soldiers in a grid.'
end
armyActions.formation=moveFormation
armyActions.march=march
armyActions.remove=removeArmy
armyActions.follow=function() return setMode('follow') end
armyActions.orbit=function() return setMode('orbit') end
armyActions.halt=halt
local function launch(action,...)
local args={...}
task.spawn(function()
local success,msg=action(unpack(args))
if gui.Parent then state(msg,success) end
end)
end
textButton(scriptsPage,'SPAWN ARMY (UP TO 24)',16,1414,376,42,function()
launch(armyActions.spawn,tonumber(armyCountBox.Text))
end)
textButton(scriptsPage,'CHECK BTOOLS',408,1414,388,42,function()
local tool,ep,problem=detectBuildTool()
state(ep and ('Ready: '..tool.Name..' F3X SyncAPI') or problem,ep~=nil)
end)
textButton(scriptsPage,'FORM LINE',16,1469,250,39,function() launch(moveFormation,'line') end)
textButton(scriptsPage,'FORM V',282,1469,246,39,function() launch(moveFormation,'v') end)
textButton(scriptsPage,'FORM CIRCLE',544,1469,252,39,function() launch(moveFormation,'circle') end)
textButton(scriptsPage,'FORM GRID',16,1521,250,39,function() launch(moveFormation,'grid') end)
textButton(scriptsPage,'SHIELD WALL',282,1521,246,39,function() launch(moveFormation,'shield') end)
textButton(scriptsPage,'RALLY TO ME',544,1521,252,39,function() launch(moveFormation,armyMode) end)
textButton(scriptsPage,'MARCH (SMOOTH)',16,1573,250,40,function()
launch(march,tonumber(armyDistanceBox.Text))
end)
followButton=textButton(scriptsPage,'FOLLOW ME: OFF',282,1573,246,40,function()
launch(armyActions.follow)
end)
orbitButton=textButton(scriptsPage,'ORBIT ME: OFF',544,1573,252,40,function()
launch(armyActions.orbit)
end)
textButton(scriptsPage,'HALT / GUARD HERE',16,1625,379,40,function() launch(halt) end)
textButton(scriptsPage,'REMOVE ARMY',411,1625,385,40,function() launch(removeArmy) end)
label(scriptsPage,'FOLLOW moves soldiers toward slots in small F3X steps; ORBIT circles you. Animation is a stepping gait, not NPC pathfinding. Larger armies update slower.',16,1681,780,61,13,AMBER)
textButton(scriptsPage,'HD ADMIN: '..config.commandPrefix..'clone me (separate)',16,1758,380,40,function()
sendScriptShortcut('HD Avatar Clone',{';clone me'})
end)
label(scriptsPage,'Army colors: pearl / rose / gold / charcoal. Only the models this script creates are removed.',410,1756,384,49,12,QUIET)
label(scriptsPage,'TIP: Start with 6–12 soldiers. 24 soldiers need more server edits per movement step.',16,1815,777,38,13,QUIET)
task.spawn(function()
while gui.Parent do
task.wait(movementTime())
if not gui.Parent then break end
if (armyFollow or armyOrbit) and not armyBusy and #currentArmy()>0 then
local endpoint,problem=endpointForArmy()
if not endpoint then
stopModes()
state('Automatic movement stopped: '..tostring(problem),false)
else
armyBusy=true
local ok,err=pcall(function()
local hrp=getRoot()
local soldiers=currentArmy()
local n=#soldiers
if armyOrbit then orbitAngle=orbitAngle+.18 end
local mode=armyOrbit and 'orbit' or 'follow'
local targets={}
for i,record in ipairs(soldiers) do
targets[i]=targetFor(record,i,n,mode,hrp,orbitAngle)
end
advance(endpoint,targets,movementTime(),true)
end)
armyBusy=false
if not ok then
stopModes()
state('Auto motion stopped: '..tostring(err):sub(1,125),false)
end
end
end
end
end)
gui.Destroying:Connect(function()
stopModes();armyStop=true
end)
end
-- SETTINGS TAB: Adjustable request delays and cooldowns for this GUI session.
label(settingsPage,'SETTINGS • HD ADMIN COMMAND PREFIX',16,10,710,27,19,GOLD)
label(settingsPage,"Choose the prefix used by THIS GUI. It must match the game's HD Admin prefix.",16,40,770,24,13,AMBER)
local prefixBox=textbox(settingsPage,'; or :',16,75,122,36)
prefixBox.Text=config.commandPrefix
prefixBox.TextSize=22
prefixBox.TextXAlignment=Enum.TextXAlignment.Center
textButton(settingsPage,'APPLY PREFIX',155,75,200,36,function()
if commandBusy then return say('Wait until the current HD Admin command sequence finishes.') end
local prefix=prefixBox.Text:gsub('%s+','')
if #prefix~=1 or not prefix:match('^[;:!.]$') then
return say('Choose one prefix: ; : ! or .')
end
config.commandPrefix=prefix
local persisted=false
if type(writefile)=='function' then
persisted=pcall(function() writefile(PREFIX_FILE,prefix) end)
end
hint.Text='Choose a song: '..prefix..'music → '..prefix..'volume inf → '..prefix..'pitch.'
for _,control in ipairs(gui:GetDescendants()) do
if control:IsA('TextButton') then
if control.Text:match('^TEST [;:!.]music ONLY$') then
control.Text='TEST '..config.commandPrefix..'music ONLY'
elseif control.Text:match('^DISCO [;:!.]disco$') then
control.Text='DISCO '..config.commandPrefix..'disco'
elseif control.Text:match('^OPEN [;:!.]cmds$') then
control.Text='OPEN '..config.commandPrefix..'cmds'
elseif control.Text:match('^HD ADMIN: [;:!.]clone me %(SEPARATE%)$') then
control.Text='HD ADMIN: '..config.commandPrefix..'clone me (SEPARATE)'
end
end
end
say('HD Admin commands now use '..prefix..' (e.g. '..prefix..'music). '..
(persisted and 'Prefix saved locally.' or 'Prefix set for this session.')..' The game must use this prefix.',true)
end)
textButton(settingsPage,'RESET TO ;',370,75,180,36,function()
if commandBusy then return say('Wait until the current HD Admin sequence finishes.') end
config.commandPrefix=';'
prefixBox.Text=';'
if type(writefile)=='function' then pcall(function() writefile(PREFIX_FILE,';') end) end
hint.Text='Choose a song: ;music → ;volume inf → ;pitch.'
for _,control in ipairs(gui:GetDescendants()) do
if control:IsA('TextButton') then
if control.Text:match('^TEST [;:!.]music ONLY$') then
control.Text='TEST '..config.commandPrefix..'music ONLY'
elseif control.Text:match('^DISCO [;:!.]disco$') then
control.Text='DISCO '..config.commandPrefix..'disco'
elseif control.Text:match('^OPEN [;:!.]cmds$') then
control.Text='OPEN '..config.commandPrefix..'cmds'
elseif control.Text:match('^HD ADMIN: [;:!.]clone me %(SEPARATE%)$') then
control.Text='HD ADMIN: '..config.commandPrefix..'clone me (SEPARATE)'
end
end
end
say('GUI prefix reset to ; . The server must accept it.',true)
end)
label(settingsPage,'Music, Scripts, Events, Barrier Kick, and Loopkick all use this selection.',16,120,785,23,13,WHITE)
label(settingsPage,'TIMING SETTINGS • BUILD SPEED',16,163,690,27,19,GOLD)
label(settingsPage,'Delays apply between F3X requests and HD Admin commands.',16,188,770,24,13,AMBER)
local fields={
{key='commandDelay', caption='HD command delay (0.4–5)', min=0.4,max=5, x=16,y=218},
{key='commandCooldown',caption='HD batch cooldown (1–20)',min=1,max=20,x=419,y=218},
{key='f3xDelay',caption='F3X request spacing (0.18–3)',min=0.18,max=3,x=16,y=290},
{key='decalCooldown',caption='Single decal cooldown (2–30)',min=2,max=30,x=419,y=290},
{key='batchCooldown',caption='Build fill cooldown (5–60)',min=5,max=60,x=16,y=362},
{key='skyCooldown',caption='Sky dome cooldown (5–60)',min=5,max=60,x=419,y=362},
{key='realmPartDelay',caption='Realm part delay (0.12–3)',min=0.12,max=3,x=16,y=434},
{key='realmCooldown',caption='Realm cooldown (8–90)',min=8,max=90,x=419,y=434},
}
local settingBoxes={}
for _,field in ipairs(fields) do
label(settingsPage,field.caption,field.x,field.y,377,24,14,WHITE)
local input=textbox(settingsPage,'Seconds',field.x,field.y+27,367,30)
input.Text=tostring(config[field.key])
settingBoxes[field.key]=input
end
local function redrawSettings()
for _,field in ipairs(fields) do settingBoxes[field.key].Text=tostring(config[field.key]) end
end
textButton(settingsPage,'SAVE SETTINGS',16,526,185,40,function()
local nextValues={}
for _,field in ipairs(fields) do
local entry=settingBoxes[field.key].Text:gsub('%s+','')
local value=tonumber(entry)
if not value or value~=value or value<field.min or value>field.max or value==math.huge then
return say(field.caption..' must be between '..field.min..' and '..field.max..' seconds.')
end
nextValues[field.key]=value
end
for k,v in pairs(nextValues) do config[k]=v end
redrawSettings()
say('Settings saved for this GUI session. HD delay='..config.commandDelay..'s, F3X delay='..config.f3xDelay..'s.',true)
end)
textButton(settingsPage,'RESET DEFAULTS',218,526,185,40,function()
for k,v in pairs(defaults) do if k~='commandPrefix' then config[k]=v end end
redrawSettings()
say('Timing settings reset to safe defaults.',true)
end)
label(settingsPage,'Timing resets when re-running. Prefix is saved if local file storage exists.',420,523,385,45,12,QUIET)
-- EVENTS TAB: an opt-in, ordered command sequencer for authorized use.
-- Preset 1 is baked into the file and always recoverable even without file APIs.
-- No permission bypass: HD Admin ranks and F3X SyncAPI server checks remain in charge.
-- Each event is finite; it never loops, floods remotes, or deletes an unrelated map.
local HttpService = game:GetService('HttpService')
local EVENT_FILE = 'RetroF3X_Event_Preset1.json'
local EVENT_ACTIONS = {
{id='realm',title='Build Pixel Hall (BTools)',hint='Enclosed avatar-themed hall with wings, roof and animation'},
{id='owner_map',title='Switch to Pixel Hall (BTools)',hint='Same F3X build workflow as MAPS; old presets supported'},
{id='music',title='Music + volume + pitch',hint='selected, song 1–24, or audio ID'},
{id='sparkles',title='Sparkles',hint='Player selector (default me)'},
{id='smoke',title='Smoke',hint='Player selector (default me)'},
{id='fire',title='Fire',hint='Player selector (default me)'},
{id='disco',title='Disco',hint='No argument needed'},
{id='unrank',title='Unrank Others',hint='Requires explicit run confirmation and admin permission'},
{id='bring',title='Bring All',hint='Requires explicit run confirmation and admin permission'},
{id='god',title='God Me',hint='Player selector (default me)'},
{id='sky',title='Pixel BTools Sky Dome',hint='Optional sky image ID; blank = selected Pixel Sky'},
{id='message',title='Announcement ;message',hint='Blank = announcement field above'},
{id='beacon',title='Neon Realm Beacon',hint='Requires an existing BTools Realm'},
{id='army_spawn',title='F3X Clone Army — Spawn',hint='Soldier count (1–24)'},
{id='army_line',title='Army Formation — Line',hint='No argument needed'},
{id='army_v',title='Army Formation — V',hint='No argument needed'},
{id='army_circle',title='Army Formation — Circle',hint='No argument needed'},
{id='army_grid',title='Army Formation — Grid',hint='No argument needed'},
{id='army_shield',title='Army Formation — Shield Wall',hint='No argument needed'},
{id='army_follow',title='Army — Toggle Smooth Follow',hint='F3X motion; turn on or off'},
{id='army_orbit',title='Army — Toggle Orbit',hint='F3X motion; turn on or off'},
{id='army_halt',title='Army — Halt / Guard',hint='Stop automatic motion'},
{id='army_march',title='Army — March',hint='Distance in studs (2–50)'},
{id='army_remove',title='Army — Remove',hint='Removes only soldiers created by this GUI'},
{id='decal1',title='Pixel Decal 1 (BTools)',hint='Applies ID 93544399411478 to selected part or first realm pieces'},
{id='decal2',title='Pixel Decal 2 (BTools)',hint='Applies ID 87626606643966 to selected part or first realm pieces'},
{id='stopdisco',title='Stop Disco',hint='No argument needed'},
{id='unmusic',title='Stop Music',hint='No argument needed'},
}
local EVENT_BY_ID={}
for _,a in ipairs(EVENT_ACTIONS) do EVENT_BY_ID[a.id]=a end
local function makeDefaultEvent()
return {
name='Preset 1 — ITS TIMEEEE', announcement='ITS TIMEEEE',
steps={
{kind='realm',arg='',delay=1.5},
{kind='music',arg='selected',delay=1.5},
{kind='sparkles',arg='all',delay=1.5},
{kind='smoke',arg='all',delay=1.5},
{kind='fire',arg='all',delay=1.5},
{kind='disco',arg='',delay=1.5},
{kind='unrank',arg='',delay=1.5},
{kind='bring',arg='',delay=1.5},
{kind='god',arg='me',delay=1.5},
{kind='sky',arg='',delay=1.5},
{kind='message',arg='',delay=0},
}
}
end
local function cloneEvent(ev)
local c={name=ev.name,announcement=ev.announcement,steps={}}
for _,step in ipairs(ev.steps) do
table.insert(c.steps,{kind=step.kind,arg=step.arg,delay=step.delay})
end
return c
end
local function cleanEvent(raw)
if type(raw)~='table' then return nil end
local cleaned={name=tostring(raw.name or 'Preset 1'):sub(1,45),
announcement=tostring(raw.announcement or 'ITS TIMEEEE'):sub(1,130),steps={}}
if type(raw.steps)~='table' then return nil end
for _,s in ipairs(raw.steps) do
if #cleaned.steps>=40 then break end
if type(s)=='table' and EVENT_BY_ID[s.kind] then
local n=tonumber(s.delay)
if not n or n~=n or n==math.huge then n=1.5 end
n=math.clamp(n,0,60)
table.insert(cleaned.steps,{kind=s.kind,arg=tostring(s.arg or ''):sub(1,140),delay=n})
end
end
if #cleaned.steps==0 then return nil end
return cleaned
end
local eventPreset=makeDefaultEvent()
local fileLoadSuccess=false
if type(readfile)=='function' then
local ok,raw=pcall(function() return readfile(EVENT_FILE) end)
if ok and type(raw)=='string' then
local success,parsed=pcall(function() return HttpService:JSONDecode(raw) end)
local cleaned=success and cleanEvent(parsed)
if cleaned then
local oldKinds={'realm','music','sparkles','smoke','fire','disco','unrank','bring','god','sky','message'}
local oldDefault=(#cleaned.steps==#oldKinds)
if oldDefault then
for i,k in ipairs(oldKinds) do
if cleaned.steps[i].kind~=k then oldDefault=false;break end
end
end
if oldDefault and cleaned.steps[3].arg=='me' and
cleaned.steps[4].arg=='me' and cleaned.steps[5].arg=='me' and
cleaned.steps[9].arg=='me' then
cleaned.steps[3].arg='all'
cleaned.steps[4].arg='all'
cleaned.steps[5].arg='all'
end
eventPreset=cleaned;fileLoadSuccess=true
end
end
end
local eventData=cloneEvent(eventPreset)
local eventRunning=false
local cancelEvent=false
local playerImpactAllowed=false
label(eventPage,'EVENT SEQUENCER • CUSTOM ORDER + DELAYS',14,6,715,27,17,GOLD)
local eventName=textbox(eventPage,'Preset title',14,37,225,30)
eventName.Text=eventData.name
local eventAnnouncement=textbox(eventPage,'Announcement text (;message)',248,37,545,30)
eventAnnouncement.Text=eventData.announcement
local eventStatus=label(eventPage,'Preset 1 ready. Choose a song under MUSIC before running.',14,366,785,21,13,GREEN)
local eventScroller=ui('ScrollingFrame',{
Position=UDim2.fromOffset(14,121),Size=UDim2.fromOffset(785,237),
BackgroundColor3=DARK,BorderSizePixel=0,ScrollBarThickness=8,
ScrollBarImageColor3=GOLD,CanvasSize=UDim2.fromOffset(0,0),
ScrollingDirection=Enum.ScrollingDirection.Y,
},eventPage)
stroke(eventScroller,SOFT)
local eventLayout=ui('UIListLayout',{Padding=UDim.new(0,4),SortOrder=Enum.SortOrder.LayoutOrder},eventScroller)
local eventMenu=ui('Frame',{
Position=UDim2.fromOffset(204,87),Size=UDim2.fromOffset(385,270),
BackgroundColor3=Color3.fromRGB(37,37,42),ZIndex=30,
Visible=false,BorderSizePixel=0,
},eventPage)
stroke(eventMenu,GOLD,2)
local menuScroller=ui('ScrollingFrame',{
Position=UDim2.fromOffset(6,35),Size=UDim2.fromOffset(373,229),
CanvasSize=UDim2.fromOffset(0,#EVENT_ACTIONS*35),ScrollBarThickness=7,
BackgroundTransparency=1,BorderSizePixel=0,ZIndex=31,
},eventMenu)
label(eventMenu,'CHOOSE AN EVENT ACTION',8,4,325,28,14,GOLD).ZIndex=31
textButton(eventMenu,'×',341,2,33,29,function() eventMenu.Visible=false end).ZIndex=32
local menuTarget=nil
local drawEventRows
local function setEventStatus(textValue,good)
eventStatus.Text=tostring(textValue):sub(1,175)
eventStatus.TextColor3=good and GREEN or AMBER
say(textValue,good)
end
for index, action in ipairs(EVENT_ACTIONS) do
local thisAction=action
local btn=textButton(menuScroller,action.title,2,(index-1)*35,347,32,function()
if not eventRunning and menuTarget and eventData.steps[menuTarget] then
eventData.steps[menuTarget].kind=thisAction.id
eventData.steps[menuTarget].arg=(thisAction.id=='music' and 'selected') or
((thisAction.id=='sparkles' or thisAction.id=='smoke' or
thisAction.id=='fire') and 'all') or
(thisAction.id=='god' and 'me') or ''
menuTarget=nil
eventMenu.Visible=false
drawEventRows()
end
end)
btn.ZIndex=32
btn.TextSize=13
end
local function formatActionArg(kind)
if kind=='music' then return 'selected / song 1-24 / audio ID' end
if kind=='message' then return 'Leave blank to use announcement text' end
if kind=='sky' then return 'Pixel Sky ID (blank = selected)' end
if kind=='sparkles' or kind=='smoke' or kind=='fire' then return 'all / player selector' end
if kind=='god' then return 'me / player selector' end
if kind=='decal1' or kind=='decal2' then return 'realm / selected' end
return '(no parameter)'
end
local function changeStepPosition(i,offset)
if eventRunning then return end
local j=i+offset
if j<1 or j>#eventData.steps then return end
eventData.steps[i],eventData.steps[j]=eventData.steps[j],eventData.steps[i]
drawEventRows()
end
drawEventRows=function()
for _,child in ipairs(eventScroller:GetChildren()) do
if child:IsA('Frame') then child:Destroy() end
end
for index, step in ipairs(eventData.steps) do
local i=index
local rowFrame=ui('Frame',{
LayoutOrder=i,Size=UDim2.fromOffset(765,49),BackgroundColor3=PANEL,
BorderSizePixel=0,
},eventScroller)
label(rowFrame,tostring(i)..'.',5,11,25,22,13,GOLD)
local actionBtn=textButton(rowFrame,EVENT_BY_ID[step.kind].title,29,4,235,41,function()
if eventRunning then return end
menuTarget=i
eventMenu.Visible=true
end)
actionBtn.TextSize=12
local argBox=textbox(rowFrame,formatActionArg(step.kind),273,4,240,41)
argBox.Text=step.arg or ''
argBox.TextSize=12
argBox.FocusLost:Connect(function()
if eventData.steps[i]==step then step.arg=argBox.Text:sub(1,140) end
end)
local dBox=textbox(rowFrame,'s',520,4,66,41)
dBox.Text=tostring(step.delay)
dBox.TextSize=12
dBox.FocusLost:Connect(function()
if eventData.steps[i]~=step then return end
local n=tonumber(dBox.Text)
if n and n==n and n>=0 and n<=60 then step.delay=n
else dBox.Text=tostring(step.delay) end
end)
local up=textButton(rowFrame,'↑',593,6,36,37,function() changeStepPosition(i,-1) end)
local down=textButton(rowFrame,'↓',636,6,36,37,function() changeStepPosition(i,1) end)
local del=textButton(rowFrame,'×',679,6,35,37,function()
if eventRunning then return end
if #eventData.steps==1 then return setEventStatus('Keep at least one action in the event.') end
table.remove(eventData.steps,i)
drawEventRows()
end)
up.TextSize=16; down.TextSize=16; del.TextSize=16
end
eventScroller.CanvasSize=UDim2.fromOffset(0,#eventData.steps*53+6)
end
local function readEventForm()
local current=cloneEvent(eventData)
current.name=eventName.Text:sub(1,45)
current.announcement=eventAnnouncement.Text:sub(1,130)
for i, rowFrame in ipairs((function()
local rows={}
for _,child in ipairs(eventScroller:GetChildren()) do
if child:IsA('Frame') then table.insert(rows,child) end
end
table.sort(rows,function(a,b) return a.LayoutOrder<b.LayoutOrder end)
return rows
end)()) do
local inputs={}
for _,child in ipairs(rowFrame:GetChildren()) do
if child:IsA('TextBox') then table.insert(inputs,child) end
end
table.sort(inputs,function(a,b) return a.Position.X.Offset<b.Position.X.Offset end)
local arg=inputs[1] and inputs[1].Text or ''
local delay=tonumber(inputs[2] and inputs[2].Text or '')
if not delay or delay~=delay or delay<0 or delay>60 then
return nil,'Step '..i..': delay must be from 0 to 60 seconds.'
end
current.steps[i].arg=arg:sub(1,140)
current.steps[i].delay=delay
end
return cleanEvent(current)
end
local function refreshEventFields()
eventName.Text=eventData.name
eventAnnouncement.Text=eventData.announcement
eventMenu.Visible=false
drawEventRows()
end
local function saveEvent()
if eventRunning then return setEventStatus('Stop the active event before saving.') end
local nextEvent,err=readEventForm()
if not nextEvent then return setEventStatus(err or 'Invalid event setup.') end
eventData=nextEvent
eventPreset=cloneEvent(nextEvent)
local persisted=false
if type(writefile)=='function' then
local ok=pcall(function()
writefile(EVENT_FILE,HttpService:JSONEncode(eventPreset))
end)
persisted=ok
end
setEventStatus(persisted and 'Preset 1 saved locally; loads next run.' or
'Preset 1 saved for this session. Your executor does not provide local file saving.',true)
drawEventRows()
end
local function waitJobClear(field,timeoutSeconds)
local start=os.clock()
while field() and gui.Parent and not cancelEvent do
if os.clock()-start>timeoutSeconds then return false,'Timed out waiting for F3X.' end
task.wait(0.2)
end
if cancelEvent or not gui.Parent then return false,'Event canceled.' end
return true
end
local function eventCommand(cmd)
if not gui.Parent or cancelEvent then return false,'Canceled.' end
local ok,detail=submitHDAdminCommand(cmd)
if not ok then return false,tostring(detail) end
return true
end
local function runEventStep(step,announcement)
local kind=step.kind
local arg=tostring(step.arg or ''):gsub('^%s+',''):gsub('%s+$','')
if kind=='realm' then
if #liveRealmParts()>0 then return true,'Existing F3X realm reused' end
local _,endpoint,problem=detectBuildTool()
if not endpoint then return false,problem or 'F3X not equipped' end
createRealm('PIXEL')
local ok,err=waitJobClear(function() return realmBusy end,180)
if not ok then return false,err end
if not lastRealmResult.ok then return false,'BTools Realm failed: '..tostring(lastRealmResult.detail) end
return true,'BTools Pixel Realm created'
elseif kind=='owner_map' then
local _,endpoint,problem=detectBuildTool()
if not endpoint then return false,problem or 'F3X/BTools not available' end
local started=createRealm('PIXEL',true,{replace=true,quality=2,label='Pixel Hall'})
if not started then return false,'BTools map could not start; check the GUI status' end
local ok,err=waitJobClear(function() return realmBusy end,180)
if not ok then return false,err end
return lastRealmResult.ok,'Pixel Hall: '..tostring(lastRealmResult.detail)
elseif kind=='sky' then
local sky=normalizeAssetId(arg) or selectedRealmSky
if f3xSkyPart and f3xSkyPart.Parent then return true,'Existing sky dome reused' end
local _,endpoint,problem=detectBuildTool()
if not endpoint then return false,problem or 'F3X not equipped' end
skyIdBox.Text=sky
createF3XSkyDome()
local ok,err=waitJobClear(function() return busy end,90)
if not ok then return false,err end
if not (f3xSkyPart and f3xSkyPart.Parent) then return false,'Sky dome not confirmed.' end
return true,'F3X sky creation requested'
elseif kind=='beacon' then
createBeacon()
return waitJobClear(function() return realmBusy end,90)
elseif kind=='decal1' or kind=='decal2' then
local id=(kind=='decal1' and '93544399411478' or '87626606643966')
local _,endpoint,problem=detectBuildTool()
if not endpoint then return false,problem or 'BTools not available' end
local parts={}
if arg=='selected' then
if selectedPart and selectedPart:IsA('BasePart') and selectedPart:IsDescendantOf(workspace) and not isPlayerCharacterPart(selectedPart) then
parts={selectedPart}
end
else
for _,part in ipairs(liveRealmParts()) do
table.insert(parts,part)
if #parts>=6 then break end
end
end
if #parts==0 then return false,'Select a part or build Pixel Realm first.' end
local ok,attempted,confirmed=pcall(function()
return applyServerF3XDecals(parts,FACES,id,endpoint)
end)
if not ok then return false,'BTools decal error: '..tostring(attempted) end
if confirmed==0 then return false,'Decal not confirmed on any parts.' end
return true,'BTools decal '..id..' applied ('..confirmed..'/'..attempted..' faces)'
elseif kind=='army_spawn' then
return armyActions.spawn(tonumber(arg) or 4)
elseif kind=='army_line' or kind=='army_v' or kind=='army_circle' or kind=='army_grid' or kind=='army_shield' then
return armyActions.formation((kind=='army_line' and 'line') or (kind=='army_v' and 'v') or (kind=='army_circle' and 'circle') or (kind=='army_shield' and 'shield') or 'grid')
elseif kind=='army_follow' then
return armyActions.follow()
elseif kind=='army_orbit' then
return armyActions.orbit()
elseif kind=='army_halt' then
return armyActions.halt()
elseif kind=='army_march' then
return armyActions.march(tonumber(arg) or 15)
elseif kind=='army_remove' then
return armyActions.remove()
elseif kind=='music' then
local id
local pitch
if arg=='' or string.lower(arg)=='selected' then
id=songIds[selectedSong];pitch=selectedPitch(selectedSong)
else
local n=tonumber(arg)
if n and n>=1 and n<=#SONGS and n%1==0 then
id=songIds[n];pitch=selectedPitch(n)
else
id=normalizeAssetId(arg);pitch=1
end
end
if not id then return false,'No audio ID for selected song.' end
local commands={';music '..id,';volume inf',';pitch '..tostring(pitch)}
for i,cmd in ipairs(commands) do
local ok,err=eventCommand(cmd)
if not ok then return false,err end
if i<#commands then task.wait(config.commandDelay) end
end
return true,'Music submitted ('..id..')'
else
local command
if kind=='sparkles' or kind=='smoke' or kind=='fire' or kind=='god' then
local target=(arg~='' and arg or 'me')
if not target:match('^[%w_,]+$') then return false,'Invalid player selector.' end
command=';'..kind..' '..target
elseif kind=='disco' then command=';disco'
elseif kind=='stopdisco' then command=';undisco'
elseif kind=='unrank' then command=';unrank others'
elseif kind=='bring' then command=';bring all'
elseif kind=='unmusic' then command=';unmusic'
elseif kind=='message' then
local txt=(arg~='' and arg or announcement)
if txt=='' then return false,'Announcement is blank.' end
txt=txt:gsub('[\r\n]',' '):sub(1,130)
command=';message '..txt
end
if not command then return false,'Unknown event action.' end
return eventCommand(command)
end
end
local function eventNeedsImpactConfirmation(ev)
for _,step in ipairs(ev.steps) do
if step.kind=='unrank' or step.kind=='bring' then return true end
if (step.kind=='sparkles' or step.kind=='smoke' or step.kind=='fire') and
string.lower(tostring(step.arg or ''))=='all' then return true end
end
return false
end
local function executeEvent()
if eventRunning or commandBusy or busy or realmBusy then
return setEventStatus('A music, F3X, or event operation is already running.')
end
local snapshot,err=readEventForm()
if not snapshot then return setEventStatus(err or 'Invalid event configuration.') end
if eventNeedsImpactConfirmation(snapshot) and not playerImpactAllowed then
return setEventStatus('Confirm the player-impact checkbox before server-wide effects / player commands.')
end
eventRunning=true;commandBusy=true;cancelEvent=false
playerImpactAllowed=false
local steps=snapshot.steps
setEventStatus('Starting '..snapshot.name..' ('..#steps..' steps)...',true)
task.spawn(function()
local problems={}
for index,step in ipairs(steps) do
if cancelEvent or not gui.Parent then break end
local action=EVENT_BY_ID[step.kind]
local statusName=action and action.title or step.kind
setEventStatus('Event '..index..'/'..#steps..': '..statusName,true)
local ok,result=pcall(function() return runEventStep(step,snapshot.announcement) end)
if not ok then
table.insert(problems,index..': '..tostring(result):sub(1,65))
elseif result==false then
table.insert(problems,index..': action denied / unavailable')
end
if index<#steps and not cancelEvent then
-- Each step has its own configurable delay before the next.
local duration=step.delay
local elapsed=0
while elapsed<duration and not cancelEvent and gui.Parent do
local slice=math.min(0.15,duration-elapsed)
task.wait(slice)
elapsed=elapsed+slice
end
end
end
commandBusy=false;eventRunning=false
if gui.Parent then
if cancelEvent then setEventStatus('Event canceled. Effects already submitted are not undone.')
elseif #problems>0 then
setEventStatus('Event finished with '..#problems..' possible issues. Check HD Admin/F3X permissions.')
warn('[RetroF3X EVENTS] '..table.concat(problems,' | '))
else setEventStatus('Event commands submitted! Server still decides which effects applied.',true) end
end
end)
end
textButton(eventPage,'LOAD PRESET 1',14,77,146,36,function()
if eventRunning then return end
eventData=cloneEvent(eventPreset)
refreshEventFields()
setEventStatus('Loaded Preset 1. You can edit, reorder, add, or remove steps.',true)
end)
textButton(eventPage,'SAVE PRESET 1',169,77,146,36,saveEvent)
textButton(eventPage,'+ ADD STEP',323,77,128,36,function()
if eventRunning then return end
if #eventData.steps>=40 then return setEventStatus('Limit: 40 actions per event.') end
table.insert(eventData.steps,{kind='disco',arg='',delay=1.5})
drawEventRows()
eventScroller.CanvasPosition=Vector2.new(0,math.max(0,eventScroller.CanvasSize.Y.Offset-237))
menuTarget=#eventData.steps
eventMenu.Visible=true
end)
textButton(eventPage,'▶ EXECUTE',458,77,157,36,executeEvent)
textButton(eventPage,'■ CANCEL',624,77,169,36,function()
if not eventRunning then return setEventStatus('No event is running.') end
cancelEvent=true
setEventStatus('Cancel requested. In-flight server commands cannot be recalled.')
end)
local impactCheck
impactCheck=textButton(eventPage,'☐ CONFIRM ALL-PLAYER ACTIONS',14,391,300,32,function()
playerImpactAllowed=not playerImpactAllowed
impactCheck.Text=playerImpactAllowed and '☑ ALL-PLAYER ACTIONS CONFIRMED' or '☐ CONFIRM ALL-PLAYER ACTIONS'
end)
impactCheck.TextSize=12
label(eventPage,'Only for events you administer. ↑ ↓ reorder • delay is seconds AFTER each step.',324,391,470,34,12,AMBER)
-- Quick access through the Scripts tab, using the exact same event runner.
textButton(scriptsPage,'▶ RUN PRESET 1',16,345,192,35,function()
activateTab('events')
executeEvent()
end)
textButton(scriptsPage,'EDIT EVENT STEPS',215,345,188,35,function()
activateTab('events')
setEventStatus('Edit actions, arguments, and per-step delays here.',true)
end)
refreshEventFields()
if fileLoadSuccess then setEventStatus('Restored your saved event preset from local storage.',true) end
-- Select part without clicking behind the GUI.
UIS.InputBegan:Connect(function(input,processed)
if not gui.Parent or not pickMode then return end
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
local pointer=UIS:GetMouseLocation()
local p=root.AbsolutePosition
local s=root.AbsoluteSize
if pointer.X>=p.X and pointer.X<=p.X+s.X and pointer.Y>=p.Y and pointer.Y<=p.Y+s.Y then return end
if processed then return end
pickMode=false
pickButton.Text='PICK PART IN WORLD'
local target=mouse.Target
if target and target:IsA('BasePart') and target:IsDescendantOf(workspace) then
updateSelected(target)
else
say('Click a visible BasePart to select it.')
end
end)
-- Drag title bar.
local dragging=false
local dragStart
local frameStart
bar.InputBegan:Connect(function(input)
if input.UserInputType==Enum.UserInputType.MouseButton1 then
dragging=true
dragStart=input.Position
frameStart=root.Position
end
end)
UIS.InputChanged:Connect(function(input)
if not dragging or input.UserInputType~=Enum.UserInputType.MouseMovement then return end
local delta=input.Position-dragStart
root.Position=UDim2.new(frameStart.X.Scale,frameStart.X.Offset+delta.X,
frameStart.Y.Scale,frameStart.Y.Offset+delta.Y)
end)
UIS.InputEnded:Connect(function(input)
if input.UserInputType==Enum.UserInputType.MouseButton1 then dragging=false end
end)
minimize.MouseButton1Click:Connect(function()
root.Visible=false
end)
toggle.MouseButton1Click:Connect(function()
root.Visible=not root.Visible
end)
close.MouseButton1Click:Connect(function()
outlineSelection:Destroy()
gui:Destroy()
end)
activateTab('events')
say('RetroF3X v1.14.3 loaded. Requesting '..config.commandPrefix..'btools...',true)
-- Send the BTools command once when the GUI starts. The centralized HD Admin
-- gateway applies the user's saved command prefix, e.g. ;btools or :btools.
-- The game still determines whether the player may receive building tools.
task.defer(function()
task.wait(0.75) -- let chat / HD Admin initialize; don't delay the GUI itself
if not gui.Parent then return end
local ok, detail=submitHDAdminCommand(';btools')
if not gui.Parent then return end
if ok then
say('Requested '..config.commandPrefix..'btools ('..tostring(detail)..'). Check your Backpack; server permissions apply.',true)
else
say('Auto BTools request failed: '..tostring(detail):sub(1,125))
end
end)works on any game where you have btools and admin. i had ai help with features. ai did not code this what so ever.

Universal