-- ServerScriptService/RootMotionServer.lua
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AbilityEvent = ReplicatedStorage:WaitForChild("RootMotionAbility")
local AbilityConfig = {
DashSlash = {
Duration = 0.4,
Range = 18,
HitRadius = 5,
Damage = 25,
},
}
local Active = {}
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 stopAbility(player, reason)
if Active[player] then
print("Server ability end", player.Name, Active[player].AbilityId, reason or "finished")
Active[player] = nil
end
end
local function startAbility(player, abilityId)
local config = AbilityConfig[abilityId]
if not config then
print("Server unknown ability", abilityId)
return
end
if Active[player] then
print("Server ability rejected, already active", player.Name)
return
end
local character = player.Character
if not character then
print("Server no character", player.Name)
return
end
local root = character:FindFirstChild("HumanoidRootPart")
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not root or not humanoid or humanoid.Health <= 0 then
print("Server invalid character state", player.Name)
return
end
local startTime = os.clock()
local startCFrame = root.CFrame
local facing = startCFrame.LookVector
Active[player] = {
AbilityId = abilityId,
StartTime = startTime,
LastAlpha = 0,
StartCFrame = startCFrame,
Facing = facing,
HitDone = false,
}
print("Server ability start", player.Name, abilityId, "at", startTime)
AbilityEvent:FireClient(player, "Start", abilityId, startTime, startCFrame)
end
AbilityEvent.OnServerEvent:Connect(function(player, action, abilityId)
if action == "Request" then
print("Server request received", player.Name, abilityId)
startAbility(player, abilityId)
end
end)
Players.PlayerRemoving:Connect(function(player)
if Active[player] then
Active[player] = nil
end
end)
RunService.Heartbeat:Connect(function()
local now = os.clock()
for player, state in pairs(Active) do
local config = AbilityConfig[state.AbilityId]
if not config then
stopAbility(player, "missing_config")
continue
end
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not root or not humanoid or humanoid.Health <= 0 then
stopAbility(player, "no_root_or_dead")
continue
end
local alpha = (now - state.StartTime) / config.Duration
if alpha >= 1 then
stopAbility(player, "duration_complete")
continue
end
local distNow = config.Range * easeInOutQuad(alpha)
local distLast = config.Range * easeInOutQuad(state.LastAlpha)
state.LastAlpha = alpha
local step = distNow - distLast
if step > 0 then
local target = state.StartCFrame + state.Facing * distNow
root.CFrame = target
end
if not state.HitDone then
local origin = root.Position
for _, other in ipairs(Players:GetPlayers()) do
if other ~= player then
local och = other.Character
local oroot = och and och:FindFirstChild("HumanoidRootPart")
local ohum = och and och:FindFirstChildOfClass("Humanoid")
if oroot and ohum and ohum.Health > 0 then
if (oroot.Position - origin).Magnitude <= config.HitRadius then
print("Server hit", player.Name, "->", other.Name, "for", config.Damage)
ohum:TakeDamage(config.Damage)
state.HitDone = true
break
end
end
end
end
end
end
end)