--[[ ╔══════════════════════════════════════════════════════╗ ║ FNF ANIMATION INJECTOR v6.1 ║ ║ Arrow HUD • Dynamic Face • Anim Maker • Leg Lock ║ ║ + Idle BPM Speed Control ║ ║ + Live Preview • Favorites • Playlist • Themes ║ ║ + Hold-to-Loop + Weighted Alt Animations (per bind) ║ ╚══════════════════════════════════════════════════════╝ • Z/X/C/V → play directional anims • Arrow HUD reacts on screen when you hit keys • Face decal swaps per direction (if your character has one) • Animation Sequence Maker (chain anims with delays) • Optional Legs-on-Ground lock (no walk / no jump) • Idle animation speed scales with BPM • Live character viewport preview • Equalizer / waveform / beat-sync visualizers • Favorites, recents, presets, playlist manager • Pink / Cyan / Purple / RGB theme selector • NEW: per-direction HOLD-LOOP toggle — hold the key to keep the animation looping instead of snap-freezing on the pose • NEW: per-direction weighted ALT ANIMATIONS — each key press can roll into an alt animation instead of the primary one, at whatever % chance you configure ]] -- ============================================================ -- SERVICES & GLOBALS -- ============================================================ local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local TweenService = game:GetService("TweenService") local RunService = game:GetService("RunService") local LocalPlayer = Players.LocalPlayer local PlayerGui = LocalPlayer:WaitForChild("PlayerGui") -- ============================================================ -- DEFAULT BINDINGS -- ============================================================ local DEFAULT_BINDINGS = { { dir = "LEFT", key = "Z", id = "243827693" }, { dir = "DOWN", key = "X", id = "182724289" }, { dir = "UP", key = "C", id = "183412246" }, { dir = "RIGHT", key = "V", id = "243656287" }, } local CurrentBindings = {} for _, v in ipairs(DEFAULT_BINDINGS) do -- holdLoop: false = original "snap to pose and freeze" behavior, -- true = animation actually loops for as long as the key is held -- alts: { {id = "123", chance = 25}, ... } — weighted alt anim IDs, -- rolled against on every key press; uncovered % falls back to `id` table.insert(CurrentBindings, { dir = v.dir, key = v.key, id = v.id, speedMult = 1, holdLoop = false, alts = {} }) end -- Placeholder quick-preset library (edit IDs freely; these are just slots) local PRESETS = { { name = "Default Set", ids = { LEFT="204062532", DOWN="204292303", UP="183412246", RIGHT="218504594" } }, { name = "Preset Slot A",ids = { LEFT="0", DOWN="0", UP="0", RIGHT="0" } }, { name = "Preset Slot B",ids = { LEFT="0", DOWN="0", UP="0", RIGHT="0" } }, } -- ============================================================ -- STATE -- ============================================================ local animTracks = {} local idleTrack = nil local connections = {} local animator = nil local character = nil local rebindingRow = nil local guiVisible = true local idleLooping = false -- RP Tug-of-War State local rpModeEnabled = false local currentTarget = nil local rpMoveStep = 1.5 -- Base speed/distance per note local rpMinDistance = 3.5 -- How close before stopping local rpPushbackStep = 1.0 -- How far you get pushed back when the target hits a note local targetAnimConn = nil -- Stores the listener for the target's animations -- Legs on ground local legsLocked = false local savedWalkSpeed = 16 local savedJumpPower = 50 -- Face system local faceDecal = nil local defaultFaceId = nil local faceIds = { LEFT = "", DOWN = "", UP = "", RIGHT = "" } -- Sequence maker local sequence = {} local seqPlaying = false local seqTrack = nil local seqRowFrames = {} -- Arrow HUD handles local arrowHudFrames = {} -- [dir] = { bg, lbl } -- Idle BPM control local idleBPM = 100 -- the tempo you want the idle to play at local BASE_BPM = 58 -- the tempo the idle animation was originally made/synced for -- Status / playback tracking (drives the status panel + visualizers) local StatusState = { currentAnimName = "—", currentAnimId = "", speedMult = 1, bpm = idleBPM, playing = false, } -- Favorites / recents / playlist -- Seeded with Roblox's own default emote IDs — these are built into every -- avatar by Roblox itself, so they're free to use and always available. local favorites = { { name = "Default: Dance", id = "507771019" }, { name = "Default: Dance2", id = "507776043" }, { name = "Default: Dance3", id = "507777268" }, { name = "Default: Laugh", id = "507770818" }, { name = "Default: Cheer", id = "507770677" }, { name = "Default: Point", id = "507770453" }, { name = "Default: Sit", id = "2506281703" }, } local recents = {} -- { id, id, ... } most-recent-first, capped local RECENTS_CAP = 8 local playlist = {} -- { {name=, id=, delay=} } local playlistIndex = 1 local playlistPlaying = false -- Preview viewport local previewClone = nil local previewAnimator = nil local previewCam = nil local previewAngle = 0 -- Theme local THEMES = { Pink = { accent = Color3.fromRGB(255, 80, 180), accent2 = Color3.fromRGB(0, 200, 255) }, Cyan = { accent = Color3.fromRGB(0, 200, 255), accent2 = Color3.fromRGB(255, 80, 180) }, Purple = { accent = Color3.fromRGB(165, 85, 255), accent2 = Color3.fromRGB(255, 80, 180) }, RGB = { accent = Color3.fromRGB(255, 0, 0), accent2 = Color3.fromRGB(0, 255, 255) }, } local currentThemeName = "Pink" local themedStrokes = {} -- UIStroke objects to recolor with accent local themedBgs = {} -- Frame/TextButton objects to recolor with accent local themedTexts = {} -- TextLabel objects to recolor with accent local rgbCycleOn = false -- ============================================================ -- VINYL RECORD WIDGET -- ============================================================ local vinylImage = nil -- ImageLabel, set your decal via vinylImage.Image or the Settings box local VINYL_DECAL_ID = "" -- paste an asset id here, or set it live from the FX tab local vinylRotation = 0 local vinylBaseRPM = 33.3 -- classic vinyl RPM at speedMult == 1 / idle BPM baseline -- ============================================================ -- CRT MONITOR MODE -- ============================================================ local crtEnabled = false local crtOverlay = nil -- Frame that holds scanlines + tint, toggled visible local crtFlickerConn = nil -- ============================================================ -- SPECTROGRAM (MIC) VISUALIZER -- ============================================================ -- Uses Roblox's newer Audio API (AudioDeviceInput + AudioAnalyzer). This is -- still a fairly recent, evolving part of the engine, needs the experience -- to have voice/microphone permission granted by the player, and isn't -- guaranteed to exist on every client — everything below is wrapped in -- pcall and falls back to an animation-synced "fake" spectrogram if real -- mic analysis isn't available. local micDeviceInput = nil local micAnalyzer = nil local micWire = nil local micAvailable = false local specBars = {} local SPEC_BAR_COUNT = 20 -- ============================================================ -- ANIMATION BPM DETECTOR -- ============================================================ -- Two detection modes: -- 1) TAP MODE: every directional key press timestamp is recorded; the -- rolling average gap between presses is converted straight to BPM. -- 2) IDLE-LENGTH MODE: BPM is derived from the currently loaded idle -- animation's length and a user-supplied "beats per loop" count. local bpmDetectTaps = {} -- recent os.clock() timestamps of direction presses local BPM_TAP_WINDOW = 8 -- how many taps to average over local detectedBPM = nil local beatsPerIdleLoop = 4 -- ============================================================ -- SHARED MUSIC PLAYER (no VC — plays an uploaded audio asset for -- every player in the server via a server-replicated Sound, instead -- of routing anything through voice chat) -- ============================================================ -- Requires a small companion SERVER script (provided separately) that -- creates the "FNFMusicRemote" RemoteEvent and the actual Sound -- instance — a LocalScript alone cannot make a sound audible to other -- players, only the server can do that. local ReplicatedStorage = game:GetService("ReplicatedStorage") local SoundService = game:GetService("SoundService") local musicRemote = nil local MUSIC_SOUND_NAME = "FNFMusicPlayer" local musicStatusLbl = nil -- assigned once the FX tab is built local musicRequestTick = 0 -- ============================================================ -- CONSTANTS -- ============================================================ local PRIORITY = Enum.AnimationPriority.Action local FRAME_TARGET = 10 local BLEND_IN = 0 local BLEND_OUT = 0.05 -- FNF arrow colours local ARROW_COLORS = { LEFT = { idle = Color3.fromRGB(150, 50, 130), hit = Color3.fromRGB(255, 110, 230) }, DOWN = { idle = Color3.fromRGB(0, 130, 170), hit = Color3.fromRGB(0, 240, 255) }, UP = { idle = Color3.fromRGB(15, 130, 10), hit = Color3.fromRGB(20, 250, 5) }, RIGHT = { idle = Color3.fromRGB(160, 35, 35), hit = Color3.fromRGB(255, 90, 90) }, } local ARROW_SYMBOLS = { LEFT = "◄", DOWN = "▼", UP = "▲", RIGHT = "►" } -- Arrow HUD layout local ARROW_SIZE = 64 local ARROW_HIT_SIZE = 74 local ARROW_GAP = 14 local ARROW_ORDER = { "LEFT", "DOWN", "UP", "RIGHT" } -- ============================================================ -- LOGGING -- ============================================================ local function log(m) print("[FNF] "..tostring(m)) end -- ============================================================ -- ANIMATOR HELPERS -- ============================================================ local function getAnimator(char) local hum = char:FindFirstChildOfClass("Humanoid") if not hum then return nil end local a = hum:FindFirstChildOfClass("Animator") if not a then a = Instance.new("Animator"); a.Parent = hum end return a end local function destroyTrack(key) local t = animTracks[key] if t then pcall(function() t:Stop(0) end) pcall(function() t:Destroy() end) animTracks[key] = nil end end local function destroyIdleTrack() if idleTrack then pcall(function() idleTrack:Stop(0) end) pcall(function() idleTrack:Destroy() end) idleTrack = nil end idleLooping = false end local function destroySeqTrack() if seqTrack then pcall(function() seqTrack:Stop(0) end) pcall(function() seqTrack:Destroy() end) seqTrack = nil end end local function cleanupAll() idleLooping = false; seqPlaying = false destroyIdleTrack(); destroySeqTrack() for k in pairs(animTracks) do destroyTrack(k) end for _, c in ipairs(connections) do pcall(function() c:Disconnect() end) end connections = {}; animator = nil; character = nil faceDecal = nil; defaultFaceId = nil end -- ============================================================ -- NOTIFICATION TOASTS (forward-declared, container built in GUI section) -- ============================================================ local notifyContainer = nil local function notify(text, kind) if not notifyContainer then log("[toast] "..text); return end kind = kind or "info" local colors = { info = Color3.fromRGB(0, 200, 255), success = Color3.fromRGB(80, 220, 120), warn = Color3.fromRGB(255, 180, 60), error = Color3.fromRGB(255, 80, 80), } local toast = Instance.new("Frame") toast.Size = UDim2.new(1, 0, 0, 40) toast.BackgroundColor3 = Color3.fromRGB(20, 20, 30) toast.BackgroundTransparency = 0.05 toast.BorderSizePixel = 0 toast.ClipsDescendants = true toast.Parent = notifyContainer local corner = Instance.new("UICorner"); corner.CornerRadius = UDim.new(0,8); corner.Parent = toast local stroke = Instance.new("UIStroke"); stroke.Color = colors[kind] or colors.info; stroke.Thickness = 1.5; stroke.Parent = toast local bar = Instance.new("Frame") bar.Size = UDim2.new(0, 4, 1, 0) bar.BackgroundColor3 = colors[kind] or colors.info bar.BorderSizePixel = 0 bar.Parent = toast local lbl = Instance.new("TextLabel") lbl.Size = UDim2.new(1, -16, 1, 0); lbl.Position = UDim2.new(0, 12, 0, 0) lbl.BackgroundTransparency = 1; lbl.Text = text lbl.TextColor3 = Color3.fromRGB(230,230,240); lbl.Font = Enum.Font.GothamBold lbl.TextSize = 12; lbl.TextXAlignment = Enum.TextXAlignment.Left lbl.TextWrapped = true; lbl.Parent = toast toast.Position = UDim2.new(1, 20, 0, 0) TweenService:Create(toast, TweenInfo.new(0.25, Enum.EasingStyle.Quint, Enum.EasingDirection.Out), { Position = UDim2.new(0,0,0,0) }):Play() task.delay(2.6, function() if not toast or not toast.Parent then return end local t = TweenService:Create(toast, TweenInfo.new(0.25, Enum.EasingStyle.Quint, Enum.EasingDirection.In), { BackgroundTransparency = 1 }) TweenService:Create(lbl, TweenInfo.new(0.2), { TextTransparency = 1 }):Play() TweenService:Create(stroke, TweenInfo.new(0.2), { Transparency = 1 }):Play() t:Play() t.Completed:Connect(function() if toast then toast:Destroy() end end) end) end -- ============================================================ -- THEME SYSTEM -- ============================================================ local function regStroke(obj) table.insert(themedStrokes, obj); obj.Color = THEMES[currentThemeName].accent end local function regBg(obj) table.insert(themedBgs, obj); obj.BackgroundColor3 = THEMES[currentThemeName].accent end local function regText(obj) table.insert(themedTexts, obj); obj.TextColor3 = THEMES[currentThemeName].accent end local function applyThemeColors(accent) for _, o in ipairs(themedStrokes) do pcall(function() o.Color = accent end) end for _, o in ipairs(themedBgs) do pcall(function() o.BackgroundColor3 = accent end) end for _, o in ipairs(themedTexts) do pcall(function() o.TextColor3 = accent end) end end local function setTheme(name) if not THEMES[name] then return end currentThemeName = name rgbCycleOn = (name == "RGB") if not rgbCycleOn then applyThemeColors(THEMES[name].accent) end notify("Theme set to "..name, "info") end -- RGB cycling loop (only active while theme == RGB) task.spawn(function() local hue = 0 while true do task.wait(0.05) if rgbCycleOn then hue = (hue + 0.006) % 1 applyThemeColors(Color3.fromHSV(hue, 0.85, 1)) end end end) -- ============================================================ -- FACE SYSTEM -- ============================================================ local function initFace(char) local head = char:FindFirstChild("Head") if not head then return end local d = head:FindFirstChild("face") if not d or d.ClassName ~= "Decal" then return end faceDecal = d defaultFaceId = d.Texture log("Face found: "..defaultFaceId) end local function swapFace(dir) if not faceDecal then return end local id = faceIds[dir] if id and id ~= "" then faceDecal.Texture = "rbxassetid://"..id end end local function restoreFace() if faceDecal and defaultFaceId then faceDecal.Texture = defaultFaceId end end -- ============================================================ -- LEGS ON GROUND -- ============================================================ local function applyLegsLock(locked) if not character then return end local hum = character:FindFirstChildOfClass("Humanoid") if not hum then return end if locked then savedWalkSpeed = hum.WalkSpeed savedJumpPower = hum.JumpPower hum.WalkSpeed = 0 hum.JumpPower = 0 else hum.WalkSpeed = savedWalkSpeed hum.JumpPower = savedJumpPower end end -- ============================================================ -- RECENTS -- ============================================================ local function pushRecent(id, label) if not id or id == "" then return end for i = #recents, 1, -1 do if recents[i].id == id then table.remove(recents, i) end end table.insert(recents, 1, { id = id, label = label or id }) while #recents > RECENTS_CAP do table.remove(recents) end if refreshRecentsUI then refreshRecentsUI() end end -- ============================================================ -- LIVE PREVIEW HELPERS (forward declared, viewport built in GUI section) -- ============================================================ local function updatePreviewAnimation(assetId, label) StatusState.currentAnimName = label or assetId StatusState.currentAnimId = assetId if refreshStatusUI then refreshStatusUI() end if not previewAnimator or not assetId or assetId == "" then return end local ok, result = pcall(function() local anim = Instance.new("Animation") anim.AnimationId = "rbxassetid://"..assetId local t = previewAnimator:LoadAnimation(anim) t.Priority = Enum.AnimationPriority.Action return t end) if ok and result then for _, t in ipairs(previewAnimator:GetPlayingAnimationTracks()) do t:Stop(0) end result:Play(0.05) end end -- ============================================================ -- RP TUG-OF-WAR STATE & CONFIG -- ============================================================ local rpModeEnabled = true local currentTarget = nil -- Forward Movement (YOUR Note Hits) local rpMoveStep = 1.5 -- Distance you slide forward per note local rpMinDistance = 3.5 -- Closest you can get to target -- Backward Movement (TARGET'S Note Hits / Health Drain) local rpRetreatStep = 1.2 -- Distance you get pulled back when target hits a note local rpMaxDistance = 25.0 -- Maximum pushback distance limit -- Event Connections local targetAnimConn = nil -- Stores AnimationPlayed listener local targetCharAddedConn = nil -- Stores target respawn listener -- ============================================================ -- TUG-OF-WAR LOGIC -- ============================================================ -- Disconnects active listeners to prevent duplicate triggers or memory leaks local function stopWatchingTargetAnims() if targetAnimConn then targetAnimConn:Disconnect() targetAnimConn = nil end if targetCharAddedConn then targetCharAddedConn:Disconnect() targetCharAddedConn = nil end end -- Pushes YOUR character BACKWARD when the TARGET plays an animation local function triggerRetreat() if not rpModeEnabled or not currentTarget then return end if not character or not currentTarget.Character then return end local myRoot = character:FindFirstChild("HumanoidRootPart") local targetRoot = currentTarget.Character:FindFirstChild("HumanoidRootPart") if not myRoot or not targetRoot then return end -- Vector pointing directly AWAY from the target local awayVector = (myRoot.Position - targetRoot.Position) local distance = awayVector.Magnitude if distance <= 0.001 then return end -- Avoid divide-by-zero if overlapping -- Push backward only if within max distance if distance < rpMaxDistance then local actualStep = math.min(rpRetreatStep, rpMaxDistance - distance) myRoot.CFrame = myRoot.CFrame + (awayVector.Unit * actualStep) end end -- Hooks into the target's Animator to detect any incoming notes/anims local function startWatchingTargetAnims() stopWatchingTargetAnims() if not currentTarget then return end local function bindAnimator(targetChar) if not targetChar then return end local hum = targetChar:WaitForChild("Humanoid", 5) if not hum then return end local anr = hum:WaitForChild("Animator", 5) or hum:FindFirstChildOfClass("Animator") if not anr then return end -- Triggers retreat every time target plays ANY animation track targetAnimConn = anr.AnimationPlayed:Connect(function(track) if rpModeEnabled then triggerRetreat() end end) end -- Bind immediately if character exists if currentTarget.Character then bindAnimator(currentTarget.Character) end -- Re-bind automatically if target dies/respawns targetCharAddedConn = currentTarget.CharacterAdded:Connect(function(newChar) stopWatchingTargetAnims() bindAnimator(newChar) end) end -- Target Selection Function local function setTargetPlayer(partialName) if not partialName or partialName == "" then currentTarget = nil stopWatchingTargetAnims() if notify then notify("RP Target cleared.", "info") end return nil end local lowerName = partialName:lower() for _, player in ipairs(Players:GetPlayers()) do if player ~= LocalPlayer and player.Name:lower():sub(1, #lowerName) == lowerName then currentTarget = player startWatchingTargetAnims() if notify then notify("Target locked: " .. player.Name, "success") end return player end end if notify then notify("Player not found.", "warn") end return nil end -- Pushes YOUR character FORWARD when YOU hit a note key local function triggerRPMovement() if not rpModeEnabled or not currentTarget then return end if not character or not currentTarget.Character then return end local myRoot = character:FindFirstChild("HumanoidRootPart") local targetRoot = currentTarget.Character:FindFirstChild("HumanoidRootPart") if not myRoot or not targetRoot then return end local direction = (targetRoot.Position - myRoot.Position) local distance = direction.Magnitude -- Slide forward towards target if distance > rpMinDistance then local actualStep = math.min(rpMoveStep, distance - rpMinDistance) myRoot.CFrame = myRoot.CFrame + (direction.Unit * actualStep) end end -- ============================================================ -- DIRECTIONAL ANIMATION PLAYBACK -- ============================================================ -- holdLoop: false (default) reproduces the original behavior — quickly -- play to the target frame, then freeze there until the key is released. -- holdLoop: true instead keeps the animation genuinely looping at -- speedMult for as long as the key stays down, and lets stopAnim's -- normal blend-out handle the release. local function playAnim(key, assetId, speedMult, holdLoop) if not animator then return end speedMult = speedMult or 1 if speedMult <= 0 then speedMult = 0.01 end local wantLooped = holdLoop and true or false local track = animTracks[key] if not track or not track.Animation or track.Animation.AnimationId ~= "rbxassetid://"..assetId or track.Looped ~= wantLooped then destroyTrack(key) local ok, result = pcall(function() local anim = Instance.new("Animation") anim.AnimationId = "rbxassetid://"..assetId local t = animator:LoadAnimation(anim) t.Priority = PRIORITY; t.Looped = wantLooped return t end) if not ok then warn("[FNF] Load fail: "..assetId); return end track = result; animTracks[key] = track end if wantLooped then -- HOLD-TO-SUSTAIN MODE: just keep it looping at speedMult; release -- is handled by stopAnim()'s normal blend-out below. track:Play(BLEND_IN) track:AdjustSpeed(speedMult) else -- ORIGINAL "SNAP TO POSE" MODE: quickly play to the target frame -- then freeze there until release. local baseReach = FRAME_TARGET / 60 local reach = baseReach / speedMult track:Play(BLEND_IN) track:AdjustSpeed(track.Length > 0 and (track.Length / reach) or 10) task.delay(reach, function() if track and track.IsPlaying then track:AdjustSpeed(0) track.TimePosition = math.max(0, track.Length - 0.01) end end) end StatusState.currentAnimName = key StatusState.currentAnimId = assetId StatusState.speedMult = speedMult StatusState.playing = true if refreshStatusUI then refreshStatusUI() end updatePreviewAnimation(assetId, key) pushRecent(assetId, key) end local function stopAnim(key) local t = animTracks[key] if t and t.IsPlaying then t:Stop(BLEND_OUT) end StatusState.playing = false if refreshStatusUI then refreshStatusUI() end end -- Rolls which animation id to actually use for a given binding's press. -- Alt slots are checked in order, each claiming its configured % slice of -- a 0-100 roll; anything left uncovered (including "no alts configured -- at all") falls back to the binding's primary animation id. local function rollBindingAnimId(row) if row.alts and #row.alts > 0 then local roll = math.random() * 100 local acc = 0 for _, alt in ipairs(row.alts) do acc += alt.chance if roll <= acc and alt.id ~= "" then return alt.id end end end return row.id end -- ============================================================ -- ARROW HUD REACTIONS -- ============================================================ local function hitArrow(dir) local f = arrowHudFrames[dir] if not f then return end TweenService:Create(f.bg, TweenInfo.new(0.05, Enum.EasingStyle.Linear), { BackgroundColor3 = ARROW_COLORS[dir].hit, Size = UDim2.new(0, ARROW_HIT_SIZE, 0, ARROW_HIT_SIZE), }):Play() if f.stroke then TweenService:Create(f.stroke, TweenInfo.new(0.05, Enum.EasingStyle.Linear), { Thickness = 4 }):Play() end if f.glow then f.glow.Visible = true TweenService:Create(f.glow, TweenInfo.new(0.05), { BackgroundTransparency = 0.35 }):Play() end end local function releaseArrow(dir) local f = arrowHudFrames[dir] if not f then return end TweenService:Create(f.bg, TweenInfo.new(0.1, Enum.EasingStyle.Linear), { BackgroundColor3 = ARROW_COLORS[dir].idle, Size = UDim2.new(0, ARROW_SIZE, 0, ARROW_SIZE), }):Play() if f.stroke then TweenService:Create(f.stroke, TweenInfo.new(0.1, Enum.EasingStyle.Linear), { Thickness = 2 }):Play() end if f.glow then local tw = TweenService:Create(f.glow, TweenInfo.new(0.15), { BackgroundTransparency = 1 }) tw:Play() tw.Completed:Connect(function() if f.glow then f.glow.Visible = false end end) end end -- ============================================================ -- AFTERIMAGE HELPER -- ============================================================ local function spawnAfterimage(char) if not char then return end -- Briefly allow cloning in case the game disabled it local oldArch = char.Archivable char.Archivable = true local ok, clone = pcall(function() return char:Clone() end) char.Archivable = oldArch if not ok or not clone then return end -- Strip out logic and configure parts for the ghost effect for _, v in ipairs(clone:GetDescendants()) do if v:IsA("Script") or v:IsA("LocalScript") then v:Destroy() elseif v:IsA("BasePart") then v.CanCollide = false v.Anchored = true v.Massless = true v.Material = Enum.Material.ForceField -- Gives a slick, ghostly energy vibe -- Fade out parts smoothly TweenService:Create(v, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {Transparency = 1}):Play() elseif v:IsA("Decal") or v:IsA("Texture") or v:IsA("SurfaceAppearance") then TweenService:Create(v, TweenInfo.new(0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {Transparency = 1}):Play() end end -- Make sure the clone stays frozen exactly where it spawned local root = clone:FindFirstChild("HumanoidRootPart") or clone.PrimaryPart if root then root.Anchored = true end clone.Parent = workspace -- Delete the clone entirely once the fade tween is done task.delay(0.45, function() if clone then clone:Destroy() end end) end -- ============================================================ -- IDLE ANIMATION -- ============================================================ local function idleSpeedFromBPM() if not BASE_BPM or BASE_BPM <= 0 then return 1 end return idleBPM / BASE_BPM end local function loadIdleTrack(assetId, shouldLoop) destroyIdleTrack() if not animator then return nil end local ok, result = pcall(function() local anim = Instance.new("Animation") anim.AnimationId = "rbxassetid://"..assetId local t = animator:LoadAnimation(anim) t.Priority = Enum.AnimationPriority.Idle; t.Looped = shouldLoop return t end) if not ok then return nil end return result end local function playIdleOnce(assetId, cb) local t = loadIdleTrack(assetId, false) if not t then return end idleTrack = t; t:Play(0.1) t:AdjustSpeed(idleSpeedFromBPM()) updatePreviewAnimation(assetId, "IDLE (once)") t.Stopped:Connect(function() if cb then cb() end end) end local function playIdleLoop(assetId) local t = loadIdleTrack(assetId, true) if not t then return end idleTrack = t; idleLooping = true; t:Play(0.1) t:AdjustSpeed(idleSpeedFromBPM()) updatePreviewAnimation(assetId, "IDLE (loop)") StatusState.playing = true if refreshStatusUI then refreshStatusUI() end end local function stopIdle() idleLooping = false if idleTrack and idleTrack.IsPlaying then idleTrack:Stop(0.2) end StatusState.playing = false if refreshStatusUI then refreshStatusUI() end end local function setIdleBPM(bpm) if bpm and bpm > 0 then idleBPM = bpm end StatusState.bpm = idleBPM if idleTrack and idleTrack.IsPlaying then idleTrack:AdjustSpeed(idleSpeedFromBPM()) end if refreshStatusUI then refreshStatusUI() end end local function setIdleBaseBPM(bpm) if bpm and bpm > 0 then BASE_BPM = bpm end if idleTrack and idleTrack.IsPlaying then idleTrack:AdjustSpeed(idleSpeedFromBPM()) end end -- ============================================================ -- ANIMATION BPM DETECTOR -- ============================================================ -- Tap mode: call this every time a directional anim is triggered by the -- player. Keeps a rolling window of press timestamps and converts the -- average gap between them into BPM. local function registerBpmTap() local now = os.clock() table.insert(bpmDetectTaps, now) while #bpmDetectTaps > BPM_TAP_WINDOW do table.remove(bpmDetectTaps, 1) end if #bpmDetectTaps < 3 then return nil end local gaps = {} for i = 2, #bpmDetectTaps do local gap = bpmDetectTaps[i] - bpmDetectTaps[i-1] if gap > 0.08 and gap < 3 then table.insert(gaps, gap) end end if #gaps == 0 then return nil end -- discard outliers more than 2x away from the median gap, then average table.sort(gaps) local median = gaps[math.ceil(#gaps/2)] local sum, count = 0, 0 for _, g in ipairs(gaps) do if g < median * 2 and g > median * 0.5 then sum = sum + g; count = count + 1 end end if count == 0 then return nil end local avgGap = sum / count detectedBPM = math.floor((60 / avgGap) + 0.5) return detectedBPM end -- Idle-length mode: BPM = (beats per loop * 60) / animation length in seconds. -- Reads whatever idle track is currently loaded (call after playIdleLoop/Once). local function detectBpmFromIdleLength(beatsPerLoop) beatsPerLoop = beatsPerLoop or beatsPerIdleLoop if not idleTrack or not idleTrack.Length or idleTrack.Length <= 0 then return nil end -- Length reflects the un-adjusted animation, AdjustSpeed doesn't change it, -- so this is the authored duration regardless of current playback speed. local bpm = (beatsPerLoop * 60) / idleTrack.Length detectedBPM = math.floor(bpm + 0.5) return detectedBPM end -- ============================================================ -- SHARED MUSIC PLAYER (no VC) -- ============================================================ -- Waits for the companion server script's RemoteEvent. If that server -- script hasn't been added to the place yet, this just times out and -- the music section reports "server script missing" instead of erroring. local function setupMusicRemote() local ok, remote = pcall(function() return ReplicatedStorage:WaitForChild("FNFMusicRemote", 8) end) if ok and remote then musicRemote = remote end return musicRemote ~= nil end local function setMusicStatus(text, color) if musicStatusLbl then musicStatusLbl.Text = text if color then musicStatusLbl.TextColor3 = color end end end -- Watches the actual replicated Sound object (created server-side) to -- report real load/playback state — this reflects reality for every -- player, not just an optimistic guess on the requesting client. local function watchMusicStatus(expectedId) task.spawn(function() local myTick = musicRequestTick local deadline = os.clock() + 8 local snd = nil while os.clock() < deadline and myTick == musicRequestTick do snd = SoundService:FindFirstChild(MUSIC_SOUND_NAME) if snd and snd.SoundId:find(expectedId, 1, true) then break end task.wait(0.2) end if myTick ~= musicRequestTick then return end -- a newer request superseded this one if not snd or not snd.SoundId:find(expectedId, 1, true) then setMusicStatus("✖ NO SERVER SCRIPT FOUND", Color3.fromRGB(255,80,80)) return end -- give it a moment to load, then check IsLoaded local loadDeadline = os.clock() + 6 while os.clock() < loadDeadline and myTick == musicRequestTick do if snd.IsLoaded then break end task.wait(0.15) end if myTick ~= musicRequestTick then return end if snd.IsLoaded and snd.IsPlaying then setMusicStatus("✔ PLAYING — allowed in this game", Color3.fromRGB(80,220,120)) elseif snd.IsLoaded then setMusicStatus("● loaded, not playing", Color3.fromRGB(255,180,60)) else setMusicStatus("✖ NOT PLAYABLE HERE (blocked/private asset)", Color3.fromRGB(255,80,80)) end end) end local function playMusicTrack(id) if not musicRemote then setMusicStatus("✖ NO SERVER SCRIPT FOUND", Color3.fromRGB(255,80,80)) return end musicRequestTick = musicRequestTick + 1 setMusicStatus("⏳ requesting...", Color3.fromRGB(0,200,255)) musicRemote:FireServer("play", id) watchMusicStatus(id) end local function stopMusicTrack() if not musicRemote then return end musicRequestTick = musicRequestTick + 1 musicRemote:FireServer("stop") setMusicStatus("● stopped", Color3.fromRGB(160,80,80)) end -- ============================================================ -- ANIMATION SEQUENCE MAKER -- ============================================================ local function playSequence() if seqPlaying or #sequence == 0 or not animator then return end seqPlaying = true task.spawn(function() for _, step in ipairs(sequence) do if not seqPlaying then break end if step.id and step.id ~= "" then destroySeqTrack() local ok, result = pcall(function() local anim = Instance.new("Animation") anim.AnimationId = "rbxassetid://"..step.id local t = animator:LoadAnimation(anim) t.Priority = PRIORITY; t.Looped = false return t end) if ok then seqTrack = result; seqTrack:Play(0) updatePreviewAnimation(step.id, "SEQUENCE") end end task.wait(math.max(0.01, step.delay or 0.5)) end destroySeqTrack() seqPlaying = false end) end local function stopSequence() seqPlaying = false destroySeqTrack() end -- ============================================================ -- PLAYLIST MANAGER -- ============================================================ local function playPlaylist() if playlistPlaying or #playlist == 0 or not animator then return end playlistPlaying = true task.spawn(function() while playlistPlaying do local step = playlist[playlistIndex] if step and step.id and step.id ~= "" then destroySeqTrack() local ok, result = pcall(function() local anim = Instance.new("Animation") anim.AnimationId = "rbxassetid://"..step.id local t = animator:LoadAnimation(anim) t.Priority = PRIORITY; t.Looped = false return t end) if ok then seqTrack = result; seqTrack:Play(0) updatePreviewAnimation(step.id, step.name or ("Playlist #"..playlistIndex)) end end task.wait(math.max(0.05, (step and step.delay) or 1)) if not playlistPlaying then break end playlistIndex = playlistIndex + 1 if playlistIndex > #playlist then playlistIndex = 1 end if refreshPlaylistUI then refreshPlaylistUI() end end destroySeqTrack() end) end local function stopPlaylist() playlistPlaying = false destroySeqTrack() end local function nextPlaylistTrack() if #playlist == 0 then return end playlistIndex = playlistIndex + 1 if playlistIndex > #playlist then playlistIndex = 1 end if refreshPlaylistUI then refreshPlaylistUI() end end local function prevPlaylistTrack() if #playlist == 0 then return end playlistIndex = playlistIndex - 1 if playlistIndex < 1 then playlistIndex = #playlist end if refreshPlaylistUI then refreshPlaylistUI() end end -- ============================================================ -- INPUT HANDLER -- ============================================================ local lastNoteTime = 0 local DOUBLE_NOTE_WINDOW = 0.08 -- 80 milliseconds to count as a "double note" local function onInput(input, _gp) if rebindingRow then local kn = input.KeyCode and input.KeyCode.Name or "Unknown" if input.UserInputState == Enum.UserInputState.Begin and kn ~= "Unknown" and not kn:match("Mouse") then rebindingRow.key = kn rebindingRow.keyBtn.Text = kn rebindingRow.keyBtn.BackgroundColor3 = Color3.fromRGB(30, 20, 45) rebindingRow = nil end return end for _, row in ipairs(CurrentBindings) do if input.KeyCode and input.KeyCode.Name == row.key then if input.UserInputState == Enum.UserInputState.Begin then -- Check for double notes local now = os.clock() local isDouble = (now - lastNoteTime) <= DOUBLE_NOTE_WINDOW lastNoteTime = now -- INTERRUPT: Instantly stop ALL other active directional animations for k, track in pairs(animTracks) do if track.IsPlaying then track:Stop(0) -- 0 second blend-out = instant snap end end -- TRIGGER AFTERIMAGE: If we hit a double note, spawn the ghost if isDouble then spawnAfterimage(character) end -- Play the new animation local chosenId = rollBindingAnimId(row) playAnim(row.key, chosenId, row.speedMult, row.holdLoop) hitArrow(row.dir) swapFace(row.dir) triggerRPMovement() registerBpmTap() if refreshBpmDetectorUI then refreshBpmDetectorUI() end elseif input.UserInputState == Enum.UserInputState.End then stopAnim(row.key) releaseArrow(row.dir) restoreFace() end end end end -- ============================================================ -- CHARACTER SETUP -- ============================================================ local function buildPreviewClone(char) if not previewCloneContainer then return end if previewClone then previewClone:Destroy(); previewClone = nil end -- Archivable must be true for Clone() to work; some games/anti-cheat set it false. local wasArchivable = char.Archivable if not wasArchivable then char.Archivable = true end local ok, clone = pcall(function() return char:Clone() end) if not wasArchivable then char.Archivable = wasArchivable end if not ok or not clone then notify("Preview failed: character isn't clonable right now", "error") return end -- strip scripts so the clone doesn't run game logic for _, d in ipairs(clone:GetDescendants()) do if d:IsA("Script") or d:IsA("LocalScript") then d:Destroy() end end clone.Name = "PreviewClone" clone.Parent = previewCloneContainer local hum = clone:FindFirstChildOfClass("Humanoid") if hum then hum.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None previewAnimator = hum:FindFirstChildOfClass("Animator") or Instance.new("Animator", hum) else notify("Preview clone has no Humanoid — can't animate it", "warn") end -- Anchor ONLY the root part. ViewportFrame contents aren't part of the -- main physics simulation, so unanchored limbs won't fall — but they DO -- need to stay unanchored for the Animator's Motor6D joints to pose them. -- Anchoring every part (as an earlier version of this script did) freezes -- each limb independently and blocks animations from moving anything. local root = clone:FindFirstChild("HumanoidRootPart") or clone.PrimaryPart if root then root.Anchored = true end local okPivot = pcall(function() clone:PivotTo(CFrame.new(0, 3, 0)) end) if not okPivot then local hrp = clone:FindFirstChild("HumanoidRootPart") or clone.PrimaryPart if hrp then clone:SetPrimaryPartCFrame(CFrame.new(0, 3, 0)) end end previewClone = clone end local function setupCharacter(char) cleanupAll(); character = char pcall(function() character:WaitForChild("HumanoidRootPart", 10) end) animator = getAnimator(char) if not animator then return end initFace(char) if legsLocked then applyLegsLock(true) end table.insert(connections, UserInputService.InputBegan:Connect(onInput)) table.insert(connections, UserInputService.InputEnded:Connect(onInput)) local hum = char:FindFirstChildOfClass("Humanoid") if hum then table.insert(connections, hum.Died:Connect(cleanupAll)) end task.defer(buildPreviewClone, char) log("Character ready.") notify("Character loaded — ready to inject", "success") end if LocalPlayer.Character then task.spawn(setupCharacter, LocalPlayer.Character) end LocalPlayer.CharacterAdded:Connect(function(c) task.spawn(setupCharacter, c) end) -- ============================================================ -- NEARBY PLAYER ANIM SCANNER -- Reads whatever animation IDs are currently playing on other -- players' characters. This only reads data Roblox already -- replicates to every client (the same way you see other -- players' emotes/moves play out in real time) — it can't see -- anything that isn't actively playing right now. -- ============================================================ local function scanPlayingAnimations() local found = {} for _, plr in ipairs(Players:GetPlayers()) do if plr ~= LocalPlayer and plr.Character then local hum = plr.Character:FindFirstChildOfClass("Humanoid") local anr = hum and hum:FindFirstChildOfClass("Animator") if anr then for _, track in ipairs(anr:GetPlayingAnimationTracks()) do local animObj = track.Animation if animObj and animObj.AnimationId ~= "" then local id = animObj.AnimationId:match("%d+") or "" if id ~= "" then table.insert(found, { player = plr.Name, id = id }) end end end end end end return found end local function uiCorner(p, r) local c = Instance.new("UICorner"); c.CornerRadius = r or UDim.new(0,8); c.Parent = p end local function uiStroke(p, col, th) local s = Instance.new("UIStroke"); s.Color = col; s.Thickness = th or 1.5; s.Parent = p; return s end local function makeLabel(parent, text, pos, size, color, xAlign, ts) local l = Instance.new("TextLabel") l.Size = size; l.Position = pos; l.BackgroundTransparency = 1 l.Text = text; l.TextColor3 = color or Color3.fromRGB(180,180,180) l.TextSize = ts or 11; l.Font = Enum.Font.GothamBold l.TextXAlignment = xAlign or Enum.TextXAlignment.Center l.Parent = parent; return l end local function makeBtn(parent, text, pos, size, bg, tc, ts) local b = Instance.new("TextButton") b.Size = size; b.Position = pos; b.BackgroundColor3 = bg b.Text = text; b.TextColor3 = tc or Color3.fromRGB(255,255,255) b.Font = Enum.Font.GothamBold; b.TextSize = ts or 12 b.BorderSizePixel = 0; b.Parent = parent; b.AutoButtonColor = false uiCorner(b) -- hover / glow behaviour local baseSize = size local hoverGlow = uiStroke(b, Color3.fromRGB(255,255,255), 0) hoverGlow.Transparency = 1 b.MouseEnter:Connect(function() TweenService:Create(b, TweenInfo.new(0.12, Enum.EasingStyle.Quad), { Size = UDim2.new(baseSize.X.Scale, baseSize.X.Offset + 4, baseSize.Y.Scale, baseSize.Y.Offset + 2) }):Play() TweenService:Create(hoverGlow, TweenInfo.new(0.12), { Thickness = 2, Transparency = 0.2 }):Play() end) b.MouseLeave:Connect(function() TweenService:Create(b, TweenInfo.new(0.15, Enum.EasingStyle.Quad), { Size = baseSize }):Play() TweenService:Create(hoverGlow, TweenInfo.new(0.15), { Thickness = 0, Transparency = 1 }):Play() end) b.MouseButton1Down:Connect(function() TweenService:Create(b, TweenInfo.new(0.06), { Size = UDim2.new(baseSize.X.Scale, baseSize.X.Offset - 3, baseSize.Y.Scale, baseSize.Y.Offset - 3) }):Play() end) b.MouseButton1Up:Connect(function() TweenService:Create(b, TweenInfo.new(0.08), { Size = baseSize }):Play() end) return b end local function makeDivider(parent, yPos, color) local d = Instance.new("Frame"); d.Size = UDim2.new(1,-16,0,1) d.Position = UDim2.new(0,8,0,yPos) d.BackgroundColor3 = color or Color3.fromRGB(0,200,255) d.BorderSizePixel = 0; d.Parent = parent return d end local function makeBox(parent, pos, size, placeholder, default) local b = Instance.new("TextBox") b.Size = size; b.Position = pos b.BackgroundColor3 = Color3.fromRGB(22,22,32) b.PlaceholderText = placeholder or ""; b.PlaceholderColor3 = Color3.fromRGB(90,90,110) b.Text = default or ""; b.TextColor3 = Color3.fromRGB(255,255,255) b.ClearTextOnFocus = false; b.Font = Enum.Font.Code; b.TextSize = 11 b.Parent = parent; uiCorner(b); return b end -- Draggable slider: track + fill + handle. Works with mouse and touch. local function makeSlider(parent, pos, size, minVal, maxVal, default, onChange) local track = Instance.new("Frame") track.Size = size; track.Position = pos track.BackgroundColor3 = Color3.fromRGB(28,28,42) track.BorderSizePixel = 0; track.Active = true track.Parent = parent; uiCorner(track, UDim.new(0,6)) local fill = Instance.new("Frame") fill.BackgroundColor3 = Color3.fromRGB(255,80,180) fill.BorderSizePixel = 0; fill.Size = UDim2.new(0,0,1,0) fill.Parent = track; uiCorner(fill, UDim.new(0,6)) regBg(fill) local handle = Instance.new("Frame") handle.Size = UDim2.new(0,14,0,14) handle.AnchorPoint = Vector2.new(0.5,0.5) handle.Position = UDim2.new(0,0,0.5,0) handle.BackgroundColor3 = Color3.fromRGB(255,255,255) handle.BorderSizePixel = 0; handle.ZIndex = 2 handle.Parent = track; uiCorner(handle, UDim.new(1,0)) local hStroke = uiStroke(handle, Color3.fromRGB(255,80,180), 2) regStroke(hStroke) local value = default local dragging = false local function setValue(v, fire) v = math.clamp(v, minVal, maxVal) value = v local pct = (maxVal > minVal) and (v - minVal) / (maxVal - minVal) or 0 fill.Size = UDim2.new(pct, 0, 1, 0) handle.Position = UDim2.new(pct, 0, 0.5, 0) if fire and onChange then onChange(v) end end local function updateFromX(x) local abs = track.AbsolutePosition.X local w = track.AbsoluteSize.X if w <= 0 then return end local pct = math.clamp((x - abs) / w, 0, 1) setValue(minVal + pct * (maxVal - minVal), true) end local function tryBegin(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true updateFromX(input.Position.X) end end track.InputBegan:Connect(tryBegin) handle.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = true end end) UserInputService.InputChanged:Connect(function(input) if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then updateFromX(input.Position.X) end end) UserInputService.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then dragging = false end end) setValue(default, false) return { track = track, fill = fill, handle = handle, setValue = setValue, getValue = function() return value end } end -- ============================================================ -- PER-BINDING ALT-ANIMATION LIST UI HELPER -- ============================================================ -- Renders the current alt-animation entries for one binding row into a -- given ScrollingFrame, with a remove button per entry and a running -- total % footer. Self-referencing so removal can re-render in place. local function refreshRowAltList(row, listFrame) for _, c in ipairs(listFrame:GetChildren()) do if c:IsA("Frame") or c:IsA("TextLabel") then c:Destroy() end end for j, alt in ipairs(row.alts) do local entry = Instance.new("Frame") entry.Size = UDim2.new(1,-4,0,20) entry.BackgroundColor3 = Color3.fromRGB(24,24,36) entry.BorderSizePixel = 0 entry.LayoutOrder = j entry.Parent = listFrame uiCorner(entry, UDim.new(0,4)) makeLabel(entry, string.format("⭐ %s — %d%%", alt.id, alt.chance), UDim2.new(0,6,0,0), UDim2.new(1,-28,1,0), Color3.fromRGB(200,200,215), Enum.TextXAlignment.Left, 9) local rem = makeBtn(entry, "✕", UDim2.new(1,-22,0,0), UDim2.new(0,20,0,20), Color3.fromRGB(170,35,35), nil, 9) rem.MouseButton1Click:Connect(function() table.remove(row.alts, j) refreshRowAltList(row, listFrame) end) end local total = 0 for _, a in ipairs(row.alts) do total += a.chance end local footer = makeLabel(listFrame, string.format("Total: %d%% (remainder = primary anim)", total), UDim2.new(1,-4,0,14), UDim2.new(1,-4,0,14), (total > 100) and Color3.fromRGB(255,80,80) or Color3.fromRGB(140,140,160), Enum.TextXAlignment.Right, 9) footer.LayoutOrder = 9999 end -- ============================================================ -- ARROW HUD BUILD -- ============================================================ local function buildArrowHud() local old = PlayerGui:FindFirstChild("FNFArrowHud") if old then old:Destroy() end local sg = Instance.new("ScreenGui") sg.Name = "FNFArrowHud"; sg.ResetOnSpawn = false sg.ZIndexBehavior = Enum.ZIndexBehavior.Sibling; sg.Parent = PlayerGui local totalW = #ARROW_ORDER * ARROW_SIZE + (#ARROW_ORDER - 1) * ARROW_GAP local bottomY = 50 -- px from bottom for i, dir in ipairs(ARROW_ORDER) do local colors = ARROW_COLORS[dir] local cx = -totalW/2 + (i-1)*(ARROW_SIZE + ARROW_GAP) + ARROW_SIZE/2 local cy = -(ARROW_SIZE/2 + bottomY) local bg = Instance.new("Frame") bg.AnchorPoint = Vector2.new(0.5, 0.5) bg.Size = UDim2.new(0, ARROW_SIZE, 0, ARROW_SIZE) bg.Position = UDim2.new(0.5, cx, 1, cy) bg.BackgroundColor3 = colors.idle bg.BorderSizePixel = 0; bg.Parent = sg uiCorner(bg, UDim.new(0, 10)) local stroke = uiStroke(bg, Color3.fromRGB(255,255,255), 2) local glow = Instance.new("Frame") glow.Size = UDim2.new(1,0,1,0); glow.BackgroundTransparency = 0.7 glow.BackgroundColor3 = colors.hit; glow.BorderSizePixel = 0; glow.Parent = bg uiCorner(glow, UDim.new(0,10)) glow.Visible = false local lbl = Instance.new("TextLabel") lbl.Size = UDim2.new(1,0,1,0); lbl.BackgroundTransparency = 1 lbl.Text = ARROW_SYMBOLS[dir] lbl.TextColor3 = Color3.fromRGB(255,255,255) lbl.TextSize = 26; lbl.Font = Enum.Font.GothamBold; lbl.Parent = bg arrowHudFrames[dir] = { bg = bg, lbl = lbl, stroke = stroke, glow = glow } end end -- ============================================================ -- MIC SPECTROGRAM SETUP -- ============================================================ -- Tries to wire up Roblox's Audio API (AudioDeviceInput -> AudioAnalyzer) -- so the spectrogram can react to real microphone input. This part of the -- engine is newer and can vary by client/game settings, so every step is -- pcall-guarded. If anything fails, micAvailable stays false and the -- spectrogram loop below falls back to reacting to the animation/BPM state -- instead (same as the equalizer), so the widget still looks alive. local function setupMicSpectrogram() micAvailable = false local ok = pcall(function() micDeviceInput = Instance.new("AudioDeviceInput") micDeviceInput.Parent = LocalPlayer micAnalyzer = Instance.new("AudioAnalyzer") micAnalyzer.WindowSize = 1024 micAnalyzer.Parent = LocalPlayer micWire = Instance.new("Wire") micWire.SourceInstance = micDeviceInput micWire.TargetInstance = micAnalyzer micWire.Parent = micAnalyzer micDeviceInput.Active = true end) micAvailable = ok and micDeviceInput ~= nil and micAnalyzer ~= nil if not micAvailable then log("Mic spectrogram unavailable on this client/game — using animation-synced fallback") end return micAvailable end -- ============================================================ -- BEAT / VISUALIZER LOOPS (equalizer, waveform, floating notes, beat pulse, particles) -- ============================================================ local eqBars, waveBars, beatDot, notesLayer, particleLayer = nil, nil, nil, nil, nil local function startVisualizerLoops() -- Equalizer bars: random-ish heights, faster while playing task.spawn(function() while true do local speed = StatusState.playing and 0.08 or 0.22 task.wait(speed) if eqBars then for _, bar in ipairs(eqBars) do local h = StatusState.playing and (0.25 + math.random() * 0.75) or (0.08 + math.random() * 0.2) TweenService:Create(bar, TweenInfo.new(speed, Enum.EasingStyle.Sine), { Size = UDim2.new(1, 0, h, 0) }):Play() end end end end) -- BPM waveform: sine ripple scaled to bpm task.spawn(function() local t = 0 while true do RunService.Heartbeat:Wait() t = t + (StatusState.bpm / 60) * 0.03 if waveBars then for i, bar in ipairs(waveBars) do local h = 0.15 + 0.35 * (0.5 + 0.5 * math.sin(t + i * 0.6)) bar.Size = UDim2.new(1, 0, h, 0) end end end end) -- Beat sync pulse: fires exactly on the beat interval task.spawn(function() while true do local interval = 60 / math.max(1, StatusState.bpm) task.wait(interval) if beatDot then beatDot.Size = UDim2.new(0, 22, 0, 22) TweenService:Create(beatDot, TweenInfo.new(interval * 0.85, Enum.EasingStyle.Quad), { Size = UDim2.new(0, 10, 0, 10) }):Play() end end end) -- Floating music notes task.spawn(function() local symbols = { "♪", "♫", "♬", "♩" } while true do task.wait(0.7 + math.random() * 0.8) if notesLayer then local n = Instance.new("TextLabel") n.Size = UDim2.new(0, 20, 0, 20) n.Position = UDim2.new(math.random(), 0, 1, 0) n.BackgroundTransparency = 1 n.Text = symbols[math.random(1, #symbols)] n.TextColor3 = THEMES[currentThemeName].accent n.TextTransparency = 0.2 n.Font = Enum.Font.GothamBold n.TextSize = 14 + math.random(0, 10) n.Parent = notesLayer regText(n) local tw = TweenService:Create(n, TweenInfo.new(3 + math.random()*2, Enum.EasingStyle.Linear), { Position = UDim2.new(n.Position.X.Scale, 0, -0.15, 0), TextTransparency = 1 }) tw:Play() tw.Completed:Connect(function() n:Destroy() end) end end end) -- Ambient particles task.spawn(function() while true do task.wait(0.4 + math.random() * 0.5) if particleLayer then local p = Instance.new("Frame") local sz = math.random(2, 4) p.Size = UDim2.new(0, sz, 0, sz) p.Position = UDim2.new(math.random(), 0, 1, 0) p.BackgroundColor3 = THEMES[currentThemeName].accent2 p.BackgroundTransparency = 0.3 p.BorderSizePixel = 0 p.Parent = particleLayer uiCorner(p, UDim.new(1,0)) local tw = TweenService:Create(p, TweenInfo.new(1.5 + math.random(), Enum.EasingStyle.Sine), { Position = UDim2.new(p.Position.X.Scale + (math.random()-0.5)*0.1, 0, -0.05, 0), BackgroundTransparency = 1 }) tw:Play() tw.Completed:Connect(function() p:Destroy() end) end end end) -- Vinyl record spin: RPM scales with whatever's currently playing — -- directional anim speedMult if one is active, otherwise idle BPM ratio. task.spawn(function() while true do RunService.Heartbeat:Wait() if vinylImage then local rpm if StatusState.playing and StatusState.speedMult then rpm = vinylBaseRPM * StatusState.speedMult else rpm = vinylBaseRPM * idleSpeedFromBPM() end vinylRotation = (vinylRotation + (rpm / 60) * 360 * (1/60)) % 360 vinylImage.Rotation = vinylRotation end end end) -- Spectrogram: real mic spectrum if available, otherwise an -- animation/BPM-synced fallback so the bars are never just dead. task.spawn(function() while true do task.wait(0.05) if #specBars > 0 then if micAvailable and micAnalyzer then local ok, spectrum = pcall(function() return micAnalyzer:GetSpectrum() end) if ok and spectrum then local n = #spectrum for i, bar in ipairs(specBars) do local idx = math.max(1, math.floor((i / #specBars) * n)) local mag = spectrum[idx] or 0 local h = math.clamp(mag * 2, 0.05, 1) TweenService:Create(bar, TweenInfo.new(0.05), { Size = UDim2.new(1,0,h,0) }):Play() end else micAvailable = false -- analyzer stopped working mid-session, fall back end else for i, bar in ipairs(specBars) do local base = StatusState.playing and 0.3 or 0.08 local h = base + math.random() * (StatusState.playing and 0.6 or 0.15) TweenService:Create(bar, TweenInfo.new(0.05), { Size = UDim2.new(1,0,h,0) }):Play() end end end end end) -- CRT flicker: tiny random transparency jitter on the scanline overlay task.spawn(function() while true do task.wait(0.08 + math.random() * 0.15) if crtEnabled and crtOverlay then crtOverlay.BackgroundTransparency = 0.82 + math.random() * 0.08 end end end) -- (preview camera orbit loop lives in buildGUI, started once previewCam exists) end -- ============================================================ -- MAIN GUI BUILD -- ============================================================ local function buildGUI() local old = PlayerGui:FindFirstChild("FNFInjectorGui") if old then old:Destroy() end local sg = Instance.new("ScreenGui") sg.Name = "FNFInjectorGui" sg.ResetOnSpawn = false sg.ZIndexBehavior = Enum.ZIndexBehavior.Sibling sg.Parent = PlayerGui -- Notification stack (top-right of screen, independent of main panel) notifyContainer = Instance.new("Frame") notifyContainer.Size = UDim2.new(0, 260, 1, -20) notifyContainer.Position = UDim2.new(1, -270, 0, 10) notifyContainer.BackgroundTransparency = 1 notifyContainer.Parent = sg local notifyLayout = Instance.new("UIListLayout") notifyLayout.SortOrder = Enum.SortOrder.LayoutOrder notifyLayout.Padding = UDim.new(0, 6) notifyLayout.Parent = notifyContainer local MAIN_W, MAIN_H = 430, 600 local main = Instance.new("Frame") main.Name = "Main"; main.Size = UDim2.new(0,MAIN_W,0,MAIN_H) main.Position = UDim2.new(0,60,0,60) main.BackgroundColor3 = Color3.fromRGB(13,13,18) main.BorderSizePixel = 0; main.Active = true; main.Draggable = true main.Parent = sg; uiCorner(main, UDim.new(0,10)) local mainStroke = uiStroke(main, Color3.fromRGB(255,80,180), 2) regStroke(mainStroke) -- decorative background particle / notes layers, clipped to main panel particleLayer = Instance.new("Frame") particleLayer.Size = UDim2.new(1,0,1,0); particleLayer.BackgroundTransparency = 1 particleLayer.ClipsDescendants = true; particleLayer.ZIndex = 0; particleLayer.Parent = main notesLayer = Instance.new("Frame") notesLayer.Size = UDim2.new(1,0,1,0); notesLayer.BackgroundTransparency = 1 notesLayer.ClipsDescendants = true; notesLayer.ZIndex = 0; notesLayer.Parent = main -- CRT overlay: green tint + scanlines, sits above everything else in -- the panel but ignores input so it never blocks clicks. Hidden by -- default; toggled from the FX tab. crtOverlay = Instance.new("Frame") crtOverlay.Size = UDim2.new(1,0,1,0); crtOverlay.BackgroundColor3 = Color3.fromRGB(20,255,120) crtOverlay.BackgroundTransparency = 0.88 crtOverlay.BorderSizePixel = 0; crtOverlay.ZIndex = 50 crtOverlay.Visible = false crtOverlay.Parent = main local crtOverlayCorner = Instance.new("UICorner"); crtOverlayCorner.CornerRadius = UDim.new(0,10); crtOverlayCorner.Parent = crtOverlay local scanlineHolder = Instance.new("Frame") scanlineHolder.Size = UDim2.new(1,0,1,0); scanlineHolder.BackgroundTransparency = 1 scanlineHolder.ClipsDescendants = true; scanlineHolder.ZIndex = 51 scanlineHolder.Parent = crtOverlay local SCANLINE_GAP = 4 for y = 0, 700, SCANLINE_GAP do local line = Instance.new("Frame") line.Size = UDim2.new(1,0,0,1) line.Position = UDim2.new(0,0,0,y) line.BackgroundColor3 = Color3.fromRGB(0,0,0) line.BackgroundTransparency = 0.55 line.BorderSizePixel = 0 line.ZIndex = 51 line.Parent = scanlineHolder end -- Title bar local titleBar = Instance.new("Frame") titleBar.Size = UDim2.new(1,0,0,36); titleBar.BackgroundColor3 = Color3.fromRGB(22,8,32) titleBar.BorderSizePixel = 0; titleBar.Parent = main; uiCorner(titleBar, UDim.new(0,10)) local titleLbl = makeLabel(titleBar, "🎵 FNF ANIM INJECTOR v6.1", UDim2.new(0,12,0,0), UDim2.new(1,-80,1,0), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 13) regText(titleLbl) local toggleBtn = makeBtn(titleBar, "HIDE", UDim2.new(1,-68,0,5), UDim2.new(0,60,0,26), Color3.fromRGB(255,80,180)) regBg(toggleBtn) -- Tab bar local TAB_H = 32 local tabBar = Instance.new("Frame") tabBar.Size = UDim2.new(1,0,0,TAB_H); tabBar.Position = UDim2.new(0,0,0,36) tabBar.BackgroundColor3 = Color3.fromRGB(18,8,28) tabBar.BorderSizePixel = 0; tabBar.Parent = main -- Content area local contentArea = Instance.new("Frame") contentArea.Size = UDim2.new(1,0,1,-(36+TAB_H)) contentArea.Position = UDim2.new(0,0,0,36+TAB_H) contentArea.BackgroundTransparency = 1; contentArea.Parent = main -- ── Tabs ── local tabNames = { "📋 BINDS", "🎬 MAKER", "👁 PREVIEW", "❤ FAVES", "🎶 LIST", "🎛 FX", "⚙ SETTINGS" } local pages = {} local tabButtons = {} local tabW = math.floor(MAIN_W / #tabNames) for i, name in ipairs(tabNames) do local page = Instance.new("ScrollingFrame") page.Size = UDim2.new(1,0,1,0); page.BackgroundTransparency = 1 page.ScrollBarThickness = 4 page.CanvasSize = UDim2.new(0,0,0,0) page.AutomaticCanvasSize = Enum.AutomaticSize.Y page.Visible = (i == 1); page.Parent = contentArea pages[name] = page local tb = makeBtn(tabBar, name, UDim2.new(0,(i-1)*tabW,0,0), UDim2.new(0,tabW,1,0), i == 1 and Color3.fromRGB(255,80,180) or Color3.fromRGB(28,12,42), nil, 9) tabButtons[name] = tb if i == 1 then regBg(tb) end tb.MouseButton1Click:Connect(function() for n, p in pairs(pages) do p.Visible = (n == name) end for n, btn in pairs(tabButtons) do local isActive = (n == name) btn.BackgroundColor3 = isActive and THEMES[currentThemeName].accent or Color3.fromRGB(28,12,42) -- swap registration so theme/RGB cycling keeps following the active tab for j = #themedBgs, 1, -1 do if themedBgs[j] == btn then table.remove(themedBgs, j) end end if isActive then regBg(btn) end end end) end -- ============================================================ -- TAB 1 : BINDS + IDLE + BPM -- ============================================================ local pg1 = pages["📋 BINDS"] makeLabel(pg1, "DIR", UDim2.new(0,8,0,6), UDim2.new(0,36,0,20), Color3.fromRGB(255,80,180)) makeLabel(pg1, "KEY", UDim2.new(0,48,0,6), UDim2.new(0,62,0,20), Color3.fromRGB(255,80,180)) makeLabel(pg1, "ANIM ID", UDim2.new(0,116,0,6), UDim2.new(0,180,0,20),Color3.fromRGB(255,80,180)) makeLabel(pg1, "TEST", UDim2.new(0,308,0,6), UDim2.new(0,90,0,20), Color3.fromRGB(255,80,180)) makeDivider(pg1, 28, Color3.fromRGB(0,200,255)) -- ROW_H covers: dir/key/id/test line, speed slider line, hold-loop + -- alt-input line, and a small scrollable alt list — per binding row. local ROW_H = 150 local startY = 36 for i, row in ipairs(CurrentBindings) do local y = startY + (i-1)*ROW_H local dirColor = ARROW_COLORS[row.dir] and ARROW_COLORS[row.dir].hit or Color3.fromRGB(255,255,255) makeLabel(pg1, row.dir, UDim2.new(0,8,0,y), UDim2.new(0,36,0,28), dirColor) local keyBtn = makeBtn(pg1, row.key, UDim2.new(0,48,0,y), UDim2.new(0,60,0,28), Color3.fromRGB(25,35,48)) row.keyBtn = keyBtn keyBtn.MouseButton1Click:Connect(function() rebindingRow = row; keyBtn.Text = "..." keyBtn.BackgroundColor3 = Color3.fromRGB(0,200,255) end) local idBox = makeBox(pg1, UDim2.new(0,116,0,y), UDim2.new(0,180,0,28), "anim id", row.id) idBox.FocusLost:Connect(function() local n = idBox.Text:gsub("%D","") if n ~= "" then row.id = n; destroyTrack(row.key) else idBox.Text = row.id end end) local testBtn = makeBtn(pg1, "▶ TEST", UDim2.new(0,306,0,y), UDim2.new(0,90,0,28), Color3.fromRGB(0,180,230), nil, 11) testBtn.MouseButton1Down:Connect(function() local chosenId = rollBindingAnimId(row) playAnim(row.key, chosenId, row.speedMult, row.holdLoop); hitArrow(row.dir); swapFace(row.dir) end) testBtn.MouseButton1Up:Connect(function() stopAnim(row.key); releaseArrow(row.dir); restoreFace() end) local sy = y + 30 makeLabel(pg1, "SPD", UDim2.new(0,8,0,sy), UDim2.new(0,32,0,22), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local spdValLbl = makeLabel(pg1, string.format("x%.2f", row.speedMult), UDim2.new(0,280,0,sy), UDim2.new(0,112,0,22), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Right, 10) regText(spdValLbl) makeSlider(pg1, UDim2.new(0,42,0,sy+2), UDim2.new(0,232,0,14), 0.25, 3, row.speedMult, function(v) row.speedMult = v spdValLbl.Text = string.format("x%.2f", v) if StatusState.currentAnimName == row.key then StatusState.speedMult = v if refreshStatusUI then refreshStatusUI() end end end) -- ── HOLD-LOOP toggle + alt-animation entry inputs ── local sy2 = y + 58 local holdBtn = makeBtn(pg1, "🔁 HOLD-LOOP: OFF", UDim2.new(0,8,0,sy2), UDim2.new(0,140,0,24), Color3.fromRGB(35,35,55), nil, 9) holdBtn.MouseButton1Click:Connect(function() row.holdLoop = not row.holdLoop holdBtn.Text = row.holdLoop and "🔁 HOLD-LOOP: ON" or "🔁 HOLD-LOOP: OFF" holdBtn.BackgroundColor3 = row.holdLoop and Color3.fromRGB(0,180,230) or Color3.fromRGB(35,35,55) end) local altIdBox = makeBox(pg1, UDim2.new(0,154,0,sy2), UDim2.new(0,142,0,24), "alt anim id") local altPctBox = makeBox(pg1, UDim2.new(0,300,0,sy2), UDim2.new(0,52,0,24), "%") local addAltBtn = makeBtn(pg1, "+", UDim2.new(0,356,0,sy2), UDim2.new(0,40,0,24), Color3.fromRGB(60,190,120), nil, 12) -- ── mini scrolling list of this row's configured alts ── local sy3 = y + 86 local altListFrame = Instance.new("ScrollingFrame") altListFrame.Size = UDim2.new(1,-16,0,56) altListFrame.Position = UDim2.new(0,8,0,sy3) altListFrame.BackgroundColor3 = Color3.fromRGB(18,18,26) altListFrame.BorderSizePixel = 0 altListFrame.ScrollBarThickness = 3 altListFrame.CanvasSize = UDim2.new(0,0,0,0) altListFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y altListFrame.Parent = pg1 uiCorner(altListFrame, UDim.new(0,6)) local altListLayout = Instance.new("UIListLayout") altListLayout.Padding = UDim.new(0,2) altListLayout.Parent = altListFrame local altListPad = Instance.new("UIPadding") altListPad.PaddingTop = UDim.new(0,2); altListPad.PaddingLeft = UDim.new(0,2); altListPad.PaddingRight = UDim.new(0,2) altListPad.Parent = altListFrame addAltBtn.MouseButton1Click:Connect(function() local id = altIdBox.Text:gsub("%D","") local chance = tonumber(altPctBox.Text) if id == "" or not chance then notify("Enter an alt anim ID + % chance", "warn"); return end table.insert(row.alts, { id = id, chance = math.clamp(chance, 0, 100) }) altIdBox.Text = ""; altPctBox.Text = "" refreshRowAltList(row, altListFrame) end) refreshRowAltList(row, altListFrame) end -- Idle section local idleY = startY + #CurrentBindings * ROW_H + 6 makeDivider(pg1, idleY, Color3.fromRGB(255,80,180)) local idleTitleLbl = makeLabel(pg1, "🎵 IDLE ANIMATION", UDim2.new(0,8,0,idleY+6), UDim2.new(0,200,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left) regText(idleTitleLbl) local statusLbl = makeLabel(pg1, "● OFF", UDim2.new(0,200,0,idleY+6), UDim2.new(0,196,0,20), Color3.fromRGB(160,80,80), Enum.TextXAlignment.Right) local idleIdBox = makeBox(pg1, UDim2.new(0,8,0,idleY+30), UDim2.new(1,-16,0,28), "Paste idle anim ID...") local loopBtn = makeBtn(pg1, "⟳ LOOP", UDim2.new(0,8,0,idleY+64), UDim2.new(0,120,0,28), Color3.fromRGB(70,120,255)) local onceBtn = makeBtn(pg1, "▶ ONCE", UDim2.new(0,136,0,idleY+64), UDim2.new(0,120,0,28), Color3.fromRGB(60,190,120)) local idleStop = makeBtn(pg1, "■ STOP", UDim2.new(0,264,0,idleY+64), UDim2.new(0,130,0,28), Color3.fromRGB(190,50,50)) local function getIdleId() return idleIdBox.Text:gsub("%D","") end loopBtn.MouseButton1Click:Connect(function() local id = getIdleId(); if id == "" then notify("Enter an idle anim ID first", "warn"); return end playIdleLoop(id); statusLbl.Text = "● LOOPING"; statusLbl.TextColor3 = Color3.fromRGB(80,220,120) notify("Idle looping started", "success") end) onceBtn.MouseButton1Click:Connect(function() local id = getIdleId(); if id == "" then notify("Enter an idle anim ID first", "warn"); return end playIdleOnce(id, function() if not idleLooping then statusLbl.Text = "● OFF"; statusLbl.TextColor3 = Color3.fromRGB(160,80,80) end end) statusLbl.Text = "▶ PLAYING"; statusLbl.TextColor3 = Color3.fromRGB(0,200,255) end) idleStop.MouseButton1Click:Connect(function() stopIdle(); statusLbl.Text = "● OFF"; statusLbl.TextColor3 = Color3.fromRGB(160,80,80) end) -- BPM controls local bpmY = idleY + 98 makeLabel(pg1, "BPM", UDim2.new(0,8,0,bpmY), UDim2.new(0,38,0,28), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 11) local bpmBox = makeBox(pg1, UDim2.new(0,44,0,bpmY), UDim2.new(0,68,0,28), "100", tostring(idleBPM)) makeLabel(pg1, "BASE", UDim2.new(0,120,0,bpmY), UDim2.new(0,44,0,28), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local baseBox = makeBox(pg1, UDim2.new(0,164,0,bpmY), UDim2.new(0,68,0,28), "100", tostring(BASE_BPM)) local applyBpmBtn = makeBtn(pg1, "✓ APPLY", UDim2.new(0,240,0,bpmY), UDim2.new(0,154,0,28), Color3.fromRGB(70,120,255), nil, 11) local bpmSliderY = bpmY + 36 makeLabel(pg1, "DRAG", UDim2.new(0,8,0,bpmSliderY), UDim2.new(0,50,0,22), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local bpmValLbl = makeLabel(pg1, string.format("%d BPM", idleBPM), UDim2.new(0,300,0,bpmSliderY), UDim2.new(0,94,0,22), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Right, 11) regText(bpmValLbl) local bpmSlider = makeSlider(pg1, UDim2.new(0,62,0,bpmSliderY+2), UDim2.new(0,232,0,14), 40, 300, idleBPM, function(v) v = math.floor(v + 0.5) setIdleBPM(v) bpmBox.Text = tostring(v) bpmValLbl.Text = string.format("%d BPM", v) end) bpmBox.FocusLost:Connect(function() local n = tonumber(bpmBox.Text) if n and n > 0 then idleBPM = n bpmSlider.setValue(n, false) bpmValLbl.Text = string.format("%d BPM", n) setIdleBPM(n) else bpmBox.Text = tostring(idleBPM) end end) baseBox.FocusLost:Connect(function() local n = tonumber(baseBox.Text) if n and n > 0 then BASE_BPM = n else baseBox.Text = tostring(BASE_BPM) end setIdleBPM(idleBPM) end) applyBpmBtn.MouseButton1Click:Connect(function() local n = tonumber(bpmBox.Text) local b = tonumber(baseBox.Text) if n and n > 0 then idleBPM = n; bpmSlider.setValue(n, false); bpmValLbl.Text = string.format("%d BPM", n) end if b and b > 0 then BASE_BPM = b end setIdleBPM(idleBPM) notify("BPM settings applied", "info") end) -- Beat-sync indicator + mini equalizer, docked at bottom of binds tab local vizY = bpmSliderY + 34 makeDivider(pg1, vizY, Color3.fromRGB(0,200,255)) makeLabel(pg1, "BEAT SYNC", UDim2.new(0,8,0,vizY+6), UDim2.new(0,90,0,18), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) beatDot = Instance.new("Frame") beatDot.AnchorPoint = Vector2.new(0.5,0.5) beatDot.Size = UDim2.new(0,10,0,10) beatDot.Position = UDim2.new(0,100,0,vizY+15) beatDot.BackgroundColor3 = Color3.fromRGB(255,80,180) beatDot.BorderSizePixel = 0; beatDot.Parent = pg1 uiCorner(beatDot, UDim.new(1,0)) regBg(beatDot) makeLabel(pg1, "EQUALIZER", UDim2.new(0,140,0,vizY+6), UDim2.new(0,100,0,18), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local eqHolder = Instance.new("Frame") eqHolder.Size = UDim2.new(0,120,0,26); eqHolder.Position = UDim2.new(0,140,0,vizY+22) eqHolder.BackgroundTransparency = 1; eqHolder.Parent = pg1 local eqLayout = Instance.new("UIListLayout") eqLayout.FillDirection = Enum.FillDirection.Horizontal eqLayout.Padding = UDim.new(0,3) eqLayout.VerticalAlignment = Enum.VerticalAlignment.Bottom eqLayout.Parent = eqHolder eqBars = {} for i = 1, 8 do local bar = Instance.new("Frame") bar.Size = UDim2.new(0, 10, 0.2, 0) bar.BackgroundColor3 = THEMES[currentThemeName].accent bar.BorderSizePixel = 0 bar.Parent = eqHolder uiCorner(bar, UDim.new(0,2)) regBg(bar) table.insert(eqBars, bar) end makeLabel(pg1, "WAVEFORM", UDim2.new(0,270,0,vizY+6), UDim2.new(0,120,0,18), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local waveHolder = Instance.new("Frame") waveHolder.Size = UDim2.new(0,130,0,26); waveHolder.Position = UDim2.new(0,270,0,vizY+22) waveHolder.BackgroundTransparency = 1; waveHolder.Parent = pg1 local waveLayout = Instance.new("UIListLayout") waveLayout.FillDirection = Enum.FillDirection.Horizontal waveLayout.Padding = UDim.new(0,2) waveLayout.VerticalAlignment = Enum.VerticalAlignment.Center waveLayout.Parent = waveHolder waveBars = {} for i = 1, 16 do local bar = Instance.new("Frame") bar.Size = UDim2.new(0, 5, 0.3, 0) bar.BackgroundColor3 = THEMES[currentThemeName].accent2 bar.BorderSizePixel = 0 bar.Parent = waveHolder uiCorner(bar, UDim.new(0,2)) table.insert(waveBars, bar) end -- ============================================================ -- STATUS PANEL (bottom-fixed strip inside binds tab; also mirrored on preview tab) -- ============================================================ local statusY = vizY + 60 makeDivider(pg1, statusY, Color3.fromRGB(255,80,180)) local statusTitle = makeLabel(pg1, "📊 STATUS", UDim2.new(0,8,0,statusY+6), UDim2.new(0,120,0,18), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 11) regText(statusTitle) local statLbl1 = makeLabel(pg1, "Anim: —", UDim2.new(0,8,0,statusY+26), UDim2.new(1,-16,0,16), Color3.fromRGB(210,210,220), Enum.TextXAlignment.Left, 10) local statLbl2 = makeLabel(pg1, "Speed: x1.00 BPM: 100 State: STOPPED", UDim2.new(0,8,0,statusY+44), UDim2.new(1,-16,0,16), Color3.fromRGB(160,160,180), Enum.TextXAlignment.Left, 10) function refreshStatusUI() statLbl1.Text = string.format("Anim: %s (%s)", StatusState.currentAnimName, StatusState.currentAnimId ~= "" and StatusState.currentAnimId or "—") statLbl2.Text = string.format("Speed: x%.2f BPM: %d State: %s", StatusState.speedMult, StatusState.bpm, StatusState.playing and "PLAYING" or "STOPPED") end refreshStatusUI() -- ============================================================ -- TAB 2 : ANIMATION SEQUENCE MAKER -- ============================================================ local pg2 = pages["🎬 MAKER"] local makerTitle = makeLabel(pg2, "🎬 ANIMATION SEQUENCE MAKER", UDim2.new(0,8,0,6), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(makerTitle) makeDivider(pg2, 28, Color3.fromRGB(0,200,255)) makeLabel(pg2, "ANIM ID", UDim2.new(0,8,0,34), UDim2.new(0,220,0,16), Color3.fromRGB(160,160,160)) makeLabel(pg2, "DELAY (s)", UDim2.new(0,232,0,34), UDim2.new(0,80,0,16), Color3.fromRGB(160,160,160)) local seqScroll = Instance.new("ScrollingFrame") seqScroll.Size = UDim2.new(1,-16,0,182); seqScroll.Position = UDim2.new(0,8,0,52) seqScroll.BackgroundColor3 = Color3.fromRGB(18,18,26); seqScroll.BorderSizePixel = 0 seqScroll.ScrollBarThickness = 4; seqScroll.CanvasSize = UDim2.new(0,0,0,0) seqScroll.Parent = pg2; uiCorner(seqScroll) local seqLayout = Instance.new("UIListLayout") seqLayout.SortOrder = Enum.SortOrder.LayoutOrder; seqLayout.Padding = UDim.new(0,4) seqLayout.Parent = seqScroll local seqPad = Instance.new("UIPadding") seqPad.PaddingTop = UDim.new(0,4); seqPad.PaddingLeft = UDim.new(0,4); seqPad.PaddingRight = UDim.new(0,4) seqPad.Parent = seqScroll local function updateCanvas() seqScroll.CanvasSize = UDim2.new(0,0,0, seqLayout.AbsoluteContentSize.Y + 10) end seqLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(updateCanvas) local seqStatusLbl = makeLabel(pg2, "● IDLE", UDim2.new(0,8,0,310), UDim2.new(1,-16,0,18), Color3.fromRGB(160,80,80), Enum.TextXAlignment.Left) local function addSeqRow(id, delay) local step = { id = id or "", delay = delay or 0.5 } table.insert(sequence, step) local row = Instance.new("Frame") row.Size = UDim2.new(1,-8,0,30); row.BackgroundColor3 = Color3.fromRGB(24,24,36) row.BorderSizePixel = 0; row.LayoutOrder = #sequence; row.Parent = seqScroll uiCorner(row, UDim.new(0,6)) local idIn = makeBox(row, UDim2.new(0,4,0,3), UDim2.new(0,206,0,24), "Animation ID", step.id) idIn.FocusLost:Connect(function() step.id = idIn.Text:gsub("%D","") end) local dlIn = makeBox(row, UDim2.new(0,216,0,3), UDim2.new(0,64,0,24), "0.5", tostring(step.delay)) dlIn.FocusLost:Connect(function() step.delay = tonumber(dlIn.Text) or 0.5 end) local rem = makeBtn(row, "✕", UDim2.new(0,286,0,3), UDim2.new(0,28,0,24), Color3.fromRGB(170,35,35), nil, 12) rem.MouseButton1Click:Connect(function() for j, s in ipairs(sequence) do if s == step then table.remove(sequence, j); break end end for j, r in ipairs(seqRowFrames) do if r == row then table.remove(seqRowFrames, j); break end end row:Destroy(); updateCanvas() end) table.insert(seqRowFrames, row); updateCanvas() end makeBtn(pg2, "+ ADD STEP", UDim2.new(0,8,0,240), UDim2.new(1,-16,0,26), Color3.fromRGB(35,35,55)) .MouseButton1Click:Connect(function() addSeqRow("", 0.5) end) local seqPlay = makeBtn(pg2, "▶ PLAY SEQUENCE", UDim2.new(0,8,0,272), UDim2.new(0,176,0,30), Color3.fromRGB(60,190,120)) local seqStop = makeBtn(pg2, "■ STOP", UDim2.new(0,192,0,272), UDim2.new(0,90,0,30), Color3.fromRGB(190,50,50)) local seqClear = makeBtn(pg2, "🗑 CLEAR", UDim2.new(0,290,0,272), UDim2.new(0,106,0,30), Color3.fromRGB(70,25,70)) seqPlay.MouseButton1Click:Connect(function() if #sequence == 0 then seqStatusLbl.Text = "⚠ Add steps first"; notify("Add a step first", "warn"); return end playSequence() seqStatusLbl.Text = "▶ RUNNING"; seqStatusLbl.TextColor3 = Color3.fromRGB(80,220,120) notify("Sequence started", "info") task.spawn(function() while seqPlaying do task.wait(0.1) end seqStatusLbl.Text = "● IDLE"; seqStatusLbl.TextColor3 = Color3.fromRGB(160,80,80) end) end) seqStop.MouseButton1Click:Connect(function() stopSequence(); seqStatusLbl.Text = "● IDLE"; seqStatusLbl.TextColor3 = Color3.fromRGB(160,80,80) end) seqClear.MouseButton1Click:Connect(function() stopSequence(); sequence = {} for _, r in ipairs(seqRowFrames) do pcall(function() r:Destroy() end) end seqRowFrames = {}; updateCanvas() seqStatusLbl.Text = "● IDLE"; seqStatusLbl.TextColor3 = Color3.fromRGB(160,80,80) end) -- Quick preset loader makeDivider(pg2, 330, Color3.fromRGB(0,200,255)) local presetTitle = makeLabel(pg2, "⚡ QUICK PRESETS", UDim2.new(0,8,0,336), UDim2.new(1,-16,0,18), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 11) regText(presetTitle) for i, preset in ipairs(PRESETS) do local py = 356 + (i-1)*32 local pbtn = makeBtn(pg2, "📥 "..preset.name, UDim2.new(0,8,0,py), UDim2.new(1,-16,0,26), Color3.fromRGB(28,12,42), nil, 11) pbtn.MouseButton1Click:Connect(function() for _, row in ipairs(CurrentBindings) do local newId = preset.ids[row.dir] if newId then row.id = newId if row.keyBtn then destroyTrack(row.key) end end end notify("Loaded preset: "..preset.name, "success") end) end -- ============================================================ -- TAB 3 : LIVE PREVIEW -- ============================================================ local pg3v = pages["👁 PREVIEW"] local pvTitle = makeLabel(pg3v, "👁 LIVE CHARACTER PREVIEW", UDim2.new(0,8,0,6), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(pvTitle) makeDivider(pg3v, 28, Color3.fromRGB(0,200,255)) local viewport = Instance.new("ViewportFrame") viewport.Size = UDim2.new(1,-16,0,260); viewport.Position = UDim2.new(0,8,0,36) viewport.BackgroundColor3 = Color3.fromRGB(10,10,16) -- ViewportFrames render pure black without an explicit light source — -- these three properties act as a simple fill light for the clone. viewport.Ambient = Color3.fromRGB(140,140,150) viewport.LightColor = Color3.fromRGB(255,255,255) viewport.LightDirection = Vector3.new(-0.6, -1, -0.4) viewport.Parent = pg3v uiCorner(viewport) local vpStroke = uiStroke(viewport, Color3.fromRGB(255,80,180), 2) regStroke(vpStroke) previewCloneContainer = Instance.new("WorldModel") previewCloneContainer.Parent = viewport previewCam = Instance.new("Camera") previewCam.FieldOfView = 60 previewCam.CFrame = CFrame.new(Vector3.new(0, 3, 6), Vector3.new(0, 3, 0)) viewport.CurrentCamera = previewCam previewCam.Parent = viewport -- fix the earlier placeholder orbit loop now that previewCam/container exist task.spawn(function() while true do RunService.Heartbeat:Wait() if previewCam then previewAngle = previewAngle + 0.3 local rad = math.rad(previewAngle) local dist = 6 local pos = Vector3.new(math.sin(rad) * dist, 4, math.cos(rad) * dist) previewCam.CFrame = CFrame.new(pos, Vector3.new(0, 3, 0)) end end end) local pvHint = makeLabel(pg3v, "Rotates automatically. Plays whatever anim you last triggered.", UDim2.new(0,8,0,300), UDim2.new(1,-16,0,18), Color3.fromRGB(140,140,160), Enum.TextXAlignment.Left, 10) local pvRefreshBtn = makeBtn(pg3v, "🔄 REFRESH CLONE", UDim2.new(0,8,0,324), UDim2.new(1,-16,0,28), Color3.fromRGB(70,120,255)) pvRefreshBtn.MouseButton1Click:Connect(function() if character then buildPreviewClone(character); notify("Preview clone refreshed", "info") end end) -- Status mirror on preview tab makeDivider(pg3v, 362, Color3.fromRGB(255,80,180)) local pvStat1 = makeLabel(pg3v, "Anim: —", UDim2.new(0,8,0,370), UDim2.new(1,-16,0,16), Color3.fromRGB(210,210,220), Enum.TextXAlignment.Left, 10) local pvStat2 = makeLabel(pg3v, "Speed: x1.00 BPM: 100 State: STOPPED", UDim2.new(0,8,0,388), UDim2.new(1,-16,0,16), Color3.fromRGB(160,160,180), Enum.TextXAlignment.Left, 10) local oldRefresh = refreshStatusUI refreshStatusUI = function() oldRefresh() pvStat1.Text = string.format("Anim: %s (%s)", StatusState.currentAnimName, StatusState.currentAnimId ~= "" and StatusState.currentAnimId or "—") pvStat2.Text = string.format("Speed: x%.2f BPM: %d State: %s", StatusState.speedMult, StatusState.bpm, StatusState.playing and "PLAYING" or "STOPPED") end -- Recents list makeDivider(pg3v, 414, Color3.fromRGB(0,200,255)) local recentsTitle = makeLabel(pg3v, "🕐 RECENT ANIMATIONS", UDim2.new(0,8,0,420), UDim2.new(1,-16,0,18), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 11) regText(recentsTitle) local recentsScroll = Instance.new("ScrollingFrame") recentsScroll.Size = UDim2.new(1,-16,0,120); recentsScroll.Position = UDim2.new(0,8,0,442) recentsScroll.BackgroundColor3 = Color3.fromRGB(18,18,26); recentsScroll.BorderSizePixel = 0 recentsScroll.ScrollBarThickness = 4; recentsScroll.CanvasSize = UDim2.new(0,0,0,0) recentsScroll.Parent = pg3v; uiCorner(recentsScroll) local recentsLayout = Instance.new("UIListLayout") recentsLayout.Padding = UDim.new(0,4); recentsLayout.Parent = recentsScroll function refreshRecentsUI() for _, c in ipairs(recentsScroll:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end for i, r in ipairs(recents) do local row = Instance.new("Frame") row.Size = UDim2.new(1,-8,0,26); row.BackgroundColor3 = Color3.fromRGB(24,24,36) row.BorderSizePixel = 0; row.LayoutOrder = i; row.Parent = recentsScroll uiCorner(row, UDim.new(0,6)) makeLabel(row, r.label.." — "..r.id, UDim2.new(0,8,0,0), UDim2.new(1,-90,1,0), Color3.fromRGB(200,200,215), Enum.TextXAlignment.Left, 10) local playBtn = makeBtn(row, "▶", UDim2.new(1,-40,0,2), UDim2.new(0,32,0,22), Color3.fromRGB(60,190,120), nil, 11) playBtn.MouseButton1Click:Connect(function() updatePreviewAnimation(r.id, r.label) notify("Previewing "..r.label, "info") end) end end refreshRecentsUI() -- ============================================================ -- TAB 4 : FAVORITES -- ============================================================ local pg4 = pages["❤ FAVES"] local favTitle = makeLabel(pg4, "❤ FAVORITE ANIMATIONS", UDim2.new(0,8,0,6), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(favTitle) makeDivider(pg4, 28, Color3.fromRGB(0,200,255)) local favNameBox = makeBox(pg4, UDim2.new(0,8,0,36), UDim2.new(0,180,0,28), "Name") local favIdBox = makeBox(pg4, UDim2.new(0,196,0,36), UDim2.new(0,138,0,28), "Anim ID") local favAddBtn = makeBtn(pg4, "+ SAVE", UDim2.new(0,340,0,36), UDim2.new(0,74,0,28), Color3.fromRGB(60,190,120), nil, 11) local favScroll = Instance.new("ScrollingFrame") favScroll.Size = UDim2.new(1,-16,0,300); favScroll.Position = UDim2.new(0,8,0,72) favScroll.BackgroundColor3 = Color3.fromRGB(18,18,26); favScroll.BorderSizePixel = 0 favScroll.ScrollBarThickness = 4; favScroll.CanvasSize = UDim2.new(0,0,0,0) favScroll.Parent = pg4; uiCorner(favScroll) local favLayout = Instance.new("UIListLayout") favLayout.Padding = UDim.new(0,4); favLayout.Parent = favScroll local favPad = Instance.new("UIPadding") favPad.PaddingTop = UDim.new(0,4); favPad.PaddingLeft = UDim.new(0,4); favPad.PaddingRight = UDim.new(0,4) favPad.Parent = favScroll local function refreshFavUI() for _, c in ipairs(favScroll:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end for i, f in ipairs(favorites) do local row = Instance.new("Frame") row.Size = UDim2.new(1,-8,0,32); row.BackgroundColor3 = Color3.fromRGB(24,24,36) row.BorderSizePixel = 0; row.LayoutOrder = i; row.Parent = favScroll uiCorner(row, UDim.new(0,6)) makeLabel(row, f.name.." — "..f.id, UDim2.new(0,8,0,0), UDim2.new(1,-116,1,0), Color3.fromRGB(200,200,215), Enum.TextXAlignment.Left, 10) local playBtn = makeBtn(row, "▶", UDim2.new(1,-146,0,3), UDim2.new(0,34,0,26), Color3.fromRGB(0,180,230), nil, 11) local addPlBtn = makeBtn(row, "➕LIST", UDim2.new(1,-108,0,3), UDim2.new(0,58,0,26), Color3.fromRGB(70,120,255), nil, 9) local delBtn = makeBtn(row, "✕", UDim2.new(1,-42,0,3), UDim2.new(0,34,0,26), Color3.fromRGB(170,35,35), nil, 11) playBtn.MouseButton1Click:Connect(function() updatePreviewAnimation(f.id, f.name) pushRecent(f.id, f.name) notify("Previewing favorite: "..f.name, "info") end) addPlBtn.MouseButton1Click:Connect(function() table.insert(playlist, { name = f.name, id = f.id, delay = 1 }) if refreshPlaylistUI then refreshPlaylistUI() end notify(f.name.." added to playlist", "success") end) delBtn.MouseButton1Click:Connect(function() table.remove(favorites, i) refreshFavUI() end) end end favAddBtn.MouseButton1Click:Connect(function() local id = favIdBox.Text:gsub("%D","") local nm = favNameBox.Text ~= "" and favNameBox.Text or ("Fav "..(#favorites+1)) if id == "" then notify("Enter an anim ID to save", "warn"); return end table.insert(favorites, { name = nm, id = id }) favNameBox.Text = ""; favIdBox.Text = "" refreshFavUI() notify("Saved to favorites: "..nm, "success") end) refreshFavUI() -- ============================================================ -- TAB 5 : PLAYLIST MANAGER -- ============================================================ local pg5 = pages["🎶 LIST"] local plTitle = makeLabel(pg5, "🎶 PLAYLIST MANAGER", UDim2.new(0,8,0,6), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(plTitle) makeDivider(pg5, 28, Color3.fromRGB(0,200,255)) local plScroll = Instance.new("ScrollingFrame") plScroll.Size = UDim2.new(1,-16,0,260); plScroll.Position = UDim2.new(0,8,0,36) plScroll.BackgroundColor3 = Color3.fromRGB(18,18,26); plScroll.BorderSizePixel = 0 plScroll.ScrollBarThickness = 4; plScroll.CanvasSize = UDim2.new(0,0,0,0) plScroll.Parent = pg5; uiCorner(plScroll) local plLayout = Instance.new("UIListLayout") plLayout.Padding = UDim.new(0,4); plLayout.Parent = plScroll local plPad = Instance.new("UIPadding") plPad.PaddingTop = UDim.new(0,4); plPad.PaddingLeft = UDim.new(0,4); plPad.PaddingRight = UDim.new(0,4) plPad.Parent = plScroll function refreshPlaylistUI() for _, c in ipairs(plScroll:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end for i, item in ipairs(playlist) do local row = Instance.new("Frame") row.Size = UDim2.new(1,-8,0,30) row.BackgroundColor3 = (i == playlistIndex and playlistPlaying) and Color3.fromRGB(50,30,60) or Color3.fromRGB(24,24,36) row.BorderSizePixel = 0; row.LayoutOrder = i; row.Parent = plScroll uiCorner(row, UDim.new(0,6)) makeLabel(row, string.format("%d. %s — %s", i, item.name or item.id, item.id), UDim2.new(0,8,0,0), UDim2.new(1,-40,1,0), Color3.fromRGB(200,200,215), Enum.TextXAlignment.Left, 10) local rem = makeBtn(row, "✕", UDim2.new(1,-34,0,3), UDim2.new(0,26,0,24), Color3.fromRGB(170,35,35), nil, 11) rem.MouseButton1Click:Connect(function() table.remove(playlist, i) refreshPlaylistUI() end) end end refreshPlaylistUI() local plAddBox = makeBox(pg5, UDim2.new(0,8,0,300), UDim2.new(0,220,0,26), "Animation ID to add") local plAddBtn = makeBtn(pg5, "+ ADD", UDim2.new(0,236,0,300), UDim2.new(0,80,0,26), Color3.fromRGB(35,35,55)) plAddBtn.MouseButton1Click:Connect(function() local id = plAddBox.Text:gsub("%D","") if id == "" then notify("Enter an anim ID", "warn"); return end table.insert(playlist, { name = "Track "..(#playlist+1), id = id, delay = 1 }) plAddBox.Text = "" refreshPlaylistUI() end) local plPlay = makeBtn(pg5, "▶ PLAY", UDim2.new(0,8,0,332), UDim2.new(0,96,0,28), Color3.fromRGB(60,190,120), nil, 11) local plPrev = makeBtn(pg5, "⏮ PREV", UDim2.new(0,110,0,332), UDim2.new(0,86,0,28), Color3.fromRGB(70,120,255), nil, 11) local plNext = makeBtn(pg5, "NEXT ⏭", UDim2.new(0,202,0,332), UDim2.new(0,86,0,28), Color3.fromRGB(70,120,255), nil, 11) local plStop = makeBtn(pg5, "■ STOP", UDim2.new(0,294,0,332), UDim2.new(0,100,0,28), Color3.fromRGB(190,50,50), nil, 11) plPlay.MouseButton1Click:Connect(function() if #playlist == 0 then notify("Playlist is empty", "warn"); return end playPlaylist(); notify("Playlist started", "info") end) plPrev.MouseButton1Click:Connect(prevPlaylistTrack) plNext.MouseButton1Click:Connect(nextPlaylistTrack) plStop.MouseButton1Click:Connect(function() stopPlaylist(); refreshPlaylistUI() end) -- ============================================================ -- TAB : FX (vinyl record / CRT mode / spectrogram / BPM detector) -- ============================================================ local pgFx = pages["🎛 FX"] -- ── Vinyl record widget ── local vinylTitle = makeLabel(pgFx, "💿 VINYL RECORD", UDim2.new(0,8,0,6), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(vinylTitle) makeDivider(pgFx, 28, Color3.fromRGB(0,200,255)) makeLabel(pgFx, "Spins faster the higher the current speed / BPM is.", UDim2.new(0,8,0,34), UDim2.new(1,-16,0,18), Color3.fromRGB(140,140,160), Enum.TextXAlignment.Left, 10) local vinylHolder = Instance.new("Frame") vinylHolder.Size = UDim2.new(0,120,0,120); vinylHolder.Position = UDim2.new(0,8,0,56) vinylHolder.BackgroundColor3 = Color3.fromRGB(8,8,10); vinylHolder.BorderSizePixel = 0 vinylHolder.Parent = pgFx; uiCorner(vinylHolder, UDim.new(1,0)) local vinylHolderStroke = uiStroke(vinylHolder, Color3.fromRGB(255,80,180), 2) regStroke(vinylHolderStroke) vinylImage = Instance.new("ImageLabel") vinylImage.Size = UDim2.new(1,-8,1,-8); vinylImage.Position = UDim2.new(0,4,0,4) vinylImage.BackgroundTransparency = 1 vinylImage.Image = VINYL_DECAL_ID ~= "" and ("rbxassetid://"..VINYL_DECAL_ID) or "" vinylImage.ScaleType = Enum.ScaleType.Fit vinylImage.Parent = vinylHolder -- fallback ring so it still reads as a record before a decal is set local vinylRing = Instance.new("Frame") vinylRing.Size = UDim2.new(1,0,1,0); vinylRing.BackgroundTransparency = 1 vinylRing.Parent = vinylHolder uiCorner(vinylRing, UDim.new(1,0)) uiStroke(vinylRing, Color3.fromRGB(60,60,65), 1) local vinylCenter = Instance.new("Frame") vinylCenter.Size = UDim2.new(0,14,0,14); vinylCenter.AnchorPoint = Vector2.new(0.5,0.5) vinylCenter.Position = UDim2.new(0.5,0,0.5,0); vinylCenter.BackgroundColor3 = Color3.fromRGB(255,80,180) vinylCenter.BorderSizePixel = 0; vinylCenter.Parent = vinylHolder uiCorner(vinylCenter, UDim.new(1,0)); regBg(vinylCenter) local vinylIdBox = makeBox(pgFx, UDim2.new(0,140,0,56), UDim2.new(1,-148,0,28), "Vinyl decal asset ID") vinylIdBox.FocusLost:Connect(function() local id = vinylIdBox.Text:gsub("%D","") VINYL_DECAL_ID = id vinylImage.Image = id ~= "" and ("rbxassetid://"..id) or "" end) makeLabel(pgFx, "Paste your record-label decal ID above — art rotates with the disc.", UDim2.new(0,140,0,88), UDim2.new(1,-148,0,32), Color3.fromRGB(130,130,150), Enum.TextXAlignment.Left, 10) makeLabel(pgFx, "BASE RPM", UDim2.new(0,140,0,124), UDim2.new(0,70,0,24), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local vinylRpmBox = makeBox(pgFx, UDim2.new(0,214,0,124), UDim2.new(0,70,0,24), "33.3", tostring(vinylBaseRPM)) vinylRpmBox.FocusLost:Connect(function() local n = tonumber(vinylRpmBox.Text) if n and n > 0 then vinylBaseRPM = n else vinylRpmBox.Text = tostring(vinylBaseRPM) end end) -- ── CRT monitor mode ── makeDivider(pgFx, 190, Color3.fromRGB(255,80,180)) local crtTitle = makeLabel(pgFx, "📺 CRT MONITOR MODE", UDim2.new(0,8,0,196), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(crtTitle) makeLabel(pgFx, "Adds scanlines + a green-tinted flicker over the whole panel.", UDim2.new(0,8,0,218), UDim2.new(1,-16,0,18), Color3.fromRGB(140,140,160), Enum.TextXAlignment.Left, 10) local crtBtn = makeBtn(pgFx, "📴 CRT MODE OFF", UDim2.new(0,8,0,240), UDim2.new(1,-16,0,32), Color3.fromRGB(35,35,55), nil, 11) crtBtn.MouseButton1Click:Connect(function() crtEnabled = not crtEnabled if crtOverlay then crtOverlay.Visible = crtEnabled end if crtEnabled then crtBtn.Text = "📺 CRT MODE ON" crtBtn.BackgroundColor3 = THEMES[currentThemeName].accent else crtBtn.Text = "📴 CRT MODE OFF" crtBtn.BackgroundColor3 = Color3.fromRGB(35,35,55) end notify(crtEnabled and "CRT mode enabled" or "CRT mode disabled", "info") end) -- ── Mic spectrogram ── makeDivider(pgFx, 284, Color3.fromRGB(0,200,255)) local specTitle = makeLabel(pgFx, "🎙 SPECTROGRAM (MIC)", UDim2.new(0,8,0,290), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(specTitle) local specStatusLbl = makeLabel(pgFx, "● not connected", UDim2.new(0,200,0,290), UDim2.new(1,-208,0,20), Color3.fromRGB(160,80,80), Enum.TextXAlignment.Right, 10) makeLabel(pgFx, "Uses Roblox's mic Audio API if this client/game supports it; otherwise the bars react to animation state instead.", UDim2.new(0,8,0,312), UDim2.new(1,-16,0,28), Color3.fromRGB(130,130,150), Enum.TextXAlignment.Left, 10) local specHolder = Instance.new("Frame") specHolder.Size = UDim2.new(1,-16,0,60); specHolder.Position = UDim2.new(0,8,0,346) specHolder.BackgroundColor3 = Color3.fromRGB(18,18,26); specHolder.BorderSizePixel = 0 specHolder.Parent = pgFx; uiCorner(specHolder) local specLayout = Instance.new("UIListLayout") specLayout.FillDirection = Enum.FillDirection.Horizontal specLayout.Padding = UDim.new(0,3) specLayout.VerticalAlignment = Enum.VerticalAlignment.Bottom specLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center specLayout.Parent = specHolder specBars = {} for i = 1, SPEC_BAR_COUNT do local bar = Instance.new("Frame") bar.Size = UDim2.new(0, 6, 0.15, 0) bar.BackgroundColor3 = THEMES[currentThemeName].accent2 bar.BorderSizePixel = 0 bar.Parent = specHolder uiCorner(bar, UDim.new(0,2)) table.insert(specBars, bar) end local micBtn = makeBtn(pgFx, "🎙 REQUEST MIC ACCESS", UDim2.new(0,8,0,414), UDim2.new(1,-16,0,30), Color3.fromRGB(70,120,255), nil, 11) micBtn.MouseButton1Click:Connect(function() local success = setupMicSpectrogram() if success then specStatusLbl.Text = "● mic connected"; specStatusLbl.TextColor3 = Color3.fromRGB(80,220,120) notify("Mic spectrogram connected", "success") else specStatusLbl.Text = "● using fallback"; specStatusLbl.TextColor3 = Color3.fromRGB(255,180,60) notify("Mic unavailable here — using animation-synced fallback", "warn") end end) -- ── Animation BPM detector ── makeDivider(pgFx, 456, Color3.fromRGB(255,80,180)) local bpmDetectTitle = makeLabel(pgFx, "🎯 BPM DETECTOR", UDim2.new(0,8,0,462), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(bpmDetectTitle) makeLabel(pgFx, "Tap mode reads the rhythm of your Z/X/C/V presses. Idle mode reads the loaded idle clip's length.", UDim2.new(0,8,0,484), UDim2.new(1,-16,0,28), Color3.fromRGB(130,130,150), Enum.TextXAlignment.Left, 10) local detectedLbl = makeLabel(pgFx, "Detected: — BPM", UDim2.new(0,8,0,516), UDim2.new(1,-16,0,22), Color3.fromRGB(0,200,255), Enum.TextXAlignment.Left, 13) function refreshBpmDetectorUI() if detectedBPM then detectedLbl.Text = string.format("Detected: %d BPM (from %d taps)", detectedBPM, #bpmDetectTaps) end end makeLabel(pgFx, "BEATS/LOOP", UDim2.new(0,8,0,542), UDim2.new(0,80,0,26), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local beatsBox = makeBox(pgFx, UDim2.new(0,90,0,542), UDim2.new(0,50,0,26), "4", tostring(beatsPerIdleLoop)) beatsBox.FocusLost:Connect(function() local n = tonumber(beatsBox.Text) if n and n > 0 then beatsPerIdleLoop = n else beatsBox.Text = tostring(beatsPerIdleLoop) end end) local idleDetectBtn = makeBtn(pgFx, "📐 FROM IDLE LENGTH", UDim2.new(0,148,0,542), UDim2.new(0,150,0,26), Color3.fromRGB(70,120,255), nil, 10) idleDetectBtn.MouseButton1Click:Connect(function() local bpm = detectBpmFromIdleLength(beatsPerIdleLoop) if bpm then refreshBpmDetectorUI() notify("Detected "..bpm.." BPM from idle length", "success") else notify("Load an idle animation first", "warn") end end) local resetTapBtn = makeBtn(pgFx, "↺ RESET TAPS", UDim2.new(0,306,0,542), UDim2.new(0,116,0,26), Color3.fromRGB(170,35,35), nil, 10) resetTapBtn.MouseButton1Click:Connect(function() bpmDetectTaps = {}; detectedBPM = nil detectedLbl.Text = "Detected: — BPM" notify("Tap history cleared", "info") end) local applyDetectedBtn = makeBtn(pgFx, "✓ APPLY DETECTED BPM TO IDLE", UDim2.new(0,8,0,574), UDim2.new(1,-16,0,28), Color3.fromRGB(60,190,120), nil, 11) applyDetectedBtn.MouseButton1Click:Connect(function() if detectedBPM then setIdleBPM(detectedBPM) notify("Idle BPM set to "..detectedBPM, "success") else notify("No BPM detected yet — tap some keys or use idle length", "warn") end end) -- ── Shared music player (no VC) ── makeDivider(pgFx, 616, Color3.fromRGB(0,200,255)) local musicTitle = makeLabel(pgFx, "🎵 MUSIC PLAYER (NO VC)", UDim2.new(0,8,0,622), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(musicTitle) makeLabel(pgFx, "Plays an uploaded audio asset for the whole server directly — no voice chat involved, so no mic compression/suppression.", UDim2.new(0,8,0,644), UDim2.new(1,-16,0,30), Color3.fromRGB(140,140,160), Enum.TextXAlignment.Left, 10) makeLabel(pgFx, "Needs the companion server script installed once in ServerScriptService (see notes).", UDim2.new(0,8,0,676), UDim2.new(1,-16,0,18), Color3.fromRGB(130,130,150), Enum.TextXAlignment.Left, 9) local musicIdBox = makeBox(pgFx, UDim2.new(0,8,0,698), UDim2.new(1,-16,0,28), "Audio asset ID (e.g. 1234567890)") local musicPlayBtn = makeBtn(pgFx, "▶ PLAY FOR EVERYONE", UDim2.new(0,8,0,730), UDim2.new(0,220,0,30), Color3.fromRGB(60,190,120), nil, 11) local musicStopBtn = makeBtn(pgFx, "■ STOP", UDim2.new(0,236,0,730), UDim2.new(0,160,0,30), Color3.fromRGB(190,50,50), nil, 11) musicStatusLbl = makeLabel(pgFx, "● idle", UDim2.new(0,8,0,766), UDim2.new(1,-16,0,20), Color3.fromRGB(160,80,80), Enum.TextXAlignment.Left, 11) musicPlayBtn.MouseButton1Click:Connect(function() local id = musicIdBox.Text:gsub("%D","") if id == "" then notify("Enter an audio asset ID first", "warn"); return end playMusicTrack(id) notify("Requested track "..id.." for the whole server", "info") end) musicStopBtn.MouseButton1Click:Connect(function() stopMusicTrack() notify("Stopped shared music", "info") end) -- ============================================================ -- TAB 6 : SETTINGS (legs / face / theme) -- ============================================================ local pg6 = pages["⚙ SETTINGS"] local legsTitle = makeLabel(pg6, "🦵 LEGS ON GROUND (Optional)", UDim2.new(0,8,0,8), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(legsTitle) makeDivider(pg6, 30, Color3.fromRGB(0,200,255)) makeLabel(pg6, "Prevents walking and jumping — character stays planted.", UDim2.new(0,8,0,36), UDim2.new(1,-16,0,28), Color3.fromRGB(140,140,160), Enum.TextXAlignment.Left, 10) local legsBtn = makeBtn(pg6, "🔓 LEGS UNLOCKED (OFF)", UDim2.new(0,8,0,68), UDim2.new(1,-16,0,32), Color3.fromRGB(35,35,55), nil, 11) legsBtn.MouseButton1Click:Connect(function() legsLocked = not legsLocked applyLegsLock(legsLocked) if legsLocked then legsBtn.Text = "🔒 LEGS LOCKED (ON)" legsBtn.BackgroundColor3 = THEMES[currentThemeName].accent else legsBtn.Text = "🔓 LEGS UNLOCKED (OFF)" legsBtn.BackgroundColor3 = Color3.fromRGB(35,35,55) end notify(legsLocked and "Legs locked" or "Legs unlocked", "info") end) -- Dynamic face makeDivider(pg6, 114, Color3.fromRGB(255,80,180)) local faceTitle = makeLabel(pg6, "😊 DYNAMIC FACE IDs (per arrow direction)", UDim2.new(0,8,0,120), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(faceTitle) makeLabel(pg6, "Only works if your character has a face decal on its head.", UDim2.new(0,8,0,142), UDim2.new(1,-16,0,18), Color3.fromRGB(130,130,150), Enum.TextXAlignment.Left, 10) local faceOrder = { "LEFT", "DOWN", "UP", "RIGHT" } for i, dir in ipairs(faceOrder) do local y = 164 + (i-1)*38 local col = ARROW_COLORS[dir] and ARROW_COLORS[dir].hit or Color3.fromRGB(255,255,255) makeLabel(pg6, dir, UDim2.new(0,8,0,y), UDim2.new(0,52,0,28), col, Enum.TextXAlignment.Left) local fb = makeBox(pg6, UDim2.new(0,66,0,y), UDim2.new(1,-74,0,28), "Face decal ID for "..dir, faceIds[dir]) local capturedDir = dir fb.FocusLost:Connect(function() faceIds[capturedDir] = fb.Text:gsub("%D","") end) end -- Theme selector makeDivider(pg6, 320, Color3.fromRGB(0,200,255)) local themeTitle = makeLabel(pg6, "🎨 THEME", UDim2.new(0,8,0,326), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(themeTitle) local themeNames = { "Pink", "Cyan", "Purple", "RGB" } local themeSwatch = { Pink = Color3.fromRGB(255,80,180), Cyan = Color3.fromRGB(0,200,255), Purple = Color3.fromRGB(165,85,255), RGB = Color3.fromRGB(255,255,255) } for i, tn in ipairs(themeNames) do local tx = 8 + (i-1) * 104 local tbtn = makeBtn(pg6, tn, UDim2.new(0, tx, 0, 356), UDim2.new(0, 96, 0, 32), themeSwatch[tn], Color3.fromRGB(20,20,20), 11) tbtn.MouseButton1Click:Connect(function() setTheme(tn) end) end -- ============================================================ -- RP TUG-OF-WAR UI -- ============================================================ makeDivider(pg6, 400, Color3.fromRGB(0,200,255)) local rpTitle = makeLabel(pg6, "⚔ RP TUG-OF-WAR (PROXIMITY)", UDim2.new(0,8,0,406), UDim2.new(1,-16,0,20), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Left, 12) regText(rpTitle) makeLabel(pg6, "Moves you toward the target on every note press.", UDim2.new(0,8,0,428), UDim2.new(1,-16,0,18), Color3.fromRGB(130,130,150), Enum.TextXAlignment.Left, 10) -- Toggle Button local rpBtn = makeBtn(pg6, "📴 RP MODE OFF", UDim2.new(0,8,0,450), UDim2.new(0,160,0,32), Color3.fromRGB(35,35,55), nil, 11) rpBtn.MouseButton1Click:Connect(function() rpModeEnabled = not rpModeEnabled if rpModeEnabled then rpBtn.Text = "⚔ RP MODE ON" rpBtn.BackgroundColor3 = THEMES[currentThemeName].accent else rpBtn.Text = "📴 RP MODE OFF" rpBtn.BackgroundColor3 = Color3.fromRGB(35,35,55) end end) -- Player Search Box local targetBox = makeBox(pg6, UDim2.new(0,176,0,450), UDim2.new(1,-184,0,32), "Target Player Name...") targetBox.FocusLost:Connect(function() setTargetPlayer(targetBox.Text) end) -- Speed Slider makeLabel(pg6, "SPEED", UDim2.new(0,8,0,490), UDim2.new(0,40,0,22), Color3.fromRGB(160,160,160), Enum.TextXAlignment.Left, 10) local rpValLbl = makeLabel(pg6, string.format("%.1f studs", rpMoveStep), UDim2.new(0,300,0,490), UDim2.new(0,94,0,22), Color3.fromRGB(255,80,180), Enum.TextXAlignment.Right, 11) regText(rpValLbl) makeSlider(pg6, UDim2.new(0,50,0,494), UDim2.new(0,250,0,14), 0.5, 5, rpMoveStep, function(v) rpMoveStep = v rpValLbl.Text = string.format("%.1f studs", v) end) -- Hide / show toggle toggleBtn.MouseButton1Click:Connect(function() guiVisible = not guiVisible contentArea.Visible = guiVisible tabBar.Visible = guiVisible toggleBtn.Text = guiVisible and "HIDE" or "SHOW" end) startVisualizerLoops() notify("FNF Injector v6.1 loaded", "success") return sg end -- ============================================================ -- LAUNCH -- ============================================================ task.spawn(setupMusicRemote) buildArrowHud() buildGUI() --[[ ╔══════════════════════════════════════════════════════╗ ║ FNF CINEMATIC STANDOFF INTRO — v1.0 ║ ║ Back-to-back walk-off → turn → local VS split-cam ║ ╚══════════════════════════════════════════════════════╝ HOW IT WORKS: • Run this SAME script on both accounts (yours + your alt). • Give each one a "role": "A" or "B", and the other account's username. Role decides which spot / which walk direction. • Movement (walking to spots, turning to face each other) is done by tweening YOUR OWN HumanoidRootPart CFrame. Since you have network ownership of your own character, this actually replicates to everyone else in the server — they see two players walk to their marks and square up, nothing more. • The VS split-screen face-cam overlay is a local ScreenGui — built from cloned heads in a ViewportFrame, never touches the real character, so only the client running this sees it. • True frame-perfect sync between two separate accounts needs a server RemoteEvent (one is auto-detected if present, see SYNC section). Without one, it falls back to a 3-2-1 on-screen countdown so you just start both scripts and count together. ]] local Players = game:GetService("Players") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") local UserInputService = game:GetService("UserInputService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local LocalPlayer = Players.LocalPlayer local PlayerGui = LocalPlayer:WaitForChild("PlayerGui") local camera = workspace.CurrentCamera -- ============================================================ -- CONFIG — tweak freely -- ============================================================ local Config = { TriggerKey = Enum.KeyCode.B, -- press this to arm/start AltUsername = "mentransmigrasilo",-- who the other rig is -- world anchor: standoff happens relative to wherever LocalPlayer -- is standing the moment you press the trigger key SpotDistance = 14, -- how far apart the final marks are CenterGap = 3, -- how close together they start, back to back WalkDuration = 2.4, TurnDuration = 0.55, HoldBeforeSplit = 0.35, SplitCamDuration = 2.6, -- how long the VS face-cam stays up CountdownEnabled = true, -- 3-2-1 fallback sync WalkAnimId = "180426354", -- placeholder walk-forward loop IdleStanceAnimId = "507766388", -- placeholder FNF-ready idle stance -- role placement — "A" ends up on the right (Boyfriend side), -- "B" ends up on the left (Opponent side), classic FNF stage layout Role = "A", -- "A" or "B", set per-account } -- ============================================================ -- OPTIONAL SERVER SYNC (auto-detected, not required) -- ============================================================ -- If a companion server script drops a "StandoffSyncRemote" RemoteEvent -- into ReplicatedStorage, we use it to get a synced go-time. If it's not -- there, we just fall back to the local 3-2-1 countdown. local syncRemote = nil task.spawn(function() pcall(function() syncRemote = ReplicatedStorage:WaitForChild("StandoffSyncRemote", 3) end) end) -- ============================================================ -- STATE -- ============================================================ local standoffActive = false local rigConns = {} local function cleanupConns() for _, c in ipairs(rigConns) do pcall(function() c:Disconnect() end) end rigConns = {} end -- ============================================================ -- CHARACTER / ANIMATOR HELPERS -- ============================================================ local function getHumanoidBits(char) if not char then return nil end local hum = char:FindFirstChildOfClass("Humanoid") local root = char:FindFirstChild("HumanoidRootPart") if not hum or not root then return nil end local animator = hum:FindFirstChildOfClass("Animator") or Instance.new("Animator", hum) return { hum = hum, root = root, animator = animator } end local function loadAnim(animator, id, looped, priority) local ok, track = pcall(function() local anim = Instance.new("Animation") anim.AnimationId = "rbxassetid://"..id local t = animator:LoadAnimation(anim) t.Looped = looped t.Priority = priority or Enum.AnimationPriority.Action return t end) if ok then return track end return nil end -- Smoothly tween a HumanoidRootPart's CFrame. Root gets anchored for the -- duration so physics doesn't fight the tween, then restored after. local function tweenRoot(root, targetCFrame, duration, style, direction) local wasAnchored = root.Anchored root.Anchored = true local tw = TweenService:Create(root, TweenInfo.new(duration, style or Enum.EasingStyle.Sine, direction or Enum.EasingDirection.InOut), { CFrame = targetCFrame }) tw:Play() tw.Completed:Wait() root.Anchored = wasAnchored return true end -- ============================================================ -- ON-SCREEN 3-2-1 SYNC COUNTDOWN (local only, helps line up two clients) -- ============================================================ local function showCountdown(cb) local sg = Instance.new("ScreenGui") sg.Name = "StandoffCountdown"; sg.ResetOnSpawn = false; sg.Parent = PlayerGui local lbl = Instance.new("TextLabel") lbl.Size = UDim2.new(0,300,0,150) lbl.AnchorPoint = Vector2.new(0.5,0.5) lbl.Position = UDim2.new(0.5,0,0.5,0) lbl.BackgroundTransparency = 1 lbl.Font = Enum.Font.GothamBlack lbl.TextSize = 90 lbl.TextColor3 = Color3.fromRGB(255,80,180) lbl.TextStrokeTransparency = 0 lbl.TextStrokeColor3 = Color3.new(0,0,0) lbl.Parent = sg task.spawn(function() for _, n in ipairs({"3","2","1","GO"}) do lbl.Text = n lbl.Size = UDim2.new(0,420,0,210) TweenService:Create(lbl, TweenInfo.new(0.5, Enum.EasingStyle.Back, Enum.EasingDirection.Out), { Size = UDim2.new(0,300,0,150) }):Play() task.wait(0.7) end sg:Destroy() if cb then cb() end end) end -- ============================================================ -- CINEMATIC CAMERA (local only) -- ============================================================ local savedCamType, savedCamCFrame local function lockCinematicCamera(startCFrame) savedCamType = camera.CameraType savedCamCFrame = camera.CFrame camera.CameraType = Enum.CameraType.Scriptable camera.CFrame = startCFrame end local function restoreCamera() camera.CameraType = savedCamType or Enum.CameraType.Custom end -- Wide establishing dolly: pulls back slowly as both walk to their marks local function dollyCamera(fromCFrame, toCFrame, duration) local tw = TweenService:Create(camera, TweenInfo.new(duration, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), { CFrame = toCFrame }) camera.CFrame = fromCFrame tw:Play() return tw end -- Subtle handheld shake, used during the VS face-cam hold local shakeConn = nil local function startCameraShake(intensity) if shakeConn then shakeConn:Disconnect() end shakeConn = RunService.RenderStepped:Connect(function() if camera.CameraType == Enum.CameraType.Scriptable then local ox = (math.random() - 0.5) * intensity local oy = (math.random() - 0.5) * intensity camera.CFrame = camera.CFrame * CFrame.new(ox, oy, 0) end end) end local function stopCameraShake() if shakeConn then shakeConn:Disconnect(); shakeConn = nil end end -- ============================================================ -- VS FACE-CAM SPLIT SCREEN (local only — WorldModel clone trick, -- same pattern as a standard viewport character preview) -- ============================================================ local function buildFaceViewport(char, parent, position, size) local vp = Instance.new("ViewportFrame") vp.Size = size; vp.Position = position vp.BackgroundColor3 = Color3.fromRGB(6,6,10) vp.Ambient = Color3.fromRGB(160,160,170) vp.LightColor = Color3.fromRGB(255,255,255) vp.LightDirection = Vector3.new(-0.4,-0.6,-0.6) vp.Parent = parent local world = Instance.new("WorldModel") world.Parent = vp local wasArchivable = char.Archivable if not wasArchivable then char.Archivable = true end local ok, clone = pcall(function() return char:Clone() end) if not wasArchivable then char.Archivable = wasArchivable end if not ok or not clone then return vp end for _, d in ipairs(clone:GetDescendants()) do if d:IsA("Script") or d:IsA("LocalScript") then d:Destroy() end end clone.Parent = world local head = clone:FindFirstChild("Head") local hrp = clone:FindFirstChild("HumanoidRootPart") or clone.PrimaryPart if hrp then hrp.Anchored = true end local cam = Instance.new("Camera") if head then cam.CFrame = CFrame.new(head.Position + Vector3.new(0,0,2.2), head.Position) else cam.CFrame = CFrame.new(Vector3.new(0,3,4), Vector3.new(0,3,0)) end cam.FieldOfView = 45 cam.Parent = vp vp.CurrentCamera = cam return vp end local function playVSFaceCam(myChar, altChar, holdTime) local sg = Instance.new("ScreenGui") sg.Name = "StandoffVS"; sg.ResetOnSpawn = false; sg.IgnoreGuiInset = true sg.Parent = PlayerGui local root = Instance.new("Frame") root.Size = UDim2.new(1,0,1,0); root.BackgroundColor3 = Color3.new(0,0,0) root.BackgroundTransparency = 1; root.ClipsDescendants = true; root.Parent = sg -- LEFT half (opponent / role B viewport by convention) local leftHalf = Instance.new("Frame") leftHalf.Size = UDim2.new(0.5,0,1,0); leftHalf.Position = UDim2.new(-0.5,0,0,0) leftHalf.BackgroundTransparency = 1; leftHalf.ClipsDescendants = true; leftHalf.Parent = root -- RIGHT half (player / role A viewport by convention) local rightHalf = Instance.new("Frame") rightHalf.Size = UDim2.new(0.5,0,1,0); rightHalf.Position = UDim2.new(1,0,0,0) rightHalf.BackgroundTransparency = 1; rightHalf.ClipsDescendants = true; rightHalf.Parent = root local isRoleA = (Config.Role == "A") local myHalf, altHalf = (isRoleA and rightHalf or leftHalf), (isRoleA and leftHalf or rightHalf) buildFaceViewport(altChar, altHalf, UDim2.new(0,0,0,0), UDim2.new(1,0,1,0)) buildFaceViewport(myChar, myHalf, UDim2.new(0,0,0,0), UDim2.new(1,0,1,0)) -- diagonal seam divider local seam = Instance.new("Frame") seam.Size = UDim2.new(0,10,2.4,0) seam.AnchorPoint = Vector2.new(0.5,0.5) seam.Position = UDim2.new(0.5,0,0.5,0) seam.Rotation = 18 seam.BackgroundColor3 = Color3.fromRGB(255,80,180) seam.BorderSizePixel = 0; seam.ZIndex = 5; seam.Parent = root -- VS badge, pops in with a punchy scale tween local vsLbl = Instance.new("TextLabel") vsLbl.Size = UDim2.new(0,140,0,70) vsLbl.AnchorPoint = Vector2.new(0.5,0.5) vsLbl.Position = UDim2.new(0.5,0,0.5,0) vsLbl.BackgroundTransparency = 1 vsLbl.Text = "VS"; vsLbl.Font = Enum.Font.GothamBlack; vsLbl.TextSize = 0 vsLbl.TextColor3 = Color3.fromRGB(255,255,255) vsLbl.TextStrokeTransparency = 0; vsLbl.TextStrokeColor3 = Color3.fromRGB(255,80,180) vsLbl.ZIndex = 6; vsLbl.Parent = root -- slide both halves in TweenService:Create(leftHalf, TweenInfo.new(0.45, Enum.EasingStyle.Back, Enum.EasingDirection.Out), { Position = UDim2.new(0,0,0,0) }):Play() local rightTw = TweenService:Create(rightHalf, TweenInfo.new(0.45, Enum.EasingStyle.Back, Enum.EasingDirection.Out), { Position = UDim2.new(0.5,0,0,0) }) rightTw:Play() task.wait(0.25) TweenService:Create(vsLbl, TweenInfo.new(0.35, Enum.EasingStyle.Back, Enum.EasingDirection.Out), { TextSize = 64 }):Play() startCameraShake(0.02) task.wait(holdTime) stopCameraShake() -- fade out local fadeTime = 0.4 TweenService:Create(leftHalf, TweenInfo.new(fadeTime, Enum.EasingStyle.Quad), { Position = UDim2.new(-0.5,0,0,0) }):Play() TweenService:Create(rightHalf, TweenInfo.new(fadeTime, Enum.EasingStyle.Quad), { Position = UDim2.new(1,0,0,0) }):Play() TweenService:Create(vsLbl, TweenInfo.new(fadeTime, Enum.EasingStyle.Quad), { TextSize = 0 }):Play() task.wait(fadeTime + 0.1) sg:Destroy() end -- ============================================================ -- MAIN SEQUENCE -- ============================================================ local function runStandoff() if standoffActive then return end standoffActive = true local altPlayer = Players:FindFirstChild(Config.AltUsername) if not altPlayer or not altPlayer.Character then warn("[Standoff] Alt player not found or has no character.") standoffActive = false return end local myChar = LocalPlayer.Character local altChar = altPlayer.Character local myBits = getHumanoidBits(myChar) local altBits = getHumanoidBits(altChar) if not myBits or not altBits then warn("[Standoff] Missing Humanoid/HumanoidRootPart on one of the rigs.") standoffActive = false return end local function begin() -- anchor point = my current position, facing my current LookVector local originCFrame = CFrame.new(myBits.root.Position, myBits.root.Position + myBits.root.CFrame.LookVector) local isRoleA = (Config.Role == "A") -- back-to-back start: both on the same line, facing opposite ways, -- separated only by CenterGap local halfGap = Config.CenterGap / 2 local myStart = originCFrame * CFrame.new(isRoleA and halfGap or -halfGap, 0, 0) * (isRoleA and CFrame.Angles(0, math.rad(90), 0) or CFrame.Angles(0, math.rad(-90), 0)) tweenRoot(myBits.root, myStart, 0.01, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut) -- cinematic camera: wide shot centered on both, looking down the line local camStart = originCFrame * CFrame.new(0, 5, 10) local camEnd = originCFrame * CFrame.new(0, 4, Config.SpotDistance * 0.9) lockCinematicCamera(camStart) local camTw = dollyCamera(camStart, camEnd, Config.WalkDuration + Config.TurnDuration) -- walk animation while marching to spot local walkTrack = loadAnim(myBits.animator, Config.WalkAnimId, true, Enum.AnimationPriority.Movement) if walkTrack then walkTrack:Play(0.15) end -- walk out to final mark, still facing away from each other local mySpot = originCFrame * CFrame.new(isRoleA and Config.SpotDistance/2 or -Config.SpotDistance/2, 0, 0) * (isRoleA and CFrame.Angles(0, math.rad(90), 0) or CFrame.Angles(0, math.rad(-90), 0)) tweenRoot(myBits.root, mySpot, Config.WalkDuration, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut) if walkTrack then walkTrack:Stop(0.15) end -- turn to face each other — role A faces -X (toward B), role B faces +X (toward A) local turnCFrame = mySpot * (isRoleA and CFrame.Angles(0, math.rad(-90), 0) or CFrame.Angles(0, math.rad(90), 0)) -- correct: rebuild from position only, then rotate to actually look at the other spot local lookTarget = originCFrame.Position + Vector3.new(isRoleA and -1 or 1, 0, 0) turnCFrame = CFrame.new(mySpot.Position, Vector3.new(lookTarget.X, mySpot.Position.Y, mySpot.Position.Z)) tweenRoot(myBits.root, turnCFrame, Config.TurnDuration, Enum.EasingStyle.Back, Enum.EasingDirection.Out) -- FNF-ready idle stance local idleTrack = loadAnim(myBits.animator, Config.IdleStanceAnimId, true, Enum.AnimationPriority.Idle) if idleTrack then idleTrack:Play(0.2) end task.wait(Config.HoldBeforeSplit) -- local-only face-cam VS split playVSFaceCam(myChar, altChar, Config.SplitCamDuration) restoreCamera() standoffActive = false end if Config.CountdownEnabled and not syncRemote then showCountdown(begin) elseif syncRemote then -- server-synced start: fire our readiness, wait for GO from server syncRemote:FireServer("ready") local conn conn = syncRemote.OnClientEvent:Connect(function(cmd) if cmd == "go" then conn:Disconnect() task.spawn(begin) end end) table.insert(rigConns, conn) else task.spawn(begin) end end -- ============================================================ -- TRIGGER -- ============================================================ table.insert(rigConns, UserInputService.InputBegan:Connect(function(input, gp) if gp then return end if input.KeyCode == Config.TriggerKey then runStandoff() end end)) print("[Standoff] Loaded. Role: "..Config.Role.." — press "..Config.TriggerKey.Name.." to start, boss man.")