-- HEAD OF THE HEI - MOVEMENT SYSTEM -- -- X = TARGET ORBIT + CONTINUOUS FLIGHT -- Z = ORBIT -- C = PROJECTION -- C + A/D = LANE SHIFT -- C + Z = CIRCULAR PROJECTION -- V = HIGH SPEED LATERAL SHIFT -- J = MAXIMUM OUTPUT -- B = AFTERIMAGES ON/OFF local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") -- Todas las constantes de configuración viven en esta tabla para no -- superar el límite de 200 variables locales de Luau. local CFG = {} -- Declaraciones adelantadas (se usan antes de definirse más abajo). local CharacterIsValid local ProjectionActive local StartAimLock local StopAimLock local Debris = game:GetService("Debris") local Player = Players.LocalPlayer local Camera = workspace.CurrentCamera -- GENERAL CFG.COOLDOWN = 0.1 -- AFTERIMAGES CFG.AFTERIMAGES_ENABLED = true CFG.MAX_AFTERIMAGES = 8 CFG.AFTERIMAGE_INTERVAL = 0.055 CFG.AFTERIMAGE_LIFETIME = 0.22 local ActiveAfterimages = {} -- Z - NORMAL ORBIT CFG.ORBIT_TIME = 1 CFG.ORBIT_RADIUS = 2.2 CFG.ORBIT_SPEED = math.rad(1720) CFG.Z_TARGET_DISTANCE = 300 -- C - PROJECTION CFG.PROJECTION_MAX_DISTANCE = 45000 CFG.PROJECTION_MIN_SPEED = 6 CFG.PROJECTION_ACCELERATION = 10 CFG.PROJECTION_MAX_SPEED = 450 -- Desaceleración de C al soltarla. -- A mayor velocidad acumulada, menor será el tiempo de frenado. CFG.PROJECTION_DECEL_MIN_TIME = 0.12 CFG.PROJECTION_DECEL_MAX_TIME = 0.55 -- C A 24 FPS: la posición se actualiza en "pasos" de 1/24 s (aspecto de animación a saltos), -- pero cada paso avanza todo el tiempo transcurrido, así que NO se pierde velocidad. CFG.PROJECTION_STEPPED = true -- false = movimiento fluido normal CFG.PROJECTION_STEP_FPS = 24 -- Cada N pasos (24 = 1 s) la velocidad sube de golpe y luego baja hasta la velocidad normal. CFG.PROJECTION_BURST_EVERY_STEPS = 24 CFG.PROJECTION_BURST_BOOST = 0.8 -- +80% de velocidad en el pico CFG.PROJECTION_BURST_DECAY = 0.7 -- segundos que tarda en volver a la normal -- Viñeta morada pastel que acompaña al golpe de velocidad. CFG.VIGNETTE_COLOR = Color3.fromRGB(196, 170, 255) CFG.VIGNETTE_MAX_OPACITY = 0.65 -- 0 = invisible, 1 = sólida CFG.VIGNETTE_SIZE = 0.35 -- cuánto entra desde los bordes (fracción de la pantalla) -- Cámara estable durante C, V, X, J... (sigue al personaje sin temblar por el giro/pasos). CFG.CAMERA_STABILIZER_ENABLED = true CFG.CAMERA_FOCUS_HEIGHT = 1.5 -- altura del punto que sigue la cámara (≈ la cabeza) CFG.CAMERA_FOLLOW_RESPONSE = 12 -- más alto = más pegada al personaje; más bajo = más suave CFG.CAMERA_SETTLE_TIME = 0.35 -- tiempo para devolver la cámara al personaje al terminar -- Sonido de C (120901233293120): cada reproducción baja un poco el volumen hasta un mínimo. CFG.C_SOUND_VOLUME_STEP = 0.04 -- cuánto baja cada vez (fracción del volumen base) CFG.C_SOUND_MIN_VOLUME_FACTOR = 0.5 -- nunca baja de 50% -- CRYSTALS CFG.INITIAL_CRYSTALS = 0 CFG.CRYSTALS_PER_STEP = 0.1 CFG.CRYSTAL_SPACING = 1 -- C - PROJECTION ANIMATIONS -- PARA AÑADIR ANIMACIONES: pega cada ID en una línea nueva, dentro de los corchetes [[ ]]. -- No necesitas comillas ni comas, y vale con el número solo o con "rbxassetid://" delante. -- Las líneas con 0000000000 (o texto sin ID) se ignoran, así que puedes reemplazarlas -- o añadir las que quieras debajo, sin tocar nada más del código. -- -- ANIMACIONES PRINCIPALES (en bucle mientras C está activa). Se elige una cada vez que activas C. CFG.PROJECTION_ANIMATION_ID = "rbxassetid://0000000000" -- sigue funcionando; cuenta como una más de la lista CFG.PROJECTION_ANIMATIONS = { Main = [[ 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 ]], } -- Cómo se elige: "Random", "Cycle" (una tras otra) o "First" (siempre la primera). CFG.PROJECTION_ANIMATION_MODE = "Random" -- ANIMACIONES PUNTUALES (se reproducen una vez, encima de la principal). Mismo formato. -- Start = al activar C | LaneLeft / LaneRight = C + A / C + D | Circular = C + Z CFG.PROJECTION_EVENT_ANIMATIONS = { Start = [[ 0000000000 0000000000 0000000000 ]], LaneLeft = [[ 0000000000 0000000000 0000000000 ]], LaneRight = [[ 0000000000 0000000000 0000000000 ]], Circular = [[ 0000000000 0000000000 0000000000 ]], } -- Si hay varias en una lista: "Random", "Cycle" o "First". CFG.PROJECTION_EVENT_MODE = "Random" -- true = ajusta la velocidad de la animación para que dure lo mismo que el movimiento (lane shift). CFG.PROJECTION_EVENT_FIT_TO_DURATION = true local ProjectionAnimation = nil local ProjectionAnimationTrack = nil local ProjectionEventAnimation = nil local ProjectionEventTrack = nil local ProjectionCycleIndices = {} -- C + A/D - LANE SHIFT CFG.LANE_SHIFT_STEPS = 70 CFG.LANE_SHIFT_DURATION = 1.0 CFG.LANE_SHIFT_DISTANCE = 18 CFG.LANE_SHIFT_LEAN = math.rad(15) -- Inclinación en X: el cuerpo se echa hacia atrás como al deslizarse por el suelo (parkour). CFG.LANE_SHIFT_SLIDE_PITCH = math.rad(55) -- 1 = el cuerpo se ladea hacia el lado contrario al desplazamiento; -1 = hacia el mismo lado. CFG.LANE_SHIFT_LEAN_DIRECTION = 1 -- C + Z - CIRCULAR PROJECTION CFG.CIRCLE_TIME = 0.85 CFG.CIRCLE_RADIUS = 7 CFG.CIRCLE_ROTATIONS = 1.35 -- V - HIGH SPEED LATERAL SHIFT -- V usa la trayectoria que tenía X en el script de 2.7k: -- lateral progresivo -> avance de 48 studs con desaceleración. CFG.V_LATERAL_TIME = 0.20 CFG.V_LATERAL_DISTANCE = 10 CFG.V_LATERAL_SWINGS = 4 CFG.V_FORWARD_DISTANCE = 48 CFG.V_FORWARD_TIME = 0.45 CFG.V_DECELERATION_POWER = 2.5 CFG.V_AFTERIMAGE_INTERVAL = 0.055 -- V en Maximum Output: lateral HOLD, rango hasta 30 studs y velocidad creciente. CFG.MAX_V_LATERAL_START_DISTANCE = 6 CFG.MAX_V_LATERAL_DISTANCE_STEP = 4 CFG.MAX_V_LATERAL_DISTANCE_CAP = 30 CFG.MAX_V_LATERAL_START_SPEED = 220 CFG.MAX_V_LATERAL_SPEED_GROWTH = 1.35 CFG.MAX_V_LATERAL_MIN_TIME = 0.028 -- MAXIMUM OUTPUT V REWORK: AERIAL SPINDASH -> ORBIT -> RELEASE -> GLIDE CFG.MAX_V_SPINDASH_RISE_TIME = 0.42 CFG.MAX_V_SPINDASH_RISE_HEIGHT = 12 CFG.MAX_V_ORBIT_DURATION = 1.8 CFG.MAX_V_ORBIT_RADIUS = 30 CFG.MAX_V_ORBIT_STEER_SPEED = 28 -- studs/second; cámara + WASD para corregir rumbo CFG.MAX_V_ORBIT_IDLE_RADIUS = 2.5 CFG.MAX_V_RELEASE_DISTANCE = 34 CFG.MAX_V_RELEASE_DURATION = 0.62 CFG.MAX_V_NEAR_PLAYER_RADIUS = 9 -- radio de detección del hit de V (antes 5) -- HITS DURANTE EL VUELO DE V: al pasar cerca de un jugador cuenta un hit. CFG.MAX_V_GLIDE_MAX_HITS = 5 -- al llegar a este número de hits (release + vuelo) el vuelo termina CFG.MAX_V_GLIDE_HIT_COOLDOWN = 0.6 -- segundos antes de poder volver a golpear al mismo jugador CFG.MAX_V_GLIDE_HIT_BOOST = 28 -- impulso de velocidad al conectar (baja solo por la inercia) CFG.MAX_V_GLIDE_HIT_TIME_BONUS = 0.4 -- segundos extra de vuelo por cada hit CFG.MAX_V_NEAR_PLAYER_REFRESH_DISTANCE = 8 CFG.MAX_V_GLIDE_DURATION = 2.4 CFG.MAX_V_GLIDE_HORIZONTAL_SPEED = 90 CFG.MAX_V_GLIDE_MAX_EXTRA_HEIGHT = 4.5 CFG.MAX_V_GLIDE_DESCENT_SPEED = 3.5 CFG.MAX_V_SPIN_RATE = math.rad(1800) -- 5 vueltas por segundo (antes 2): giro frontal sobre X -- Sentido del giro: -1 = hacia delante como Sonic (la cabeza va por delante); 1 = hacia atrás. CFG.MAX_V_SPIN_DIRECTION = -1 -- VUELO CONTROLABLE (fase final de V) CFG.MAX_V_GLIDE_ENTRY_SPEED = 65 -- velocidad con la que entra al vuelo CFG.MAX_V_GLIDE_IDLE_SPEED_FACTOR = 0.45 -- sin teclas, planea hacia delante a esta fracción de la velocidad CFG.MAX_V_GLIDE_VERTICAL_SPEED = 22 -- Space / LeftControl CFG.MAX_V_FLIGHT_RESPONSE = 12 -- inercia: más alto = acelera/frena más rápido CFG.MAX_V_FLIGHT_TURN_RESPONSE = 10 -- qué tan rápido gira el cuerpo hacia donde vuelas CFG.MAX_V_FLIGHT_LEAN = math.rad(50) -- inclinación hacia delante a máxima velocidad CFG.MAX_V_FLIGHT_BANK = math.rad(18) -- ladeo al moverte con A/D CFG.MAX_V_FLIGHT_CLEARANCE = 3 -- distancia mínima a paredes/suelo (evita atravesarlos) CFG.MAX_V_GLIDE_SPIN_FADE = 0.45 -- el giro termina la vuelta y se endereza en este tiempo CFG.MAX_V_GLIDE_KEEP_SPIN = false -- true = sigue girando durante todo el vuelo -- Al soltar V en Maximum Output: salida frontal con desaceleración. CFG.MAX_V_RELEASE_BASE_DISTANCE = 48 CFG.MAX_V_RELEASE_EXTRA_DISTANCE = 48 CFG.MAX_V_RELEASE_TIME = 0.45 CFG.MAX_V_RELEASE_DECELERATION_POWER = 2.5 -- X - HOMING CHAIN / IDA Y VUELTA -- Distancia a la que se detiene frente al objetivo. CFG.X_TARGET_OFFSET = 2.8 -- Tiempo del impulso hacia el objetivo. CFG.X_DASH_TO_TARGET_TIME = 0.065 -- Pausa mínima después de llegar al objetivo. CFG.X_HIT_PAUSE = 0.015 -- Distancia de referencia para la salida/inercia posterior al impacto. CFG.X_REBOUND_DISTANCE = 18 -- Tiempo del rebote hacia atrás (conservado por compatibilidad; X ahora pierde la inercia). CFG.X_REBOUND_TIME = 0.055 -- Distancia corta que Naoya recorre justo DESPUÉS de atravesar al objetivo. CFG.X_POST_HIT_DISTANCE = 6 -- Ventana para volver a pulsar X y encadenar otro dash. CFG.X_CHAIN_WINDOW = 1.4 -- Tiempo de "gamble": al terminar la pasada, la trayectoria elige -- ALEATORIAMENTE una de 4 posiciones alrededor del objetivo. CFG.X_ORBIT_DECISION_TIME = 0.075 CFG.X_ORBIT_RADIUS = 18 -- La curva hacia la posición elegida pierde velocidad progresivamente. CFG.X_GLIDE_TIME = 1.4 -- Primer tramo: atraviesa unos 5 studs antes de empezar la curva. CFG.X_PASS_THROUGH_TIME = 0.095 -- Pausa mínima entre cadenas. CFG.X_CHAIN_PAUSE = 0.01 -- Distancia máxima para encontrar objetivos con X. CFG.X_TARGET_DISTANCE = CFG.Z_TARGET_DISTANCE -- AIM LOCK / AUTO LOCK para X y Z. CFG.AIM_LOCK_ENABLED = true CFG.AIM_LOCK_SMOOTHNESS = 0.55 CFG.AIM_LOCK_HEIGHT = 1.25 -- MAXIMUM OUTPUT - J CFG.MAX_OUTPUT_KEY = Enum.KeyCode.J CFG.MAX_OUTPUT_MAX_ENERGY = 500 CFG.MAX_OUTPUT_START_ENERGY = 500 -- empiezas con la reserva llena CFG.MAX_OUTPUT_DRAIN_PER_SECOND = 18 CFG.MAX_OUTPUT_REGEN_PER_SECOND = 24 CFG.MAX_OUTPUT_MIN_ACTIVATION_ENERGY = 8 -- ENERGÍA MALDITA: una sola reserva compartida por todas las habilidades (la de Maximum Output). -- El GUI está a la izquierda, a media altura. Cada gasto suelta circulitos azules alrededor. CFG.ENERGY_REGEN_DELAY = 1.2 -- segundos sin gastar antes de empezar a regenerar CFG.ENERGY_LOW_FRACTION = 0.10 -- bajo este % (10%) hay penalizaciones CFG.ENERGY_COST_C_START = 25 -- activar C CFG.ENERGY_COST_C_PER_SECOND = 12 -- C mientras está activa CFG.ENERGY_COST_LANE_SHIFT = 20 -- C + A / C + D CFG.ENERGY_COST_CIRCULAR = 45 -- C + Z CFG.ENERGY_COST_Z = 55 CFG.ENERGY_COST_X = 60 CFG.ENERGY_COST_V = 70 CFG.ENERGY_COST_J = 80 -- activar Maximum Output (luego gasta MAX_OUTPUT_DRAIN_PER_SECOND) -- Con menos del 10%: C pierde velocidad (hasta este factor a 0%) y Z, X, V, J, lane shift y círculo no se activan. CFG.ENERGY_LOW_C_MIN_SPEED_FACTOR = 0.5 -- VIDA BAJA CFG.LOW_HEALTH_ENTER = 33 -- bajo esta vida: viñeta morada pastel + canción CFG.LOW_HEALTH_EXIT = 40 -- se quitan al superar esta vida CFG.LOW_HEALTH_SOUND_ID = 98366838911876 CFG.LOW_HEALTH_SOUND_VOLUME = 0.8 CFG.LOW_HEALTH_VIGNETTE_OPACITY = 0.10 CFG.CRITICAL_HEALTH_ENTER = 25 -- bajo esta vida: segunda canción + buffs (riesgo de muerte) CFG.CRITICAL_HEALTH_EXIT = 30 -- los buffs se quitan al llegar a esta vida CFG.CRITICAL_SOUND_ID = 85149356982609 CFG.CRITICAL_SOUND_VOLUME = 0.9 -- BUFFS CON POCA VIDA (< CRITICAL_HEALTH_ENTER) CFG.CRIT_C_START_BOOST = 0.9 -- C: +90% de velocidad al iniciar... CFG.CRIT_C_BOOST_DECAY = 3.0 -- ...que se desvanece poco a poco en este tiempo CFG.CRIT_Z_SPEED_FACTOR = 1.35 -- Z: órbita más rápida (y saltos más frecuentes y largos) CFG.CRIT_X_DISTANCE_FACTOR = 1.45 -- X: más distancia entre saltos CFG.CRIT_X_SPEED_FACTOR = 0.7 -- X: multiplica los tiempos (menos = más rápido) CFG.CRIT_MAX_X_SPEED_FACTOR = 1.35 -- X en Maximum Output: velocidad CFG.CRIT_MAX_X_CENTER_PAUSE = 0.06 -- X en Maximum Output: pausa extra entre intervalos junto al objetivo CFG.CRIT_V_BACKFLIP_TIME = 0.5 -- V: backflip CFG.CRIT_V_HOVER_HEIGHT = 12 -- V: altura a la que se queda en el aire CFG.CRIT_V_HOVER_TIME = 1.0 -- V: segundos haciendo spindash en el aire CFG.CRIT_V_SHOT_DISTANCE = 60 -- V: disparo hacia donde mira la cámara CFG.CRIT_V_SHOT_TIME = 0.65 -- V: duración del disparo (desacelera) CFG.MAX_X_PASS_DISTANCE = 10 CFG.MAX_X_PASS_TIME = 0.105 CFG.MAX_X_RETURN_TIME = 0.105 CFG.MAX_X_ORBIT_TIME = 2.69 -- conservado, ya no se usa al soltar X CFG.MAX_X_ORBIT_RADIUS = 9 CFG.MAX_X_ORBIT_ROTATIONS = 4.5 CFG.MAX_X_DIRECTION_COUNT = 8 CFG.MAX_X_DISTANCE_STEP_TIME = 1.4 CFG.MAX_X_DISTANCE_PER_STEP = 8 CFG.MAX_X_DISTANCE_CAP = 120 -- MOVEMENT SOUNDS CFG.MAX_X_SOUND_ID = "rbxassetid://84079709496345" CFG.C_SOUND_ID = "rbxassetid://120901233293120" -- Sonidos adicionales de las versiones normales. CFG.C_NORMAL_SOUND_ID = "rbxassetid://126200955693341" CFG.J_SOUND_ID = "rbxassetid://89733752526254" CFG.X_NORMAL_SOUND_ID = "rbxassetid://131057746228488" CFG.CRITICAL_HEALTH_SOUND_ID = "rbxassetid://93485978852391" CFG.V_MAX_SOUND_ID = "rbxassetid://115962892067823" -- legacy V sound CFG.V_MAX_START_SOUND_ID = "rbxassetid://79833203443837" CFG.V_MAX_LAUNCH_SOUND_ID = "rbxassetid://137322083927464" CFG.V_MAX_NEAR_HIT_SOUND_ID = "rbxassetid://110594287267319" CFG.MAX_OUTPUT_HOLD_SOUND_ID = "rbxassetid://79207659461753" CFG.CIRCLE_SOUND_ID = "rbxassetid://91316945895658" CFG.MAX_OUTPUT_MUSIC_ID = "rbxassetid://103521525282478" -- Maximum Output dura 22 s. La música entra suavemente y queda a volumen moderado. CFG.MAX_OUTPUT_DURATION = 22 CFG.MAX_OUTPUT_MUSIC_FADE_IN = 1.4 CFG.MAX_OUTPUT_MUSIC_VOLUME = 0.4 CFG.MAX_OUTPUT_HOLD_SOUND_DELAY = 1.5 CFG.MOVEMENT_SOUND_VOLUME = 1.5 -- Todos los sonidos se reproducen con tono normal, sin efectos de agudez. local ProjectionSoundStart = 0 local function GetCProgressivePitch() return 1 end -- Cada disparo crea una copia para que los sonidos puedan -- superponerse cuando la movilidad los genera muy rápido. -- Los sonidos se reproducen sobre la CurrentCamera para que sean -- 2D y no dependan de que la cámara siga al personaje. -- PitchMultiplier solo se usa para C. local function PlayMovementSound(SoundId, Volume, PlaybackSpeed, PitchMultiplier) if not CharacterIsValid() then return end local Sound = Instance.new("Sound") Sound.Name = "ProjectionMovementSound" Sound.SoundId = SoundId Sound.Volume = Volume or CFG.MOVEMENT_SOUND_VOLUME Sound.PlaybackSpeed = PlaybackSpeed or 1 Sound.RollOffMaxDistance = 120 Sound.RollOffMinDistance = 8 Sound.Parent = workspace.CurrentCamera or Camera Sound:Play() Debris:AddItem(Sound, 10) end local function StartNormalCSoundLoop() task.spawn(function() while ProjectionActive do PlayMovementSound( CFG.C_NORMAL_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1, GetCProgressivePitch() ) -- Un sonido cada 0.95 s, sin aumentar la frecuencia. task.wait(0.95) end end) end local MaximumOutputActive = false local MaximumOutputEnergy = CFG.MAX_OUTPUT_START_ENERGY local MaximumOutputGui = nil local EnergyOrbHolder = nil local EnergyDeniedUntil = 0 local MaximumOutputFill = nil local MaximumOutputLabel = nil local MaximumOutputConnection = nil local MaximumOutputFlowLevel = 0 local MaximumOutputStartedAt = 0 local MaximumOutputSession = 0 local MaximumOutputMusic = nil local MaximumOutputHoldSound = nil local LastMaxVPress = -math.huge local MaxVHolding = false local MaxVRunning = false local MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED local MaxVCurrentDistance = CFG.MAX_V_LATERAL_START_DISTANCE local MaxVCenterPosition = nil local MaxVForward = nil local MaxVRight = nil local MaxVStage = "Idle" -- Idle / Spindash / Orbit / Release / Glide local MaxVReleaseRequested = false local MaxVOrbitCenter = nil local MaxVOrbitStartTime = 0 local MaxVUsedNearPass = {} local MaxXHolding = false local MaxXRunning = false -- CHARACTER local Character local Humanoid local RootPart local function SetupCharacter(CharacterObject) Character = CharacterObject Humanoid = CharacterObject:WaitForChild("Humanoid") RootPart = CharacterObject:WaitForChild( "HumanoidRootPart" ) end SetupCharacter( Player.Character or Player.CharacterAdded:Wait() ) -- STATE local Busy = false ProjectionActive = false local ProjectionHolding = false local ProjectionReleasing = false local ProjectionReleaseStart = 0 local ProjectionReleaseSpeed = 0 local ProjectionReleaseDirection = Vector3.zero local LaneShiftActive = false local CircularProjectionActive = false local LaneShiftKey = nil local LaneShiftCurrentOffset = Vector3.zero local LaneShiftStartOffset = Vector3.zero local LaneShiftTargetOffset = Vector3.zero local LaneShiftStartTime = 0 local LaneShiftSide = 0 local LastUse = { X = 0, Z = 0, C = 0, V = 0 } local ProjectionConnection = nil local ProjectionInputConnection = nil local ProjectionDeathConnection = nil local CurrentRagdollData = nil -- COOLDOWN local function CanUse(Skill) return os.clock() - LastUse[Skill] >= CFG.COOLDOWN end -- CHARACTER VALIDATION function CharacterIsValid() return Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0 and RootPart and RootPart.Parent end -- LOW HEALTH SOUND / MAX X HOLD SOUND local LowHealthConnection = nil local LowHealthSoundPlayed = false local function BindCriticalHealthSound() if LowHealthConnection then LowHealthConnection:Disconnect() LowHealthConnection = nil end LowHealthSoundPlayed = false if not Humanoid then return end LowHealthConnection = Humanoid.HealthChanged:Connect(function(Health) if Health <= 5 and not LowHealthSoundPlayed then LowHealthSoundPlayed = true PlayMovementSound(CFG.CRITICAL_HEALTH_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) elseif Health > 5 then LowHealthSoundPlayed = false end end) if Humanoid.Health <= 5 and not LowHealthSoundPlayed then LowHealthSoundPlayed = true PlayMovementSound(CFG.CRITICAL_HEALTH_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) end end -- Reproduce únicamente el tramo 0.5 -> 2.8 s del sonido -- mientras X Maximum Output esté mantenida. Si se sigue manteniendo, repite. local function PlayMaximumOutputXHoldSound() task.spawn(function() while MaximumOutputActive and MaxXHolding and CharacterIsValid() do local Sound = Instance.new("Sound") Sound.Name = "MaximumOutputXHoldSound" Sound.SoundId = CFG.MAX_X_SOUND_ID Sound.Volume = CFG.MOVEMENT_SOUND_VOLUME Sound.PlaybackSpeed = 1 Sound.RollOffMaxDistance = 120 Sound.RollOffMinDistance = 8 Sound.Parent = workspace.CurrentCamera or Camera Sound.TimePosition = 0.5 Sound:Play() while MaximumOutputActive and MaxXHolding and CharacterIsValid() and Sound.Parent do if Sound.TimePosition >= 2.8 then break end RunService.RenderStepped:Wait() end Sound:Stop() Sound:Destroy() end end) end BindCriticalHealthSound() -- ROTATION RECOVERY / FAILSAFE if _G.HeadHeiRotationRecoveryEnabled == nil then _G.HeadHeiRotationRecoveryEnabled = true end local function RotationRecovery() if _G.HeadHeiRotationRecoveryEnabled == false then return end if not CharacterIsValid() then return end local Start = os.clock() Humanoid.AutoRotate = true pcall(function() RootPart.AssemblyAngularVelocity = Vector3.zero end) local Connection Connection = RunService.RenderStepped:Connect(function() if not CharacterIsValid() then if Connection then Connection:Disconnect() end return end if os.clock() - Start >= 0.20 then if Connection then Connection:Disconnect() end return end Humanoid.AutoRotate = true local MoveDirection = Humanoid.MoveDirection if MoveDirection.Magnitude > 0.05 then local FlatDirection = Vector3.new(MoveDirection.X, 0, MoveDirection.Z) if FlatDirection.Magnitude > 0.05 then FlatDirection = FlatDirection.Unit local Position = RootPart.Position RootPart.CFrame = CFrame.lookAt(Position, Position + FlatDirection) end end pcall(function() RootPart.AssemblyAngularVelocity = Vector3.zero end) end) end local function RunSkillWithRecovery(SkillFunction) local Success, ErrorMessage = xpcall(SkillFunction, debug.traceback) if not Success then warn("Head of the Hei skill error:\n" .. tostring(ErrorMessage)) end RotationRecovery() end -- MAXIMUM OUTPUT UI local function UpdateMaximumOutputUI() if not MaximumOutputFill then return end local Ratio = math.clamp(MaximumOutputEnergy / CFG.MAX_OUTPUT_MAX_ENERGY, 0, 1) local Diameter = 44 * Ratio MaximumOutputFill.Size = UDim2.fromOffset(math.max(Diameter, 2), math.max(Diameter, 2)) local Warning = Ratio < CFG.ENERGY_LOW_FRACTION or os.clock() < EnergyDeniedUntil MaximumOutputFill.BackgroundColor3 = Warning and Color3.fromRGB(255, 95, 110) or Color3.fromRGB(80, 155, 255) if MaximumOutputLabel then MaximumOutputLabel.Text = "ENERGÍA " .. math.floor(Ratio * 100 + 0.5) .. "%" MaximumOutputLabel.TextColor3 = Warning and Color3.fromRGB(255, 140, 150) or Color3.new(1, 1, 1) end end local function CreateMaximumOutputUI() if MaximumOutputGui and MaximumOutputGui.Parent then return end local PlayerGui = Player:FindFirstChildOfClass("PlayerGui") if not PlayerGui then return end local Gui = Instance.new("ScreenGui") Gui.Name = "MaximumOutputUI" Gui.IgnoreGuiInset = true Gui.ResetOnSpawn = false Gui.Parent = PlayerGui MaximumOutputGui = Gui local Holder = Instance.new("Frame") Holder.Size = UDim2.fromOffset(62, 82) Holder.Position = UDim2.new(0, 22, 0.5, -41) Holder.BackgroundTransparency = 1 Holder.Parent = Gui EnergyOrbHolder = Holder local BlackCircle = Instance.new("Frame") BlackCircle.AnchorPoint = Vector2.new(0.5, 0.5) BlackCircle.Position = UDim2.new(0.5, 0, 0.43, 0) BlackCircle.Size = UDim2.fromOffset(58, 58) BlackCircle.BackgroundColor3 = Color3.fromRGB(8, 8, 12) BlackCircle.BorderSizePixel = 0 BlackCircle.Parent = Holder local BC = Instance.new("UICorner"); BC.CornerRadius = UDim.new(1,0); BC.Parent = BlackCircle local BlueCircle = Instance.new("Frame") BlueCircle.AnchorPoint = Vector2.new(0.5, 0.5) BlueCircle.Position = UDim2.fromScale(0.5, 0.5) BlueCircle.Size = UDim2.fromOffset(44, 44) BlueCircle.BackgroundColor3 = Color3.fromRGB(80, 155, 255) BlueCircle.BorderSizePixel = 0 BlueCircle.Parent = BlackCircle local BLC = Instance.new("UICorner"); BLC.CornerRadius = UDim.new(1,0); BLC.Parent = BlueCircle MaximumOutputFill = BlueCircle local Label = Instance.new("TextLabel") Label.Size = UDim2.fromOffset(90, 18) Label.Position = UDim2.new(0.5, -45, 1, -3) Label.BackgroundTransparency = 1 Label.Text = "ENERGÍA 100%" Label.TextSize = 11 Label.Font = Enum.Font.GothamBold Label.TextColor3 = Color3.new(1,1,1) Label.Parent = Holder MaximumOutputLabel = Label UpdateMaximumOutputUI() end -- ENERGÍA MALDITA local LastEnergySpend = -math.huge local EnergyDotAccumulator = 0 local function GetEnergyFraction() return math.clamp(MaximumOutputEnergy / CFG.MAX_OUTPUT_MAX_ENERGY, 0, 1) end local function IsEnergyLow() return GetEnergyFraction() < CFG.ENERGY_LOW_FRACTION end -- Circulitos azules que se esparcen alrededor del GUI al gastar energía. local function SpawnEnergyDots(Amount) if not EnergyOrbHolder or not EnergyOrbHolder.Parent then return end for _ = 1, Amount do local Dot = Instance.new("Frame") local DotSize = math.random(4, 9) Dot.AnchorPoint = Vector2.new(0.5, 0.5) Dot.Size = UDim2.fromOffset(DotSize, DotSize) Dot.Position = UDim2.new(0.5, 0, 0.43, 0) Dot.BackgroundColor3 = Color3.fromRGB(110, 175, 255) Dot.BackgroundTransparency = 0.1 Dot.BorderSizePixel = 0 Dot.ZIndex = 5 local Corner = Instance.new("UICorner") Corner.CornerRadius = UDim.new(1, 0) Corner.Parent = Dot Dot.Parent = EnergyOrbHolder local Angle = math.random() * math.pi * 2 local Distance = math.random(34, 64) TweenService:Create( Dot, TweenInfo.new(0.5 + math.random() * 0.4, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), { Position = UDim2.new(0.5, math.cos(Angle) * Distance, 0.43, math.sin(Angle) * Distance), BackgroundTransparency = 1, Size = UDim2.fromOffset(2, 2), } ):Play() Debris:AddItem(Dot, 1.2) end end -- Gasto continuo (C, Maximum Output): suelta un circulito cada cierta cantidad gastada. local function DrainEnergy(Amount) MaximumOutputEnergy = math.max(0, MaximumOutputEnergy - Amount) LastEnergySpend = os.clock() EnergyDotAccumulator += Amount if EnergyDotAccumulator >= 12 then EnergyDotAccumulator = 0 SpawnEnergyDots(1) end UpdateMaximumOutputUI() end -- Gasto puntual al activar una habilidad. Devuelve false (y avisa en el GUI) si no se puede. -- Con menos del 10% solo se permite si AllowWhenLow es true (C, que en cambio pierde velocidad). local function TrySpendEnergy(Cost, AllowWhenLow) if MaximumOutputEnergy < Cost or (not AllowWhenLow and IsEnergyLow()) then EnergyDeniedUntil = os.clock() + 0.4 UpdateMaximumOutputUI() return false end MaximumOutputEnergy = MaximumOutputEnergy - Cost LastEnergySpend = os.clock() SpawnEnergyDots(math.clamp(math.ceil(Cost / 12), 2, 8)) UpdateMaximumOutputUI() return true end local function StopMaximumOutputMusic() if MaximumOutputMusic then local Music = MaximumOutputMusic MaximumOutputMusic = nil pcall(function() Music:Stop() Music:Destroy() end) end end local function StartMaximumOutputMusic() StopMaximumOutputMusic() local Music = Instance.new("Sound") Music.Name = "MaximumOutputMusic" Music.SoundId = CFG.MAX_OUTPUT_MUSIC_ID Music.Volume = 0 Music.Looped = false Music.PlaybackSpeed = 1 Music.Parent = workspace.CurrentCamera or Camera MaximumOutputMusic = Music Music:Play() -- Fade-in local de 1.4 s: no modifica ni reduce el volumen de otros sonidos. local Fade = TweenService:Create( Music, TweenInfo.new(CFG.MAX_OUTPUT_MUSIC_FADE_IN, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), {Volume = CFG.MAX_OUTPUT_MUSIC_VOLUME} ) Fade:Play() Debris:AddItem(Music, CFG.MAX_OUTPUT_DURATION + 2) end local function PlayMaximumOutputHoldSound() local Sound = Instance.new("Sound") Sound.Name = "MaximumOutputHoldSound" Sound.SoundId = CFG.MAX_OUTPUT_HOLD_SOUND_ID Sound.Volume = CFG.MOVEMENT_SOUND_VOLUME Sound.PlaybackSpeed = 1 Sound.Parent = workspace.CurrentCamera or Camera MaximumOutputHoldSound = Sound Sound:Play() Debris:AddItem(Sound, 10) end local function StopMaximumOutput() MaximumOutputActive = false MaximumOutputFlowLevel = 0 MaximumOutputSession += 1 StopMaximumOutputMusic() if MaximumOutputHoldSound then pcall(function() MaximumOutputHoldSound:Stop() MaximumOutputHoldSound:Destroy() end) MaximumOutputHoldSound = nil end MaxVHolding = false MaxVRunning = false MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED MaxVCurrentDistance = CFG.MAX_V_LATERAL_START_DISTANCE MaxVCenterPosition = nil MaxVForward = nil MaxVRight = nil MaxVStage = "Idle" MaxVReleaseRequested = false MaxVOrbitCenter = nil MaxVUsedNearPass = {} MaxXHolding = false MaxXRunning = false for _, Sound in ipairs(RootPart and RootPart:GetChildren() or {}) do if Sound:IsA("Sound") and Sound.Name == "MaximumOutputXHoldSound" then Sound:Stop() Sound:Destroy() end end if MaximumOutputConnection then MaximumOutputConnection:Disconnect(); MaximumOutputConnection = nil end UpdateMaximumOutputUI() end local function StartMaximumOutput() if MaximumOutputActive then StopMaximumOutput(); return end if Busy then return end if not TrySpendEnergy(CFG.ENERGY_COST_J) then return end MaximumOutputActive = true MaximumOutputFlowLevel = 0 MaximumOutputStartedAt = os.clock() MaximumOutputSession += 1 local ThisSession = MaximumOutputSession StartMaximumOutputMusic() -- Sonido adicional solo si Maximum Output sigue activo después de 1.5 s. task.delay(CFG.MAX_OUTPUT_HOLD_SOUND_DELAY, function() if MaximumOutputActive and MaximumOutputSession == ThisSession then PlayMaximumOutputHoldSound() end end) -- Límite total de la habilidad: 22 segundos por activación. task.delay(CFG.MAX_OUTPUT_DURATION, function() if MaximumOutputActive and MaximumOutputSession == ThisSession then StopMaximumOutput() end end) MaxVHolding = false MaxVRunning = false MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED MaxVCurrentDistance = CFG.MAX_V_LATERAL_START_DISTANCE MaxVCenterPosition = nil MaxVForward = nil MaxVRight = nil CreateMaximumOutputUI() MaximumOutputConnection = RunService.RenderStepped:Connect(function(Delta) if not MaximumOutputActive then return end DrainEnergy(CFG.MAX_OUTPUT_DRAIN_PER_SECOND * Delta) if MaximumOutputEnergy <= 0 then StopMaximumOutput() end end) end -- Regeneración: solo cuando no estás usando nada y pasó un momento desde el último gasto. -- El GUI tiembla y se pone rojizo con menos del 10% (o un instante al intentar gastar sin energía). local EnergyOrbBasePosition = UDim2.new(0, 22, 0.5, -41) RunService.Heartbeat:Connect(function(Delta) if not MaximumOutputActive and not ProjectionActive and not Busy and os.clock() - LastEnergySpend >= CFG.ENERGY_REGEN_DELAY and MaximumOutputEnergy < CFG.MAX_OUTPUT_MAX_ENERGY then MaximumOutputEnergy = math.min(CFG.MAX_OUTPUT_MAX_ENERGY, MaximumOutputEnergy + CFG.MAX_OUTPUT_REGEN_PER_SECOND * Delta) end UpdateMaximumOutputUI() if EnergyOrbHolder and EnergyOrbHolder.Parent then if IsEnergyLow() or os.clock() < EnergyDeniedUntil then EnergyOrbHolder.Position = EnergyOrbBasePosition + UDim2.fromOffset(math.random(-1, 1), math.random(-1, 1)) else EnergyOrbHolder.Position = EnergyOrbBasePosition end end end) task.spawn(function() Player:WaitForChild("PlayerGui", 10) CreateMaximumOutputUI() end) -- VIGNETTE local function CreateVignette() local PlayerGui = Player:FindFirstChildOfClass( "PlayerGui" ) if not PlayerGui then return end local Gui = Instance.new("ScreenGui") Gui.Name = "ProjectionVignette" Gui.IgnoreGuiInset = true Gui.ResetOnSpawn = false Gui.Parent = PlayerGui local Frame = Instance.new("Frame") Frame.Size = UDim2.fromScale( 1, 1 ) Frame.BackgroundColor3 = Color3.fromRGB( 180, 155, 255 ) Frame.BackgroundTransparency = 0.65 Frame.BorderSizePixel = 0 Frame.Parent = Gui local Fade = TweenService:Create( Frame, TweenInfo.new( 0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out ), { BackgroundTransparency = 1 } ) Fade:Play() Debris:AddItem( Gui, 0.4 ) end -- AFTERIMAGE local function CreateAfterimage() if not CFG.AFTERIMAGES_ENABLED then return end if not CharacterIsValid() then return end while #ActiveAfterimages >= CFG.MAX_AFTERIMAGES do local Oldest = table.remove( ActiveAfterimages, 1 ) if Oldest and Oldest.Parent then Oldest:Destroy() end end local Folder = Instance.new("Folder") Folder.Name = "ProjectionAfterimage" Folder.Parent = workspace table.insert( ActiveAfterimages, Folder ) for _, Object in ipairs( Character:GetDescendants() ) do if Object:IsA("BasePart") and Object.Name ~= "HumanoidRootPart" and Object.Transparency < 1 then local Ghost = Instance.new("Part") Ghost.Name = "AfterimagePart" Ghost.Size = Object.Size Ghost.CFrame = Object.CFrame Ghost.Anchored = true Ghost.CanCollide = false Ghost.CanTouch = false Ghost.CanQuery = false Ghost.Material = Enum.Material.Neon Ghost.Color = Color3.fromRGB( 155, 125, 255 ) Ghost.Transparency = 0.45 Ghost.Parent = Folder local Fade = TweenService:Create( Ghost, TweenInfo.new( CFG.AFTERIMAGE_LIFETIME, Enum.EasingStyle.Linear ), { Transparency = 1 } ) Fade:Play() end end task.delay( CFG.AFTERIMAGE_LIFETIME, function() local Index = table.find( ActiveAfterimages, Folder ) if Index then table.remove( ActiveAfterimages, Index ) end if Folder and Folder.Parent then Folder:Destroy() end end ) end -- CLEAR AFTERIMAGES local function ClearAfterimages() for _, Folder in ipairs( ActiveAfterimages ) do if Folder and Folder.Parent then Folder:Destroy() end end table.clear( ActiveAfterimages ) end -- VIÑETAS PASTEL (capas independientes: golpe de velocidad de C y vida baja) local function NewVignetteLayer(Name, DisplayOrder, MaxOpacity, Response) return { Name = Name, Order = DisplayOrder, MaxOpacity = MaxOpacity, Response = Response, Gui = nil, Gradients = {}, Current = 0, Target = 0, } end local BurstVignette = NewVignetteLayer("HeadHeiVignette", 40, CFG.VIGNETTE_MAX_OPACITY, 14) local LowHealthVignette = NewVignetteLayer("HeadHeiLowHealthVignette", 41, CFG.LOW_HEALTH_VIGNETTE_OPACITY, 4) local function BuildVignette(Layer) local PlayerGui = Player:FindFirstChildOfClass("PlayerGui") if not PlayerGui then return false end Layer.Gradients = {} local Gui = Instance.new("ScreenGui") Gui.Name = Layer.Name Gui.IgnoreGuiInset = true Gui.ResetOnSpawn = false Gui.DisplayOrder = Layer.Order Gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling local Size = CFG.VIGNETTE_SIZE local Edges = { { Anchor = Vector2.new(0, 0), Position = UDim2.fromScale(0, 0), Size = UDim2.fromScale(Size, 1), Rotation = 0 }, { Anchor = Vector2.new(1, 0), Position = UDim2.fromScale(1, 0), Size = UDim2.fromScale(Size, 1), Rotation = 180 }, { Anchor = Vector2.new(0, 0), Position = UDim2.fromScale(0, 0), Size = UDim2.fromScale(1, Size), Rotation = 90 }, { Anchor = Vector2.new(0, 1), Position = UDim2.fromScale(0, 1), Size = UDim2.fromScale(1, Size), Rotation = 270 }, } for _, Edge in ipairs(Edges) do local Frame = Instance.new("Frame") Frame.BorderSizePixel = 0 Frame.BackgroundColor3 = CFG.VIGNETTE_COLOR Frame.AnchorPoint = Edge.Anchor Frame.Position = Edge.Position Frame.Size = Edge.Size local Gradient = Instance.new("UIGradient") Gradient.Rotation = Edge.Rotation Gradient.Transparency = NumberSequence.new(1) Gradient.Parent = Frame Frame.Parent = Gui table.insert(Layer.Gradients, Gradient) end Gui.Enabled = false Gui.Parent = PlayerGui Layer.Gui = Gui return true end local function UpdateVignetteLayer(Layer, Delta) Layer.Current += (Layer.Target - Layer.Current) * (1 - math.exp(-Layer.Response * Delta)) if Layer.Current < 0.003 and Layer.Target <= 0 then Layer.Current = 0 if Layer.Gui and Layer.Gui.Parent then Layer.Gui.Enabled = false end return end if not Layer.Gui or not Layer.Gui.Parent then if not BuildVignette(Layer) then return end end Layer.Gui.Enabled = true local EdgeTransparency = 1 - Layer.MaxOpacity * math.clamp(Layer.Current, 0, 1) local Sequence = NumberSequence.new({ NumberSequenceKeypoint.new(0, EdgeTransparency), NumberSequenceKeypoint.new(1, 1), }) for _, Gradient in ipairs(Layer.Gradients) do Gradient.Transparency = Sequence end end local function UpdateVignettes(Delta) UpdateVignetteLayer(BurstVignette, Delta) UpdateVignetteLayer(LowHealthVignette, Delta) end -- CÁMARA ESTABLE -- La cámara sigue a un punto invisible que se mueve suave detrás del personaje (con la velocidad -- como anticipación para no quedarse atrás). Así no tiembla por los giros, los pasos a 24 fps -- ni los saltos de velocidad. Al terminar, vuelve a seguir al Humanoid. local CameraProxy = nil local CameraStabilizerState = "off" -- "off" | "on" | "settling" local CameraSettleUntil = 0 local CameraLastRootPosition = Vector3.zero local CameraSmoothVelocity = Vector3.zero local function DestroyCameraProxy() if CameraProxy then CameraProxy:Destroy() CameraProxy = nil end end local function UpdateCameraStabilizer(Delta) if not CFG.CAMERA_STABILIZER_ENABLED then return end local CurrentCamera = workspace.CurrentCamera if not CurrentCamera then return end -- Si otro script (o el respawn) cambió el sujeto de la cámara, nos apartamos. if CameraStabilizerState ~= "off" and (not CameraProxy or CurrentCamera.CameraSubject ~= CameraProxy) then CameraStabilizerState = "off" DestroyCameraProxy() return end local Valid = CharacterIsValid() local Wanted = Valid and (ProjectionActive or MaximumOutputActive or Busy) if Wanted then if CameraStabilizerState == "off" then local Focus = RootPart.Position + Vector3.new(0, CFG.CAMERA_FOCUS_HEIGHT, 0) CameraProxy = Instance.new("Part") CameraProxy.Name = "HeadHeiCameraProxy" CameraProxy.Anchored = true CameraProxy.CanCollide = false CameraProxy.CanQuery = false CameraProxy.CanTouch = false CameraProxy.Transparency = 1 CameraProxy.Size = Vector3.new(1, 1, 1) CameraProxy.Position = Focus CameraProxy.Parent = workspace CameraLastRootPosition = RootPart.Position CameraSmoothVelocity = Vector3.zero CurrentCamera.CameraSubject = CameraProxy CameraStabilizerState = "on" elseif CameraStabilizerState == "settling" then CameraStabilizerState = "on" end elseif CameraStabilizerState == "on" then CameraStabilizerState = "settling" CameraSettleUntil = os.clock() + CFG.CAMERA_SETTLE_TIME end if CameraStabilizerState == "off" then return end if not Valid then CurrentCamera.CameraSubject = (Humanoid and Humanoid.Parent) and Humanoid or nil CameraStabilizerState = "off" DestroyCameraProxy() return end local RootPosition = RootPart.Position local RawVelocity = (RootPosition - CameraLastRootPosition) / math.max(Delta, 1 / 240) CameraLastRootPosition = RootPosition CameraSmoothVelocity = CameraSmoothVelocity:Lerp(RawVelocity, 1 - math.exp(-8 * Delta)) local Target = RootPosition + Vector3.new(0, CFG.CAMERA_FOCUS_HEIGHT, 0) local Follow = CFG.CAMERA_FOLLOW_RESPONSE * ((CameraStabilizerState == "settling") and 2 or 1) local NewPosition = CameraProxy.Position + CameraSmoothVelocity * Delta NewPosition = NewPosition:Lerp(Target, 1 - math.exp(-Follow * Delta)) if (NewPosition - Target).Magnitude > 80 then NewPosition = Target end CameraProxy.Position = NewPosition if CameraStabilizerState == "settling" and ((NewPosition - Target).Magnitude < 0.5 or os.clock() >= CameraSettleUntil) then CurrentCamera.CameraSubject = Humanoid CameraStabilizerState = "off" DestroyCameraProxy() end end pcall(function() RunService:UnbindFromRenderStep("HeadHeiCameraStabilizer") end) pcall(function() RunService:UnbindFromRenderStep("HeadHeiVignette") end) RunService:BindToRenderStep("HeadHeiCameraStabilizer", Enum.RenderPriority.Camera.Value - 1, UpdateCameraStabilizer) RunService:BindToRenderStep("HeadHeiVignette", Enum.RenderPriority.Last.Value, UpdateVignettes) -- VIDA BAJA -- Menos de LOW_HEALTH_ENTER (33): viñeta morada pastel al 10% + canción; se quitan al superar LOW_HEALTH_EXIT (40). -- Menos de CRITICAL_HEALTH_ENTER (25): segunda canción + buffs en las habilidades (riesgo de muerte). local CriticalActive = false local LowHealthActive = false local LowHealthSound = nil local CriticalSound = nil local function StartLoopSound(SoundIdNumber, Volume, Name) local Sound = Instance.new("Sound") Sound.Name = Name Sound.SoundId = "rbxassetid://" .. SoundIdNumber Sound.Volume = Volume Sound.Looped = true Sound.Parent = game:GetService("SoundService") Sound:Play() return Sound end local function StopLoopSound(Sound) if Sound then pcall(function() Sound:Stop() Sound:Destroy() end) end end local function UpdateLowHealth() local Health = nil if Humanoid and Humanoid.Parent and Humanoid.Health > 0 then Health = Humanoid.Health end if not Health then -- Muerto o sin personaje: se apaga todo. LowHealthActive = false CriticalActive = false StopLoopSound(LowHealthSound) LowHealthSound = nil StopLoopSound(CriticalSound) CriticalSound = nil LowHealthVignette.Target = 0 return end if not LowHealthActive and Health < CFG.LOW_HEALTH_ENTER then LowHealthActive = true LowHealthSound = StartLoopSound(CFG.LOW_HEALTH_SOUND_ID, CFG.LOW_HEALTH_SOUND_VOLUME, "HeadHeiLowHealthSound") elseif LowHealthActive and Health > CFG.LOW_HEALTH_EXIT then LowHealthActive = false StopLoopSound(LowHealthSound) LowHealthSound = nil end if not CriticalActive and Health < CFG.CRITICAL_HEALTH_ENTER then CriticalActive = true CriticalSound = StartLoopSound(CFG.CRITICAL_SOUND_ID, CFG.CRITICAL_SOUND_VOLUME, "HeadHeiCriticalSound") elseif CriticalActive and Health >= CFG.CRITICAL_HEALTH_EXIT then CriticalActive = false StopLoopSound(CriticalSound) CriticalSound = nil end LowHealthVignette.Target = LowHealthActive and 1 or 0 end RunService.Heartbeat:Connect(UpdateLowHealth) -- PROJECTION ANIMATIONS -- Convierte lo que haya en una lista (texto con un ID por línea, tabla, o números) en IDs válidos. local function ExpandAnimationIds(Source, Out) Out = Out or {} if type(Source) == "table" then for _, Item in ipairs(Source) do ExpandAnimationIds(Item, Out) end elseif type(Source) == "number" then if Source > 0 then table.insert(Out, "rbxassetid://" .. string.format("%d", Source)) end elseif type(Source) == "string" then for Line in string.gmatch(Source, "[^\r\n,;]+") do Line = string.gsub(Line, "%-%-.*$", "") -- ignora comentarios local Digits = string.match(Line, "%d%d%d%d%d%d+") -- un ID real tiene 6+ dígitos if Digits and tonumber(Digits) and tonumber(Digits) > 0 then table.insert(Out, "rbxassetid://" .. Digits) end end end return Out end -- Elige un ID de una lista según el modo ("Random", "Cycle" o "First"). local function PickAnimationId(Source, Mode, Key) local Valid = ExpandAnimationIds(Source) if #Valid == 0 then return nil end if Mode == "Cycle" then local Index = (ProjectionCycleIndices[Key] or 0) % #Valid + 1 ProjectionCycleIndices[Key] = Index return Valid[Index] elseif Mode == "First" then return Valid[1] end return Valid[math.random(#Valid)] end local function LoadAnimationTrack(Id, Looped, Priority) if not CharacterIsValid() then return nil end local Animator = Humanoid:FindFirstChildOfClass("Animator") if not Animator then Animator = Instance.new("Animator") Animator.Parent = Humanoid end local Animation = Instance.new("Animation") Animation.AnimationId = Id local Success, Result = pcall(function() return Animator:LoadAnimation(Animation) end) if not Success then warn("Projection Animation Error:", Result) Animation:Destroy() return nil end Result.Looped = Looped Result.Priority = Priority return Result, Animation end -- Animación principal de C (bucle). local function LoadProjectionAnimation() if not CharacterIsValid() then return end local Id = PickAnimationId( { CFG.PROJECTION_ANIMATION_ID, CFG.PROJECTION_ANIMATIONS.Main }, CFG.PROJECTION_ANIMATION_MODE, "Main" ) if not Id then return end local Track, Animation = LoadAnimationTrack(Id, true, Enum.AnimationPriority.Action) if not Track then return end ProjectionAnimation = Animation ProjectionAnimationTrack = Track end -- Animaciones puntuales (Start, LaneLeft, LaneRight, Circular). local function StopProjectionEventAnimation(FadeTime) local Track = ProjectionEventTrack local Animation = ProjectionEventAnimation ProjectionEventTrack = nil ProjectionEventAnimation = nil if Track then pcall(function() Track:Stop(FadeTime) end) task.delay(FadeTime + 0.1, function() pcall(function() Track:Destroy() end) end) end if Animation then task.delay(FadeTime + 0.1, function() pcall(function() Animation:Destroy() end) end) end end local function PlayProjectionEvent(Name, FitDuration) if not CharacterIsValid() then return end local List = CFG.PROJECTION_EVENT_ANIMATIONS[Name] if not List then return end local Id = PickAnimationId(List, CFG.PROJECTION_EVENT_MODE, "Event_" .. Name) if not Id then return end StopProjectionEventAnimation(0.08) local Track, Animation = LoadAnimationTrack(Id, false, Enum.AnimationPriority.Action2) if not Track then return end ProjectionEventTrack = Track ProjectionEventAnimation = Animation Track:Play(0.08, 1, 1) if CFG.PROJECTION_EVENT_FIT_TO_DURATION and FitDuration and FitDuration > 0 and Track.Length > 0 then Track:AdjustSpeed(Track.Length / FitDuration) end end -- STOP PROJECTION ANIMATION local function StopProjectionAnimation() StopProjectionEventAnimation(0.12) if ProjectionAnimationTrack then ProjectionAnimationTrack.Looped = false ProjectionAnimationTrack:Stop(0.12) ProjectionAnimationTrack:Destroy() ProjectionAnimationTrack = nil end if ProjectionAnimation then ProjectionAnimation:Destroy() ProjectionAnimation = nil end end -- TARGET SELECTION local function GetPlayerTarget( MaxDistance ) if not CharacterIsValid() then return nil end local BestTarget = nil local BestScore = math.huge local CameraPosition = Camera.CFrame.Position local CameraDirection = Camera.CFrame.LookVector for _, OtherPlayer in ipairs( Players:GetPlayers() ) do if OtherPlayer ~= Player then local OtherCharacter = OtherPlayer.Character if OtherCharacter then local OtherHumanoid = OtherCharacter: FindFirstChildOfClass( "Humanoid" ) local OtherRoot = OtherCharacter: FindFirstChild( "HumanoidRootPart" ) if OtherHumanoid and OtherRoot and OtherHumanoid.Health > 0 then local Offset = OtherRoot.Position - CameraPosition local Distance = Offset.Magnitude if Distance <= MaxDistance and Distance > 1 then local Direction = Offset.Unit local Dot = CameraDirection:Dot( Direction ) if Dot > 0.90 then local Score = 1 - Dot if Score < BestScore then BestScore = Score BestTarget = OtherRoot end end end end end end end return BestTarget end -- MOVEMENT DIRECTION local function GetHorizontalDirection() if not CharacterIsValid() then return Vector3.new( 0, 0, -1 ) end local MoveDirection = Humanoid.MoveDirection if MoveDirection.Magnitude > 0.05 then local Direction = Vector3.new( MoveDirection.X, 0, MoveDirection.Z ) if Direction.Magnitude > 0 then return Direction.Unit end end local Look = RootPart.CFrame.LookVector local Direction = Vector3.new( Look.X, 0, Look.Z ) if Direction.Magnitude > 0 then return Direction.Unit end return Vector3.new( 0, 0, -1 ) end -- LANE SHIFT -- LANE SHIFT -- No toma el control del HRP: C sigue avanzando mientras se aplica el deslizamiento. local function DoLaneShift(Side) if not ProjectionActive or not ProjectionHolding or LaneShiftActive then return end if Side ~= -1 and Side ~= 1 then return end if not CharacterIsValid() then return end if not TrySpendEnergy(CFG.ENERGY_COST_LANE_SHIFT) then return end local Forward = GetHorizontalDirection() if Forward.Magnitude < 0.1 then Forward = Vector3.new(0, 0, -1) end Forward = Forward.Unit local Right = Vector3.new(-Forward.Z, 0, Forward.X) LaneShiftActive = true LaneShiftSide = Side LaneShiftStartOffset = LaneShiftCurrentOffset LaneShiftTargetOffset = LaneShiftStartOffset + Right * (CFG.LANE_SHIFT_DISTANCE * Side) LaneShiftStartTime = os.clock() LaneShiftKey = (Side < 0) and "A" or "D" PlayProjectionEvent((Side < 0) and "LaneLeft" or "LaneRight", CFG.LANE_SHIFT_DURATION) end -- C + Z - CIRCULAR PROJECTION local function DoCircularProjection() if not ProjectionActive then return end if CircularProjectionActive then return end local Target = GetPlayerTarget( CFG.Z_TARGET_DISTANCE ) if not Target then return end if not TrySpendEnergy(CFG.ENERGY_COST_CIRCULAR) then return end CircularProjectionActive = true PlayProjectionEvent("Circular", CFG.CIRCLE_TIME) local StartTime = os.clock() local StartPosition = RootPart.Position local TargetPosition = Target.Position local InitialDirection = ( StartPosition - TargetPosition ) if InitialDirection.Magnitude < 1 then InitialDirection = Vector3.new( 1, 0, 0 ) end InitialDirection = InitialDirection.Unit local StartAngle = math.atan2( InitialDirection.Z, InitialDirection.X ) local LastAfterimage = os.clock() while os.clock() - StartTime < CFG.CIRCLE_TIME do if not ProjectionActive or not ProjectionHolding or not CharacterIsValid() then break end if not Target or not Target.Parent then break end local Alpha = ( os.clock() - StartTime ) / CFG.CIRCLE_TIME local Angle = StartAngle + ( Alpha * math.pi * 2 * CFG.CIRCLE_ROTATIONS ) local CurrentTarget = Target.Position local Offset = Vector3.new( math.cos(Angle) * CFG.CIRCLE_RADIUS, 0, math.sin(Angle) * CFG.CIRCLE_RADIUS ) local NewPosition = CurrentTarget + Offset RootPart.CFrame = CFrame.lookAt( NewPosition, CurrentTarget ) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end if ProjectionActive and ProjectionHolding and CharacterIsValid() then local CurrentPosition = RootPart.Position local Center = Target.Position local RadiusVector = CurrentPosition - Center if RadiusVector.Magnitude > 0.1 then local Tangent = Vector3.new( -RadiusVector.Z, 0, RadiusVector.X ).Unit RootPart.CFrame = CFrame.lookAt( CurrentPosition, CurrentPosition + Tangent ) end end CircularProjectionActive = false end -- CREATE CRYSTAL local function CreateCrystal( Position, Direction ) local Crystal = Instance.new("Part") Crystal.Name = "ProjectionCrystal" Crystal.Size = Vector3.new( 0.35, 0.35, 1.8 ) Crystal.Material = Enum.Material.Glass Crystal.Color = Color3.fromRGB( 190, 170, 255 ) Crystal.Transparency = 0.25 Crystal.Anchored = true Crystal.CanCollide = false Crystal.CanTouch = false Crystal.CanQuery = false Crystal.CFrame = CFrame.lookAt( Position, Position + Direction ) Crystal.Parent = workspace Debris:AddItem( Crystal, 1.5 ) end -- STOP PROJECTION local function StopProjection() ProjectionActive = false ProjectionSoundStart = 0 BurstVignette.Target = 0 ProjectionHolding = false ProjectionReleasing = false LaneShiftActive = false LaneShiftCurrentOffset = Vector3.zero LaneShiftStartOffset = Vector3.zero LaneShiftTargetOffset = Vector3.zero LaneShiftSide = 0 CircularProjectionActive = false LaneShiftKey = nil StopProjectionAnimation() if ProjectionConnection then ProjectionConnection:Disconnect() ProjectionConnection = nil end if ProjectionInputConnection then ProjectionInputConnection:Disconnect() ProjectionInputConnection = nil end if ProjectionDeathConnection then ProjectionDeathConnection:Disconnect() ProjectionDeathConnection = nil end Busy = false end -- C - PROJECTION local function Projection() if Busy then return end if not CanUse("C") then return end if not CharacterIsValid() then return end if not TrySpendEnergy(CFG.ENERGY_COST_C_START, true) then return end LastUse.C = os.clock() Busy = true ProjectionActive = true ProjectionHolding = true CreateVignette() LoadProjectionAnimation() ProjectionSoundStart = os.clock() if ProjectionAnimationTrack then ProjectionAnimationTrack:Play( 0.1, 1, 1 ) end PlayProjectionEvent("Start") -- 🔊 Ritmo progresivo de sonido de C: -- Las primeras etapas ahora llegan rápido para que C acelere -- mucho antes. La progresión continúa hasta 24 sonidos/segundo. -- -- En vez de lanzar una ráfaga instantánea, el intervalo entre -- sonidos se hace cada vez menor. Así el audio acompaña la -- aceleración progresiva de C. StartNormalCSoundLoop() task.spawn(function() local Stages = { {0.55, 1}, {0.95, 2}, {1.30, 3}, {1.65, 4}, {2.00, 5}, {2.30, 6}, {2.60, 7}, {2.90, 8}, {3.20, 9}, {3.50, 10}, {3.80, 12}, {4.10, 14}, {4.40, 16}, {4.70, 18}, {5.00, 20}, {5.30, 22}, {5.60, 24}, } local SoundStart = os.clock() local StageIndex = 1 local NextSound = SoundStart + Stages[1][1] local SoundsPerSecond = 1 local CSoundPlays = 0 while ProjectionActive do local now = os.clock() -- Cambia la frecuencia cuando C alcanza cada etapa. while StageIndex < #Stages and now - SoundStart >= Stages[StageIndex + 1][1] do StageIndex += 1 SoundsPerSecond = Stages[StageIndex][2] -- El siguiente sonido conserva el nuevo ritmo. NextSound = now end if now >= NextSound then -- Cada reproducción suena un poco más bajo; se queda en el mínimo hasta que termine C. local VolumeFactor = math.max(CFG.C_SOUND_MIN_VOLUME_FACTOR, 1 - CSoundPlays * CFG.C_SOUND_VOLUME_STEP) CSoundPlays += 1 PlayMovementSound(CFG.C_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME * VolumeFactor, 1, GetCProgressivePitch()) -- Máximo: 24 sonidos por segundo. NextSound = now + (1 / SoundsPerSecond) end task.wait(0.01) end end) local StartPosition = RootPart.Position local DistanceTravelled = 0 local Speed = CFG.PROJECTION_MIN_SPEED local LastCrystalPosition = StartPosition local LastCrystalSpawn = os.clock() local LastAfterimage = os.clock() local StepInterval = 1 / math.max(CFG.PROJECTION_STEP_FPS, 1) local StepAccumulator = 0 local TimeSinceStep = 0 local StepCounter = 0 local BurstEnergy = 0 local CritBoost = CriticalActive and 1 or 0 -- buff de riesgo de muerte: velocidad extra al iniciar C for i = 1, CFG.INITIAL_CRYSTALS do if not CharacterIsValid() then break end local Direction = GetHorizontalDirection() local Position = LastCrystalPosition + Direction * CFG.CRYSTAL_SPACING if (Position - StartPosition) .Magnitude >= CFG.PROJECTION_MAX_DISTANCE then break end LastCrystalPosition = Position CreateCrystal( Position, Direction ) end ProjectionInputConnection = UserInputService.InputEnded:Connect( function(Input) if Input.KeyCode == Enum.KeyCode.C then ProjectionHolding = false if ProjectionActive and not ProjectionReleasing then ProjectionReleasing = true ProjectionReleaseStart = os.clock() ProjectionReleaseSpeed = Speed ProjectionReleaseDirection = GetHorizontalDirection() end end end ) ProjectionDeathConnection = Humanoid.Died:Connect( function() ProjectionHolding = false StopProjection() end ) ProjectionConnection = RunService.RenderStepped:Connect( function(Delta) if not ProjectionActive then StopProjection() return end if not CharacterIsValid() then StopProjection() return end -- MOVIMIENTO A 24 FPS: la posición solo cambia en cada "paso" de 1/24 s, pero cada paso -- avanza todo el tiempo transcurrido desde el anterior, así que no se pierde velocidad. local DoStep = true local StepTime = Delta local StepCount = 1 if CFG.PROJECTION_STEPPED then StepAccumulator += Delta TimeSinceStep += Delta StepCount = math.floor(StepAccumulator / StepInterval) if StepCount >= 1 then StepAccumulator -= StepCount * StepInterval StepTime = TimeSinceStep TimeSinceStep = 0 else DoStep = false end end if DoStep then StepCounter += StepCount end -- ENERGÍA: C gasta energía mientras está activa; si se acaba, se suelta sola. if not ProjectionReleasing then DrainEnergy(CFG.ENERGY_COST_C_PER_SECOND * Delta) if MaximumOutputEnergy <= 0 then ProjectionHolding = false ProjectionReleasing = true ProjectionReleaseStart = os.clock() ProjectionReleaseSpeed = Speed ProjectionReleaseDirection = GetHorizontalDirection() end end -- C SOLTADA: DESACELERACIÓN PROPORCIONAL -- Si ibas lento, tarda más en perder la velocidad. -- Si ibas rapidísimo, frena mucho más rápido. if ProjectionReleasing then local ReleaseElapsed = os.clock() - ProjectionReleaseStart local SpeedRatio = math.clamp(ProjectionReleaseSpeed / math.max(CFG.PROJECTION_MAX_SPEED, 1), 0, 1) local DecelDuration = CFG.PROJECTION_DECEL_MAX_TIME - (CFG.PROJECTION_DECEL_MAX_TIME - CFG.PROJECTION_DECEL_MIN_TIME) * SpeedRatio local ReleaseAlpha = math.clamp(ReleaseElapsed / DecelDuration, 0, 1) local EaseOut = 1 - (1 - ReleaseAlpha)^3 local CurrentSpeed = ProjectionReleaseSpeed * (1 - EaseOut) if CurrentSpeed <= CFG.PROJECTION_MIN_SPEED + 0.05 or ReleaseAlpha >= 1 then StopProjection() return end local Direction = ProjectionReleaseDirection if Direction.Magnitude < 0.1 then Direction = GetHorizontalDirection() end Direction = Direction.Unit BurstVignette.Target = 0 if not DoStep then return end local FrameDistance = CurrentSpeed * StepTime local NewPosition = RootPart.Position + Direction * FrameDistance RootPart.CFrame = CFrame.lookAt(NewPosition, NewPosition + Direction) DistanceTravelled += FrameDistance if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end return end Speed += CFG.PROJECTION_ACCELERATION * Delta Speed = math.clamp( Speed, CFG.PROJECTION_MIN_SPEED, CFG.PROJECTION_MAX_SPEED ) -- La animacion de C acelera junto con la velocidad real de Projection. if ProjectionAnimationTrack then local AnimationSpeed = math.clamp( Speed / math.max(CFG.PROJECTION_MIN_SPEED, 1), 1, 20 ) ProjectionAnimationTrack:AdjustSpeed(AnimationSpeed) end -- GOLPE DE VELOCIDAD: cada N pasos sube de golpe y baja hasta la velocidad normal. -- La viñeta morada pastel sigue la misma curva. BurstEnergy = math.max(0, BurstEnergy - Delta / CFG.PROJECTION_BURST_DECAY) CritBoost = math.max(0, CritBoost - Delta / CFG.CRIT_C_BOOST_DECAY) BurstVignette.Target = BurstEnergy if not DoStep then return end local Every = math.max(CFG.PROJECTION_BURST_EVERY_STEPS, 1) if math.floor(StepCounter / Every) > math.floor((StepCounter - StepCount) / Every) then BurstEnergy = 1 BurstVignette.Target = 1 end local BurstFactor = 1 + CFG.PROJECTION_BURST_BOOST * BurstEnergy * BurstEnergy -- Energía baja (<10%): C pierde velocidad. Riesgo de muerte: buff que se desvanece. local EnergyFraction = GetEnergyFraction() local EnergyFactor = 1 if EnergyFraction < CFG.ENERGY_LOW_FRACTION then EnergyFactor = CFG.ENERGY_LOW_C_MIN_SPEED_FACTOR + (1 - CFG.ENERGY_LOW_C_MIN_SPEED_FACTOR) * (EnergyFraction / CFG.ENERGY_LOW_FRACTION) end local CritFactor = 1 + CFG.CRIT_C_START_BOOST * CritBoost local Direction = GetHorizontalDirection() local FrameDistance = Speed * BurstFactor * EnergyFactor * CritFactor * StepTime local Remaining = CFG.PROJECTION_MAX_DISTANCE - DistanceTravelled if Remaining <= 0 then StopProjection() return end if FrameDistance > Remaining then FrameDistance = Remaining end if not CircularProjectionActive then local NewPosition = RootPart.Position + Direction * FrameDistance local LaneLean = 0 local LanePitch = 0 if LaneShiftActive then local LaneAlpha = math.clamp( (os.clock() - LaneShiftStartTime) / CFG.LANE_SHIFT_DURATION, 0, 1 ) local LaneEase = LaneAlpha * LaneAlpha * (3 - 2 * LaneAlpha) local DesiredOffset = LaneShiftStartOffset:Lerp(LaneShiftTargetOffset, LaneEase) NewPosition += DesiredOffset - LaneShiftCurrentOffset LaneShiftCurrentOffset = DesiredOffset -- Entra y sale rápido de la pose y la mantiene en el centro del movimiento. local LaneEnvelope = math.clamp(math.sin(LaneAlpha * math.pi) * 1.6, 0, 1) LanePitch = CFG.LANE_SHIFT_SLIDE_PITCH * LaneEnvelope LaneLean = CFG.LANE_SHIFT_LEAN_DIRECTION * CFG.LANE_SHIFT_LEAN * LaneShiftSide * LaneEnvelope if LaneAlpha >= 1 then LaneShiftActive = false LaneShiftSide = 0 end end local BaseCFrame = CFrame.lookAt(NewPosition, NewPosition + Direction) RootPart.CFrame = BaseCFrame * CFrame.Angles(LanePitch, 0, LaneLean) end DistanceTravelled += FrameDistance local SpawnInterval = CFG.CRYSTAL_SPACING / Speed if os.clock() - LastCrystalSpawn >= SpawnInterval then LastCrystalSpawn = os.clock() for i = 1, CFG.CRYSTALS_PER_STEP do local CrystalDirection = GetHorizontalDirection() local NewPosition = LastCrystalPosition + CrystalDirection * CFG.CRYSTAL_SPACING if ( NewPosition - StartPosition ).Magnitude >= CFG.PROJECTION_MAX_DISTANCE then break end LastCrystalPosition = NewPosition CreateCrystal( NewPosition, CrystalDirection ) end end if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end end ) end -- MAXIMUM OUTPUT X local function MaximumOutputX() if not MaximumOutputActive or MaxXRunning or not CharacterIsValid() then return end local Target = GetPlayerTarget(CFG.X_TARGET_DISTANCE) if not Target or not Target.Parent then return end MaxXRunning = true -- Riesgo de muerte: más velocidad y una pausa corta extra entre intervalos junto al objetivo. local XPassLegTime = CFG.MAX_X_PASS_TIME local XReturnLegTime = CFG.MAX_X_RETURN_TIME local XCenterPause = 0 if CriticalActive then XPassLegTime /= CFG.CRIT_MAX_X_SPEED_FACTOR XReturnLegTime /= CFG.CRIT_MAX_X_SPEED_FACTOR XCenterPause = CFG.CRIT_MAX_X_CENTER_PAUSE end local OldAutoRotate = Humanoid.AutoRotate Humanoid.AutoRotate = false StartAimLock(Target) local HoldStart = os.clock() local DirectionIndex = 0 local LastAfterimage = os.clock() -- El sonido aparece al comenzar el hold y usa el tramo 0.5 -> 2.8 s. -- Si el hold continúa, el tramo vuelve a empezar automáticamente. PlayMaximumOutputXHoldSound() -- Ocho direcciones alrededor del objetivo; el alcance crece cada 1.4 s. while MaximumOutputActive and MaxXHolding and CharacterIsValid() and Target.Parent do local elapsed = os.clock() - HoldStart local tier = math.floor(elapsed / CFG.MAX_X_DISTANCE_STEP_TIME) local distance = math.min(CFG.MAX_X_PASS_DISTANCE + tier * CFG.MAX_X_DISTANCE_PER_STEP, CFG.MAX_X_DISTANCE_CAP) DirectionIndex += 1 local angle = (DirectionIndex - 1) * (math.pi * 2 / CFG.MAX_X_DIRECTION_COUNT) local radial = Vector3.new(math.cos(angle), 0, math.sin(angle)) local targetPosition = Target.Position local outward = targetPosition + radial * distance local inward = targetPosition - radial * distance local function MoveTo(goal, duration) local origin = RootPart.Position local goalOffset = goal - targetPosition local t0 = os.clock() while MaximumOutputActive and MaxXHolding and CharacterIsValid() and Target.Parent and os.clock() - t0 < duration do local a = math.clamp((os.clock() - t0) / duration, 0, 1) local eased = 1 - (1 - a)^2.4 local currentTarget = Target.Position local pos = origin:Lerp(currentTarget + goalOffset, eased) RootPart.CFrame = CFrame.lookAt(pos, currentTarget) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end end MoveTo(inward, XPassLegTime) if XCenterPause > 0 then MoveTo(inward, XCenterPause) end if not MaxXHolding then break end MoveTo(outward, XReturnLegTime) end -- Al soltar X, termina sin iniciar la órbita. StopAimLock() if Humanoid and Humanoid.Parent then Humanoid.AutoRotate = OldAutoRotate end MaxXRunning = false end -- X - MOMENTUM CHAIN / IMPACTO + SUSPENSIÓN local XHolding = false local XActive = false local XChainRequested = false local XChainWindowOpen = false local XChainCount = 0 local AimLockTarget = nil local AimLockBound = false function StartAimLock(Target) if not CFG.AIM_LOCK_ENABLED then return end AimLockTarget = Target if AimLockBound then return end AimLockBound = true pcall(function() RunService:UnbindFromRenderStep("HeadHeiAimLock") end) RunService:BindToRenderStep("HeadHeiAimLock", Enum.RenderPriority.Camera.Value + 1, function() if not CFG.AIM_LOCK_ENABLED or not AimLockTarget or not AimLockTarget.Parent then return end local CurrentCamera = workspace.CurrentCamera if not CurrentCamera then return end local CameraPosition = CurrentCamera.CFrame.Position local AimPoint = AimLockTarget.Position + Vector3.new(0, CFG.AIM_LOCK_HEIGHT, 0) local DesiredCFrame = CFrame.lookAt(CameraPosition, AimPoint) CurrentCamera.CFrame = CurrentCamera.CFrame:Lerp(DesiredCFrame, CFG.AIM_LOCK_SMOOTHNESS) end) end function StopAimLock() AimLockTarget = nil if AimLockBound then AimLockBound = false pcall(function() RunService:UnbindFromRenderStep("HeadHeiAimLock") end) end end local function ProjectionBreaker() -- Si X ya está activo, una nueva pulsación durante la ventana -- solicita el siguiente dash en vez de reiniciar la habilidad. if XActive then if XChainWindowOpen then XChainRequested = true end return end if Busy then return end if not CanUse("X") then return end if not CharacterIsValid() then return end if not TrySpendEnergy(CFG.ENERGY_COST_X) then return end -- Riesgo de muerte: más distancia y más velocidad entre saltos. local XSpeed = CriticalActive and CFG.CRIT_X_SPEED_FACTOR or 1 local XDist = CriticalActive and CFG.CRIT_X_DISTANCE_FACTOR or 1 local XTargetDistance = CFG.X_TARGET_DISTANCE * XDist local XDashTime = CFG.X_DASH_TO_TARGET_TIME * XSpeed local XPostHitDistance = CFG.X_POST_HIT_DISTANCE * XDist local XOrbitRadius = CFG.X_ORBIT_RADIUS * XDist local XPassTime = CFG.X_PASS_THROUGH_TIME * XSpeed local XDecisionTime = CFG.X_ORBIT_DECISION_TIME * XSpeed local XGlideTime = CFG.X_GLIDE_TIME * XSpeed local XChainPause = CFG.X_CHAIN_PAUSE * XSpeed LastUse.X = os.clock() Busy = true XActive = true XHolding = true XChainRequested = false XChainWindowOpen = false XChainCount = 0 CreateVignette() local OldAutoRotate = Humanoid.AutoRotate Humanoid.AutoRotate = false local LastAfterimage = os.clock() -- CADA CADENA = IMPULSO -> PASADA -> PÉRDIDA DE INERCIA while XActive and CharacterIsValid() do local Target = GetPlayerTarget(XTargetDistance) if not Target or not Target.Parent then break end StartAimLock(Target) -- La dirección de salida pertenece a la TRAYECTORIA, no a la rotación -- del personaje. Cada nueva X invierte 180° el lado de la curva. local ChainIndex = (XChainCount or 0) + 1 XChainCount = ChainIndex local ChainTrajectorySign = (ChainIndex % 2 == 0) and -1 or 1 -- 1. IDA: ENTRADA RÁPIDA AL OBJETIVO local DashStart = RootPart.Position local DashStartTime = os.clock() while os.clock() - DashStartTime < XDashTime do if not XActive or not CharacterIsValid() or not Target or not Target.Parent then break end local Elapsed = os.clock() - DashStartTime local Alpha = math.clamp(Elapsed / XDashTime, 0, 1) local MovementAlpha = 1 - (1 - Alpha)^2.2 local CurrentTarget = Target.Position local ToTarget = CurrentTarget - DashStart if ToTarget.Magnitude < 0.1 then break end local Direction = ToTarget.Unit local DesiredPosition = CurrentTarget - Direction * CFG.X_TARGET_OFFSET local NewPosition = DashStart:Lerp(DesiredPosition, MovementAlpha) -- El cuerpo mira al objetivo normalmente; NO hacemos roll/rotación visual. RootPart.CFrame = CFrame.lookAt(NewPosition, CurrentTarget) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end if not XActive or not CharacterIsValid() or not Target or not Target.Parent then break end -- 2. PASADA: LLEGAMOS JUSTO AL LADO DEL OBJETIVO local CurrentTarget = Target.Position local FromTarget = RootPart.Position - CurrentTarget if FromTarget.Magnitude < 0.1 then FromTarget = -GetHorizontalDirection() end local HitPosition = CurrentTarget + FromTarget.Unit * CFG.X_TARGET_OFFSET RootPart.CFrame = CFrame.lookAt(HitPosition, CurrentTarget) -- 3. PASADA + GAMBLE DE ÓRBITA -- ATRAVIESA 6 STUDS Y, EN 0.075s, EL SISTEMA -- ELIGE AL AZAR 1 DE 4 POSICIONES ALREDEDOR -- DEL OBJETIVO. EL JUGADOR NO DECIDE EL LADO. local PassDirection = FromTarget.Unit local HorizontalPass = Vector3.new(PassDirection.X, 0, PassDirection.Z) if HorizontalPass.Magnitude < 0.1 then HorizontalPass = -GetHorizontalDirection() end HorizontalPass = HorizontalPass.Unit -- Ejes locales alrededor del objetivo. local OrbitForward = HorizontalPass local OrbitRight = Vector3.new(-OrbitForward.Z, 0, OrbitForward.X).Unit -- 4 posiciones cardinales: frente, derecha, atrás, izquierda. local OrbitPositions = { OrbitForward, OrbitRight, -OrbitForward, -OrbitRight, } -- GAMBLE: se decide una sola vez por cada activación de X. local GambleIndex = math.random(1, 4) local GambleDirection = OrbitPositions[GambleIndex] local PassStart = RootPart.Position local PassEnd = PassStart + PassDirection * XPostHitDistance -- La posición final de la trayectoria se recalcula respecto al -- objetivo actual para que el movimiento siga siendo dinámico. local TargetAtDecision = Target.Position local OrbitEnd = TargetAtDecision + GambleDirection * XOrbitRadius + Vector3.new(0, math.random(-2, 4), 0) local GlideStart = PassEnd local GlideStartTime = os.clock() -- 4. VENTANA AÉREA: 1.4s PARA VOLVER A PULSAR X -- PRIMEROS 0.1s = DECISIÓN/GIRO RÁPIDO HACIA -- UNA DE LAS 4 POSICIONES. -- DESPUÉS = DESACELERACIÓN SUAVE, SIN CAMBIO BRUSCO. XChainRequested = false XChainWindowOpen = true while os.clock() - GlideStartTime < CFG.X_CHAIN_WINDOW do if not XActive or not CharacterIsValid() or not Target or not Target.Parent then break end if XChainRequested then break end local Now = os.clock() local Elapsed = Now - GlideStartTime local CurrentTarget = Target.Position local DesiredPosition if Elapsed < XPassTime then -- Primero atraviesa al objetivo. Todavía no intenta orbitarlo. local PassAlpha = math.clamp(Elapsed / XPassTime, 0, 1) local PassEase = 1 - (1 - PassAlpha)^2.5 DesiredPosition = PassStart:Lerp(PassEnd, PassEase) elseif Elapsed < XPassTime + XDecisionTime then -- GAMBLE RÁPIDO: en esta pequeña ventana se fija la dirección -- de salida. La trayectoria acelera hacia ella sin teletransportar. local DecisionElapsed = Elapsed - XPassTime local DecisionAlpha = math.clamp(DecisionElapsed / XDecisionTime, 0, 1) local DecisionEase = 1 - (1 - DecisionAlpha)^2.2 DesiredPosition = GlideStart:Lerp(OrbitEnd, DecisionEase) else -- INERCIA: continúa alejándose hacia la posición ganadora, -- pero cada vez más lento. El objetivo puede moverse. local GlideElapsed = Elapsed - XPassTime - XDecisionTime local GlideDuration = math.max(XGlideTime - XPassTime - XDecisionTime, 0.05) local GlideAlphaRaw = math.clamp(GlideElapsed / GlideDuration, 0, 1) local GlideAlpha = 1 - (1 - GlideAlphaRaw)^3.2 local DynamicOrbitEnd = CurrentTarget + GambleDirection * XOrbitRadius + Vector3.new(0, OrbitEnd.Y - TargetAtDecision.Y, 0) DesiredPosition = GlideStart:Lerp(DynamicOrbitEnd, GlideAlpha) -- Arco suave pequeño: evita que la trayectoria se vea como -- una línea con un cambio de dirección brusco. local Arc = math.sin(GlideAlphaRaw * math.pi) local ArcDirection = OrbitRight * ((GambleIndex % 2 == 0) and 1 or -1) DesiredPosition += ArcDirection * (Arc * 2.25) end local LookPoint = CurrentTarget + Vector3.new(0, CFG.AIM_LOCK_HEIGHT, 0) -- El lock-on mantiene la mirada sobre el objetivo. La orientación -- NO controla la trayectoria ni hace que el personaje lo orbite. RootPart.CFrame = CFrame.lookAt(DesiredPosition, LookPoint) pcall(function() RootPart.AssemblyLinearVelocity = Vector3.zero RootPart.AssemblyAngularVelocity = Vector3.zero end) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end XChainWindowOpen = false if not XActive or not CharacterIsValid() then break end if not XChainRequested then break end -- 5. SIGUIENTE CADENA XChainRequested = false local ChainPauseStart = os.clock() while os.clock() - ChainPauseStart < XChainPause do if not XActive or not CharacterIsValid() then break end RunService.RenderStepped:Wait() end -- En la siguiente X se vuelve a hacer un nuevo gamble 1-4. end -- RESTAURAR CONTROL XActive = false XHolding = false XChainRequested = false XChainWindowOpen = false StopAimLock() if Humanoid and Humanoid.Parent then Humanoid.AutoRotate = OldAutoRotate end pcall(function() RootPart.AssemblyLinearVelocity = Vector3.zero RootPart.AssemblyAngularVelocity = Vector3.zero end) Busy = false end -- Z - NORMAL ORBIT local function Orbit() if Busy then return end if not CanUse("Z") then return end if not CharacterIsValid() then return end -- El objetivo se selecciona UNA sola vez al presionar Z. -- A partir de aquí no se vuelve a buscar otro jugador. local Target = GetPlayerTarget( CFG.Z_TARGET_DISTANCE ) if not Target then return end if not TrySpendEnergy(CFG.ENERGY_COST_Z) then return end StartAimLock(Target) LastUse.Z = os.clock() Busy = true CreateVignette() local OldAutoRotate = Humanoid.AutoRotate Humanoid.AutoRotate = false -- ACERCAMIENTO AL OBJETIVO -- Esta distancia evita aparecer literalmente dentro del jugador. local APPROACH_DISTANCE = 7 local APPROACH_TIME = 0.35 local ApproachStart = RootPart.Position local ApproachStartTime = os.clock() local LastAfterimage = os.clock() while os.clock() - ApproachStartTime < APPROACH_TIME do if not CharacterIsValid() or not Target or not Target.Parent then break end local Elapsed = os.clock() - ApproachStartTime local Alpha = math.clamp( Elapsed / APPROACH_TIME, 0, 1 ) -- Actualiza Target.Position EN CADA FRAME. -- Esto es lo que evita viajar hacia una posición vieja. local CurrentTarget = Target.Position local ToTarget = CurrentTarget - ApproachStart if ToTarget.Magnitude < 0.1 then break end local Direction = ToTarget.Unit local DesiredPosition = CurrentTarget - Direction * APPROACH_DISTANCE -- Suavizado de entrada. local Ease = 1 - (1 - Alpha)^3 local NewPosition = ApproachStart:Lerp( DesiredPosition, Ease ) RootPart.CFrame = CFrame.lookAt( NewPosition, CurrentTarget ) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end -- ÓRBITA if CharacterIsValid() and Target and Target.Parent then local CurrentTarget = Target.Position local RadiusVector = RootPart.Position - CurrentTarget if RadiusVector.Magnitude < 0.1 then RadiusVector = Vector3.new( 1, 0, 0 ) end RadiusVector = RadiusVector.Unit local Angle = math.atan2( RadiusVector.Z, RadiusVector.X ) local OrbitStart = os.clock() LastAfterimage = os.clock() local Z_GAP_INTERVAL = CriticalActive and 0.2 or 0.27 -- riesgo de muerte: saltos más frecuentes local Z_SPEED = CriticalActive and CFG.CRIT_Z_SPEED_FACTOR or 1 -- ...y órbita más rápida local Z_GAP_TIME = 0.085 local Z_GAP_DISTANCE = CriticalActive and 8 or 5.5 local Z_GAP_HEIGHT = 3.0 local Z_GAP_BACK = 1.5 local NextGap = OrbitStart + Z_GAP_INTERVAL local GapStart = nil local GapOrigin = nil while os.clock() - OrbitStart < CFG.ORBIT_TIME do if not CharacterIsValid() or not Target or not Target.Parent then break end RunService.RenderStepped:Wait() local Now = os.clock() local InGap = GapStart ~= nil if not InGap and Now >= NextGap then GapStart = Now GapOrigin = RootPart.Position InGap = true end local TargetPosition = Target.Position if InGap then local GapAlpha = math.clamp((Now - GapStart) / Z_GAP_TIME, 0, 1) local GapEase = 1 - (1 - GapAlpha)^2 local Away = GapOrigin - TargetPosition if Away.Magnitude < 0.1 then Away = Vector3.new(0, 0, -1) end Away = Away.Unit local GapPosition = GapOrigin:Lerp( TargetPosition + Away * Z_GAP_DISTANCE + Vector3.new(0, Z_GAP_HEIGHT, 0) - Away * Z_GAP_BACK, GapEase ) RootPart.CFrame = CFrame.lookAt(GapPosition, TargetPosition) if Now - GapStart >= Z_GAP_TIME then GapStart = nil GapOrigin = nil NextGap = Now + Z_GAP_INTERVAL end else local Phase = ((Now - OrbitStart) * CFG.ORBIT_SPEED * Z_SPEED / (math.pi / 2)) % 4 local Side = math.floor(Phase) local T = Phase - Side local R = CFG.ORBIT_RADIUS local Offset if Side == 0 then Offset = Vector3.new(R, 0, -R + 2 * R * T) elseif Side == 1 then Offset = Vector3.new(R - 2 * R * T, 0, R) elseif Side == 2 then Offset = Vector3.new(-R, 0, R - 2 * R * T) else Offset = Vector3.new(-R + 2 * R * T, 0, -R) end RootPart.CFrame = CFrame.lookAt(TargetPosition + Offset, TargetPosition) end if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end end end StopAimLock() if Humanoid and Humanoid.Parent then Humanoid.AutoRotate = OldAutoRotate end Busy = false end -- V - HIGH SPEED LATERAL SHIFT -- V - TRANSFER DEL X DEL SCRIPT DE 2.7K -- NORMAL: LATERAL PROGRESIVO -> AVANCE DE 48 STUDS CON DESACELERACIÓN -- MAXIMUM OUTPUT: HOLD LATERAL -> DISTANCIA/SPEED CRECIENTES -> RELEASE DASH local function MaximumOutputV() if not MaximumOutputActive or MaxVRunning or not CharacterIsValid() then return end MaxVRunning = true Busy = true MaxVReleaseRequested = false MaxVUsedNearPass = {} MaxVStage = "Spindash" local FlightHumanoid = Humanoid local FlightRoot = RootPart local OldAutoRotate = FlightHumanoid.AutoRotate local OldPlatformStand = FlightHumanoid.PlatformStand FlightHumanoid.AutoRotate = false -- PlatformStand evita que la física del Humanoid pelee con el CFrame (principal causa del "bug" del vuelo). FlightHumanoid.PlatformStand = true local FlightParams = RaycastParams.new() FlightParams.FilterType = Enum.RaycastFilterType.Exclude FlightParams.FilterDescendantsInstances = { Character } FlightParams.IgnoreWater = true -- Coloca el personaje y anula cualquier velocidad residual cada frame. local function applyPose(PoseCFrame) FlightRoot.CFrame = PoseCFrame FlightRoot.AssemblyLinearVelocity = Vector3.zero FlightRoot.AssemblyAngularVelocity = Vector3.zero end -- Mueve de A a B sin atravesar paredes/suelo; desliza a lo largo de la superficie. local function clipMove(FromPosition, ToPosition) local Movement = ToPosition - FromPosition local Distance = Movement.Magnitude if Distance < 0.001 then return ToPosition, nil end local Direction = Movement / Distance local Hit = workspace:Raycast(FromPosition, Direction * (Distance + CFG.MAX_V_FLIGHT_CLEARANCE), FlightParams) if not Hit then return ToPosition, nil end local HitDistance = (Hit.Position - FromPosition).Magnitude local Allowed = math.clamp(HitDistance - CFG.MAX_V_FLIGHT_CLEARANCE, 0, Distance) local Stop = FromPosition + Direction * Allowed local Slide = Direction * (Distance - Allowed) Slide -= Hit.Normal * Slide:Dot(Hit.Normal) return Stop + Slide, Hit.Normal end -- Distancia mínima de un punto al tramo recorrido entre dos frames (el hit no se salta -- jugadores aunque la velocidad sea muy alta o el FPS bajo). local function DistanceToSegment(Point, SegmentStart, SegmentEnd) local Segment = SegmentEnd - SegmentStart local LengthSquared = Segment:Dot(Segment) if LengthSquared < 1e-6 then return (Point - SegmentStart).Magnitude end local T = math.clamp((Point - SegmentStart):Dot(Segment) / LengthSquared, 0, 1) return (Point - (SegmentStart + Segment * T)).Magnitude end -- Devuelve el control normal y endereza al personaje (sin importar cómo termine el vuelo). local function finishFlight(UprightFacing) if FlightHumanoid and FlightHumanoid.Parent then FlightHumanoid.AutoRotate = OldAutoRotate FlightHumanoid.PlatformStand = OldPlatformStand end if FlightRoot and FlightRoot.Parent then local Look = Vector3.new(UprightFacing.X, 0, UprightFacing.Z) if Look.Magnitude < 0.1 then Look = Forward end local Position = FlightRoot.Position FlightRoot.CFrame = CFrame.lookAt(Position, Position + Look.Unit) end end local StartPosition = RootPart.Position local Forward = GetHorizontalDirection() if Forward.Magnitude < 0.1 then Forward = Vector3.new(0, 0, -1) end Forward = Forward.Unit local Right = Vector3.new(-Forward.Z, 0, Forward.X) MaxVForward = Forward MaxVRight = Right MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED local function spinCFrame(Position, Facing, SpinAngle) local Look = Facing if Look.Magnitude < 0.1 then Look = Forward end -- Giro frontal tipo Sonic: la cabeza va hacia delante (sentido configurable en MAX_V_SPIN_DIRECTION). return CFrame.lookAt(Position, Position + Look.Unit) * CFrame.Angles(SpinAngle * CFG.MAX_V_SPIN_DIRECTION, 0, 0) end local function makeAfterimageIfDue(LastImage, Interval) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastImage >= Interval then CreateAfterimage() return os.clock() end return LastImage end local function getAirSteering() local CurrentCamera = workspace.CurrentCamera local CameraForward = CurrentCamera and CurrentCamera.CFrame.LookVector or Forward local CameraRight = CurrentCamera and CurrentCamera.CFrame.RightVector or Right CameraForward = Vector3.new(CameraForward.X, 0, CameraForward.Z) CameraRight = Vector3.new(CameraRight.X, 0, CameraRight.Z) if CameraForward.Magnitude < 0.1 then CameraForward = Forward end if CameraRight.Magnitude < 0.1 then CameraRight = Right end CameraForward = CameraForward.Unit CameraRight = CameraRight.Unit local InputDirection = Vector3.zero if UserInputService:IsKeyDown(Enum.KeyCode.W) then InputDirection += CameraForward end if UserInputService:IsKeyDown(Enum.KeyCode.S) then InputDirection -= CameraForward end if UserInputService:IsKeyDown(Enum.KeyCode.D) then InputDirection += CameraRight end if UserInputService:IsKeyDown(Enum.KeyCode.A) then InputDirection -= CameraRight end if InputDirection.Magnitude > 1 then InputDirection = InputDirection.Unit end return InputDirection end -- V Maximum Output start sound (one-shot). PlayMovementSound(CFG.V_MAX_START_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) local LastAfterimage = os.clock() local PhaseStart = os.clock() -- PHASE 1: curved upward spindash. -- Riesgo de muerte: en su lugar hace un backflip y se queda arriba (luego 1 s de spindash en el aire). local CriticalFlight = CriticalActive local RiseTime = CriticalFlight and CFG.CRIT_V_BACKFLIP_TIME or CFG.MAX_V_SPINDASH_RISE_TIME while MaximumOutputActive and CharacterIsValid() and os.clock() - PhaseStart < RiseTime do local Alpha = math.clamp((os.clock() - PhaseStart) / RiseTime, 0, 1) if CriticalFlight then local BackflipEase = 1 - (1 - Alpha) ^ 2 local BackflipPosition = StartPosition + Vector3.new(0, CFG.CRIT_V_HOVER_HEIGHT * BackflipEase, 0) - Forward * (4 * BackflipEase) applyPose(CFrame.lookAt(BackflipPosition, BackflipPosition + Forward) * CFrame.Angles(Alpha * 2 * math.pi, 0, 0)) else local Ease = Alpha * Alpha * (3 - 2 * Alpha) local ForwardDistance = 10 * Ease local Height = CFG.MAX_V_SPINDASH_RISE_HEIGHT * math.sin(Alpha * math.pi * 0.5) local Position = StartPosition + Forward * ForwardDistance + Vector3.new(0, Height, 0) local Spin = (os.clock() - PhaseStart) * CFG.MAX_V_SPIN_RATE applyPose(spinCFrame(Position, Forward, Spin)) end LastAfterimage = makeAfterimageIfDue(LastAfterimage, CFG.V_AFTERIMAGE_INTERVAL) RunService.RenderStepped:Wait() end if CriticalFlight then PhaseStart = os.clock() -- el spindash arranca desde cero tras el backflip end if not MaximumOutputActive or not CharacterIsValid() then MaxVRunning = false MaxVStage = "Idle" Busy = false finishFlight(Forward) return end -- PHASE 2: a fixed aerial center with restrained forward/back steering. -- A/D lateral movement is disabled here; the 30-stud sphere remains the hard limit. MaxVStage = "Orbit" MaxVOrbitCenter = RootPart.Position MaxVOrbitStartTime = os.clock() local OrbitStart = os.clock() local LastInputOffset = Vector3.zero local OrbitDuration = CriticalFlight and CFG.CRIT_V_HOVER_TIME or CFG.MAX_V_ORBIT_DURATION local OrbitAngle = 0 while MaximumOutputActive and CharacterIsValid() and os.clock() - OrbitStart < OrbitDuration and not MaxVReleaseRequested do local Delta = RunService.RenderStepped:Wait() local AirInput = getAirSteering() -- Control aéreo 360°: W/S/A/D permiten corregir rumbo y buscar jugadores. LastInputOffset += AirInput * CFG.MAX_V_ORBIT_STEER_SPEED * Delta -- Keep the movement within a 30-stud sphere around the orbital center. if LastInputOffset.Magnitude > CFG.MAX_V_ORBIT_RADIUS then LastInputOffset = LastInputOffset.Unit * CFG.MAX_V_ORBIT_RADIUS end OrbitAngle += Delta * math.rad(100) local SmallOrbit = Right * math.cos(OrbitAngle) * CFG.MAX_V_ORBIT_IDLE_RADIUS + Forward * math.sin(OrbitAngle) * CFG.MAX_V_ORBIT_IDLE_RADIUS local OrbitOffset = LastInputOffset + SmallOrbit if OrbitOffset.Magnitude > CFG.MAX_V_ORBIT_RADIUS then OrbitOffset = OrbitOffset.Unit * CFG.MAX_V_ORBIT_RADIUS end local Position = MaxVOrbitCenter + OrbitOffset local Spin = (os.clock() - PhaseStart) * CFG.MAX_V_SPIN_RATE applyPose(spinCFrame(Position, Forward, Spin)) LastAfterimage = makeAfterimageIfDue(LastAfterimage, CFG.V_AFTERIMAGE_INTERVAL) end if not MaximumOutputActive or not CharacterIsValid() then MaxVRunning = false MaxVStage = "Idle" Busy = false finishFlight(Forward) return end -- If the user doesn't tap V again, the 1.8s orbit naturally ends and releases. -- PHASE 3: forward release with a deceleration curve. MaxVStage = "Release" PlayMovementSound(CFG.V_MAX_LAUNCH_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) local ReleaseStartPosition = RootPart.Position local ReleaseStartTime = os.clock() local ReleaseSteering = getAirSteering() local ReleaseDirection = (ReleaseSteering.Magnitude > 0.1 and ReleaseSteering or Forward + LastInputOffset * 0.025).Unit local ReleaseDistance = CFG.MAX_V_RELEASE_DISTANCE local ReleaseDuration = CFG.MAX_V_RELEASE_DURATION if CriticalFlight then -- Riesgo de muerte: sale disparado 60 studs hacia donde mira la cámara; después, su modo de vuelo. local CurrentCamera = workspace.CurrentCamera if CurrentCamera then ReleaseDirection = CurrentCamera.CFrame.LookVector end ReleaseDistance = CFG.CRIT_V_SHOT_DISTANCE ReleaseDuration = CFG.CRIT_V_SHOT_TIME end local RefreshedPlayers = {} local LastHitCheckPosition = RootPart.Position local FlightHits = 0 -- hits conectados en release + vuelo local SpinBase = (ReleaseStartTime - OrbitStart) * CFG.MAX_V_SPIN_RATE while MaximumOutputActive and CharacterIsValid() and os.clock() - ReleaseStartTime < ReleaseDuration do local Elapsed = os.clock() - ReleaseStartTime local Alpha = math.clamp(Elapsed / ReleaseDuration, 0, 1) -- A nearby player resets the deceleration curve once per player, -- giving a close pass a brief momentum refresh (without stopping). for _, OtherPlayer in ipairs(Players:GetPlayers()) do if OtherPlayer ~= Player and not RefreshedPlayers[OtherPlayer] and FlightHits < CFG.MAX_V_GLIDE_MAX_HITS then local OtherCharacter = OtherPlayer.Character local OtherRoot = OtherCharacter and OtherCharacter:FindFirstChild("HumanoidRootPart") local OtherHumanoid = OtherCharacter and OtherCharacter:FindFirstChildOfClass("Humanoid") if OtherRoot and OtherHumanoid and OtherHumanoid.Health > 0 and DistanceToSegment(OtherRoot.Position, LastHitCheckPosition, RootPart.Position) <= CFG.MAX_V_NEAR_PLAYER_RADIUS then RefreshedPlayers[OtherPlayer] = true FlightHits += 1 ReleaseStartTime = os.clock() ReleaseStartPosition = RootPart.Position ReleaseDistance = math.min(ReleaseDistance + CFG.MAX_V_NEAR_PLAYER_REFRESH_DISTANCE, math.max(48, ReleaseDistance)) ReleaseDuration = math.min(ReleaseDuration + 0.18, 0.95) PlayMovementSound(CFG.V_MAX_NEAR_HIT_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) break end end end LastHitCheckPosition = RootPart.Position Alpha = math.clamp((os.clock() - ReleaseStartTime) / ReleaseDuration, 0, 1) -- Cubic ease-out: fast at first, then gradually loses speed. local MovementAlpha = 1 - (1 - Alpha)^3 local Position = ReleaseStartPosition + ReleaseDirection * (ReleaseDistance * MovementAlpha) local Spin = (os.clock() - PhaseStart) * CFG.MAX_V_SPIN_RATE applyPose(spinCFrame(clipMove(FlightRoot.Position, Position), ReleaseDirection, Spin)) LastAfterimage = makeAfterimageIfDue(LastAfterimage, CFG.V_AFTERIMAGE_INTERVAL) RunService.RenderStepped:Wait() end -- PHASE 4: vuelo controlable. -- Velocidad con inercia, giro suave del cuerpo, ladeo al moverte y sin atravesar paredes. -- W/S = hacia donde mira la cámara (3D), A/D = lateral, Space/LeftControl = subir/bajar. -- Sin teclas planea hacia delante con una caída suave. Pulsar V otra vez termina el vuelo. MaxVStage = "Glide" MaxVReleaseRequested = false local GlideStartTime = os.clock() local GlideDuration = CFG.MAX_V_GLIDE_DURATION local GlideLastHit = {} local GlideHeading = Vector3.new(ReleaseDirection.X, 0, ReleaseDirection.Z) if GlideHeading.Magnitude < 0.1 then GlideHeading = Forward end GlideHeading = GlideHeading.Unit local FlightVelocity = ReleaseDirection * CFG.MAX_V_GLIDE_ENTRY_SPEED local FlightLean = 0 local FlightBank = 0 local GlideSpinStart = (os.clock() - PhaseStart) * CFG.MAX_V_SPIN_RATE local FullTurn = 2 * math.pi local GlideSpinEnd = math.ceil(GlideSpinStart / FullTurn) * FullTurn while MaximumOutputActive and CharacterIsValid() and not MaxVReleaseRequested and FlightHits < CFG.MAX_V_GLIDE_MAX_HITS and os.clock() - GlideStartTime < GlideDuration do local Delta = math.min(RunService.RenderStepped:Wait(), 1 / 20) local Elapsed = os.clock() - GlideStartTime local CurrentCamera = workspace.CurrentCamera local CamLook = CurrentCamera and CurrentCamera.CFrame.LookVector or GlideHeading local CamRight = CurrentCamera and CurrentCamera.CFrame.RightVector or Right local ForwardInput = (UserInputService:IsKeyDown(Enum.KeyCode.W) and 1 or 0) - (UserInputService:IsKeyDown(Enum.KeyCode.S) and 1 or 0) local SideInput = (UserInputService:IsKeyDown(Enum.KeyCode.D) and 1 or 0) - (UserInputService:IsKeyDown(Enum.KeyCode.A) and 1 or 0) local VerticalInput = (UserInputService:IsKeyDown(Enum.KeyCode.Space) and 1 or 0) - (UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) and 1 or 0) -- Velocidad que el jugador quiere llevar. local Wish = CamLook * ForwardInput + CamRight * SideInput local DesiredVelocity if Wish.Magnitude > 0.05 then DesiredVelocity = Wish.Unit * CFG.MAX_V_GLIDE_HORIZONTAL_SPEED else DesiredVelocity = GlideHeading * (CFG.MAX_V_GLIDE_HORIZONTAL_SPEED * CFG.MAX_V_GLIDE_IDLE_SPEED_FACTOR) + Vector3.new(0, -CFG.MAX_V_GLIDE_DESCENT_SPEED, 0) end DesiredVelocity += Vector3.new(0, VerticalInput * CFG.MAX_V_GLIDE_VERTICAL_SPEED, 0) -- Inercia: la velocidad real se acerca poco a poco a la deseada. FlightVelocity = FlightVelocity:Lerp(DesiredVelocity, 1 - math.exp(-CFG.MAX_V_FLIGHT_RESPONSE * Delta)) -- Movimiento con colisiones. local FromPosition = FlightRoot.Position local NewPosition, HitNormal = clipMove(FromPosition, FromPosition + FlightVelocity * Delta) if HitNormal then local IntoWall = FlightVelocity:Dot(HitNormal) if IntoWall < 0 then FlightVelocity -= HitNormal * IntoWall end end -- HITS A JUGADORES MIENTRAS VUELAS: se comprueba todo el tramo recorrido este frame. -- Cada hit da sonido, impulso de velocidad y algo de tiempo extra; al llegar al máximo, termina el vuelo. for _, OtherPlayer in ipairs(Players:GetPlayers()) do if FlightHits >= CFG.MAX_V_GLIDE_MAX_HITS then break end if OtherPlayer ~= Player then local OtherCharacter = OtherPlayer.Character local OtherRoot = OtherCharacter and OtherCharacter:FindFirstChild("HumanoidRootPart") local OtherHumanoid = OtherCharacter and OtherCharacter:FindFirstChildOfClass("Humanoid") local LastHit = GlideLastHit[OtherPlayer] if OtherRoot and OtherHumanoid and OtherHumanoid.Health > 0 and (not LastHit or os.clock() - LastHit >= CFG.MAX_V_GLIDE_HIT_COOLDOWN) and DistanceToSegment(OtherRoot.Position, FromPosition, NewPosition) <= CFG.MAX_V_NEAR_PLAYER_RADIUS then GlideLastHit[OtherPlayer] = os.clock() FlightHits += 1 GlideDuration += CFG.MAX_V_GLIDE_HIT_TIME_BONUS local BoostDirection = GlideHeading if FlightVelocity.Magnitude > 1 then BoostDirection = FlightVelocity.Unit end FlightVelocity += BoostDirection * CFG.MAX_V_GLIDE_HIT_BOOST PlayMovementSound(CFG.V_MAX_NEAR_HIT_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) end end end -- El cuerpo gira suavemente hacia donde vuelas (no gira 180° al pulsar S). local FlatVelocity = Vector3.new(FlightVelocity.X, 0, FlightVelocity.Z) if FlatVelocity.Magnitude > 2 and ForwardInput >= 0 then local Blended = GlideHeading:Lerp(FlatVelocity.Unit, 1 - math.exp(-CFG.MAX_V_FLIGHT_TURN_RESPONSE * Delta)) if Blended.Magnitude > 0.05 then GlideHeading = Blended.Unit end end -- Inclinación hacia delante según la velocidad (más al bajar, menos al subir) y ladeo con A/D. local SpeedFraction = math.clamp(FlightVelocity.Magnitude / CFG.MAX_V_GLIDE_HORIZONTAL_SPEED, 0, 1) local VerticalFraction = math.clamp(FlightVelocity.Y / CFG.MAX_V_GLIDE_HORIZONTAL_SPEED, -1, 1) local TargetLean = CFG.MAX_V_FLIGHT_LEAN * SpeedFraction - VerticalFraction * math.rad(20) local TargetBank = -SideInput * CFG.MAX_V_FLIGHT_BANK local PoseResponse = 1 - math.exp(-8 * Delta) FlightLean += (TargetLean - FlightLean) * PoseResponse FlightBank += (TargetBank - FlightBank) * PoseResponse -- El spindash termina su vuelta actual y se endereza (o sigue girando si KEEP_SPIN = true). local SpinAngle local PoseBlend if CFG.MAX_V_GLIDE_KEEP_SPIN then SpinAngle = (os.clock() - PhaseStart) * CFG.MAX_V_SPIN_RATE PoseBlend = 0 else local FadeAlpha = math.clamp(Elapsed / CFG.MAX_V_GLIDE_SPIN_FADE, 0, 1) PoseBlend = FadeAlpha * FadeAlpha * (3 - 2 * FadeAlpha) SpinAngle = GlideSpinStart + (GlideSpinEnd - GlideSpinStart) * PoseBlend end local Pitch = SpinAngle * CFG.MAX_V_SPIN_DIRECTION - FlightLean * PoseBlend applyPose( CFrame.lookAt(NewPosition, NewPosition + GlideHeading) * CFrame.Angles(Pitch, 0, FlightBank * PoseBlend) ) LastAfterimage = makeAfterimageIfDue(LastAfterimage, CFG.V_AFTERIMAGE_INTERVAL) end -- Fin del vuelo: control normal, personaje derecho y algo de inercia. finishFlight(GlideHeading) if CharacterIsValid() then RootPart.AssemblyLinearVelocity = Vector3.new( FlightVelocity.X * 0.6, math.min(FlightVelocity.Y * 0.6, -5), FlightVelocity.Z * 0.6 ) end MaxVHolding = false MaxVRunning = false Busy = false MaxVStage = "Idle" MaxVReleaseRequested = false MaxVOrbitCenter = nil MaxVForward = nil MaxVRight = nil MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED MaxVCurrentDistance = CFG.MAX_V_LATERAL_START_DISTANCE end -- V EN RIESGO DE MUERTE (< CRITICAL_HEALTH_ENTER): backflip, 1 s de spindash en el aire y disparo -- de 60 studs hacia donde mira la cámara, desacelerando. Sustituye al V normal mientras dura el buff. local function CriticalVSequence(Forward) if not CharacterIsValid() then return end local VRoot = RootPart local VHumanoid = Humanoid local StartPosition = VRoot.Position local OldPlatformStand = VHumanoid.PlatformStand VHumanoid.PlatformStand = true local VParams = RaycastParams.new() VParams.FilterType = Enum.RaycastFilterType.Exclude VParams.FilterDescendantsInstances = { Character } VParams.IgnoreWater = true local function Pose(PoseCFrame) VRoot.CFrame = PoseCFrame VRoot.AssemblyLinearVelocity = Vector3.zero VRoot.AssemblyAngularVelocity = Vector3.zero end -- Avanza sin atravesar paredes/suelo (desliza por la superficie). local function Clip(FromPosition, ToPosition) local Movement = ToPosition - FromPosition local Distance = Movement.Magnitude if Distance < 0.001 then return ToPosition end local Direction = Movement / Distance local Hit = workspace:Raycast(FromPosition, Direction * (Distance + CFG.MAX_V_FLIGHT_CLEARANCE), VParams) if not Hit then return ToPosition end local Allowed = math.clamp((Hit.Position - FromPosition).Magnitude - CFG.MAX_V_FLIGHT_CLEARANCE, 0, Distance) local Slide = Direction * (Distance - Allowed) Slide -= Hit.Normal * Slide:Dot(Hit.Normal) return FromPosition + Direction * Allowed + Slide end local LastAfterimage = os.clock() local function Ghost() if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.V_AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end end -- 1) BACKFLIP: sube y se echa un poco atrás dando una vuelta completa hacia atrás. local BackflipStart = os.clock() while CharacterIsValid() and os.clock() - BackflipStart < CFG.CRIT_V_BACKFLIP_TIME do local Alpha = math.clamp((os.clock() - BackflipStart) / CFG.CRIT_V_BACKFLIP_TIME, 0, 1) local Ease = 1 - (1 - Alpha) ^ 2 local Position = StartPosition + Vector3.new(0, CFG.CRIT_V_HOVER_HEIGHT * Ease, 0) - Forward * (4 * Ease) Pose(CFrame.lookAt(Position, Position + Forward) * CFrame.Angles(Alpha * 2 * math.pi, 0, 0)) Ghost() RunService.RenderStepped:Wait() end -- 2) SPINDASH EN EL AIRE durante CRIT_V_HOVER_TIME. local HoverCenter = CharacterIsValid() and VRoot.Position or StartPosition local HoverStart = os.clock() while CharacterIsValid() and os.clock() - HoverStart < CFG.CRIT_V_HOVER_TIME do local Elapsed = os.clock() - HoverStart local Position = HoverCenter + Vector3.new(0, math.sin(Elapsed * 6) * 0.4, 0) local Spin = Elapsed * CFG.MAX_V_SPIN_RATE Pose(CFrame.lookAt(Position, Position + Forward) * CFrame.Angles(Spin * CFG.MAX_V_SPIN_DIRECTION, 0, 0)) Ghost() RunService.RenderStepped:Wait() end -- 3) DISPARO hacia donde mira la cámara, con desaceleración. local CurrentCamera = workspace.CurrentCamera local ShotDirection = CurrentCamera and CurrentCamera.CFrame.LookVector or Forward local ShotStart = CharacterIsValid() and VRoot.Position or HoverCenter local ShotStartTime = os.clock() PlayMovementSound("rbxassetid://115962892067823", CFG.MOVEMENT_SOUND_VOLUME, 1) while CharacterIsValid() and os.clock() - ShotStartTime < CFG.CRIT_V_SHOT_TIME do local Alpha = math.clamp((os.clock() - ShotStartTime) / CFG.CRIT_V_SHOT_TIME, 0, 1) local MovementAlpha = 1 - (1 - Alpha) ^ 3 local Goal = ShotStart + ShotDirection * (CFG.CRIT_V_SHOT_DISTANCE * MovementAlpha) local Position = Clip(VRoot.Position, Goal) local Spin = (os.clock() - HoverStart) * CFG.MAX_V_SPIN_RATE Pose(CFrame.lookAt(Position, Position + ShotDirection) * CFrame.Angles(Spin * CFG.MAX_V_SPIN_DIRECTION, 0, 0)) Ghost() RunService.RenderStepped:Wait() end -- Fin: control normal y personaje derecho. if VHumanoid and VHumanoid.Parent then VHumanoid.PlatformStand = OldPlatformStand end if VRoot and VRoot.Parent then local Look = Vector3.new(ShotDirection.X, 0, ShotDirection.Z) if Look.Magnitude < 0.1 then Look = Forward end local Position = VRoot.Position VRoot.CFrame = CFrame.lookAt(Position, Position + Look.Unit) end end local function CursoryImpact() if MaximumOutputActive then MaxVHolding = true if not MaxVRunning then task.spawn(MaximumOutputV) end return end if Busy then return end if not CanUse("V") then return end if not CharacterIsValid() then return end if not TrySpendEnergy(CFG.ENERGY_COST_V) then return end LastUse.V = os.clock() Busy = true CreateVignette() -- V normal: primer sonido al iniciar. PlayMovementSound("rbxassetid://115962892067823", CFG.MOVEMENT_SOUND_VOLUME, 1) local OldAutoRotate = Humanoid.AutoRotate Humanoid.AutoRotate = false -- DIRECCIÓN BASE local Forward = GetHorizontalDirection() local Right = Vector3.new(-Forward.Z, 0, Forward.X) -- Riesgo de muerte: backflip + 1 s de spindash en el aire + disparo de 60 studs. local CriticalPlayed = false if CriticalActive then CriticalPlayed = true CriticalVSequence(Forward) end -- PRIMERA FASE: MOVIMIENTO LATERAL PROGRESIVO local LateralStart = os.clock() local LateralStartPosition = RootPart.Position local LastAfterimage = os.clock() while not CriticalPlayed and os.clock() - LateralStart < CFG.V_LATERAL_TIME do if not CharacterIsValid() then break end local Elapsed = os.clock() - LateralStart local Alpha = math.clamp(Elapsed / CFG.V_LATERAL_TIME, 0, 1) local ProgressiveDistance = Alpha * CFG.V_LATERAL_DISTANCE local Wave = math.sin(Alpha * math.pi * CFG.V_LATERAL_SWINGS) local SideOffset = Wave * ProgressiveDistance local ForwardOffset = Alpha * 3 local NewPosition = LateralStartPosition + Right * SideOffset + Forward * ForwardOffset RootPart.CFrame = CFrame.lookAt(NewPosition, NewPosition + Forward) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.V_AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end -- SEGUNDA FASE: AVANCE DE 48 STUDS CON DESACELERACIÓN -- V normal: segundo sonido justo cuando comienza el disparo frontal. if not CriticalPlayed then PlayMovementSound("rbxassetid://115962892067823", CFG.MOVEMENT_SOUND_VOLUME, 1) end if not CriticalPlayed and CharacterIsValid() then local ForwardStart = RootPart.Position local ForwardStartTime = os.clock() LastAfterimage = os.clock() while os.clock() - ForwardStartTime < CFG.V_FORWARD_TIME do if not CharacterIsValid() then break end local Elapsed = os.clock() - ForwardStartTime local Alpha = math.clamp(Elapsed / CFG.V_FORWARD_TIME, 0, 1) local MovementAlpha = 1 - (1 - Alpha)^CFG.V_DECELERATION_POWER local NewPosition = ForwardStart + Forward * (CFG.V_FORWARD_DISTANCE * MovementAlpha) RootPart.CFrame = CFrame.lookAt(NewPosition, NewPosition + Forward) if CFG.AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= CFG.V_AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end end if Humanoid and Humanoid.Parent then Humanoid.AutoRotate = OldAutoRotate end Busy = false end -- X - LA CADENA USA PULSACIONES, NO HOLD -- Soltar X ya no cancela la técnica. Cada nueva pulsación durante -- la ventana de 1.4s solicita explícitamente el siguiente dash. -- INPUT DURANTE PROJECTION UserInputService.InputBegan:Connect( function(Input, GameProcessed) if GameProcessed then return end -- C + Z if Input.KeyCode == Enum.KeyCode.Z and ProjectionActive and ProjectionHolding then task.spawn( DoCircularProjection ) return end -- C + A if Input.KeyCode == Enum.KeyCode.A and ProjectionActive and ProjectionHolding then LaneShiftKey = "A" task.spawn( function() DoLaneShift(-1) end ) return end -- C + D if Input.KeyCode == Enum.KeyCode.D and ProjectionActive and ProjectionHolding then LaneShiftKey = "D" task.spawn( function() DoLaneShift(1) end ) return end -- J - MAXIMUM OUTPUT if Input.KeyCode == CFG.MAX_OUTPUT_KEY then -- J: un solo disparo por pulsación. PlayMovementSound(CFG.J_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) StartMaximumOutput() return end -- X if Input.KeyCode == Enum.KeyCode.X then if MaximumOutputActive then MaxXHolding=true task.spawn(MaximumOutputX) else -- X normal: un solo sonido al activar la técnica. PlayMovementSound(CFG.X_NORMAL_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, 1) task.spawn(function() RunSkillWithRecovery(ProjectionBreaker) end) end return end -- Z if Input.KeyCode == Enum.KeyCode.Z then task.spawn( function() RunSkillWithRecovery(Orbit) end ) return end -- C if Input.KeyCode == Enum.KeyCode.C then task.spawn( function() RunSkillWithRecovery(Projection) end ) return end -- V if Input.KeyCode == Enum.KeyCode.V then if MaximumOutputActive then -- First press starts Spindash; the next press during Orbit requests release. if MaxVRunning then if MaxVStage == "Orbit" or MaxVStage == "Glide" then MaxVReleaseRequested = true end else MaxVHolding = true if not MaxVRunning then task.spawn(MaximumOutputV) end end else task.spawn( function() RunSkillWithRecovery(CursoryImpact) end ) end return end -- B - AFTERIMAGES if Input.KeyCode == Enum.KeyCode.B then CFG.AFTERIMAGES_ENABLED = not CFG.AFTERIMAGES_ENABLED if not CFG.AFTERIMAGES_ENABLED then ClearAfterimages() end print( "Afterimages:", CFG.AFTERIMAGES_ENABLED and "ON" or "OFF" ) end end ) -- Ñ - ROTATION RECOVERY ON/OFF -- En teclados españoles, la tecla física Ñ suele llegar como Semicolon. -- N también se acepta como respaldo. UserInputService.InputBegan:Connect(function(Input, GameProcessed) if GameProcessed then return end if Input.KeyCode == Enum.KeyCode.Semicolon or Input.KeyCode == Enum.KeyCode.N then _G.HeadHeiRotationRecoveryEnabled = not _G.HeadHeiRotationRecoveryEnabled print( "Rotation Recovery:", _G.HeadHeiRotationRecoveryEnabled and "ON" or "OFF" ) if _G.HeadHeiRotationRecoveryEnabled then RotationRecovery() end end end) -- MAXIMUM OUTPUT V/X - RELEASE UserInputService.InputEnded:Connect(function(Input) if Input.KeyCode == Enum.KeyCode.V and MaximumOutputActive then MaxVHolding = false end if Input.KeyCode == Enum.KeyCode.X and MaximumOutputActive then MaxXHolding = false end end) -- RESPAWN FAILSAFE Player.CharacterAdded:Connect( function(NewCharacter) StopProjection() StopMaximumOutput() Busy = false ClearAfterimages() Character = NewCharacter Humanoid = NewCharacter:WaitForChild( "Humanoid" ) RootPart = NewCharacter:WaitForChild( "HumanoidRootPart" ) BindCriticalHealthSound() end ) -- FINAL FAILSAFE task.spawn( function() while true do task.wait(1) if ProjectionActive and not CharacterIsValid() then StopProjection() end end end ) print( "Head of the Hei Movement System loaded." ) --[==[ ================================================================ RESPALDO: COPIA ANTERIOR COMPLETA DEL SCRIPT (líneas 1-2038 del archivo original). NO se ejecuta: está dentro de un comentario de bloque para que no duplique los controles ni las habilidades. Si quieres recuperar algo (por ejemplo DoLaneShift antiguo), copia esa función y reemplaza la versión activa de arriba. ================================================================ -- HEAD OF THE HEI - MOVEMENT SYSTEM -- -- X = TARGET ORBIT + CONTINUOUS FLIGHT -- Z = ORBIT -- C = PROJECTION -- C + A/D = LANE SHIFT -- C + Z = CIRCULAR PROJECTION -- V = HIGH SPEED LATERAL SHIFT -- B = AFTERIMAGES ON/OFF local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") local Debris = game:GetService("Debris") local Player = Players.LocalPlayer local Camera = workspace.CurrentCamera -- GENERAL local COOLDOWN = 0.1 -- AFTERIMAGES local AFTERIMAGES_ENABLED = true local MAX_AFTERIMAGES = 8 local AFTERIMAGE_INTERVAL = 0.055 local AFTERIMAGE_LIFETIME = 0.22 local ActiveAfterimages = {} -- Z - NORMAL ORBIT local ORBIT_TIME = 1 local ORBIT_RADIUS = 2.2 local ORBIT_SPEED = math.rad(1720) local Z_TARGET_DISTANCE = 300 -- C - PROJECTION local PROJECTION_MAX_DISTANCE = 45000 local PROJECTION_MIN_SPEED = 0 local PROJECTION_ACCELERATION = 5 local PROJECTION_MAX_SPEED = 350 -- Desaceleración de C al soltarla. -- A mayor velocidad acumulada, menor será el tiempo de frenado. local PROJECTION_DECEL_MIN_TIME = 0.12 local PROJECTION_DECEL_MAX_TIME = 0.55 -- CRYSTALS local INITIAL_CRYSTALS = 0 local CRYSTALS_PER_STEP = 0.1 local CRYSTAL_SPACING = 1 -- C - PROJECTION ANIMATION -- CAMBIA SOLAMENTE ESTE ID local PROJECTION_ANIMATION_ID = "rbxassetid://0000000000" local ProjectionAnimation = nil local ProjectionAnimationTrack = nil -- C + A/D - LANE SHIFT local LANE_SHIFT_STEPS = 70 local LANE_SHIFT_DURATION = 0.20 local LANE_SHIFT_DISTANCE = 7 -- C + Z - CIRCULAR PROJECTION local CIRCLE_TIME = 0.85 local CIRCLE_RADIUS = 7 local CIRCLE_ROTATIONS = 1.35 -- V - HIGH SPEED LATERAL SHIFT local V_ZIGZAG_TIME = 0.28 local V_ZIGZAG_DISTANCE = 9 local V_ZIGZAG_SWINGS = 4 local V_ORBIT_TIME = 1.35 local V_ORBIT_START_RADIUS = 5 local V_ORBIT_END_RADIUS = 30 local V_ORBIT_ROTATIONS = 3.5 local V_AFTERIMAGE_INTERVAL = 0.035 local V_EXIT_DISTANCE = 22 local V_EXIT_TIME = 0.16 local V_SPEED_BUFF = 50 local V_SPEED_BUFF_DURATION = 2.5 -- X - HOMING CHAIN / IDA Y VUELTA -- Distancia a la que se detiene frente al objetivo. local X_TARGET_OFFSET = 2.8 -- Tiempo del impulso hacia el objetivo. local X_DASH_TO_TARGET_TIME = 0.065 -- Pausa mínima después de llegar al objetivo. local X_HIT_PAUSE = 0.015 -- Distancia de referencia para la salida/inercia posterior al impacto. local X_REBOUND_DISTANCE = 18 -- Tiempo del rebote hacia atrás (conservado por compatibilidad; X ahora pierde la inercia). local X_REBOUND_TIME = 0.055 -- Distancia corta que Naoya recorre justo DESPUÉS de atravesar al objetivo. local X_POST_HIT_DISTANCE = 6 -- Ventana para volver a pulsar X y encadenar otro dash. local X_CHAIN_WINDOW = 1.4 -- Tiempo de "gamble": al terminar la pasada, la trayectoria elige -- ALEATORIAMENTE una de 4 posiciones alrededor del objetivo. local X_ORBIT_DECISION_TIME = 0.075 local X_ORBIT_RADIUS = 18 -- La curva hacia la posición elegida pierde velocidad progresivamente. local X_GLIDE_TIME = 1.4 -- Primer tramo: atraviesa unos 5 studs antes de empezar la curva. local X_PASS_THROUGH_TIME = 0.095 -- Pausa mínima entre cadenas. local X_CHAIN_PAUSE = 0.01 -- Distancia máxima para encontrar objetivos con X. local X_TARGET_DISTANCE = Z_TARGET_DISTANCE -- AIM LOCK / AUTO LOCK para X y Z. local AIM_LOCK_ENABLED = true local AIM_LOCK_SMOOTHNESS = 0.55 local AIM_LOCK_HEIGHT = 1.25 -- CHARACTER local Character local Humanoid local RootPart local function SetupCharacter(CharacterObject) Character = CharacterObject Humanoid = CharacterObject:WaitForChild("Humanoid") RootPart = CharacterObject:WaitForChild( "HumanoidRootPart" ) end SetupCharacter( Player.Character or Player.CharacterAdded:Wait() ) -- STATE local Busy = false local ProjectionActive = false local ProjectionCurrentSpeed = 0 local ProjectionHolding = false local ProjectionReleasing = false local ProjectionReleaseStart = 0 local ProjectionReleaseSpeed = 0 local ProjectionReleaseDirection = Vector3.zero local LaneShiftActive = false local CircularProjectionActive = false local LaneShiftKey = nil local LastUse = { X = 0, Z = 0, C = 0, V = 0 } local ProjectionConnection = nil local ProjectionInputConnection = nil local ProjectionDeathConnection = nil local CurrentRagdollData = nil -- COOLDOWN local function CanUse(Skill) return os.clock() - LastUse[Skill] >= COOLDOWN end -- CHARACTER VALIDATION local function CharacterIsValid() return Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0 and RootPart and RootPart.Parent end -- ROTATION RECOVERY / FAILSAFE if _G.HeadHeiRotationRecoveryEnabled == nil then _G.HeadHeiRotationRecoveryEnabled = true end local function RotationRecovery() if _G.HeadHeiRotationRecoveryEnabled == false then return end if not CharacterIsValid() then return end local Start = os.clock() Humanoid.AutoRotate = true pcall(function() RootPart.AssemblyAngularVelocity = Vector3.zero end) local Connection Connection = RunService.RenderStepped:Connect(function() if not CharacterIsValid() then if Connection then Connection:Disconnect() end return end if os.clock() - Start >= 0.20 then if Connection then Connection:Disconnect() end return end Humanoid.AutoRotate = true local MoveDirection = Humanoid.MoveDirection if MoveDirection.Magnitude > 0.05 then local FlatDirection = Vector3.new(MoveDirection.X, 0, MoveDirection.Z) if FlatDirection.Magnitude > 0.05 then FlatDirection = FlatDirection.Unit local Position = RootPart.Position RootPart.CFrame = CFrame.lookAt(Position, Position + FlatDirection) end end pcall(function() RootPart.AssemblyAngularVelocity = Vector3.zero end) end) end local function RunSkillWithRecovery(SkillFunction) local Success, ErrorMessage = xpcall(SkillFunction, debug.traceback) if not Success then warn("Head of the Hei skill error:\n" .. tostring(ErrorMessage)) end RotationRecovery() end -- VIGNETTE local function CreateVignette() local PlayerGui = Player:FindFirstChildOfClass( "PlayerGui" ) if not PlayerGui then return end local Gui = Instance.new("ScreenGui") Gui.Name = "ProjectionVignette" Gui.IgnoreGuiInset = true Gui.ResetOnSpawn = false Gui.Parent = PlayerGui local Frame = Instance.new("Frame") Frame.Size = UDim2.fromScale( 1, 1 ) Frame.BackgroundColor3 = Color3.fromRGB( 180, 155, 255 ) Frame.BackgroundTransparency = 0.65 Frame.BorderSizePixel = 0 Frame.Parent = Gui local Fade = TweenService:Create( Frame, TweenInfo.new( 0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out ), { BackgroundTransparency = 1 } ) Fade:Play() Debris:AddItem( Gui, 0.4 ) end -- AFTERIMAGE local function CreateAfterimage() if not AFTERIMAGES_ENABLED then return end if not CharacterIsValid() then return end while #ActiveAfterimages >= MAX_AFTERIMAGES do local Oldest = table.remove(ActiveAfterimages, 1) if Oldest and Oldest.Parent then Oldest:Destroy() end end local Folder = Instance.new("Folder") Folder.Name = "ProjectionAfterimage" Folder.Parent = workspace table.insert(ActiveAfterimages, Folder) -- CRT-style afterimage: each visible body part is rebuilt from thin, -- horizontal neon bands. Everything is a frozen, non-interactive snapshot. local STRIPE_HEIGHT = 0.10 local STRIPE_GAP = 0.035 local BASE_COLOR = Color3.fromRGB(155, 125, 255) local BRIGHT_COLOR = Color3.fromRGB(218, 202, 255) for _, Object in ipairs(Character:GetDescendants()) do if Object:IsA("BasePart") and Object.Name ~= "HumanoidRootPart" and Object.Transparency < 1 then local Height = Object.Size.Y local Step = STRIPE_HEIGHT + STRIPE_GAP local BandCount = math.max(1, math.floor((Height + STRIPE_GAP) / Step)) local UsedHeight = math.min(Height, BandCount * Step - STRIPE_GAP) local StartY = -UsedHeight / 2 + STRIPE_HEIGHT / 2 for i = 0, BandCount - 1 do local YOffset = StartY + i * Step if YOffset <= Height / 2 then local BandHeight = math.min(STRIPE_HEIGHT, Height / 2 - YOffset + STRIPE_HEIGHT / 2) if BandHeight > 0.015 then local Ghost = Instance.new("Part") Ghost.Name = "AfterimageScanline" Ghost.Size = Vector3.new(Object.Size.X, BandHeight, Object.Size.Z) Ghost.CFrame = Object.CFrame * CFrame.new(0, YOffset, 0) Ghost.Anchored = true Ghost.CanCollide = false Ghost.CanTouch = false Ghost.CanQuery = false Ghost.CastShadow = false Ghost.Material = Enum.Material.Neon -- Alternating brightness creates the CRT scanline look. local BrightBand = (i % 4 == 0) or (i % 7 == 0) Ghost.Color = BrightBand and BRIGHT_COLOR or BASE_COLOR Ghost.Transparency = BrightBand and 0.28 or 0.48 Ghost.Parent = Folder local Fade = TweenService:Create( Ghost, TweenInfo.new(AFTERIMAGE_LIFETIME, Enum.EasingStyle.Linear), { Transparency = 1 } ) Fade:Play() end end end end end task.delay(AFTERIMAGE_LIFETIME, function() local Index = table.find(ActiveAfterimages, Folder) if Index then table.remove(ActiveAfterimages, Index) end if Folder and Folder.Parent then Folder:Destroy() end end) end -- CLEAR AFTERIMAGES local function ClearAfterimages() for _, Folder in ipairs( ActiveAfterimages ) do if Folder and Folder.Parent then Folder:Destroy() end end table.clear( ActiveAfterimages ) end -- PROJECTION ANIMATION local function LoadProjectionAnimation() if not CharacterIsValid() then return end if PROJECTION_ANIMATION_ID == "" or PROJECTION_ANIMATION_ID == "rbxassetid://0000000000" then return end local Animator = Humanoid:FindFirstChildOfClass( "Animator" ) if not Animator then Animator = Instance.new( "Animator" ) Animator.Parent = Humanoid end ProjectionAnimation = Instance.new( "Animation" ) ProjectionAnimation.AnimationId = PROJECTION_ANIMATION_ID local Success, Result = pcall( function() return Animator:LoadAnimation( ProjectionAnimation ) end ) if not Success then warn( "Projection Animation Error:", Result ) ProjectionAnimation:Destroy() ProjectionAnimation = nil return end ProjectionAnimationTrack = Result ProjectionAnimationTrack.Looped = true ProjectionAnimationTrack.Priority = Enum.AnimationPriority.Action end -- STOP PROJECTION ANIMATION local function StopProjectionAnimation() if ProjectionAnimationTrack then ProjectionAnimationTrack.Looped = false ProjectionAnimationTrack:Stop( 0.12 ) ProjectionAnimationTrack:Destroy() ProjectionAnimationTrack = nil end if ProjectionAnimation then ProjectionAnimation:Destroy() ProjectionAnimation = nil end end -- TARGET SELECTION local function GetPlayerTarget( MaxDistance ) if not CharacterIsValid() then return nil end local BestTarget = nil local BestScore = math.huge local CameraPosition = Camera.CFrame.Position local CameraDirection = Camera.CFrame.LookVector for _, OtherPlayer in ipairs( Players:GetPlayers() ) do if OtherPlayer ~= Player then local OtherCharacter = OtherPlayer.Character if OtherCharacter then local OtherHumanoid = OtherCharacter: FindFirstChildOfClass( "Humanoid" ) local OtherRoot = OtherCharacter: FindFirstChild( "HumanoidRootPart" ) if OtherHumanoid and OtherRoot and OtherHumanoid.Health > 0 then local Offset = OtherRoot.Position - CameraPosition local Distance = Offset.Magnitude if Distance <= MaxDistance and Distance > 1 then local Direction = Offset.Unit local Dot = CameraDirection:Dot( Direction ) if Dot > 0.90 then local Score = 1 - Dot if Score < BestScore then BestScore = Score BestTarget = OtherRoot end end end end end end end return BestTarget end -- MOVEMENT DIRECTION local function GetHorizontalDirection() if not CharacterIsValid() then return Vector3.new( 0, 0, -1 ) end local MoveDirection = Humanoid.MoveDirection if MoveDirection.Magnitude > 0.05 then local Direction = Vector3.new( MoveDirection.X, 0, MoveDirection.Z ) if Direction.Magnitude > 0 then return Direction.Unit end end local Look = RootPart.CFrame.LookVector local Direction = Vector3.new( Look.X, 0, Look.Z ) if Direction.Magnitude > 0 then return Direction.Unit end return Vector3.new( 0, 0, -1 ) end -- LANE SHIFT local function DoLaneShift( Side ) if not ProjectionActive then return end if LaneShiftActive then return end if Side ~= -1 and Side ~= 1 then return end LaneShiftActive = true local StartCFrame = RootPart.CFrame local StartPosition = StartCFrame.Position local Forward = GetHorizontalDirection() local Right = Vector3.new( -Forward.Z, 0, Forward.X ) local StartTime = os.clock() local LastAfterimage = os.clock() for Step = 1, LANE_SHIFT_STEPS do if not ProjectionActive or not ProjectionHolding or not CharacterIsValid() then break end local Alpha = Step / LANE_SHIFT_STEPS local Wave = math.sin( Alpha * math.pi * 4 ) local SideOffset = Wave * LANE_SHIFT_DISTANCE * Side local ForwardOffset = Alpha * 2 local NewPosition = StartPosition + Right * SideOffset + Forward * ForwardOffset RootPart.CFrame = CFrame.lookAt( NewPosition, NewPosition + Forward ) if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end local TargetTime = StartTime + ( Alpha * LANE_SHIFT_DURATION ) while os.clock() < TargetTime do if not ProjectionActive or not ProjectionHolding or not CharacterIsValid() then break end RunService.RenderStepped:Wait() end end LaneShiftActive = false end -- C + Z - CIRCULAR PROJECTION local function DoCircularProjection() if not ProjectionActive then return end if CircularProjectionActive then return end local Target = GetPlayerTarget( Z_TARGET_DISTANCE ) if not Target then return end PlayMovementSound(CIRCLE_SOUND_ID, MOVEMENT_SOUND_VOLUME, 1) CircularProjectionActive = true local StartTime = os.clock() local StartPosition = RootPart.Position local TargetPosition = Target.Position local InitialDirection = ( StartPosition - TargetPosition ) if InitialDirection.Magnitude < 1 then InitialDirection = Vector3.new( 1, 0, 0 ) end InitialDirection = InitialDirection.Unit local StartAngle = math.atan2( InitialDirection.Z, InitialDirection.X ) local LastAfterimage = os.clock() while os.clock() - StartTime < CIRCLE_TIME do if not ProjectionActive or not ProjectionHolding or not CharacterIsValid() then break end if not Target or not Target.Parent then break end local Alpha = ( os.clock() - StartTime ) / CIRCLE_TIME local Angle = StartAngle + ( Alpha * math.pi * 2 * CIRCLE_ROTATIONS ) local CurrentTarget = Target.Position local Offset = Vector3.new( math.cos(Angle) * CIRCLE_RADIUS, 0, math.sin(Angle) * CIRCLE_RADIUS ) local NewPosition = CurrentTarget + Offset RootPart.CFrame = CFrame.lookAt( NewPosition, CurrentTarget ) if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end if ProjectionActive and ProjectionHolding and CharacterIsValid() then local CurrentPosition = RootPart.Position local Center = Target.Position local RadiusVector = CurrentPosition - Center if RadiusVector.Magnitude > 0.1 then local Tangent = Vector3.new( -RadiusVector.Z, 0, RadiusVector.X ).Unit RootPart.CFrame = CFrame.lookAt( CurrentPosition, CurrentPosition + Tangent ) end end CircularProjectionActive = false ProjectionCurrentSpeed = 0 end -- CREATE CRYSTAL local function CreateCrystal( Position, Direction ) local Crystal = Instance.new("Part") Crystal.Name = "ProjectionCrystal" Crystal.Size = Vector3.new( 0.35, 0.35, 1.8 ) Crystal.Material = Enum.Material.Glass Crystal.Color = Color3.fromRGB( 190, 170, 255 ) Crystal.Transparency = 0.25 Crystal.Anchored = true Crystal.CanCollide = false Crystal.CanTouch = false Crystal.CanQuery = false Crystal.CFrame = CFrame.lookAt( Position, Position + Direction ) Crystal.Parent = workspace Debris:AddItem( Crystal, 1.5 ) end -- STOP PROJECTION local function StopProjection() ProjectionActive = false ProjectionHolding = false ProjectionReleasing = false LaneShiftActive = false CircularProjectionActive = false LaneShiftKey = nil StopProjectionAnimation() if ProjectionConnection then ProjectionConnection:Disconnect() ProjectionConnection = nil end if ProjectionInputConnection then ProjectionInputConnection:Disconnect() ProjectionInputConnection = nil end if ProjectionDeathConnection then ProjectionDeathConnection:Disconnect() ProjectionDeathConnection = nil end Busy = false end -- C - PROJECTION local function Projection() if Busy then return end if not CanUse("C") then return end if not CharacterIsValid() then return end LastUse.C = os.clock() Busy = true ProjectionActive = true ProjectionHolding = true CreateVignette() LoadProjectionAnimation() if ProjectionAnimationTrack then ProjectionAnimationTrack:Play( 0.1, 1, 1 ) end local StartPosition = RootPart.Position local DistanceTravelled = 0 local Speed = PROJECTION_MIN_SPEED ProjectionCurrentSpeed = Speed local LastCrystalPosition = StartPosition local LastCrystalSpawn = os.clock() local LastAfterimage = os.clock() for i = 1, INITIAL_CRYSTALS do if not CharacterIsValid() then break end local Direction = GetHorizontalDirection() local Position = LastCrystalPosition + Direction * CRYSTAL_SPACING if (Position - StartPosition) .Magnitude >= PROJECTION_MAX_DISTANCE then break end LastCrystalPosition = Position CreateCrystal( Position, Direction ) end ProjectionInputConnection = UserInputService.InputEnded:Connect( function(Input) if Input.KeyCode == Enum.KeyCode.C then ProjectionHolding = false if ProjectionActive and not ProjectionReleasing then ProjectionReleasing = true ProjectionReleaseStart = os.clock() ProjectionReleaseSpeed = Speed ProjectionReleaseDirection = GetHorizontalDirection() end end end ) ProjectionDeathConnection = Humanoid.Died:Connect( function() ProjectionHolding = false StopProjection() end ) ProjectionConnection = RunService.RenderStepped:Connect( function(Delta) if not ProjectionActive then StopProjection() return end if not CharacterIsValid() then StopProjection() return end -- C SOLTADA: DESACELERACIÓN PROPORCIONAL -- Si ibas lento, tarda más en perder la velocidad. -- Si ibas rapidísimo, frena mucho más rápido. if ProjectionReleasing then local ReleaseElapsed = os.clock() - ProjectionReleaseStart local SpeedRatio = math.clamp(ProjectionReleaseSpeed / math.max(PROJECTION_MAX_SPEED, 1), 0, 1) local DecelDuration = PROJECTION_DECEL_MAX_TIME - (PROJECTION_DECEL_MAX_TIME - PROJECTION_DECEL_MIN_TIME) * SpeedRatio local ReleaseAlpha = math.clamp(ReleaseElapsed / DecelDuration, 0, 1) local EaseOut = 1 - (1 - ReleaseAlpha)^3 local CurrentSpeed = ProjectionReleaseSpeed * (1 - EaseOut) if CurrentSpeed <= PROJECTION_MIN_SPEED + 0.05 or ReleaseAlpha >= 1 then StopProjection() return end local Direction = ProjectionReleaseDirection if Direction.Magnitude < 0.1 then Direction = GetHorizontalDirection() end Direction = Direction.Unit local FrameDistance = CurrentSpeed * Delta local NewPosition = RootPart.Position + Direction * FrameDistance RootPart.CFrame = CFrame.lookAt(NewPosition, NewPosition + Direction) DistanceTravelled += FrameDistance if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end return end Speed += PROJECTION_ACCELERATION * Delta Speed = math.clamp( Speed, PROJECTION_MIN_SPEED, PROJECTION_MAX_SPEED ) -- La animacion de C acelera junto con la velocidad real de Projection. if ProjectionAnimationTrack then local AnimationSpeed = math.clamp( Speed / math.max(PROJECTION_MIN_SPEED, 1), 1, 20 ) ProjectionAnimationTrack:AdjustSpeed(AnimationSpeed) end local Direction = GetHorizontalDirection() local FrameDistance = Speed * Delta local Remaining = PROJECTION_MAX_DISTANCE - DistanceTravelled if Remaining <= 0 then StopProjection() return end if FrameDistance > Remaining then FrameDistance = Remaining end if not LaneShiftActive and not CircularProjectionActive then local NewPosition = RootPart.Position + Direction * FrameDistance RootPart.CFrame = CFrame.lookAt( NewPosition, NewPosition + Direction ) end DistanceTravelled += FrameDistance local SpawnInterval = CRYSTAL_SPACING / Speed if os.clock() - LastCrystalSpawn >= SpawnInterval then LastCrystalSpawn = os.clock() for i = 1, CRYSTALS_PER_STEP do local CrystalDirection = GetHorizontalDirection() local NewPosition = LastCrystalPosition + CrystalDirection * CRYSTAL_SPACING if ( NewPosition - StartPosition ).Magnitude >= PROJECTION_MAX_DISTANCE then break end LastCrystalPosition = NewPosition CreateCrystal( NewPosition, CrystalDirection ) end end if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end end ) end -- X - MOMENTUM CHAIN / IMPACTO + SUSPENSIÓN local XHolding = false local XActive = false local XChainRequested = false local XChainWindowOpen = false local XChainCount = 0 local AimLockTarget = nil local AimLockBound = false local function StartAimLock(Target) if not AIM_LOCK_ENABLED then return end AimLockTarget = Target if AimLockBound then return end AimLockBound = true pcall(function() RunService:UnbindFromRenderStep("HeadHeiAimLock") end) RunService:BindToRenderStep("HeadHeiAimLock", Enum.RenderPriority.Camera.Value + 1, function() if not AIM_LOCK_ENABLED or not AimLockTarget or not AimLockTarget.Parent then return end local CurrentCamera = workspace.CurrentCamera if not CurrentCamera then return end local CameraPosition = CurrentCamera.CFrame.Position local AimPoint = AimLockTarget.Position + Vector3.new(0, AIM_LOCK_HEIGHT, 0) local DesiredCFrame = CFrame.lookAt(CameraPosition, AimPoint) CurrentCamera.CFrame = CurrentCamera.CFrame:Lerp(DesiredCFrame, AIM_LOCK_SMOOTHNESS) end) end local function StopAimLock() AimLockTarget = nil if AimLockBound then AimLockBound = false pcall(function() RunService:UnbindFromRenderStep("HeadHeiAimLock") end) end end local function ProjectionBreaker() -- Si X ya está activo, una nueva pulsación durante la ventana -- solicita el siguiente dash en vez de reiniciar la habilidad. if XActive then if XChainWindowOpen then XChainRequested = true end if TargetHighlight then TargetHighlight:Destroy() end return end if Busy then return end if not CanUse("X") then return end if not CharacterIsValid() then return end LastUse.X = os.clock() Busy = true XActive = true XHolding = true XChainRequested = false XChainWindowOpen = false XChainCount = 0 CreateVignette() local OldAutoRotate = Humanoid.AutoRotate Humanoid.AutoRotate = false local LastAfterimage = os.clock() -- CADA CADENA = IMPULSO -> PASADA -> PÉRDIDA DE INERCIA while XActive and CharacterIsValid() do local Target = GetPlayerTarget(X_TARGET_DISTANCE) if not Target or not Target.Parent then break end StartAimLock(Target) -- La dirección de salida pertenece a la TRAYECTORIA, no a la rotación -- del personaje. Cada nueva X invierte 180° el lado de la curva. local ChainIndex = (XChainCount or 0) + 1 XChainCount = ChainIndex local ChainTrajectorySign = (ChainIndex % 2 == 0) and -1 or 1 -- 1. IDA: ENTRADA RÁPIDA AL OBJETIVO local DashStart = RootPart.Position local DashStartTime = os.clock() while os.clock() - DashStartTime < X_DASH_TO_TARGET_TIME do if not XActive or not CharacterIsValid() or not Target or not Target.Parent then break end local Elapsed = os.clock() - DashStartTime local Alpha = math.clamp(Elapsed / X_DASH_TO_TARGET_TIME, 0, 1) local MovementAlpha = 1 - (1 - Alpha)^2.2 local CurrentTarget = Target.Position local ToTarget = CurrentTarget - DashStart if ToTarget.Magnitude < 0.1 then break end local Direction = ToTarget.Unit local DesiredPosition = CurrentTarget - Direction * X_TARGET_OFFSET local NewPosition = DashStart:Lerp(DesiredPosition, MovementAlpha) -- El cuerpo mira al objetivo normalmente; NO hacemos roll/rotación visual. RootPart.CFrame = CFrame.lookAt(NewPosition, CurrentTarget) if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end if not XActive or not CharacterIsValid() or not Target or not Target.Parent then break end -- 2. PASADA: LLEGAMOS JUSTO AL LADO DEL OBJETIVO local CurrentTarget = Target.Position local FromTarget = RootPart.Position - CurrentTarget if FromTarget.Magnitude < 0.1 then FromTarget = -GetHorizontalDirection() end local HitPosition = CurrentTarget + FromTarget.Unit * X_TARGET_OFFSET RootPart.CFrame = CFrame.lookAt(HitPosition, CurrentTarget) -- 3. PASADA + GAMBLE DE ÓRBITA -- ATRAVIESA 6 STUDS Y, EN 0.075s, EL SISTEMA -- ELIGE AL AZAR 1 DE 4 POSICIONES ALREDEDOR -- DEL OBJETIVO. EL JUGADOR NO DECIDE EL LADO. local PassDirection = FromTarget.Unit local HorizontalPass = Vector3.new(PassDirection.X, 0, PassDirection.Z) if HorizontalPass.Magnitude < 0.1 then HorizontalPass = -GetHorizontalDirection() end HorizontalPass = HorizontalPass.Unit -- Ejes locales alrededor del objetivo. local OrbitForward = HorizontalPass local OrbitRight = Vector3.new(-OrbitForward.Z, 0, OrbitForward.X).Unit -- 4 posiciones cardinales: frente, derecha, atrás, izquierda. local OrbitPositions = { OrbitForward, OrbitRight, -OrbitForward, -OrbitRight, } -- GAMBLE: se decide una sola vez por cada activación de X. local GambleIndex = math.random(1, 4) local GambleDirection = OrbitPositions[GambleIndex] local PassStart = RootPart.Position local PassEnd = PassStart + PassDirection * X_POST_HIT_DISTANCE -- La posición final de la trayectoria se recalcula respecto al -- objetivo actual para que el movimiento siga siendo dinámico. local TargetAtDecision = Target.Position local OrbitEnd = TargetAtDecision + GambleDirection * X_ORBIT_RADIUS + Vector3.new(0, math.random(-2, 4), 0) local GlideStart = PassEnd local GlideStartTime = os.clock() -- 4. VENTANA AÉREA: 1.4s PARA VOLVER A PULSAR X -- PRIMEROS 0.1s = DECISIÓN/GIRO RÁPIDO HACIA -- UNA DE LAS 4 POSICIONES. -- DESPUÉS = DESACELERACIÓN SUAVE, SIN CAMBIO BRUSCO. XChainRequested = false XChainWindowOpen = true while os.clock() - GlideStartTime < X_CHAIN_WINDOW do if not XActive or not CharacterIsValid() or not Target or not Target.Parent then break end if XChainRequested then break end local Now = os.clock() local Elapsed = Now - GlideStartTime local CurrentTarget = Target.Position local DesiredPosition if Elapsed < X_PASS_THROUGH_TIME then -- Primero atraviesa al objetivo. Todavía no intenta orbitarlo. local PassAlpha = math.clamp(Elapsed / X_PASS_THROUGH_TIME, 0, 1) local PassEase = 1 - (1 - PassAlpha)^2.5 DesiredPosition = PassStart:Lerp(PassEnd, PassEase) elseif Elapsed < X_PASS_THROUGH_TIME + X_ORBIT_DECISION_TIME then -- GAMBLE RÁPIDO: en esta pequeña ventana se fija la dirección -- de salida. La trayectoria acelera hacia ella sin teletransportar. local DecisionElapsed = Elapsed - X_PASS_THROUGH_TIME local DecisionAlpha = math.clamp(DecisionElapsed / X_ORBIT_DECISION_TIME, 0, 1) local DecisionEase = 1 - (1 - DecisionAlpha)^2.2 DesiredPosition = GlideStart:Lerp(OrbitEnd, DecisionEase) else -- INERCIA: continúa alejándose hacia la posición ganadora, -- pero cada vez más lento. El objetivo puede moverse. local GlideElapsed = Elapsed - X_PASS_THROUGH_TIME - X_ORBIT_DECISION_TIME local GlideDuration = math.max(X_GLIDE_TIME - X_PASS_THROUGH_TIME - X_ORBIT_DECISION_TIME, 0.05) local GlideAlphaRaw = math.clamp(GlideElapsed / GlideDuration, 0, 1) local GlideAlpha = 1 - (1 - GlideAlphaRaw)^3.2 local DynamicOrbitEnd = CurrentTarget + GambleDirection * X_ORBIT_RADIUS + Vector3.new(0, OrbitEnd.Y - TargetAtDecision.Y, 0) DesiredPosition = GlideStart:Lerp(DynamicOrbitEnd, GlideAlpha) -- Arco suave pequeño: evita que la trayectoria se vea como -- una línea con un cambio de dirección brusco. local Arc = math.sin(GlideAlphaRaw * math.pi) local ArcDirection = OrbitRight * ((GambleIndex % 2 == 0) and 1 or -1) DesiredPosition += ArcDirection * (Arc * 2.25) end local LookPoint = CurrentTarget + Vector3.new(0, AIM_LOCK_HEIGHT, 0) -- El lock-on mantiene la mirada sobre el objetivo. La orientación -- NO controla la trayectoria ni hace que el personaje lo orbite. RootPart.CFrame = CFrame.lookAt(DesiredPosition, LookPoint) pcall(function() RootPart.AssemblyLinearVelocity = Vector3.zero RootPart.AssemblyAngularVelocity = Vector3.zero end) if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end XChainWindowOpen = false if not XActive or not CharacterIsValid() then break end if not XChainRequested then break end -- 5. SIGUIENTE CADENA XChainRequested = false local ChainPauseStart = os.clock() while os.clock() - ChainPauseStart < X_CHAIN_PAUSE do if not XActive or not CharacterIsValid() then break end RunService.RenderStepped:Wait() end -- En la siguiente X se vuelve a hacer un nuevo gamble 1-4. end -- RESTAURAR CONTROL XActive = false XHolding = false XChainRequested = false XChainWindowOpen = false StopAimLock() if Humanoid and Humanoid.Parent then Humanoid.AutoRotate = OldAutoRotate end pcall(function() RootPart.AssemblyLinearVelocity = Vector3.zero RootPart.AssemblyAngularVelocity = Vector3.zero end) Busy = false end -- Z - NORMAL ORBIT local function Orbit() if Busy then return end if not CanUse("Z") then return end if not CharacterIsValid() then return end -- El objetivo se selecciona UNA sola vez al presionar Z. -- A partir de aquí no se vuelve a buscar otro jugador. local Target = GetPlayerTarget( Z_TARGET_DISTANCE ) if not Target then return end StartAimLock(Target) LastUse.Z = os.clock() Busy = true CreateVignette() local OldAutoRotate = Humanoid.AutoRotate Humanoid.AutoRotate = false -- ACERCAMIENTO AL OBJETIVO -- Esta distancia evita aparecer literalmente dentro del jugador. local APPROACH_DISTANCE = 7 local APPROACH_TIME = 0.35 local ApproachStart = RootPart.Position local ApproachStartTime = os.clock() local LastAfterimage = os.clock() while os.clock() - ApproachStartTime < APPROACH_TIME do if not CharacterIsValid() or not Target or not Target.Parent then break end local Elapsed = os.clock() - ApproachStartTime local Alpha = math.clamp( Elapsed / APPROACH_TIME, 0, 1 ) -- Actualiza Target.Position EN CADA FRAME. -- Esto es lo que evita viajar hacia una posición vieja. local CurrentTarget = Target.Position local ToTarget = CurrentTarget - ApproachStart if ToTarget.Magnitude < 0.1 then break end local Direction = ToTarget.Unit local DesiredPosition = CurrentTarget - Direction * APPROACH_DISTANCE -- Suavizado de entrada. local Ease = 1 - (1 - Alpha)^3 local NewPosition = ApproachStart:Lerp( DesiredPosition, Ease ) RootPart.CFrame = CFrame.lookAt( NewPosition, CurrentTarget ) if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end -- ÓRBITA if CharacterIsValid() and Target and Target.Parent then local CurrentTarget = Target.Position local RadiusVector = RootPart.Position - CurrentTarget if RadiusVector.Magnitude < 0.1 then RadiusVector = Vector3.new( 1, 0, 0 ) end RadiusVector = RadiusVector.Unit local Angle = math.atan2( RadiusVector.Z, RadiusVector.X ) local OrbitStart = os.clock() LastAfterimage = os.clock() local Z_GAP_INTERVAL = 0.27 local Z_GAP_TIME = 0.085 local Z_GAP_DISTANCE = 5.5 local Z_GAP_HEIGHT = 3.0 local Z_GAP_BACK = 1.5 local NextGap = OrbitStart + Z_GAP_INTERVAL local GapStart = nil local GapOrigin = nil while os.clock() - OrbitStart < ORBIT_TIME do if not CharacterIsValid() or not Target or not Target.Parent then break end RunService.RenderStepped:Wait() local Now = os.clock() local InGap = GapStart ~= nil if not InGap and Now >= NextGap then GapStart = Now GapOrigin = RootPart.Position InGap = true end local TargetPosition = Target.Position if InGap then local GapAlpha = math.clamp((Now - GapStart) / Z_GAP_TIME, 0, 1) local GapEase = 1 - (1 - GapAlpha)^2 local Away = GapOrigin - TargetPosition if Away.Magnitude < 0.1 then Away = Vector3.new(0, 0, -1) end Away = Away.Unit local GapPosition = GapOrigin:Lerp( TargetPosition + Away * Z_GAP_DISTANCE + Vector3.new(0, Z_GAP_HEIGHT, 0) - Away * Z_GAP_BACK, GapEase ) RootPart.CFrame = CFrame.lookAt(GapPosition, TargetPosition) if Now - GapStart >= Z_GAP_TIME then GapStart = nil GapOrigin = nil NextGap = Now + Z_GAP_INTERVAL end else local Phase = ((Now - OrbitStart) * ORBIT_SPEED / (math.pi / 2)) % 4 local Side = math.floor(Phase) local T = Phase - Side local R = ORBIT_RADIUS local Offset if Side == 0 then Offset = Vector3.new(R, 0, -R + 2 * R * T) elseif Side == 1 then Offset = Vector3.new(R - 2 * R * T, 0, R) elseif Side == 2 then Offset = Vector3.new(-R, 0, R - 2 * R * T) else Offset = Vector3.new(-R + 2 * R * T, 0, -R) end RootPart.CFrame = CFrame.lookAt(TargetPosition + Offset, TargetPosition) end if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end end end StopAimLock() if Humanoid and Humanoid.Parent then Humanoid.AutoRotate = OldAutoRotate end Busy = false end -- V - HIGH SPEED LATERAL SHIFT local function CursoryImpact() if Busy then return end if not CanUse("V") then return end if not CharacterIsValid() then return end -- Lock-on para V: mantiene al objetivo visible durante el movimiento rápido. local VTarget = GetPlayerTarget(Z_TARGET_DISTANCE) if VTarget and VTarget.Parent then StartAimLock(VTarget) end LastUse.V = os.clock() Busy = true CreateVignette() local OldAutoRotate = Humanoid.AutoRotate local OldWalkSpeed = Humanoid.WalkSpeed Humanoid.AutoRotate = false -- DIRECCIÓN BASE local Forward = GetHorizontalDirection() local Right = Vector3.new( -Forward.Z, 0, Forward.X ) local StartPosition = RootPart.Position -- REWORK V: si hay objetivo, la órbita se engancha a él. -- Si no hay objetivo, conserva el punto de inicio como respaldo. local OrbitCenter = (VTarget and VTarget.Parent) and VTarget.Position or StartPosition -- PRIMERA FASE: ZIGZAG PROGRESIVO - 0.4 SEGUNDOS local ZigzagStart = os.clock() local LastAfterimage = os.clock() while os.clock() - ZigzagStart < V_ZIGZAG_TIME do if not CharacterIsValid() then break end local Elapsed = os.clock() - ZigzagStart local Alpha = math.clamp( Elapsed / V_ZIGZAG_TIME, 0, 1 ) -- La amplitud aumenta progresivamente desde 0 -- hasta V_ZIGZAG_DISTANCE. local ProgressiveDistance = Alpha * V_ZIGZAG_DISTANCE local Wave = math.sin( Alpha * math.pi * 2 * V_ZIGZAG_SWINGS ) local SideOffset = Wave * ProgressiveDistance local ForwardOffset = Alpha * 8 local NewPosition = StartPosition + Right * SideOffset + Forward * ForwardOffset RootPart.CFrame = CFrame.lookAt( NewPosition, NewPosition + Forward ) if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= V_AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end -- SEGUNDA FASE: ÓRBITA ACELERADA / EXPANSIVA - 3 SEGUNDOS -- 12 STUDS -> 80 STUDS if CharacterIsValid() then local OrbitStart = os.clock() local LastOrbitAfterimage = os.clock() local StartAngle = math.atan2( ( RootPart.Position - OrbitCenter ).Z, ( RootPart.Position - OrbitCenter ).X ) while os.clock() - OrbitStart < V_ORBIT_TIME do if not CharacterIsValid() then break end local Elapsed = os.clock() - OrbitStart local Alpha = math.clamp( Elapsed / V_ORBIT_TIME, 0, 1 ) -- Suaviza el crecimiento del radio: -- empieza cerca de 12 y termina exactamente cerca de 80. local RadiusAlpha = Alpha * Alpha local Radius = V_ORBIT_START_RADIUS + ( V_ORBIT_END_RADIUS - V_ORBIT_START_RADIUS ) * RadiusAlpha local Angle = StartAngle + ( Alpha * math.pi * 2 * V_ORBIT_ROTATIONS ) local Offset = Vector3.new( math.cos(Angle) * Radius, 0, math.sin(Angle) * Radius ) local NewPosition = OrbitCenter + Offset -- El personaje viaja físicamente alrededor del punto -- mientras mira hacia el centro de la órbita. RootPart.CFrame = CFrame.lookAt( NewPosition, OrbitCenter ) if AFTERIMAGES_ENABLED and os.clock() - LastOrbitAfterimage >= V_AFTERIMAGE_INTERVAL then LastOrbitAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end end -- REWORK V: SLINGSHOT DE SALIDA -- En vez de volver al punto original, sale disparado -- en la dirección actual después de orbitar. if CharacterIsValid() then local ExitStartPosition = RootPart.Position local ExitStartTime = os.clock() while CharacterIsValid() and os.clock() - ExitStartTime < V_EXIT_TIME do local Alpha = math.clamp( (os.clock() - ExitStartTime) / V_EXIT_TIME, 0, 1 ) local Ease = 1 - (1 - Alpha)^3 local NewPosition = ExitStartPosition + Forward * (V_EXIT_DISTANCE * Ease) RootPart.CFrame = CFrame.lookAt( NewPosition, NewPosition + Forward ) if AFTERIMAGES_ENABLED and os.clock() - LastAfterimage >= V_AFTERIMAGE_INTERVAL then LastAfterimage = os.clock() CreateAfterimage() end RunService.RenderStepped:Wait() end RootPart.AssemblyLinearVelocity = Vector3.zero RootPart.AssemblyAngularVelocity = Vector3.zero end -- BUFF DE VELOCIDAD -- +80 DURANTE 3 SEGUNDOS DESPUÉS DEL REGRESO if Humanoid and Humanoid.Parent then Humanoid.WalkSpeed = OldWalkSpeed + V_SPEED_BUFF task.delay( V_SPEED_BUFF_DURATION, function() if Humanoid and Humanoid.Parent and CharacterIsValid() then -- Solo restaura si todavía conserva el buff -- que esta V colocó. if Humanoid.WalkSpeed == OldWalkSpeed + V_SPEED_BUFF then Humanoid.WalkSpeed = OldWalkSpeed end end end ) end StopAimLock() -- RESTAURAR CONTROL if Humanoid and Humanoid.Parent then Humanoid.AutoRotate = OldAutoRotate end Busy = false end -- X - LA CADENA USA PULSACIONES, NO HOLD -- Soltar X ya no cancela la técnica. Cada nueva pulsación durante -- la ventana de 1.4s solicita explícitamente el siguiente dash. -- INPUT DURANTE PROJECTION UserInputService.InputBegan:Connect( function(Input, GameProcessed) -- Legacy dispatcher disabled: the updated dispatcher at the end handles keys. return if GameProcessed then return end -- C + Z if Input.KeyCode == Enum.KeyCode.Z and ProjectionActive and ProjectionHolding then task.spawn( DoCircularProjection ) return end -- C + A if Input.KeyCode == Enum.KeyCode.A and ProjectionActive and ProjectionHolding then LaneShiftKey = "A" task.spawn( function() DoLaneShift(-1) end ) return end -- C + D if Input.KeyCode == Enum.KeyCode.D and ProjectionActive and ProjectionHolding then LaneShiftKey = "D" task.spawn( function() DoLaneShift(1) end ) return end -- X if Input.KeyCode == Enum.KeyCode.X then task.spawn( function() RunSkillWithRecovery(ProjectionBreaker) end ) return end -- Z if Input.KeyCode == Enum.KeyCode.Z then task.spawn( function() RunSkillWithRecovery(Orbit) end ) return end -- C if Input.KeyCode == Enum.KeyCode.C then task.spawn( function() RunSkillWithRecovery(Projection) end ) return end -- V -- La V de esta versión antigua queda desactivada. -- La V efectiva se maneja en la versión nueva del script. -- B - AFTERIMAGES if Input.KeyCode == Enum.KeyCode.B then AFTERIMAGES_ENABLED = not AFTERIMAGES_ENABLED if not AFTERIMAGES_ENABLED then ClearAfterimages() end print( "Afterimages:", AFTERIMAGES_ENABLED and "ON" or "OFF" ) end end ) -- RESPAWN FAILSAFE Player.CharacterAdded:Connect( function(NewCharacter) StopProjection() if CurrentRagdollData then StopRagdoll( CurrentRagdollData ) CurrentRagdollData = nil end Busy = false ClearAfterimages() Character = NewCharacter Humanoid = NewCharacter:WaitForChild( "Humanoid" ) RootPart = NewCharacter:WaitForChild( "HumanoidRootPart" ) end ) -- FINAL FAILSAFE task.spawn( function() while true do task.wait(1) if ProjectionActive and not CharacterIsValid() then StopProjection() end end end ) print( "Head of the Hei Movement System loaded." ]==]