Run it
Copy the script and paste it into your executor.
Players = :()
UserInputService = :()
RunService = :()
TweenService = :()
CFG = {}
CharacterIsValid
ProjectionActive
StartAimLock
StopAimLock
Debris = :()
Player = Players.LocalPlayer
Camera = .CurrentCamera
CFG.COOLDOWN =
CFG.AFTERIMAGES_ENABLED =
CFG.MAX_AFTERIMAGES =
CFG.AFTERIMAGE_INTERVAL =
CFG.AFTERIMAGE_LIFETIME =
ActiveAfterimages = {}
CFG.ORBIT_TIME =
CFG.ORBIT_RADIUS =
CFG.ORBIT_SPEED = .()
CFG.Z_TARGET_DISTANCE =
CFG.PROJECTION_MAX_DISTANCE =
CFG.PROJECTION_MIN_SPEED =
CFG.PROJECTION_ACCELERATION =
CFG.PROJECTION_MAX_SPEED =
CFG.PROJECTION_DECEL_MIN_TIME =
CFG.PROJECTION_DECEL_MAX_TIME =
CFG.PROJECTION_STEPPED =
CFG.PROJECTION_STEP_FPS =
CFG.PROJECTION_BURST_EVERY_STEPS =
CFG.PROJECTION_BURST_BOOST =
CFG.PROJECTION_BURST_DECAY =
CFG.VIGNETTE_COLOR = .(, , )
CFG.VIGNETTE_MAX_OPACITY =
CFG.VIGNETTE_SIZE =
CFG.CAMERA_STABILIZER_ENABLED =
CFG.CAMERA_FOCUS_HEIGHT =
CFG.CAMERA_FOLLOW_RESPONSE =
CFG.CAMERA_SETTLE_TIME =
CFG.C_SOUND_VOLUME_STEP =
CFG.C_SOUND_MIN_VOLUME_FACTOR =
CFG.INITIAL_CRYSTALS =
CFG.CRYSTALS_PER_STEP =
CFG.CRYSTAL_SPACING =
CFG.PROJECTION_ANIMATION_ID =
CFG.PROJECTION_ANIMATIONS = {
Main = ,
}
CFG.PROJECTION_ANIMATION_MODE =
CFG.PROJECTION_EVENT_ANIMATIONS = {
Start = ,
LaneLeft = ,
LaneRight = ,
Circular = ,
}
CFG.PROJECTION_EVENT_MODE =
CFG.PROJECTION_EVENT_FIT_TO_DURATION =
ProjectionAnimation =
ProjectionAnimationTrack =
ProjectionEventAnimation =
ProjectionEventTrack =
ProjectionCycleIndices = {}
CFG.LANE_SHIFT_STEPS =
CFG.LANE_SHIFT_DURATION =
CFG.LANE_SHIFT_DISTANCE =
CFG.LANE_SHIFT_LEAN = .()
CFG.LANE_SHIFT_SLIDE_PITCH = .()
CFG.LANE_SHIFT_LEAN_DIRECTION =
CFG.CIRCLE_TIME =
CFG.CIRCLE_RADIUS =
CFG.CIRCLE_ROTATIONS =
CFG.V_LATERAL_TIME =
CFG.V_LATERAL_DISTANCE =
CFG.V_LATERAL_SWINGS =
CFG.V_FORWARD_DISTANCE =
CFG.V_FORWARD_TIME =
CFG.V_DECELERATION_POWER =
CFG.V_AFTERIMAGE_INTERVAL =
CFG.MAX_V_LATERAL_START_DISTANCE =
CFG.MAX_V_LATERAL_DISTANCE_STEP =
CFG.MAX_V_LATERAL_DISTANCE_CAP =
CFG.MAX_V_LATERAL_START_SPEED =
CFG.MAX_V_LATERAL_SPEED_GROWTH =
CFG.MAX_V_LATERAL_MIN_TIME =
CFG.MAX_V_SPINDASH_RISE_TIME =
CFG.MAX_V_SPINDASH_RISE_HEIGHT =
CFG.MAX_V_ORBIT_DURATION =
CFG.MAX_V_ORBIT_RADIUS =
CFG.MAX_V_ORBIT_STEER_SPEED =
CFG.MAX_V_ORBIT_IDLE_RADIUS =
CFG.MAX_V_RELEASE_DISTANCE =
CFG.MAX_V_RELEASE_DURATION =
CFG.MAX_V_NEAR_PLAYER_RADIUS =
CFG.MAX_V_GLIDE_MAX_HITS =
CFG.MAX_V_GLIDE_HIT_COOLDOWN =
CFG.MAX_V_GLIDE_HIT_BOOST =
CFG.MAX_V_GLIDE_HIT_TIME_BONUS =
CFG.MAX_V_NEAR_PLAYER_REFRESH_DISTANCE =
CFG.MAX_V_GLIDE_DURATION =
CFG.MAX_V_GLIDE_HORIZONTAL_SPEED =
CFG.MAX_V_GLIDE_MAX_EXTRA_HEIGHT =
CFG.MAX_V_GLIDE_DESCENT_SPEED =
CFG.MAX_V_SPIN_RATE = .()
CFG.MAX_V_SPIN_DIRECTION = -
CFG.MAX_V_GLIDE_ENTRY_SPEED =
CFG.MAX_V_GLIDE_IDLE_SPEED_FACTOR =
CFG.MAX_V_GLIDE_VERTICAL_SPEED =
CFG.MAX_V_FLIGHT_RESPONSE =
CFG.MAX_V_FLIGHT_TURN_RESPONSE =
CFG.MAX_V_FLIGHT_LEAN = .()
CFG.MAX_V_FLIGHT_BANK = .()
CFG.MAX_V_FLIGHT_CLEARANCE =
CFG.MAX_V_GLIDE_SPIN_FADE =
CFG.MAX_V_GLIDE_KEEP_SPIN =
CFG.MAX_V_RELEASE_BASE_DISTANCE =
CFG.MAX_V_RELEASE_EXTRA_DISTANCE =
CFG.MAX_V_RELEASE_TIME =
CFG.MAX_V_RELEASE_DECELERATION_POWER =
CFG.X_TARGET_OFFSET =
CFG.X_DASH_TO_TARGET_TIME =
CFG.X_HIT_PAUSE =
CFG.X_REBOUND_DISTANCE =
CFG.X_REBOUND_TIME =
CFG.X_POST_HIT_DISTANCE =
CFG.X_CHAIN_WINDOW =
CFG.X_ORBIT_DECISION_TIME =
CFG.X_ORBIT_RADIUS =
CFG.X_GLIDE_TIME =
CFG.X_PASS_THROUGH_TIME =
CFG.X_CHAIN_PAUSE =
CFG.X_TARGET_DISTANCE = CFG.Z_TARGET_DISTANCE
CFG.AIM_LOCK_ENABLED =
CFG.AIM_LOCK_SMOOTHNESS =
CFG.AIM_LOCK_HEIGHT =
CFG.MAX_OUTPUT_KEY = .KeyCode.J
CFG.MAX_OUTPUT_MAX_ENERGY =
CFG.MAX_OUTPUT_START_ENERGY =
CFG.MAX_OUTPUT_DRAIN_PER_SECOND =
CFG.MAX_OUTPUT_REGEN_PER_SECOND =
CFG.MAX_OUTPUT_MIN_ACTIVATION_ENERGY =
CFG.ENERGY_REGEN_DELAY =
CFG.ENERGY_LOW_FRACTION =
CFG.ENERGY_COST_C_START =
CFG.ENERGY_COST_C_PER_SECOND =
CFG.ENERGY_COST_LANE_SHIFT =
CFG.ENERGY_COST_CIRCULAR =
CFG.ENERGY_COST_Z =
CFG.ENERGY_COST_X =
CFG.ENERGY_COST_V =
CFG.ENERGY_COST_J =
CFG.ENERGY_LOW_C_MIN_SPEED_FACTOR =
CFG.LOW_HEALTH_ENTER =
CFG.LOW_HEALTH_EXIT =
CFG.LOW_HEALTH_SOUND_ID =
CFG.LOW_HEALTH_SOUND_VOLUME =
CFG.LOW_HEALTH_VIGNETTE_OPACITY =
CFG.CRITICAL_HEALTH_ENTER =
CFG.CRITICAL_HEALTH_EXIT =
CFG.CRITICAL_SOUND_ID =
CFG.CRITICAL_SOUND_VOLUME =
CFG.CRIT_C_START_BOOST =
CFG.CRIT_C_BOOST_DECAY =
CFG.CRIT_Z_SPEED_FACTOR =
CFG.CRIT_X_DISTANCE_FACTOR =
CFG.CRIT_X_SPEED_FACTOR =
CFG.CRIT_MAX_X_SPEED_FACTOR =
CFG.CRIT_MAX_X_CENTER_PAUSE =
CFG.CRIT_V_BACKFLIP_TIME =
CFG.CRIT_V_HOVER_HEIGHT =
CFG.CRIT_V_HOVER_TIME =
CFG.CRIT_V_SHOT_DISTANCE =
CFG.CRIT_V_SHOT_TIME =
CFG.MAX_X_PASS_DISTANCE =
CFG.MAX_X_PASS_TIME =
CFG.MAX_X_RETURN_TIME =
CFG.MAX_X_ORBIT_TIME =
CFG.MAX_X_ORBIT_RADIUS =
CFG.MAX_X_ORBIT_ROTATIONS =
CFG.MAX_X_DIRECTION_COUNT =
CFG.MAX_X_DISTANCE_STEP_TIME =
CFG.MAX_X_DISTANCE_PER_STEP =
CFG.MAX_X_DISTANCE_CAP =
CFG.MAX_X_SOUND_ID =
CFG.C_SOUND_ID =
CFG.C_NORMAL_SOUND_ID =
CFG.J_SOUND_ID =
CFG.X_NORMAL_SOUND_ID =
CFG.CRITICAL_HEALTH_SOUND_ID =
CFG.V_MAX_SOUND_ID =
CFG.V_MAX_START_SOUND_ID =
CFG.V_MAX_LAUNCH_SOUND_ID =
CFG.V_MAX_NEAR_HIT_SOUND_ID =
CFG.MAX_OUTPUT_HOLD_SOUND_ID =
CFG.CIRCLE_SOUND_ID =
CFG.MAX_OUTPUT_MUSIC_ID =
CFG.MAX_OUTPUT_DURATION =
CFG.MAX_OUTPUT_MUSIC_FADE_IN =
CFG.MAX_OUTPUT_MUSIC_VOLUME =
CFG.MAX_OUTPUT_HOLD_SOUND_DELAY =
CFG.MOVEMENT_SOUND_VOLUME =
ProjectionSoundStart =
()
(SoundId, Volume, PlaybackSpeed, PitchMultiplier)
()
Sound = .()
Sound.Name =
Sound.SoundId = SoundId
Sound.Volume = Volume CFG.MOVEMENT_SOUND_VOLUME
Sound.PlaybackSpeed = PlaybackSpeed
Sound.RollOffMaxDistance =
Sound.RollOffMinDistance =
Sound.Parent = .CurrentCamera Camera
Sound:()
Debris:(Sound, )
()
.(()
ProjectionActive
(
CFG.C_NORMAL_SOUND_ID,
CFG.MOVEMENT_SOUND_VOLUME,
,
()
)
.()
)
MaximumOutputActive =
MaximumOutputEnergy = CFG.MAX_OUTPUT_START_ENERGY
MaximumOutputGui =
EnergyOrbHolder =
EnergyDeniedUntil =
MaximumOutputFill =
MaximumOutputLabel =
MaximumOutputConnection =
MaximumOutputFlowLevel =
MaximumOutputStartedAt =
MaximumOutputSession =
MaximumOutputMusic =
MaximumOutputHoldSound =
LastMaxVPress = -.huge
MaxVHolding =
MaxVRunning =
MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED
MaxVCurrentDistance = CFG.MAX_V_LATERAL_START_DISTANCE
MaxVCenterPosition =
MaxVForward =
MaxVRight =
MaxVStage =
MaxVReleaseRequested =
MaxVOrbitCenter =
MaxVOrbitStartTime =
MaxVUsedNearPass = {}
MaxXHolding =
MaxXRunning =
Character
Humanoid
RootPart
(CharacterObject)
Character = CharacterObject
Humanoid =
CharacterObject:()
RootPart =
CharacterObject:(
)
(
Player.Character
Player.CharacterAdded:()
)
Busy =
ProjectionActive =
ProjectionHolding =
ProjectionReleasing =
ProjectionReleaseStart =
ProjectionReleaseSpeed =
ProjectionReleaseDirection = .zero
LaneShiftActive =
CircularProjectionActive =
LaneShiftKey =
LaneShiftCurrentOffset = .zero
LaneShiftStartOffset = .zero
LaneShiftTargetOffset = .zero
LaneShiftStartTime =
LaneShiftSide =
LastUse = {
X = ,
Z = ,
C = ,
V =
}
ProjectionConnection =
ProjectionInputConnection =
ProjectionDeathConnection =
CurrentRagdollData =
(Skill)
.() - LastUse[Skill]
>= CFG.COOLDOWN
()
Character
Character.Parent
Humanoid
Humanoid.Parent
Humanoid.Health >
RootPart
RootPart.Parent
LowHealthConnection =
LowHealthSoundPlayed =
()
LowHealthConnection
LowHealthConnection:()
LowHealthConnection =
LowHealthSoundPlayed =
Humanoid
LowHealthConnection = Humanoid.HealthChanged:((Health)
Health <= LowHealthSoundPlayed
LowHealthSoundPlayed =
(CFG.CRITICAL_HEALTH_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, )
Health >
LowHealthSoundPlayed =
)
Humanoid.Health <= LowHealthSoundPlayed
LowHealthSoundPlayed =
(CFG.CRITICAL_HEALTH_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME, )
()
.(()
MaximumOutputActive MaxXHolding ()
Sound = .()
Sound.Name =
Sound.SoundId = CFG.MAX_X_SOUND_ID
Sound.Volume = CFG.MOVEMENT_SOUND_VOLUME
Sound.PlaybackSpeed =
Sound.RollOffMaxDistance =
Sound.RollOffMinDistance =
Sound.Parent = .CurrentCamera Camera
Sound.TimePosition =
Sound:()
MaximumOutputActive MaxXHolding () Sound.Parent
Sound.TimePosition >=
RunService.RenderStepped:()
Sound:()
Sound:()
)
()
.HeadHeiRotationRecoveryEnabled ==
.HeadHeiRotationRecoveryEnabled =
()
.HeadHeiRotationRecoveryEnabled ==
()
Start = .()
Humanoid.AutoRotate =
(()
RootPart.AssemblyAngularVelocity = .zero
)
Connection
Connection = RunService.RenderStepped:(()
()
Connection Connection:()
.() - Start >=
Connection Connection:()
Humanoid.AutoRotate =
MoveDirection = Humanoid.MoveDirection
MoveDirection.Magnitude >
FlatDirection = .(MoveDirection.X, , MoveDirection.Z)
FlatDirection.Magnitude >
FlatDirection = FlatDirection.Unit
Position = RootPart.Position
RootPart.CFrame = .(Position, Position + FlatDirection)
(()
RootPart.AssemblyAngularVelocity = .zero
)
)
(SkillFunction)
Success, ErrorMessage = (SkillFunction, debug.traceback)
Success
( .. (ErrorMessage))
()
()
MaximumOutputFill
Ratio = .(MaximumOutputEnergy / CFG.MAX_OUTPUT_MAX_ENERGY, , )
Diameter = * Ratio
MaximumOutputFill.Size = .(.(Diameter, ), .(Diameter, ))
Warning = Ratio < CFG.ENERGY_LOW_FRACTION .() < EnergyDeniedUntil
MaximumOutputFill.BackgroundColor3 = Warning .(, , ) .(, , )
MaximumOutputLabel
MaximumOutputLabel.Text = .. .(Ratio * + ) ..
MaximumOutputLabel.TextColor3 = Warning .(, , ) .(, , )
()
MaximumOutputGui MaximumOutputGui.Parent
PlayerGui = Player:()
PlayerGui
Gui = .()
Gui.Name =
Gui.IgnoreGuiInset =
Gui.ResetOnSpawn =
Gui.Parent = PlayerGui
MaximumOutputGui = Gui
Holder = .()
Holder.Size = .(, )
Holder.Position = .(, , , -)
Holder.BackgroundTransparency =
Holder.Parent = Gui
EnergyOrbHolder = Holder
BlackCircle = .()
BlackCircle.AnchorPoint = .(, )
BlackCircle.Position = .(, , , )
BlackCircle.Size = .(, )
BlackCircle.BackgroundColor3 = .(, , )
BlackCircle.BorderSizePixel =
BlackCircle.Parent = Holder
BC = .(); BC.CornerRadius = .(,); BC.Parent = BlackCircle
BlueCircle = .()
BlueCircle.AnchorPoint = .(, )
BlueCircle.Position = .(, )
BlueCircle.Size = .(, )
BlueCircle.BackgroundColor3 = .(, , )
BlueCircle.BorderSizePixel =
BlueCircle.Parent = BlackCircle
BLC = .(); BLC.CornerRadius = .(,); BLC.Parent = BlueCircle
MaximumOutputFill = BlueCircle
Label = .()
Label.Size = .(, )
Label.Position = .(, -, , -)
Label.BackgroundTransparency =
Label.Text =
Label.TextSize =
Label.Font = .Font.GothamBold
Label.TextColor3 = .(,,)
Label.Parent = Holder
MaximumOutputLabel = Label
()
LastEnergySpend = -.huge
EnergyDotAccumulator =
()
.(MaximumOutputEnergy / CFG.MAX_OUTPUT_MAX_ENERGY, , )
()
() < CFG.ENERGY_LOW_FRACTION
(Amount)
EnergyOrbHolder EnergyOrbHolder.Parent
_ = , Amount
Dot = .()
DotSize = .(, )
Dot.AnchorPoint = .(, )
Dot.Size = .(DotSize, DotSize)
Dot.Position = .(, , , )
Dot.BackgroundColor3 = .(, , )
Dot.BackgroundTransparency =
Dot.BorderSizePixel =
Dot.ZIndex =
Corner = .()
Corner.CornerRadius = .(, )
Corner.Parent = Dot
Dot.Parent = EnergyOrbHolder
Angle = .() * .pi *
Distance = .(, )
TweenService:(
Dot,
TweenInfo.( + .() * , .EasingStyle.Quad, .EasingDirection.Out),
{
Position = .(, .(Angle) * Distance, , .(Angle) * Distance),
BackgroundTransparency = ,
Size = .(, ),
}
):()
Debris:(Dot, )
(Amount)
MaximumOutputEnergy = .(, MaximumOutputEnergy - Amount)
LastEnergySpend = .()
EnergyDotAccumulator += Amount
EnergyDotAccumulator >=
EnergyDotAccumulator =
()
()
(Cost, AllowWhenLow)
MaximumOutputEnergy < Cost ( AllowWhenLow ())
EnergyDeniedUntil = .() +
()
MaximumOutputEnergy = MaximumOutputEnergy - Cost
LastEnergySpend = .()
(.(.(Cost / ), , ))
()
()
MaximumOutputMusic
Music = MaximumOutputMusic
MaximumOutputMusic =
(()
Music:()
Music:()
)
()
()
Music = .()
Music.Name =
Music.SoundId = CFG.MAX_OUTPUT_MUSIC_ID
Music.Volume =
Music.Looped =
Music.PlaybackSpeed =
Music.Parent = .CurrentCamera Camera
MaximumOutputMusic = Music
Music:()
Fade = TweenService:(
Music,
TweenInfo.(CFG.MAX_OUTPUT_MUSIC_FADE_IN, .EasingStyle.Quad, .EasingDirection.Out),
{Volume = CFG.MAX_OUTPUT_MUSIC_VOLUME}
)
Fade:()
Debris:(Music, CFG.MAX_OUTPUT_DURATION + )
()
Sound = .()
Sound.Name =
Sound.SoundId = CFG.MAX_OUTPUT_HOLD_SOUND_ID
Sound.Volume = CFG.MOVEMENT_SOUND_VOLUME
Sound.PlaybackSpeed =
Sound.Parent = .CurrentCamera Camera
MaximumOutputHoldSound = Sound
Sound:()
Debris:(Sound, )
()
MaximumOutputActive =
MaximumOutputFlowLevel =
MaximumOutputSession +=
()
MaximumOutputHoldSound
(()
MaximumOutputHoldSound:()
MaximumOutputHoldSound:()
)
MaximumOutputHoldSound =
MaxVHolding =
MaxVRunning =
MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED
MaxVCurrentDistance = CFG.MAX_V_LATERAL_START_DISTANCE
MaxVCenterPosition =
MaxVForward =
MaxVRight =
MaxVStage =
MaxVReleaseRequested =
MaxVOrbitCenter =
MaxVUsedNearPass = {}
MaxXHolding =
MaxXRunning =
_, Sound (RootPart RootPart:() {})
Sound:() Sound.Name ==
Sound:()
Sound:()
MaximumOutputConnection MaximumOutputConnection:(); MaximumOutputConnection =
()
()
MaximumOutputActive ();
Busy
(CFG.ENERGY_COST_J)
MaximumOutputActive =
MaximumOutputFlowLevel =
MaximumOutputStartedAt = .()
MaximumOutputSession +=
ThisSession = MaximumOutputSession
()
.(CFG.MAX_OUTPUT_HOLD_SOUND_DELAY, ()
MaximumOutputActive MaximumOutputSession == ThisSession
()
)
.(CFG.MAX_OUTPUT_DURATION, ()
MaximumOutputActive MaximumOutputSession == ThisSession
()
)
MaxVHolding =
MaxVRunning =
MaxVCurrentSpeed = CFG.MAX_V_LATERAL_START_SPEED
MaxVCurrentDistance = CFG.MAX_V_LATERAL_START_DISTANCE
MaxVCenterPosition =
MaxVForward =
MaxVRight =
()
MaximumOutputConnection = RunService.RenderStepped:((Delta)
MaximumOutputActive
(CFG.MAX_OUTPUT_DRAIN_PER_SECOND * Delta)
MaximumOutputEnergy <= ()
)
EnergyOrbBasePosition = .(, , , -)
RunService.Heartbeat:((Delta)
MaximumOutputActive ProjectionActive Busy
.() - LastEnergySpend >= CFG.ENERGY_REGEN_DELAY
MaximumOutputEnergy < CFG.MAX_OUTPUT_MAX_ENERGY
MaximumOutputEnergy = .(CFG.MAX_OUTPUT_MAX_ENERGY, MaximumOutputEnergy + CFG.MAX_OUTPUT_REGEN_PER_SECOND * Delta)
()
EnergyOrbHolder EnergyOrbHolder.Parent
() .() < EnergyDeniedUntil
EnergyOrbHolder.Position = EnergyOrbBasePosition + .(.(-, ), .(-, ))
EnergyOrbHolder.Position = EnergyOrbBasePosition
)
.(()
Player:(, )
()
)
()
PlayerGui =
Player:(
)
PlayerGui
Gui =
.()
Gui.Name =
Gui.IgnoreGuiInset =
Gui.ResetOnSpawn =
Gui.Parent =
PlayerGui
Frame =
.()
Frame.Size =
.(
,
)
Frame.BackgroundColor3 =
.(
,
,
)
Frame.BackgroundTransparency =
Frame.BorderSizePixel =
Frame.Parent =
Gui
Fade =
TweenService:(
Frame,
TweenInfo.(
,
.EasingStyle.Quad,
.EasingDirection.Out
),
{
BackgroundTransparency =
}
)
Fade:()
Debris:(
Gui,
)
()
CFG.AFTERIMAGES_ENABLED
()
#ActiveAfterimages
>= CFG.MAX_AFTERIMAGES
Oldest =
.(
ActiveAfterimages,
)
Oldest
Oldest.Parent
Oldest:()
Folder =
.()
Folder.Name =
Folder.Parent =
.(
ActiveAfterimages,
Folder
)
_, Object
(
Character:()
)
Object:()
Object.Name
~=
Object.Transparency <
Ghost =
.()
Ghost.Name =
Ghost.Size =
Object.Size
Ghost.CFrame =
Object.CFrame
Ghost.Anchored =
Ghost.CanCollide =
Ghost.CanTouch =
Ghost.CanQuery =
Ghost.Material =
.Material.Neon
Ghost.Color =
.(
,
,
)
Ghost.Transparency =
Ghost.Parent =
Folder
Fade =
TweenService:(
Ghost,
TweenInfo.(
CFG.AFTERIMAGE_LIFETIME,
.EasingStyle.Linear
),
{
Transparency =
}
)
Fade:()
.(
CFG.AFTERIMAGE_LIFETIME,
()
Index =
.(
ActiveAfterimages,
Folder
)
Index
.(
ActiveAfterimages,
Index
)
Folder
Folder.Parent
Folder:()
)
()
_, Folder
(
ActiveAfterimages
)
Folder
Folder.Parent
Folder:()
.(
ActiveAfterimages
)
(Name, DisplayOrder, MaxOpacity, Response)
{
Name = Name,
Order = DisplayOrder,
MaxOpacity = MaxOpacity,
Response = Response,
Gui = ,
Gradients = {},
Current = ,
Target = ,
}
BurstVignette = (, , CFG.VIGNETTE_MAX_OPACITY, )
LowHealthVignette = (, , CFG.LOW_HEALTH_VIGNETTE_OPACITY, )
(Layer)
PlayerGui = Player:()
PlayerGui
Layer.Gradients = {}
Gui = .()
Gui.Name = Layer.Name
Gui.IgnoreGuiInset =
Gui.ResetOnSpawn =
Gui.DisplayOrder = Layer.Order
Gui.ZIndexBehavior = .ZIndexBehavior.Sibling
Size = CFG.VIGNETTE_SIZE
Edges = {
{ Anchor = .(, ), Position = .(, ), Size = .(Size, ), Rotation = },
{ Anchor = .(, ), Position = .(, ), Size = .(Size, ), Rotation = },
{ Anchor = .(, ), Position = .(, ), Size = .(, Size), Rotation = },
{ Anchor = .(, ), Position = .(, ), Size = .(, Size), Rotation = },
}
_, Edge (Edges)
Frame = .()
Frame.BorderSizePixel =
Frame.BackgroundColor3 = CFG.VIGNETTE_COLOR
Frame.AnchorPoint = Edge.Anchor
Frame.Position = Edge.Position
Frame.Size = Edge.Size
Gradient = .()
Gradient.Rotation = Edge.Rotation
Gradient.Transparency = NumberSequence.()
Gradient.Parent = Frame
Frame.Parent = Gui
.(Layer.Gradients, Gradient)
Gui.Enabled =
Gui.Parent = PlayerGui
Layer.Gui = Gui
(Layer, Delta)
Layer.Current += (Layer.Target - Layer.Current) * ( - .(-Layer.Response * Delta))
Layer.Current < Layer.Target <=
Layer.Current =
Layer.Gui Layer.Gui.Parent
Layer.Gui.Enabled =
Layer.Gui Layer.Gui.Parent
(Layer)
Layer.Gui.Enabled =
EdgeTransparency = - Layer.MaxOpacity * .(Layer.Current, , )
Sequence = NumberSequence.({
NumberSequenceKeypoint.(, EdgeTransparency),
NumberSequenceKeypoint.(, ),
})
_, Gradient (Layer.Gradients)
Gradient.Transparency = Sequence
(Delta)
(BurstVignette, Delta)
(LowHealthVignette, Delta)
CameraProxy =
CameraStabilizerState =
CameraSettleUntil =
CameraLastRootPosition = .zero
CameraSmoothVelocity = .zero
()
CameraProxy
CameraProxy:()
CameraProxy =
(Delta)
CFG.CAMERA_STABILIZER_ENABLED
CurrentCamera = .CurrentCamera
CurrentCamera
CameraStabilizerState ~= ( CameraProxy CurrentCamera.CameraSubject ~= CameraProxy)
CameraStabilizerState =
()
Valid = ()
Wanted = Valid (ProjectionActive MaximumOutputActive Busy)
Wanted
CameraStabilizerState ==
Focus = RootPart.Position + .(, CFG.CAMERA_FOCUS_HEIGHT, )
CameraProxy = .()
CameraProxy.Name =
CameraProxy.Anchored =
CameraProxy.CanCollide =
CameraProxy.CanQuery =
CameraProxy.CanTouch =
CameraProxy.Transparency =
CameraProxy.Size = .(, , )
CameraProxy.Position = Focus
CameraProxy.Parent =
CameraLastRootPosition = RootPart.Position
CameraSmoothVelocity = .zero
CurrentCamera.CameraSubject = CameraProxy
CameraStabilizerState =
CameraStabilizerState ==
CameraStabilizerState =
CameraStabilizerState ==
CameraStabilizerState =
CameraSettleUntil = .() + CFG.CAMERA_SETTLE_TIME
CameraStabilizerState ==
Valid
CurrentCamera.CameraSubject = (Humanoid Humanoid.Parent) Humanoid
CameraStabilizerState =
()
RootPosition = RootPart.Position
RawVelocity = (RootPosition - CameraLastRootPosition) / .(Delta, / )
CameraLastRootPosition = RootPosition
CameraSmoothVelocity = CameraSmoothVelocity:(RawVelocity, - .(- * Delta))
Target = RootPosition + .(, CFG.CAMERA_FOCUS_HEIGHT, )
Follow = CFG.CAMERA_FOLLOW_RESPONSE * ((CameraStabilizerState == ) )
NewPosition = CameraProxy.Position + CameraSmoothVelocity * Delta
NewPosition = NewPosition:(Target, - .(-Follow * Delta))
(NewPosition - Target).Magnitude >
NewPosition = Target
CameraProxy.Position = NewPosition
CameraStabilizerState ==
((NewPosition - Target).Magnitude < .() >= CameraSettleUntil)
CurrentCamera.CameraSubject = Humanoid
CameraStabilizerState =
()
(() RunService:() )
(() RunService:() )
RunService:(, .RenderPriority.Camera.Value - , UpdateCameraStabilizer)
RunService:(, .RenderPriority.Last.Value, UpdateVignettes)
CriticalActive =
LowHealthActive =
LowHealthSound =
CriticalSound =
(SoundIdNumber, Volume, Name)
Sound = .()
Sound.Name = Name
Sound.SoundId = .. SoundIdNumber
Sound.Volume = Volume
Sound.Looped =
Sound.Parent = :()
Sound:()
Sound
(Sound)
Sound
(()
Sound:()
Sound:()
)
()
Health =
Humanoid Humanoid.Parent Humanoid.Health >
Health = Humanoid.Health
Health
LowHealthActive =
CriticalActive =
(LowHealthSound)
LowHealthSound =
(CriticalSound)
CriticalSound =
LowHealthVignette.Target =
LowHealthActive Health < CFG.LOW_HEALTH_ENTER
LowHealthActive =
LowHealthSound = (CFG.LOW_HEALTH_SOUND_ID, CFG.LOW_HEALTH_SOUND_VOLUME, )
LowHealthActive Health > CFG.LOW_HEALTH_EXIT
LowHealthActive =
(LowHealthSound)
LowHealthSound =
CriticalActive Health < CFG.CRITICAL_HEALTH_ENTER
CriticalActive =
CriticalSound = (CFG.CRITICAL_SOUND_ID, CFG.CRITICAL_SOUND_VOLUME, )
CriticalActive Health >= CFG.CRITICAL_HEALTH_EXIT
CriticalActive =
(CriticalSound)
CriticalSound =
LowHealthVignette.Target = LowHealthActive
RunService.Heartbeat:(UpdateLowHealth)
(Source, Out)
Out = Out {}
(Source) ==
_, Item (Source)
(Item, Out)
(Source) ==
Source >
.(Out, .. .(, Source))
(Source) ==
Line .(Source, )
Line = .(Line, , )
Digits = .(Line, )
Digits (Digits) (Digits) >
.(Out, .. Digits)
Out
(Source, Mode, Key)
Valid = (Source)
#Valid ==
Mode ==
Index = (ProjectionCycleIndices[Key] ) % #Valid +
ProjectionCycleIndices[Key] = Index
Valid[Index]
Mode ==
Valid[]
Valid[.(#Valid)]
(Id, Looped, Priority)
()
Animator = Humanoid:()
Animator
Animator = .()
Animator.Parent = Humanoid
Animation = .()
Animation.AnimationId = Id
Success, Result = (()
Animator:(Animation)
)
Success
(, Result)
Animation:()
Result.Looped = Looped
Result.Priority = Priority
Result, Animation
()
()
Id = (
{ CFG.PROJECTION_ANIMATION_ID, CFG.PROJECTION_ANIMATIONS.Main },
CFG.PROJECTION_ANIMATION_MODE,
)
Id
Track, Animation = (Id, , .AnimationPriority.Action)
Track
ProjectionAnimation = Animation
ProjectionAnimationTrack = Track
(FadeTime)
Track = ProjectionEventTrack
Animation = ProjectionEventAnimation
ProjectionEventTrack =
ProjectionEventAnimation =
Track
(()
Track:(FadeTime)
)
.(FadeTime + , ()
(()
Track:()
)
)
Animation
.(FadeTime + , ()
(()
Animation:()
)
)
(Name, FitDuration)
()
List = CFG.PROJECTION_EVENT_ANIMATIONS[Name]
List
Id = (List, CFG.PROJECTION_EVENT_MODE, .. Name)
Id
()
Track, Animation = (Id, , .AnimationPriority.Action2)
Track
ProjectionEventTrack = Track
ProjectionEventAnimation = Animation
Track:(, , )
CFG.PROJECTION_EVENT_FIT_TO_DURATION FitDuration FitDuration > Track.Length >
Track:(Track.Length / FitDuration)
()
()
ProjectionAnimationTrack
ProjectionAnimationTrack.Looped =
ProjectionAnimationTrack:()
ProjectionAnimationTrack:()
ProjectionAnimationTrack =
ProjectionAnimation
ProjectionAnimation:()
ProjectionAnimation =
(
MaxDistance
)
()
BestTarget =
BestScore =
.huge
CameraPosition =
Camera.CFrame.Position
CameraDirection =
Camera.CFrame.LookVector
_, OtherPlayer
(
Players:()
)
OtherPlayer ~= Player
OtherCharacter =
OtherPlayer.Character
OtherCharacter
OtherHumanoid =
OtherCharacter:
(
)
OtherRoot =
OtherCharacter:
(
)
OtherHumanoid
OtherRoot
OtherHumanoid.Health >
Offset =
OtherRoot.Position
- CameraPosition
Distance =
Offset.Magnitude
Distance <= MaxDistance
Distance >
Direction =
Offset.Unit
Dot =
CameraDirection:(
Direction
)
Dot >
Score =
- Dot
Score
< BestScore
BestScore =
Score
BestTarget =
OtherRoot
BestTarget
()
()
.(
,
,
-
)
MoveDirection =
Humanoid.MoveDirection
MoveDirection.Magnitude
>
Direction =
.(
MoveDirection.X,
,
MoveDirection.Z
)
Direction.Magnitude
>
Direction.Unit
Look =
RootPart.CFrame.LookVector
Direction =
.(
Look.X,
,
Look.Z
)
Direction.Magnitude
>
Direction.Unit
.(
,
,
-
)
(Side)
ProjectionActive ProjectionHolding LaneShiftActive
Side ~= - Side ~=
()
(CFG.ENERGY_COST_LANE_SHIFT)
Forward = ()
Forward.Magnitude < Forward = .(, , -)
Forward = Forward.Unit
Right = .(-Forward.Z, , Forward.X)
LaneShiftActive =
LaneShiftSide = Side
LaneShiftStartOffset = LaneShiftCurrentOffset
LaneShiftTargetOffset = LaneShiftStartOffset + Right * (CFG.LANE_SHIFT_DISTANCE * Side)
LaneShiftStartTime = .()
LaneShiftKey = (Side < )
((Side < ) , CFG.LANE_SHIFT_DURATION)
()
ProjectionActive
CircularProjectionActive
Target =
(
CFG.Z_TARGET_DISTANCE
)
Target
(CFG.ENERGY_COST_CIRCULAR)
CircularProjectionActive =
(, CFG.CIRCLE_TIME)
StartTime =
.()
StartPosition =
RootPart.Position
TargetPosition =
Target.Position
InitialDirection =
(
StartPosition
- TargetPosition
)
InitialDirection.Magnitude
<
InitialDirection =
.(
,
,
)
InitialDirection =
InitialDirection.Unit
StartAngle =
.(
InitialDirection.Z,
InitialDirection.X
)
LastAfterimage =
.()
.()
- StartTime
< CFG.CIRCLE_TIME
ProjectionActive
ProjectionHolding
()
Target
Target.Parent
Alpha =
(
.()
- StartTime
)
/
CFG.CIRCLE_TIME
Angle =
StartAngle
+
(
Alpha
* .pi
*
* CFG.CIRCLE_ROTATIONS
)
CurrentTarget =
Target.Position
Offset =
.(
.(Angle)
* CFG.CIRCLE_RADIUS,
,
.(Angle)
* CFG.CIRCLE_RADIUS
)
NewPosition =
CurrentTarget
+ Offset
RootPart.CFrame =
.(
NewPosition,
CurrentTarget
)
CFG.AFTERIMAGES_ENABLED
.()
- LastAfterimage
>= CFG.AFTERIMAGE_INTERVAL
LastAfterimage =
.()
()
RunService.RenderStepped:()
ProjectionActive
ProjectionHolding
()
CurrentPosition =
RootPart.Position
Center =
Target.Position
RadiusVector =
CurrentPosition
- Center
RadiusVector.Magnitude
>
Tangent =
.(
-RadiusVector.Z,
,
RadiusVector.X
).Unit
RootPart.CFrame =
.(
CurrentPosition,
CurrentPosition
+ Tangent
)
CircularProjectionActive =
(
Position,
Direction
)
Crystal =
.()
Crystal.Name =
Crystal.Size =
.(
,
,
)
Crystal.Material =
.Material.Glass
Crystal.Color =
.(
,
,
)
Crystal.Transparency =
Crystal.Anchored =
Crystal.CanCollide =
Crystal.CanTouch =
Crystal.CanQuery =
Crystal.CFrame =
.(
Position,
Position + Direction
)
Crystal.Parent =
Debris:(
Crystal,
)
()
ProjectionActive =
ProjectionSoundStart =
BurstVignette.Target =
ProjectionHolding =
ProjectionReleasing =
LaneShiftActive =
LaneShiftCurrentOffset = .zero
LaneShiftStartOffset = .zero
LaneShiftTargetOffset = .zero
LaneShiftSide =
CircularProjectionActive =
LaneShiftKey =
()
ProjectionConnection
ProjectionConnection:()
ProjectionConnection =
ProjectionInputConnection
ProjectionInputConnection:()
ProjectionInputConnection =
ProjectionDeathConnection
ProjectionDeathConnection:()
ProjectionDeathConnection =
Busy =
()
Busy
()
()
(CFG.ENERGY_COST_C_START, )
LastUse.C =
.()
Busy =
ProjectionActive =
ProjectionHolding =
()
()
ProjectionSoundStart = .()
ProjectionAnimationTrack
ProjectionAnimationTrack:(
,
,
)
()
()
.(()
Stages = {
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
{, },
}
SoundStart = .()
StageIndex =
NextSound = SoundStart + Stages[][]
SoundsPerSecond =
CSoundPlays =
ProjectionActive
now = .()
StageIndex < #Stages
now - SoundStart >= Stages[StageIndex + ][]
StageIndex +=
SoundsPerSecond = Stages[StageIndex][]
NextSound = now
now >= NextSound
VolumeFactor = .(CFG.C_SOUND_MIN_VOLUME_FACTOR, - CSoundPlays * CFG.C_SOUND_VOLUME_STEP)
CSoundPlays +=
(CFG.C_SOUND_ID, CFG.MOVEMENT_SOUND_VOLUME * VolumeFactor, , ())
NextSound = now + ( / SoundsPerSecond)
.()
)
StartPosition =
RootPart.Position
DistanceTravelled =
Speed =
CFG.PROJECTION_MIN_SPEED
LastCrystalPosition =
StartPosition
LastCrystalSpawn =
.()
LastAfterimage =
.()
StepInterval = / .(CFG.PROJECTION_STEP_FPS, )
StepAccumulator =
TimeSinceStep =
StepCounter =
BurstEnergy =
CritBoost = CriticalActive
i = ,
CFG.INITIAL_CRYSTALS
()
Direction =
()
Position =
LastCrystalPosition
+
Direction
*
CFG.CRYSTAL_SPACING
(Position
- StartPosition)
.Magnitude
>= CFG.PROJECTION_MAX_DISTANCE
LastCrystalPosition =
Position
(
Position,
Direction
)
ProjectionInputConnection =
UserInputService.InputEnded:(
(Input)
Input.KeyCode
==
.KeyCode.C
ProjectionHolding =
ProjectionActive ProjectionReleasing
ProjectionReleasing =
ProjectionReleaseStart = .()
ProjectionReleaseSpeed = Speed
ProjectionReleaseDirection = ()
)
ProjectionDeathConnection =
Humanoid.Died:(
()
ProjectionHolding =
()
)
ProjectionConnection =
RunService.RenderStepped:(
(Delta)
ProjectionActive
()
()
()
DoStep =
StepTime = Delta
StepCount =
CFG.PROJECTION_STEPPED
StepAccumulator += Delta
TimeSinceStep += Delta
StepCount = .(StepAccumulator / StepInterval)
StepCount >=
StepAccumulator -= StepCount * StepInterval
StepTime = TimeSinceStep
TimeSinceStep =
DoStep =
DoStep
StepCounter += StepCount
ProjectionReleasing
(CFG.ENERGY_COST_C_PER_SECOND * Delta)
MaximumOutputEnergy <=
ProjectionHolding =
ProjectionReleasing =
ProjectionReleaseStart = .()
ProjectionReleaseSpeed = Speed
ProjectionReleaseDirection = ()
ProjectionReleasing
ReleaseElapsed = .() - ProjectionReleaseStart
SpeedRatio = .(ProjectionReleaseSpeed / .(CFG.PROJECTION_MAX_SPEED, ), , )
DecelDuration = CFG.PROJECTION_DECEL_MAX_TIME
- (CFG.PROJECTION_DECEL_MAX_TIME - CFG.PROJECTION_DECEL_MIN_TIME) * SpeedRatio
ReleaseAlpha = .(ReleaseElapsed / DecelDuration, , )
EaseOut = - ( - ReleaseAlpha)^
CurrentSpeed = ProjectionReleaseSpeed * ( - EaseOut)
CurrentSpeed <= CFG.PROJECTION_MIN_SPEED + ReleaseAlpha >=
()
Direction = ProjectionReleaseDirection
Direction.Magnitude <
Direction = ()
Direction = Direction.Unit
BurstVignette.Target =
DoStep
FrameDistance = CurrentSpeed * StepTime
NewPosition = RootPart.Position + Direction * FrameDistance
RootPart.CFrame = .(NewPosition, NewPosition + Direction)
DistanceTravelled += FrameDistance
CFG.AFTERIMAGES_ENABLED .() - LastAfterimage >= CFG.AFTERIMAGE_INTERVAL
LastAfterimage = .()
()
Speed +=
CFG.PROJECTION_ACCELERATION
*
Delta
Speed =
.(
Speed,
CFG.PROJECTION_MIN_SPEED,
CFG.PROJECTION_MAX_SPEED
)
ProjectionAnimationTrack
AnimationSpeed = .(
Speed / .(CFG.PROJECTION_MIN_SPEED, ),
,
)
ProjectionAnimationTrack:(AnimationSpeed)
BurstEnergy = .(, BurstEnergy - Delta / CFG.PROJECTION_BURST_DECAY)
CritBoost = .(, CritBoost - Delta / CFG.CRIT_C_BOOST_DECAY)
BurstVignette.Target = BurstEnergy
DoStep
Every = .(CFG.PROJECTION_BURST_EVERY_STEPS, )
.(StepCounter / Every) > .((StepCounter - StepCount) / Every)
BurstEnergy =
BurstVignette.Target =
BurstFactor = + CFG.PROJECTION_BURST_BOOST * BurstEnergy * BurstEnergy
EnergyFraction = ()
EnergyFactor =
EnergyFraction < CFG.ENERGY_LOW_FRACTION
EnergyFactor = CFG.ENERGY_LOW_C_MIN_SPEED_FACTOR
+ ( - CFG.ENERGY_LOW_C_MIN_SPEED_FACTOR) * (EnergyFraction / CFG.ENERGY_LOW_FRACTION)
CritFactor = + CFG.CRIT_C_START_BOOST * CritBoost
Direction =
()
FrameDistance =
Speed
*
BurstFactor
*
EnergyFactor
*
CritFactor
*
StepTime
Remaining =
CFG.PROJECTION_MAX_DISTANCE
-
DistanceTravelled
Remaining <=
()
FrameDistance
> Remaining
FrameDistance =
Remaining
CircularProjectionActive
NewPosition = RootPart.Position + Direction * FrameDistance
LaneLean =
LanePitch =
LaneShiftActive
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."
]==]
Universal