Загрузка данных


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

-- ================= НАСТРОЙКИ =================
local Settings = {
	ToolName = "Самокат",
	HoverHeight = 0.5,  -- высота центра самоката над землёй (как в клиентском скрипте)
	SeatHeight = 2.0,   -- высота невидимого сиденья (если ноги в деке или парят, меняй это)
	BarspinTime = 0.45,
	TailwhipTime = 0.6,
	HandsOnBars = true, -- руки на руле (только R15)
}
-- =============================================

local DARK = Color3.fromRGB(35, 35, 40)
local ACCENT = Color3.fromRGB(230, 60, 60)

local trickEvent = Instance.new("RemoteEvent")
trickEvent.Name = "ScooterTrick"
trickEvent.Parent = ReplicatedStorage

local rides = {}

local function mk(parent, name, size, color)
	local p = Instance.new("Part")
	p.Name = name
	p.Size = size
	p.Color = color
	p.Material = Enum.Material.Metal
	p.CanCollide = false
	p.CanQuery = false
	p.CanTouch = false
	p.Massless = true
	p.TopSurface = Enum.SurfaceType.Smooth
	p.BottomSurface = Enum.SurfaceType.Smooth
	p.Parent = parent
	return p
end

local function weld(a, b)
	local w = Instance.new("Weld")
	w.Part0 = a
	w.Part1 = b
	w.C0 = a.CFrame:ToObjectSpace(b.CFrame)
	w.Parent = b
	return w
end

local function build(cf)
	local model = Instance.new("Model")
	model.Name = "ScooterRide"

	local core = mk(model, "Core", Vector3.new(1.2, 0.4, 3.2), DARK)
	core.Transparency = 1
	core.CanCollide = true
	core.Massless = false
	core.CustomPhysicalProperties = PhysicalProperties.new(1, 0, 0, 1, 1)
	core.CFrame = cf

	local deck = mk(model, "Deck", Vector3.new(1.4, 0.25, 3.6), DARK)
	deck.CFrame = cf * CFrame.new(0, 0.05, 0)

	local wheelF = mk(model, "WheelF", Vector3.new(0.25, 0.7, 0.7), ACCENT)
	wheelF.Shape = Enum.PartType.Cylinder
	wheelF.CFrame = cf * CFrame.new(0, -0.15, -1.55)

	local wheelB = mk(model, "WheelB", Vector3.new(0.25, 0.7, 0.7), ACCENT)
	wheelB.Shape = Enum.PartType.Cylinder
	wheelB.CFrame = cf * CFrame.new(0, -0.15, 1.55)

	local column = mk(model, "Column", Vector3.new(0.25, 2.9, 0.25), DARK)
	column.CFrame = cf * CFrame.new(0, 1.65, -1.35)

	local bar = mk(model, "Handlebar", Vector3.new(1.5, 0.22, 0.22), ACCENT)
	bar.CFrame = cf * CFrame.new(0, 3.05, -1.35)

	-- точки, куда кладутся руки
	local gripL = Instance.new("Attachment")
	gripL.Name = "GripL"
	gripL.Position = Vector3.new(-0.6, 0, 0)
	gripL.Parent = bar
	local gripR = Instance.new("Attachment")
	gripR.Name = "GripR"
	gripR.Position = Vector3.new(0.6, 0, 0)
	gripR.Parent = bar

	local seat = Instance.new("VehicleSeat")
	seat.Name = "Seat"
	seat.Size = Vector3.new(1, 0.3, 1.2)
	seat.Transparency = 1
	seat.CanCollide = false
	seat.CanQuery = false
	seat.CanTouch = false
	seat.Massless = true
	seat.MaxSpeed = 0
	seat.Torque = 0
	seat.TurnSpeed = 0
	seat.HeadsUpDisplay = false
	seat.CFrame = cf * CFrame.new(0, Settings.SeatHeight, 0)
	seat:SetAttribute("IsScooter", true)
	seat.Parent = model

	local deckWeld = weld(core, deck)
	local wfWeld = weld(deck, wheelF)
	local wbWeld = weld(deck, wheelB)
	local barWeld = weld(core, column)
	weld(column, bar)
	weld(core, seat)

	model.PrimaryPart = core

	return {
		model = model, core = core, seat = seat, bar = bar,
		deckWeld = deckWeld, deckBase = deckWeld.C0,
		barWeld = barWeld, barBase = barWeld.C0,
		wfWeld = wfWeld, wfBase = wfWeld.C0,
		wbWeld = wbWeld, wbBase = wbWeld.C0,
		wheelAngle = 0, busy = {}, iks = {},
	}
end

local function stopRide(plr)
	local r = rides[plr]
	if not r then return end
	rides[plr] = nil
	for _, ik in ipairs(r.iks) do
		ik:Destroy()
	end
	if r.model then r.model:Destroy() end
end

-- руки на руле (IK, только R15)
local function addHands(r, hum, char)
	if not Settings.HandsOnBars then return end
	if hum.RigType ~= Enum.HumanoidRigType.R15 then return end
	local defs = {
		{ "RightUpperArm", "RightHand", "GripR" },
		{ "LeftUpperArm", "LeftHand", "GripL" },
	}
	for _, d in ipairs(defs) do
		local chainRoot = char:FindFirstChild(d[1])
		local effector = char:FindFirstChild(d[2])
		local target = r.bar:FindFirstChild(d[3])
		if chainRoot and effector and target then
			local ik = Instance.new("IKControl")
			ik.Name = "ScooterIK_" .. d[3]
			ik.ChainRoot = chainRoot
			ik.EndEffector = effector
			ik.Target = target
			ik.Type = Enum.IKControlType.Position
			ik.Weight = 1
			ik.SmoothTime = 0.05
			ik.Parent = hum
			table.insert(r.iks, ik)
		end
	end
end

local function startRide(plr, tool)
	stopRide(plr)
	local char = tool.Parent
	if not char or not char:IsA("Model") then return end
	local hum = char:FindFirstChildOfClass("Humanoid")
	local root = char:FindFirstChild("HumanoidRootPart")
	if not hum or not root or hum.Health <= 0 then return end

	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = { char }
	local hit = workspace:Raycast(root.Position, Vector3.new(0, -30, 0), params)
	local groundY = hit and hit.Position.Y or (root.Position.Y - 3)

	local look = root.CFrame.LookVector
	local flat = Vector3.new(look.X, 0, look.Z)
	if flat.Magnitude < 0.01 then flat = Vector3.new(0, 0, -1) end
	flat = flat.Unit

	local pos = Vector3.new(root.Position.X, groundY + Settings.HoverHeight + 0.1, root.Position.Z)
	local cf = CFrame.lookAt(pos, pos + flat)

	local r = build(cf)
	r.model.Parent = workspace
	rides[plr] = r

	pcall(function() r.core:SetNetworkOwner(plr) end)
	r.seat:Sit(hum)
	addHands(r, hum, char)
end

-- визуальные трюки (на сервере, чтобы видели все)
local function animate(duration, setter)
	local t = 0
	while t < duration do
		t += RunService.Heartbeat:Wait()
		local a = math.clamp(t / duration, 0, 1)
		a = a * a * (3 - 2 * a)
		setter(a * 2 * math.pi)
	end
	setter(0)
end

trickEvent.OnServerEvent:Connect(function(plr, name)
	local r = rides[plr]
	if not r or r.busy[name] then return end
	if name == "Barspin" then
		r.busy[name] = true
		task.spawn(function()
			animate(Settings.BarspinTime, function(a)
				r.barWeld.C0 = r.barBase * CFrame.Angles(0, a, 0)
			end)
			r.busy[name] = nil
		end)
	elseif name == "Tailwhip" then
		r.busy[name] = true
		task.spawn(function()
			animate(Settings.TailwhipTime, function(a)
				r.deckWeld.C0 = r.deckBase * CFrame.Angles(0, a, 0)
			end)
			r.busy[name] = nil
		end)
	end
end)

-- вращение колёс по скорости
RunService.Heartbeat:Connect(function(dt)
	for _, r in pairs(rides) do
		local core = r.core
		if core and core.Parent then
			local speed = core.AssemblyLinearVelocity:Dot(core.CFrame.LookVector)
			r.wheelAngle += speed / 0.35 * dt
			local rot = CFrame.Angles(-r.wheelAngle, 0, 0)
			r.wfWeld.C0 = r.wfBase * rot
			r.wbWeld.C0 = r.wbBase * rot
		end
	end
end)

-- предмет "Самокат"
local function makeTool()
	local tool = Instance.new("Tool")
	tool.Name = Settings.ToolName
	tool.RequiresHandle = true
	tool.CanBeDropped = false
	tool.ToolTip = "W/S газ, A/D поворот, Пробел прыжок, V вили, Q/E/R/T трюки, C слезть"

	local handle = Instance.new("Part")
	handle.Name = "Handle"
	handle.Size = Vector3.new(0.4, 0.4, 0.4)
	handle.Transparency = 1
	handle.CanCollide = false
	handle.Massless = true
	handle.Parent = tool
	return tool
end

local function giveTool(plr)
	local bp = plr:WaitForChild("Backpack")
	task.wait(0.2)
	if bp:FindFirstChild(Settings.ToolName) or (plr.Character and plr.Character:FindFirstChild(Settings.ToolName)) then
		return
	end
	local tool = makeTool()
	tool.Equipped:Connect(function() startRide(plr, tool) end)
	tool.Unequipped:Connect(function() stopRide(plr) end)
	tool.Parent = bp
end

local function onPlayer(plr)
	plr.CharacterAdded:Connect(function(char)
		stopRide(plr)
		giveTool(plr)
		local hum = char:WaitForChild("Humanoid")
		hum.Died:Connect(function() stopRide(plr) end)
	end)
	plr.CharacterRemoving:Connect(function() stopRide(plr) end)
	if plr.Character then task.spawn(giveTool, plr) end
end

Players.PlayerAdded:Connect(onPlayer)
for _, plr in ipairs(Players:GetPlayers()) do onPlayer(plr) end
Players.PlayerRemoving:Connect(function(plr) stopRide(plr) end)