CCCPaste

6209a84f1c149

-- 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)