-- StarterPlayer/StarterPlayerScripts/RootMotionClient.lua local Players = game:GetService("Players") local RunService = game:GetService("RunService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local UserInputService = game:GetService("UserInputService") local AbilityEvent = ReplicatedStorage:WaitForChild("RootMotionAbility") local player = Players.LocalPlayer local AbilityConfig = { DashSlash = { Duration = 0.4, Range = 18, }, } local Animations = { DashSlash = "", } local CurrentAbility = nil local function easeInOutQuad(t) if t <= 0 then return 0 end if t >= 1 then return 1 end if t < 0.5 then return 2 * t * t else local u = -2 * t + 2 return 1 - (u * u) / 2 end end local function getCharacterParts() local character = player.Character or player.CharacterAdded:Wait() local root = character:FindFirstChild("HumanoidRootPart") local humanoid = character:FindFirstChildOfClass("Humanoid") local animator = humanoid and humanoid:FindFirstChildOfClass("Animator") return character, root, humanoid, animator end local function playAbilityAnimation(abilityId) local animId = Animations[abilityId] if not animId or animId == "" then print("Client no animation id for", abilityId, "movement will still play") return end local _, _, humanoid, animator = getCharacterParts() if not humanoid then print("Client no humanoid for animation") return end local targetAnimator = animator or humanoid:FindFirstChildOfClass("Animator") if not targetAnimator then print("Client no animator for animation") return end local animation = Instance.new("Animation") animation.AnimationId = animId local track = targetAnimator:LoadAnimation(animation) track:Play() end local function onServerStart(abilityId, startTime, startCFrame) local config = AbilityConfig[abilityId] if not config then print("Client unknown ability", abilityId) return end local _, root = getCharacterParts() if not root then print("Client no root to start ability") return end if CurrentAbility then print("Client ability already active, ignoring", abilityId) return end CurrentAbility = { AbilityId = abilityId, StartTime = startTime, StartCFrame = startCFrame, Facing = startCFrame.LookVector, LastAlpha = 0, } print("Client ability start", abilityId, "at", startTime) playAbilityAnimation(abilityId) end AbilityEvent.OnClientEvent:Connect(function(action, abilityId, startTime, startCFrame) if action == "Start" then onServerStart(abilityId, startTime, startCFrame) end end) UserInputService.InputBegan:Connect(function(input, gameProcessed) if gameProcessed then return end if input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode == Enum.KeyCode.Q then if CurrentAbility then print("Client ability already active, cannot request") return end print("Client requesting DashSlash") AbilityEvent:FireServer("Request", "DashSlash") end end) RunService.RenderStepped:Connect(function() local state = CurrentAbility if not state then return end local config = AbilityConfig[state.AbilityId] if not config then print("Client missing config during ability, cancelling") CurrentAbility = nil return end local _, root = getCharacterParts() if not root then print("Client lost root during ability, cancelling") CurrentAbility = nil return end local now = os.clock() local alpha = (now - state.StartTime) / config.Duration if alpha >= 1 then print("Client ability finished", state.AbilityId) CurrentAbility = nil return end local distNow = config.Range * easeInOutQuad(alpha) local distLast = config.Range * easeInOutQuad(state.LastAlpha) state.LastAlpha = alpha local target = state.StartCFrame + state.Facing * distNow root.CFrame = root.CFrame:Lerp(target, 0.3) end)