-- RetroF3X edition v1.14.6 (Map-wide F3X Decals) -- F3X/BTools texture edits use CreateTextures and SyncTexture on the tool endpoint. -- No bypass of the game server's build permissions or rate limits. -- One-file local GUI: no require(), no HTTP, no third-party loaders. -- Pixel Realm 2: enclosed avatar-themed hall with F3X-synced animated accents and grouped updates. -- The game's server decides which part edits are allowed. -- Decal tools support one part, a selected build, or all editable Workspace parts with capped, cancellable batches. -- Skybox: optional local Lighting preview OR scalable F3X textured-mesh dome. -- F3X Realms: track and remove only previously created RetroF3X scenery in the same server. -- Map Switcher v1.14.1: uses the SAME F3X build workflow as REALMS; no server map manager. -- Music: configurable HD Admin prefix, e.g. ;music or :music, followed by volume and pitch. -- Server decides whether music commands and F3X edits are allowed. -- Configurable delays never bypass server rate limits. local Players = game:GetService('Players') local UIS = game:GetService('UserInputService') local Lighting = game:GetService('Lighting') local SoundService = game:GetService('SoundService') local ReplicatedStorage = game:GetService('ReplicatedStorage') local TextChatService = game:GetService('TextChatService') local player = Players.LocalPlayer if not player then return end local mouse = player:GetMouse() local guiParent = player:WaitForChild('PlayerGui') local oldSphere = guiParent:FindFirstChild('RetroF3X_BarrierRadiusSphere') if oldSphere then oldSphere:Destroy() end local oldGui = guiParent:FindFirstChild('RetroF3X') if oldGui then oldGui:Destroy() end local oldOutline = guiParent:FindFirstChild('RetroF3X_PartSelection') if oldOutline then oldOutline:Destroy() end local oldSound = SoundService:FindFirstChild('RetroF3X_LocalMusic') if oldSound then oldSound:Destroy() end local GOLD = Color3.fromRGB(233,160,195) -- warm rose accent; no bright-yellow GUI local DARK = Color3.fromRGB(43,43,47) local PANEL = Color3.fromRGB(59,59,64) local SOFT = Color3.fromRGB(82,82,87) local WHITE = Color3.fromRGB(241,241,241) local QUIET = Color3.fromRGB(180,180,180) local GREEN = Color3.fromRGB(169,244,169) local AMBER = Color3.fromRGB(235,192,216) local SONGS = { 'theme', 'hood trap beat', 'realm music', 'khmer jump 2', 'jumpsyle', 'wont stop us', 'spooky scary skeletons', 'windows sparta remix', 'die in a fire', 'hard trance', 'bomb hard', '1x1x1x1', 'moskau', 'rockefeller', 'dubstep gun', 'daisy bell', 'the perfect girl', 'creepy music', 'mario music', 'awp', 'can-can', 'subway surfers', 'can-can remix', 'kiss me again', } local FACES = { Enum.NormalId.Front, Enum.NormalId.Back, Enum.NormalId.Left, Enum.NormalId.Right, Enum.NormalId.Top, Enum.NormalId.Bottom, } local SKY_FACES = {'SkyboxBk','SkyboxDn','SkyboxFt','SkyboxLf','SkyboxRt','SkyboxUp'} local songIds = { [1] = '111825153133704', [2] = '81231974674893', [4] = '104879348278763', -- Khmer Jump 2: exact title KHMER JUMPSTYLE 2 [5] = '1839246711', -- Jumpsyle: alternative Jumpstyle by Kosinus [6] = '1847661821', [7] = '1836769525', [9] = '74786603084060', [10] = '1839272156', [13] = '135055100', [14] = '16831104459', [15] = '130768080', -- Dubstep Gun: Saints Row 4 SFX, public accessibility uncertain [16] = '1845760243', -- Daisy Bell: alternative version [17] = '114258326354246', [21] = '1848029226', -- Can-Can: alternate catalog recording [22] = '6113797797', [23] = '1840443760', [24] = '111820058348556', -- Kiss Me Again: alternate song-title match [12] = '139488665764275', [18] = '134959834418523', -- Creepy Music: liminal-space ambient ALTERNATIVE [19] = '105364251729575', -- Mario Music: Super Mario Bros ALTERNATIVE } local songPitches = { [1] = 0.9, [17] = 0.8 } -- Session-only timing settings, configured in the SETTINGS tab. -- Minimums deliberately prevent the GUI from flooding remote requests. local defaults = { commandPrefix = ';', -- HD Admin's default prefix; may be ':' in some games commandDelay = 0.5, -- seconds between HD Admin commands in a sequence commandCooldown = 1.25, -- cooldown after starting an HD Admin command batch f3xDelay = 0.20, -- seconds between F3X requests in supported operations decalCooldown = 2, -- seconds between one-part decal operations batchCooldown = 5, -- seconds between build fills realmPartDelay = 0.18, -- moderate build pace; fewer requests + grouped styling realmCooldown = 8, -- seconds between realm creations skyCooldown = 5, -- seconds between sky dome attempts } local config = {} for key, value in pairs(defaults) do config[key] = value end -- Optional persistence, only when the runtime supplies its local file APIs. local PREFIX_FILE='RetroF3X_HDAdmin_Prefix.txt' if type(readfile)=='function' then local ok, stored=pcall(function() return readfile(PREFIX_FILE) end) if ok and type(stored)=='string' then local candidate=stored:gsub('%s+','') if #candidate==1 and candidate:match('^[;:!.]$') then config.commandPrefix=candidate end end end local selectedSong = 1 local selectedPart = nil local pickMode = false local busy = false local lastEdit = 0 local faceIndex = 1 local savedSkies = nil local localSky = nil local f3xSkyPart = nil -- Keep track of parts made by this GUI across repeated script executions within -- the SAME Roblox server. Without this, running the GUI again leaves old builds -- behind because all the local Lua variables are reset. local env = _G if type(getgenv)=='function' then local ok, value=pcall(getgenv) if ok and type(value)=='table' then env=value end end local mapSessionKey=tostring(game.PlaceId)..':'..tostring(game.JobId) local realmRegistry=env.__RetroF3X_OwnRealmParts if type(realmRegistry)~='table' or realmRegistry.sessionKey~=mapSessionKey then realmRegistry={sessionKey=mapSessionKey, parts={}, origin=nil} env.__RetroF3X_OwnRealmParts=realmRegistry end if type(realmRegistry.parts)~='table' then realmRegistry.parts={} end local realmParts = realmRegistry.parts local realmBusy = false local realmAnimation = {active=false,serial=0,objects={},theme=nil} local startRealmAnimation local stopRealmAnimation local armyActions = {} -- F3X army controls, shared with the Event Builder local lastRealmResult = {ok=false, detail='Not run yet'} local lastRealmBuild = -999 local lastSkyEdit = -999 local lastBatchEdit = -999 local rememberedLightingSkyState = nil local outlineSelection = Instance.new('SelectionBox') outlineSelection.Name = 'RetroF3X_PartSelection' outlineSelection.LineThickness = .06 outlineSelection.Color3 = GOLD outlineSelection.SurfaceTransparency = .86 outlineSelection.Adornee = nil outlineSelection.Parent = guiParent local function ui(className, properties, parent) local object = Instance.new(className) for key, value in pairs(properties or {}) do object[key] = value end object.Parent = parent return object end local function stroke(object, color, width) return ui('UIStroke', {Color=color or GOLD,Thickness=width or 1},object) end local function textButton(parent, label, x, y, width, height, onClick) local button = ui('TextButton', { Position=UDim2.fromOffset(x,y), Size=UDim2.fromOffset(width,height), Text=label, BackgroundColor3=DARK, BorderSizePixel=0, TextColor3=WHITE, TextSize=15, TextWrapped=true, Font=Enum.Font.GothamBold, AutoButtonColor=true, },parent) stroke(button) if onClick then button.MouseButton1Click:Connect(onClick) end return button end local function textbox(parent, placeholder, x, y, width, height) local box = ui('TextBox', { Position=UDim2.fromOffset(x,y), Size=UDim2.fromOffset(width,height), BackgroundColor3=PANEL, BorderSizePixel=0, PlaceholderText=placeholder, PlaceholderColor3=QUIET, ClearTextOnFocus=false, Text='', TextColor3=WHITE, Font=Enum.Font.Gotham, TextSize=14, },parent) stroke(box) return box end local function label(parent, s, x, y, width, height, fontSize, color) return ui('TextLabel', { Position=UDim2.fromOffset(x,y),Size=UDim2.fromOffset(width,height), BackgroundTransparency=1,Text=s, TextColor3=color or WHITE, TextSize=fontSize or 14,Font=Enum.Font.Gotham, TextXAlignment=Enum.TextXAlignment.Left, TextWrapped=true, }, parent) end local function normalizeAssetId(raw) local str = tostring(raw or ''):gsub('%s+','') if str == '' then return nil end local digits = str:match('^rbxassetid://(%d+)$') or str:match('[?&]id=(%d+)') or str:match('^(%d+)$') if not digits or #digits > 17 or tonumber(digits) == 0 then return nil end return digits end local gui = ui('ScreenGui',{ Name='RetroF3X',ResetOnSpawn=false,IgnoreGuiInset=false, DisplayOrder=30,ZIndexBehavior=Enum.ZIndexBehavior.Sibling, },guiParent) local toggle = textButton(gui,'F3X GUI',12,0,83,28) toggle.Position=UDim2.new(0,14,1,-46) local WINDOW_W, WINDOW_H = 832, 572 local root = ui('Frame',{ Name='Main',Size=UDim2.fromOffset(WINDOW_W,WINDOW_H), Position=UDim2.new(.5,-WINDOW_W/2,.5,-WINDOW_H/2), BackgroundColor3=DARK,BorderSizePixel=0,Active=true, },gui) stroke(root,GOLD,2) -- Fit within smaller displays while preserving a large, legible layout. local fit = ui('UIScale', {Scale=1}, root) local function updateFit() local camera = workspace.CurrentCamera if not camera then return end local viewport = camera.ViewportSize fit.Scale = math.min(1, math.max(.55, math.min((viewport.X - 32)/WINDOW_W, (viewport.Y - 75)/WINDOW_H))) end updateFit() if workspace.CurrentCamera then workspace.CurrentCamera:GetPropertyChangedSignal('ViewportSize'):Connect(updateFit) end local bar = ui('Frame',{ Size=UDim2.new(1,0,0,39),BackgroundColor3=SOFT,BorderSizePixel=0, },root) label(bar,'RetroF3X | Map Decals v1.14.6',15,6,695,28,19,GOLD) local minimize=textButton(bar,'−',747,4,34,30) local close=textButton(bar,'×',787,4,34,30) local tabRow = ui('Frame',{ Size=UDim2.new(1,-18,0,36),Position=UDim2.fromOffset(9,44), BackgroundTransparency=1, },root) local content = ui('Frame',{ Size=UDim2.new(1,-18,0,435),Position=UDim2.fromOffset(9,87), BackgroundColor3=PANEL,BorderSizePixel=0, },root) stroke(content,GOLD,1) local status = label(root,'Ready. Previously tracked realm parts: '..#realmParts..'. Open MAPS to switch/remove.',13,531,802,34,14,AMBER) local function say(s, good) if not status.Parent then return end status.Text = tostring(s) status.TextColor3 = good and GREEN or AMBER end stopRealmAnimation=function(silent) realmAnimation.active=false realmAnimation.serial=realmAnimation.serial+1 if not silent and gui.Parent then say('Realm animation stopped.',true) end end local pages={} local tabButtons={} local function activateTab(tab) for name, page in pairs(pages) do page.Visible=(name==tab) end for name, button in pairs(tabButtons) do button.BackgroundColor3=(name==tab) and Color3.fromRGB(104,65,91) or DARK button.TextColor3=(name==tab) and GOLD or WHITE end end local tabCount = 0 local function addTab(name, caption) local index = tabCount tabCount = tabCount + 1 tabButtons[name] = textButton(tabRow,caption,index*101,0,97,34,function() activateTab(name) end) tabButtons[name].TextSize = 13 -- The Scripts tab is scrollable so extra modules don't cover old shortcuts. if name=='scripts' or name=='settings' or name=='decals' then pages[name]=ui('ScrollingFrame',{ Size=UDim2.fromScale(1,1),BackgroundTransparency=1,Visible=false, BorderSizePixel=0,ScrollBarThickness=8,ScrollBarImageColor3=GOLD, CanvasSize=UDim2.fromOffset(0,name=='settings' and 650 or (name=='decals' and 790 or 2040)),ScrollingDirection=Enum.ScrollingDirection.Y, },content) else pages[name]=ui('Frame',{ Size=UDim2.fromScale(1,1),BackgroundTransparency=1,Visible=false, },content) end return pages[name] end local musicPage=addTab('music','MUSIC') local decalsPage=addTab('decals','F3X DECALS') local skyPage=addTab('sky','SKY') local realmPage=addTab('realm','REALMS') local mapPage=addTab('maps','MAPS') local scriptsPage=addTab('scripts','SCRIPTS') local settingsPage=addTab('settings','SETTINGS') local eventPage=addTab('events','EVENTS') -- MUSIC TAB: HD Admin commands, with optional per-song pitch. -- local grid = ui('Frame',{ Size=UDim2.new(1,-18,0,307),Position=UDim2.fromOffset(9,9), BackgroundTransparency=1, },musicPage) ui('UIGridLayout',{ CellSize=UDim2.new(.25,-6,0,46), CellPadding=UDim2.fromOffset(6,5),SortOrder=Enum.SortOrder.LayoutOrder, },grid) local songButtons = {} local presetCount = 0 for _, id in pairs(songIds) do if id then presetCount=presetCount+1 end end local function displayName(s) return (s:gsub('(%a)([%a]*)',function(a,b) return a:upper()..b end)) end local nowPlaying = label(musicPage,'Selected: Theme',15,322,505,27,19,GOLD) local hint = label(musicPage,'Choose an ID-backed song: '..config.commandPrefix..'music → '..config.commandPrefix..'volume inf → '..config.commandPrefix..'pitch.',15,350,777,23,14,WHITE) -- Prefer the HD Admin command signal when available; otherwise use ordinary -- player chat. Neither path bypasses server-side rank/permission checks. local requestCooldown = 0 local commandBusy = false -- Every HD Admin action (music, Scripts, Events, Barrier Kick, Loopkick, -- etc.) uses this single command gateway. Change only the leading prefix; -- never alter IDs, usernames, messages or other command arguments. local function withHDAdminPrefix(command) local raw=tostring(command or '') if raw:sub(1,1)==';' or raw:sub(1,1)==':' then return config.commandPrefix..raw:sub(2) end return raw end local function submitHDAdminCommand(command) command=withHDAdminPrefix(command) local hd = ReplicatedStorage:FindFirstChild('HDAdminHDClient') local signals = hd and hd:FindFirstChild('Signals') local request = signals and signals:FindFirstChild('RequestCommandSilent') if request and request:IsA('RemoteFunction') then local ok, result = pcall(function() return request:InvokeServer(command) end) if not ok then return false, tostring(result) end if result == false then return false, 'The server returned false (command may be denied).' end return true, 'HD Admin remote' end local inputBar = TextChatService:FindFirstChild('ChatInputBarConfiguration') local channel = inputBar and inputBar.TargetTextChannel if not channel then local channels = TextChatService:FindFirstChild('TextChannels') channel = channels and channels:FindFirstChild('RBXGeneral') end if channel and channel:IsA('TextChannel') then local ok, err = pcall(function() channel:SendAsync(command) end) if ok then return true, 'Roblox chat' end return false, tostring(err) end return false, 'No HD Admin command signal or usable Roblox text channel found.' end local function selectedPitch(index) local n = tonumber(songPitches[index]) if n and n >= 0.05 and n <= 4 then return n end return 1 end local pitchBox -- initialized after the song buttons local function tryPlay(songIndex) local id = songIds[songIndex] if not id then hint.Text='NO ID FOUND for this recording. Add an ID under CUSTOM ID.' say('No researched ID for '..SONGS[songIndex]..'. Use CUSTOM ID to supply one.') return end if commandBusy or os.clock() - requestCooldown < config.commandCooldown then return say('Wait a moment before requesting another song (anti-spam cooldown).') end requestCooldown=os.clock() commandBusy=true local pitch = selectedPitch(songIndex) hint.Text='Sending '..config.commandPrefix..'music '..id..' → '..config.commandPrefix..'volume inf → '..config.commandPrefix..'pitch '..tostring(pitch) say('Submitting HD Admin music, volume and pitch (requires admin permission)...',true) task.spawn(function() -- Send commands separately to support HD Admin and chat fallback. local steps = { ';music '..id, ';volume inf', ';pitch '..tostring(pitch), } local errors={} for i,cmd in ipairs(steps) do if not gui.Parent then break end local ok, detail=submitHDAdminCommand(cmd) if not ok then table.insert(errors, cmd..': '..tostring(detail):sub(1,55)) end if i<#steps then task.wait(config.commandDelay) end -- user-set pace; server limit still applies end if gui.Parent then if #errors==0 then hint.Text='Requested '..displayName(SONGS[songIndex])..' | volume inf | pitch '..tostring(pitch) say('Sent '..config.commandPrefix..'music, '..config.commandPrefix..'volume inf and '..config.commandPrefix..'pitch. Server may limit volume or audio access.',true) else hint.Text='HD Admin: '..table.concat(errors,'; '):sub(1,114) say('One or more HD Admin commands failed; check rank, rate limits, and console.') end end commandBusy=false end) end local function updateSongButtons() for index,button in ipairs(songButtons) do local ready = songIds[index] ~= nil if selectedSong==index then button.BackgroundColor3=Color3.fromRGB(134,103,29) button.TextColor3=WHITE else button.BackgroundColor3=ready and Color3.fromRGB(48,48,52) or Color3.fromRGB(74,74,78) button.TextColor3=ready and WHITE or Color3.fromRGB(216,216,216) end button.Text=displayName(SONGS[index])..(ready and ' ✓' or ' · No ID') end nowPlaying.Text='Selected: '..displayName(SONGS[selectedSong]) if pitchBox then pitchBox.Text=tostring(songPitches[selectedSong] or 1) end end for index, songName in ipairs(SONGS) do local button=ui('TextButton',{ LayoutOrder=index,Text='',TextSize=15, TextWrapped=true,TextColor3=WHITE,Font=Enum.Font.GothamBold, BorderSizePixel=0,BackgroundColor3=DARK, AutoButtonColor=true, },grid) stroke(button, songIds[index] and GOLD or Color3.fromRGB(100,100,100), 1) table.insert(songButtons,button) button.MouseButton1Click:Connect(function() selectedSong=index updateSongButtons() tryPlay(index) end) end local playAgain=textButton(musicPage,'▶ PLAY SELECTED',15,382,185,36,function() tryPlay(selectedSong) end) textButton(musicPage,'■ STOP',210,382,119,36,function() if commandBusy or os.clock()-requestCooldown < config.commandCooldown then return say('Wait a moment before stopping music (anti-spam cooldown).') end requestCooldown=os.clock() commandBusy=true task.spawn(function() local ok,detail=submitHDAdminCommand(';unmusic') if gui.Parent then hint.Text=ok and ('Requested '..config.commandPrefix..'unmusic (HD Admin)') or ('Stop failed: '..tostring(detail):sub(1,100)) say(ok and 'HD Admin stop command submitted.' or ('Stop failed: '..tostring(detail)),ok) end commandBusy=false end) end) local customField = textbox(musicPage,'Optional Roblox audio ID',477,383,205,34) customField.Visible=false local saveCustom saveCustom=textButton(musicPage,'SAVE + PLAY',691,382,117,36,function() local id=normalizeAssetId(customField.Text) if not id then return say('Enter a valid Roblox audio ID in the custom box.') end songIds[selectedSong]=id customField.Text='' updateSongButtons() local button=songButtons[selectedSong] if button then for _,outline in ipairs(button:GetChildren()) do if outline:IsA('UIStroke') then outline.Color=GOLD end end end tryPlay(selectedSong) end) saveCustom.Visible=false -- Diagnostic: issue only ;music so users can isolate command/permission/audio -- failures from pitch or ;volume inf. This is not a local audio player. local testMusicButton=textButton(musicPage,'TEST '..config.commandPrefix..'music ONLY',477,382,205,36,function() local id=songIds[selectedSong] if not id then return say('No preset ID for this song. Supply a custom ID first.') end if commandBusy or os.clock()-requestCooldown < config.commandCooldown then return say('Wait for the command cooldown before testing another song.') end requestCooldown=os.clock() commandBusy=true hint.Text='Testing ONLY '..config.commandPrefix..'music '..id..' (no volume or pitch)' say('Testing the HD Admin music command alone...',true) task.spawn(function() local ok,detail=submitHDAdminCommand(';music '..id) if gui.Parent then if ok then hint.Text='Sent '..config.commandPrefix..'music '..id..' via '..tostring(detail)..'. Check whether it is audible.' say('Music command sent (not proof it played). Check HD Admin rank and asset access.',true) else hint.Text='Music request failed: '..tostring(detail):sub(1,120) say('Music test failed. Check HD Admin, permissions and console.') end end commandBusy=false end) end) local customOpen=false local customButton customButton=textButton(musicPage,'CUSTOM ID +',340,382,128,36,function() customOpen=not customOpen customField.Visible=customOpen saveCustom.Visible=customOpen testMusicButton.Visible=not customOpen customButton.Text=customOpen and 'CUSTOM ID −' or 'CUSTOM ID +' if customOpen then customField.Text=songIds[selectedSong] or '' end end) -- Optional per-song pitch control; blank = no preset (uses normal 1x speed). label(musicPage,'Pitch',550,319,53,31,15,GOLD) pitchBox=textbox(musicPage,'1',600,321,77,30) pitchBox.Text=tostring(songPitches[selectedSong] or 1) textButton(musicPage,'SAVE PITCH',690,320,118,32,function() local entered=pitchBox.Text:gsub('%s+','') if entered=='' then songPitches[selectedSong]=nil pitchBox.Text='1' return say('Pitch reset to normal (1x) for '..displayName(SONGS[selectedSong])..'.',true) end local amount=tonumber(entered) if not amount or amount<0.05 or amount>4 then return say('Pitch must be between 0.05 and 4 (1 = normal speed).') end songPitches[selectedSong]=amount pitchBox.Text=tostring(amount) say('Saved pitch '..tostring(amount)..' for '..displayName(SONGS[selectedSong])..'.',true) end) local footer=label(musicPage, tostring(presetCount)..' / 24 songs have IDs • PLAY sends ;volume inf • TEST isolates ;music • Requires HD Admin', 14,421,790,14,12,QUIET) updateSongButtons() -- DECALS TAB: Authorized SERVER F3X Texture Tool calls only. -- label(decalsPage,'CUSTOM DECALS — TRAFFICCONE F3X METHOD',13,8,400,23,16,GOLD) local decalIdBox=textbox(decalsPage,'Roblox image ID (or convert a decal ID)',13,38,388,33) local preview=ui('ImageLabel',{ Position=UDim2.fromOffset(420,9),Size=UDim2.fromOffset(148,148), BackgroundColor3=DARK,BorderSizePixel=0, Image='rbxassetid://0',ScaleType=Enum.ScaleType.Fit, },decalsPage) stroke(preview) textButton(decalsPage,'PREVIEW IMAGE',13,81,189,30,function() local id=normalizeAssetId(decalIdBox.Text) if not id then return say('Enter a numeric Roblox image/decal ID.') end preview.Image='rbxassetid://'..id -- GUI thumbnail ONLY, not a world decal say('Preview set. APPLY/FILL buttons use F3X on the server.',true) end) -- Look for the actual tool endpoint, regardless of whether it is held or in Backpack. -- Both locations are supported by the standard F3X SyncAPI. local function findEquippedF3X() local char=player.Character local bag=player:FindFirstChildOfClass('Backpack') for _,container in ipairs({char,bag}) do if container then for _,tool in ipairs(container:GetChildren()) do if tool:IsA('Tool') then local sync=tool:FindFirstChild('SyncAPI') local endpoint=sync and sync:FindFirstChild('ServerEndpoint') if endpoint and endpoint:IsA('RemoteFunction') then return tool,endpoint end end end end end return nil,nil end textButton(decalsPage,'CONVERT DECAL ID',212,81,189,30,function() if busy then return say('An F3X request is already in progress.') end local decalId=normalizeAssetId(decalIdBox.Text) if not decalId then return say('Enter a Roblox decal asset ID first.') end local _, endpoint=findEquippedF3X() if not endpoint then return say('Equip F3X first. Conversion uses its server endpoint.') end busy=true say('Asking F3X to resolve decal asset ID to an image ID...',true) task.spawn(function() local ok,result=pcall(function() return endpoint:InvokeServer('ExtractImageFromDecal',tonumber(decalId)) end) busy=false if not gui.Parent then return end local imageId = ok and normalizeAssetId(result) if imageId then decalIdBox.Text=imageId preview.Image='rbxassetid://'..imageId say('Converted to image ID '..imageId..'. Now choose an F3X apply button.',true) else say('F3X ID conversion unavailable. Try the underlying image ID; some servers disable HTTP.') end end) end) local partLabel=label(decalsPage,'Selected part: none',13,125,402,27,13,WHITE) local pickButton local function updateSelected(part) selectedPart=part outlineSelection.Adornee=part if part then partLabel.Text='Selected: '..part.Name say('Selected '..part.Name..'. Use Apply / Fill to send a BTools decal edit.',true) else partLabel.Text='Selected part: none' say('No part selected.') end end pickButton=textButton(decalsPage,'PICK PART IN WORLD',13,165,188,34,function() pickMode=not pickMode pickButton.Text=pickMode and 'CLICK A PART NOW...' or 'PICK PART IN WORLD' say(pickMode and 'Click an editable build part in the 3D world.' or 'Part picker canceled.',true) end) textButton(decalsPage,'CLEAR SELECTION',212,165,189,34,function() updateSelected(nil) end) local faceButton=textButton(decalsPage,'FACE: FRONT',13,214,190,32,function() faceIndex=(faceIndex % #FACES)+1 local face=FACES[faceIndex] faceButton.Text='FACE: '..string.upper(face.Name) end) label(decalsPage,'Use your equipped F3X/BTools, not a local Decal. Select a part first.',12,252,530,27,12,WHITE) local function isPlayerCharacterPart(part) local model=part:FindFirstAncestorOfClass('Model') return model and model:FindFirstChildOfClass('Humanoid') ~= nil end local function decalOnFace(part,face) for _, child in ipairs(part:GetChildren()) do if child:IsA('Decal') and child.Face==face then return child end end return nil end -- CreateTextures({{Part=part,Face=face,TextureType="Decal"}}) -- SyncTexture({{Part=part,Face=face,TextureType="Decal",Texture=...}}) -- Use this on the chosen build only. It is NOT a client-only Instance.new("Decal"). -- Keep one request per face: official F3X groups requests by Part, so one 6-face -- batch for the same part can collapse to a single face. local function applyServerF3XDecals(parts,faces,imageId,endpoint) local content='rbxassetid://'..imageId local attempted=0 for _,part in ipairs(parts) do if part and part:IsA('BasePart') and part:IsDescendantOf(workspace) and not isPlayerCharacterPart(part) then for _,face in ipairs(faces) do -- Avoid extra -- copies when this face already has one, so presets can be rerun. if not decalOnFace(part,face) then endpoint:InvokeServer('CreateTextures',{{ Part=part,Face=face,TextureType='Decal' }}) task.wait(config.f3xDelay) end endpoint:InvokeServer('SyncTexture',{{ Part=part,Face=face,TextureType='Decal',Texture=content }}) attempted=attempted+1 task.wait(config.f3xDelay) end end end -- The server may reject an edit without throwing an error. Confirm what -- actually replicated rather than reporting success based on the remote call. task.wait(0.45) local confirmed=0 for _,part in ipairs(parts) do if part and part.Parent then for _,face in ipairs(faces) do local decal=decalOnFace(part,face) if decal and tostring(decal.Texture):match('(%d+)$')==imageId then confirmed=confirmed+1 end end end end return attempted,confirmed end local function executeServerF3XDecals(parts,faces,imageId,endpoint,labelText) busy=true say('Requesting '..labelText..' with F3X server Texture Tool...',true) task.spawn(function() local ok,attempted,confirmed=pcall(function() return applyServerF3XDecals(parts,faces,imageId,endpoint) end) busy=false if not gui.Parent then return end if not ok then say('F3X failed: '..tostring(attempted):sub(1,135)) elseif confirmed==attempted and attempted>0 then say('F3X changed '..confirmed..' decal face(s). Confirm another player sees them.',true) elseif confirmed>0 then say('F3X applied '..confirmed..'/'..attempted..' decal faces. Others may be denied or still replicating.') else say('No F3X decals confirmed. Check tool permissions, image ID, and server limits.') end end) end local function attemptDecalFill(allFaces) if busy then return say('Finish the current F3X operation first.') end if os.clock()-lastEdit < config.decalCooldown then return say('Wait '..tostring(config.decalCooldown)..' seconds between decal edits.') end local id=normalizeAssetId(decalIdBox.Text) if not id then return say('Enter a numeric Roblox image ID.') end local part=selectedPart if not part or not part:IsA('BasePart') or not part:IsDescendantOf(workspace) or isPlayerCharacterPart(part) then return say('Pick a build part in the world first (not a character).') end local _, endpoint=findEquippedF3X() if not endpoint then return say('No F3X-style CreateTextures tool found. Use CHECK MY BTOOLS on the Realms tab.') end lastEdit=os.clock() executeServerF3XDecals({part},allFaces and FACES or {FACES[faceIndex]},id,endpoint,allFaces and '6 faces' or '1 face') end textButton(decalsPage,'APPLY 1 FACE (F3X)',13,289,190,36,function() attemptDecalFill(false) end) textButton(decalsPage,'FILL 6 FACES (F3X)',211,289,190,36,function() attemptDecalFill(true) end) -- No Workspace-wide scan: the selection determines a local build scope. local function gatherBuildParts() if not selectedPart or not selectedPart:IsDescendantOf(workspace) then return {} end local p=selectedPart.Parent local container=(p and (p:IsA('Model') or p:IsA('Folder')) and p) or nil if container and container:FindFirstChildOfClass('Humanoid') then container=nil end if container == workspace then container=nil end local output={} local scope=container and container:GetDescendants() or {selectedPart} for _,item in ipairs(scope) do if item:IsA('BasePart') and not isPlayerCharacterPart(item) then table.insert(output,item) if #output>=12 then break end end end return output end local function fillPickedBuild() if busy then return say('An F3X operation is still running.') end if os.clock()-lastBatchEdit < config.batchCooldown then return say('Wait '..tostring(config.batchCooldown)..' seconds between build fills.') end local id=normalizeAssetId(decalIdBox.Text) if not id then return say('Enter a custom image ID before filling.') end local parts=gatherBuildParts() if #parts==0 then return say('Pick a part belonging to your build first.') end local _, endpoint=findEquippedF3X() if not endpoint then return say('No BTools texture endpoint found. Check your tool in REALMS.') end lastBatchEdit=os.clock() executeServerF3XDecals(parts,FACES,id,endpoint,'up to '..#parts..' build parts') end textButton(decalsPage,'FILL PICKED BUILD (MAX 12)',13,340,388,37,fillPickedBuild) textButton(decalsPage,'CHECK F3X TOOL',420,340,198,37,function() local tool, endpoint=findEquippedF3X() if tool and endpoint then say('Found equipped F3X: '..tool.Name..'. Server authorization is still required.',true) else say('No equipped F3X SyncAPI tool. Equip F3X in-game, then retry.') end end) local DECAL_PRESETS={ {name='PIXEL DECAL 1', id='93544399411478'}, {name='PIXEL DECAL 2', id='87626606643966'}, } label(decalsPage,'PIXEL DECAL PRESETS',420,163,370,24,16,GOLD) for index,preset in ipairs(DECAL_PRESETS) do local item=preset textButton(decalsPage,item.name..' | '..item.id,420,188+(index-1)*42,368,36,function() decalIdBox.Text=item.id preview.Image='rbxassetid://'..item.id if selectedPart and selectedPart:IsDescendantOf(workspace) then -- Preset buttons do the F3X apply immediately on the picked part. attemptDecalFill(true) else say(item.name..' selected. PICK PART IN WORLD, then click preset again.',true) end end) end label(decalsPage,'Pick a part, then click a preset to apply all six faces through F3X.',420,279,368,50,13,QUIET) label(decalsPage,'APPLY and FILL use BTools texture actions, not locally created Decal objects.',13,395,760,33,13,AMBER) -- Process one face per part, batch only DISTINCT parts in each remote call. -- No locally created Decals; server remains free to reject each request. do local mapLimitBox = textbox(decalsPage, 'Max parts (1–2500)', 13, 484, 161, 34) mapLimitBox.Text = '1000' local mapFillState = { running=false, cancel=false, pending=false, armedAt=0 } local mapProgress = label(decalsPage, 'Scan the map before applying. Characters and tools are excluded.', 13, 523, 765, 42, 13, QUIET) label(decalsPage, 'MAP-WIDE DECALS • SIX FACES PER PART', 13, 438, 770, 30, 18, GOLD) label(decalsPage, 'Uses the image ID above. Replaces matching face decals; server edits only.', 183, 480, 605, 40, 13, WHITE) local function targetLimit() local n = tonumber(mapLimitBox.Text) if not n or n~=n or n==math.huge or n~=math.floor(n) or n<1 or n>2500 then say('Enter a whole number from 1 to 2500 for maximum map parts.') return nil end return n end local function collectTargets(cap) local parts, total = {}, 0 for _, item in ipairs(workspace:GetDescendants()) do if item:IsA('BasePart') and not isPlayerCharacterPart(item) and not item:FindFirstAncestorOfClass('Tool') then total = total + 1 if #parts < cap then parts[#parts+1] = item end end end return parts, total end local function progress(text) if not gui.Parent then return end mapProgress.Text=text say(text, true) end local function runMapFill(parts, endpoint, imageId, total) -- State is armed before task.spawn to prevent duplicate starts. local requests, errors, confirmed, attempted = 0, 0, 0, 0 local batchSize=12 local stopNow=false local texture='rbxassetid://'..imageId -- F3X groups edits by part: group distinct parts for one face per call. for startIndex=1,#parts,batchSize do if mapFillState.cancel or not gui.Parent then break end local lastIndex=math.min(#parts,startIndex+batchSize-1) for _,face in ipairs(FACES) do if mapFillState.cancel or not gui.Parent then break end local createBatch, syncBatch = {}, {} for i=startIndex,lastIndex do local part=parts[i] if part and part:IsDescendantOf(workspace) and not isPlayerCharacterPart(part) then local d=decalOnFace(part,face) if not d then createBatch[#createBatch+1] = { Part=part, Face=face, TextureType='Decal' } end if not d or tostring(d.Texture)~=texture then syncBatch[#syncBatch+1] = { Part=part, Face=face, TextureType='Decal', Texture=texture } end end end if #createBatch>0 then local ok=pcall(function() endpoint:InvokeServer('CreateTextures',createBatch) end) requests=requests+1 if not ok then errors=errors+1 end task.wait(math.max(0.18,config.f3xDelay)) end if #syncBatch>0 then local ok=pcall(function() endpoint:InvokeServer('SyncTexture',syncBatch) end) requests=requests+1 if not ok then errors=errors+1 end attempted=attempted+#syncBatch task.wait(math.max(0.18,config.f3xDelay)) end if errors>=6 then stopNow=true; break end end progress('Decal All: processed '..tostring(lastIndex)..'/'..tostring(#parts).. ' parts • '..tostring(requests)..' F3X requests • '..tostring(errors)..' errors') if stopNow then break end end task.wait(0.5) for _,part in ipairs(parts) do if part and part.Parent then for _,face in ipairs(FACES) do local d=decalOnFace(part,face) if d and tostring(d.Texture):match('(%d+)$')==imageId then confirmed=confirmed+1 end end end end local interrupted=mapFillState.cancel mapFillState.running=false busy=false mapFillState.pending=false if not gui.Parent then return end local summary=(interrupted and 'CANCELED. ' or stopNow and 'STOPPED AFTER ERRORS. ' or 'FINISHED. ').. 'Decals confirmed: '..confirmed..'/'..(#parts*6)..' faces across up to '.. #parts..'/'..total..' map parts. Server permission and replication may limit results.' mapProgress.Text=summary say(summary,confirmed>0) end local actionButton local function resetConfirmation() mapFillState.pending=false if actionButton and actionButton.Parent then actionButton.Text='DECAL ALL MAP (F3X)' end end textButton(decalsPage, 'SCAN MAP PARTS',13,574,243,39,function() if mapFillState.running then return say('Map-wide fill is already running.') end local limit=targetLimit() if not limit then return end local parts,total=collectTargets(limit) mapProgress.Text='Found '..total..' map parts. Will affect up to '..#parts.. ' parts × 6 faces. Larger maps need multiple runs or a higher limit.' say(mapProgress.Text,true) resetConfirmation() end) actionButton=textButton(decalsPage,'DECAL ALL MAP (F3X)',269,574,296,39,function() if busy or mapFillState.running then return say('Finish or cancel the current F3X operation first.') end if os.clock()-lastBatchEdit < config.batchCooldown then return say('Wait for the F3X batch cooldown before applying to the map.') end local id=normalizeAssetId(decalIdBox.Text) if not id then return say('Enter an image ID or select a Pixel Decal preset first.') end local limit=targetLimit() if not limit then return end local _,endpoint=findEquippedF3X() if not endpoint then return say('F3X SyncAPI not found. Equip F3X and try again.') end if not mapFillState.pending or os.clock()-mapFillState.armedAt>15 then mapFillState.pending=true mapFillState.armedAt=os.clock() actionButton.Text='CONFIRM MAP DECALS' return say('Confirm: this may replace existing map decals for everyone. Click again within 15 seconds.') end local parts,total=collectTargets(limit) resetConfirmation() if #parts==0 then return say('No BaseParts found in Workspace.') end lastBatchEdit=os.clock() mapFillState.running=true mapFillState.cancel=false busy=true progress('Starting map-wide F3X decals on '..#parts..'/'..total..' parts...') task.spawn(function() local ok,err=pcall(function() runMapFill(parts,endpoint,id,total) end) if not ok then mapFillState.running=false mapFillState.pending=false busy=false say('Map fill stopped: '..tostring(err):sub(1,170)) end end) end) textButton(decalsPage,'CANCEL FILL',578,574,209,39,function() if mapFillState.running then mapFillState.cancel=true say('Cancellation requested; finishing the current F3X request...') else resetConfirmation() say('No map fill running.') end end) label(decalsPage, 'Affects all F3X-editable Workspace BaseParts up to your part limit. Excludes player characters and tools. '.. 'Some games reject map changes; never use on other players\' builds without permission.', 13,629,774,77,13,AMBER) end -- SKYBOX TAB (LOCAL PREVIEW + F3X-MESH DOME) -- label(skyPage,'Sky image ID (local preview / F3X sky mesh)',14,16,620,28,16,GOLD) local skyIdBox=textbox(skyPage,'Enter Roblox image ID',14,51,551,33) -- Mesh SCALE, not an exact measurement of studs across; Roblox mesh bounds vary. local DEFAULT_SKY_SCALE = 4000 label(skyPage,'DOME MESH SCALE',583,16,210,28,16,GOLD) local skyScaleBox=textbox(skyPage,'4000',583,51,207,33) skyScaleBox.Text = tostring(DEFAULT_SKY_SCALE) local function selectedSkyScale() local n=tonumber(skyScaleBox.Text) if not n or n~=n or n==math.huge or n==-math.huge then return nil,'Enter a number for sky mesh scale (500–8000).' end if n<500 or n>8000 then return nil,'Sky mesh scale must be between 500 and 8000.' end return math.floor(n) end label(skyPage,'LOCAL preview uses Lighting; SHARED dome uses authorized F3X CreatePart/SyncMesh.',14,100,760,39,13,AMBER) local function applyLocalSky() local id=normalizeAssetId(skyIdBox.Text) if not id then return say('Enter a valid numeric sky image asset ID.') end if not savedSkies then savedSkies={} for _,item in ipairs(Lighting:GetChildren()) do if item:IsA('Sky') then table.insert(savedSkies,item:Clone()) end end end for _,item in ipairs(Lighting:GetChildren()) do if item:IsA('Sky') then item:Destroy() end end localSky=Instance.new('Sky') localSky.Name='RetroF3X_LocalSky' for _,prop in ipairs(SKY_FACES) do localSky[prop]='rbxassetid://'..id end localSky.Parent=Lighting say('Local skybox preview applied. Other players cannot see it.',true) end local function restoreSky() if not savedSkies then return say('No previous skybox snapshot to restore.') end for _,item in ipairs(Lighting:GetChildren()) do if item:IsA('Sky') then item:Destroy() end end for _,item in ipairs(savedSkies) do item.Parent=Lighting end savedSkies=nil localSky=nil say('Previous local skybox restored.',true) end textButton(skyPage,'LOCAL SKY PREVIEW',14,158,267,40,applyLocalSky) textButton(skyPage,'RESTORE LOCAL SKY',299,158,266,40,restoreSky) -- The following builds one removable, anchored, noncolliding mesh through the -- official F3X SyncAPI. No require(), no client-side server permission bypass. local function createF3XSkyDome() if busy or realmBusy then return say('An F3X operation is already running.') end if os.clock()-lastSkyEdit0 then return nil,nil,'Found '..table.concat(classic,', ')..'. These tools have no F3X CreatePart interface; only F3X-style BTools can generate new realm pieces.' end return nil,nil,'No F3X/BTools CreatePart tool found in your Character or Backpack.' end local function realmSpecs(theme, detailed) if theme=='PIXEL' then -- Inspired by the user's black outfit, gold/white angel wings, crown, -- sparkling aura, and the pink platform seen in the screenshot. local charcoal=Color3.fromRGB(26,26,35) local wall=Color3.fromRGB(43,40,53) local obsidian=Color3.fromRGB(16,17,23) local rose=Color3.fromRGB(241,126,188) local roseSoft=Color3.fromRGB(195,110,164) local gold=Color3.fromRGB(202,159,76) local pearl=Color3.fromRGB(239,236,229) local ivory=Color3.fromRGB(206,209,230) local accent=Color3.fromRGB(140,109,180) local pieces={} local function add(x,y,z,sx,sy,sz,color,material,collide,animation,rx,ry,rz) table.insert(pieces,{ offset=Vector3.new(x,y,z),size=Vector3.new(sx,sy,sz),color=color, material=material or Enum.Material.SmoothPlastic,collide=(collide~=false), animation=animation, rotation=CFrame.Angles(math.rad(rx or 0),math.rad(ry or 0),math.rad(rz or 0)), }) end -- Outer dimensions: 96 x 96; side walls height 38, roof is 38 studs up. add( 0, 0, 0, 96,2,96,charcoal,Enum.Material.Slate) add( 0,38, 0, 96,2,96,obsidian,Enum.Material.Slate) add(-47,19, 0, 2,38,96,wall) add( 47,19, 0, 2,38,96,wall) add( 0,19,-47, 96,38,2,wall) -- Two front walls and lintel leave a 22-stud opening facing the player. add(-29.5,19,47,37,38,2,wall) add( 29.5,19,47,37,38,2,wall) add(0,34,47,22,8,2,wall) -- Glow trims around the entrance and at the floor / roof lines. add(-12,17,47.9,2,32,1,rose,Enum.Material.Neon,true,'pulse') add( 12,17,47.9,2,32,1,rose,Enum.Material.Neon,true,'pulse') add( 0,32,47.9,26,2,1,gold,Enum.Material.Neon,true,'pulse') add( 0, 2, 0,12,0.4,80,roseSoft,Enum.Material.Neon,false) -- Avatar throne at the back; black pedestal and elegant gold details. add( 0, 2,-28,26,2,22,obsidian) add( 0, 7,-35,12,13,3,gold) add( 0, 9,-33, 8,11,1,charcoal) add(-6, 5,-31, 3, 6,7,gold) add( 6, 5,-31, 3, 6,7,gold) add( 0,15,-33, 8,1.5,2,pearl,Enum.Material.Neon,false,'pulse') add(-4,17,-33,2,5,2,gold) add( 0,18,-33,2,7,2,gold) add( 4,17,-33,2,5,2,gold) -- White-and-gold wing silhouette behind throne. Decorative only. for _,side in ipairs({-1,1}) do add(side*13,15,-37, 3,17,2,pearl,Enum.Material.SmoothPlastic,false,nil,0,0,side*30) add(side*21,16,-37, 3,15,2,ivory,Enum.Material.SmoothPlastic,false,nil,0,0,side*50) add(side*29,15,-37, 2.5,12,2,gold,Enum.Material.Metal,false,nil,0,0,side*65) end -- Corner pylons and four hovering crystal beacons. for _,x in ipairs({-37,37}) do for _,z in ipairs({-35,35}) do add(x,9,z,4,18,4,obsidian,Enum.Material.Metal) add(x,19,z,6,2,6,gold,Enum.Material.Neon,false,'pulse') add(x,24,z,4,5,4,pearl,Enum.Material.Neon,false,'float') end end -- Ceiling star strips; pulsing instead of thousands of part spam. add(-24,36.2,0,2,1,68,rose,Enum.Material.Neon,false,'pulse') add( 24,36.2,0,2,1,68,rose,Enum.Material.Neon,false,'pulse') add(0,36.2,0,64,1,2,gold,Enum.Material.Neon,false,'pulse') if detailed then -- Detailed mode adds more ornamentation; identical enclosed hall. for _,x in ipairs({-30,-15,15,30}) do add(x,1.6,10,2,.5,55,pearl,Enum.Material.Neon,false) end add(0,2.5,21,12,2,12,gold,Enum.Material.Metal) add(0,5,21,5,3,5,pearl,Enum.Material.Neon,false,'float') end return pieces end local tint=theme=='VOID' and Color3.fromRGB(100,35,205) or Color3.fromRGB(202,159,76) local highlight=theme=='VOID' and Color3.fromRGB(180,91,255) or Color3.fromRGB(235,218,183) return { {offset=Vector3.new(0,0,0),size=Vector3.new(32,1,32),color=Color3.fromRGB(23,23,34),material=Enum.Material.Slate,collide=true}, {offset=Vector3.new(-12,4,-12),size=Vector3.new(2,8,2),color=tint,material=Enum.Material.Neon,collide=true}, {offset=Vector3.new(12,4,-12),size=Vector3.new(2,8,2),color=tint,material=Enum.Material.Neon,collide=true}, {offset=Vector3.new(-12,4,12),size=Vector3.new(2,8,2),color=tint,material=Enum.Material.Neon,collide=true}, {offset=Vector3.new(12,4,12),size=Vector3.new(2,8,2),color=tint,material=Enum.Material.Neon,collide=true}, {offset=Vector3.new(0,9,-11),size=Vector3.new(18,2,2),color=highlight,material=Enum.Material.Neon,collide=true}, {offset=Vector3.new(0,13,-11),size=Vector3.new(2,8,2),color=highlight,material=Enum.Material.Neon,collide=true}, {offset=Vector3.new(0,1,0),size=Vector3.new(9,.4,9),color=highlight,material=Enum.Material.Neon,collide=true}, } end -- Map blueprints use the SAME CreatePart + grouped SyncResize/SyncColor/ -- SyncMaterial pipeline as PIXEL REALM, never a separate server remote. -- This builds scoped scenery and never touches existing unrelated map parts. local function expandedMapSpecs(theme, quality) local specs={} quality=math.clamp(math.floor(tonumber(quality) or 2),1,3) local C={ dark=Color3.fromRGB(19,20,30),stone=Color3.fromRGB(42,43,58), grey=Color3.fromRGB(91,97,120),rose=Color3.fromRGB(238,139,191), gold=Color3.fromRGB(235,179,67),white=Color3.fromRGB(235,231,245), blue=Color3.fromRGB(91,173,252),cyan=Color3.fromRGB(52,223,238), violet=Color3.fromRGB(147,81,243),ice=Color3.fromRGB(155,218,245), ember=Color3.fromRGB(245,97,41),red=Color3.fromRGB(194,48,72), green=Color3.fromRGB(91,178,110),wood=Color3.fromRGB(103,63,67), } local M=Enum.Material local function add(x,y,z,w,h,d,color,material,collide,rot) if #specs >= 185 then return end specs[#specs+1]={offset=Vector3.new(x,y,z),size=Vector3.new(w,h,d), color=color or C.white,material=material or M.SmoothPlastic, collide=collide~=false,rotation=rot and CFrame.Angles(0,math.rad(rot),0) or nil} end local function platform(size,color) add(0,0,0,size,2,size,color or C.dark,M.Slate) end local function boxHall(color,accent) platform(110,C.dark) add(-54,20,0,2,40,108,color,M.Slate) add(54,20,0,2,40,108,color,M.Slate) add(0,20,-54,108,40,2,color,M.Slate) add(-34,20,54,40,40,2,color,M.Slate) add(34,20,54,40,40,2,color,M.Slate) add(0,37,54,27,6,2,color,M.Slate) add(0,41,0,110,2,110,color,M.Slate) add(0,3,0,10,.5,105,accent,M.Neon,false) add(-13,20,53,2,34,1,accent,M.Neon,false) add(13,20,53,2,34,1,accent,M.Neon,false) add(0,38,53,29,2,1,accent,M.Neon,false) end if theme=='VOID' or theme=='GOLD' then local accent=(theme=='GOLD' and C.gold or C.violet) local wall=(theme=='GOLD' and C.wood or C.stone) local secondary=(theme=='GOLD' and C.white or C.cyan) boxHall(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()-lastRealmBuild0 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()=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 valuefield.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.LayoutOrder60 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 elapsed0 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)