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



-- CatPlacementClient
-- Разместить в StarterPlayer/StarterPlayerScripts как LocalScript

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

local player = Players.LocalPlayer
local remote = ReplicatedStorage:WaitForChild("PlaceCatEvent")

local CONFIG = {
    ROTATION_STEP = 15,
    MAX_DISTANCE = 35,
    DEBUG = true,
}

local function log(...)
    if CONFIG.DEBUG then
        print("[CatPlacementClient]", ...)
    end
end

local placing = false
local equippedTool = nil
local preview = nil
local rotationY = 0
local targetFloor = nil
local targetPosition = nil
local canPlace = false
local lastTouchPosition = nil
local renderConnection = nil
local characterConnections = {}

local gui
local statusLabel

local function getCharacter()
    return player.Character
end

local function getOwnedFloor()
    local baseName = player:GetAttribute("BaseName")
    if typeof(baseName) ~= "string" then
        return nil
    end

    local base = Workspace:FindFirstChild(baseName)
    if not base or not base:IsA("Model") then
        return nil
    end

    if base:GetAttribute("OwnerUserId") ~= player.UserId then
        return nil
    end

    local floor = base:FindFirstChild("Floor")
    if floor and floor:IsA("BasePart") then
        return floor
    end

    return nil
end

local function getRoot()
    local character = getCharacter()
    return character and character:FindFirstChild("HumanoidRootPart")
end

local function setPreviewColor(valid)
    if not preview then
        return
    end

    local color = valid
        and Color3.fromRGB(70, 255, 100)
        or Color3.fromRGB(255, 65, 65)

    for _, obj in ipairs(preview:GetDescendants()) do
        if obj:IsA("BasePart") then
            obj.Color = color
            obj.Transparency = 0.55
            obj.Anchored = true
            obj.CanCollide = false
            obj.CanTouch = false
            obj.CanQuery = false
        end
    end
end

local function createPreview(tool)
    if preview then
        preview:Destroy()
        preview = nil
    end

    local handle = tool:FindFirstChild("Handle")
    if not handle or not handle:IsA("BasePart") then
        warn("[CatPlacementClient] У Tool отсутствует Handle:", tool.Name)
        return false
    end

    local clone = tool:Clone()
    clone.Name = "CatPreview"

    -- Удаляем скрипты и интерактивные объекты из копии.
    for _, obj in ipairs(clone:GetDescendants()) do
        if obj:IsA("Script")
            or obj:IsA("LocalScript")
            or obj:IsA("ModuleScript")
            or obj:IsA("ProximityPrompt")
            or obj:IsA("ClickDetector")
            or obj:IsA("RemoteEvent")
            or obj:IsA("RemoteFunction") then
            obj:Destroy()
        end
    end

    local model = Instance.new("Model")
    model.Name = "CatPreview"

    -- Переносим содержимое клона в отдельную Model.
    for _, child in ipairs(clone:GetChildren()) do
        child.Parent = model
    end
    clone:Destroy()

    local previewHandle = model:FindFirstChild("Handle", true)
    if not previewHandle or not previewHandle:IsA("BasePart") then
        model:Destroy()
        warn("[CatPlacementClient] Не удалось создать предпросмотр:", tool.Name)
        return false
    end

    model.PrimaryPart = previewHandle

    for _, obj in ipairs(model:GetDescendants()) do
        if obj:IsA("BasePart") then
            obj.Anchored = true
            obj.CanCollide = false
            obj.CanTouch = false
            obj.CanQuery = false
            obj.Transparency = 0.55
        end
    end

    model.Parent = Workspace
    preview = model
    setPreviewColor(false)

    return true
end

local function stopPlacement(unequip)
    placing = false
    canPlace = false
    targetFloor = nil
    targetPosition = nil
    lastTouchPosition = nil

    if renderConnection then
        renderConnection:Disconnect()
        renderConnection = nil
    end

    if preview then
        preview:Destroy()
        preview = nil
    end

    if gui then
        gui.Enabled = false
    end

    if unequip then
        local character = getCharacter()
        local humanoid = character and character:FindFirstChildOfClass("Humanoid")
        if humanoid then
            humanoid:UnequipTools()
        end
    end

    equippedTool = nil
end

local function rotate(amount)
    if not placing then
        return
    end

    rotationY = (rotationY + amount) % 360
    log("Угол поворота:", rotationY)
end

local function updateStatus(text)
    if statusLabel then
        statusLabel.Text = text
    end
end

local function createGui()
    local playerGui = player:WaitForChild("PlayerGui")

    local existing = playerGui:FindFirstChild("CatPlacementGui")
    if existing then
        existing:Destroy()
    end

    gui = Instance.new("ScreenGui")
    gui.Name = "CatPlacementGui"
    gui.ResetOnSpawn = false
    gui.Enabled = false
    gui.Parent = playerGui

    statusLabel = Instance.new("TextLabel")
    statusLabel.Name = "Status"
    statusLabel.AnchorPoint = Vector2.new(0.5, 0)
    statusLabel.Position = UDim2.new(0.5, 0, 0.06, 0)
    statusLabel.Size = UDim2.new(0.8, 0, 0, 38)
    statusLabel.BackgroundColor3 = Color3.fromRGB(25, 25, 25)
    statusLabel.BackgroundTransparency = 0.2
    statusLabel.TextColor3 = Color3.new(1, 1, 1)
    statusLabel.Font = Enum.Font.GothamBold
    statusLabel.TextScaled = true
    statusLabel.Text = "Выбери место для кота"
    statusLabel.Parent = gui

    local function makeButton(name, text, position, size, callback)
        local button = Instance.new("TextButton")
        button.Name = name
        button.AnchorPoint = Vector2.new(0.5, 0.5)
        button.Position = position
        button.Size = size
        button.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
        button.BackgroundTransparency = 0.12
        button.TextColor3 = Color3.new(1, 1, 1)
        button.Font = Enum.Font.GothamBold
        button.TextScaled = true
        button.Text = text
        button.Parent = gui
        button.Activated:Connect(callback)
        return button
    end

    local touch = UserInputService.TouchEnabled

    if touch then
        makeButton(
            "RotateLeft", "↺ 15°",
            UDim2.new(1, -175, 1, -170),
            UDim2.fromOffset(76, 46),
            function() rotate(-CONFIG.ROTATION_STEP) end
        )

        makeButton(
            "RotateRight", "↻ 15°",
            UDim2.new(1, -85, 1, -170),
            UDim2.fromOffset(76, 46),
            function() rotate(CONFIG.ROTATION_STEP) end
        )

        makeButton(
            "Place", "ПОСТАВИТЬ",
            UDim2.new(1, -130, 1, -105),
            UDim2.fromOffset(165, 48),
            function()
                if placing and canPlace and targetFloor and targetPosition then
                    remote:FireServer(targetFloor, targetPosition, rotationY)
                    updateStatus("Отправлен запрос на размещение…")
                else
                    updateStatus("Нельзя поставить кота здесь")
                end
            end
        )

        makeButton(
            "Cancel", "ОТМЕНА",
            UDim2.new(1, -130, 1, -48),
            UDim2.fromOffset(165, 42),
            function() stopPlacement(true) end
        )
    else
        local hint = Instance.new("TextLabel")
        hint.Name = "PCControls"
        hint.AnchorPoint = Vector2.new(0.5, 1)
        hint.Position = UDim2.new(0.5, 0, 1, -20)
        hint.Size = UDim2.new(0.9, 0, 0, 34)
        hint.BackgroundTransparency = 1
        hint.TextColor3 = Color3.new(1, 1, 1)
        hint.TextStrokeTransparency = 0.3
        hint.Font = Enum.Font.GothamBold
        hint.TextScaled = true
        hint.Text = "ЛКМ — поставить   R/T — поворот   Esc — отмена"
        hint.Parent = gui
    end
end

createGui()

local function beginPlacement(tool)
    if placing then
        stopPlacement(false)
    end

    if not tool or not tool:IsA("Tool") then
        return
    end

    local floor = getOwnedFloor()
    if not floor then
        warn("[CatPlacementClient] Не найдена собственная база или Floor. Проверь BaseName и OwnerUserId.")
        return
    end

    if not createPreview(tool) then
        return
    end

    equippedTool = tool
    rotationY = 0
    placing = true
    gui.Enabled = true
    updateStatus("Зелёный — можно поставить, красный — нельзя")

    if renderConnection then
        renderConnection:Disconnect()
    end

    renderConnection = RunService.RenderStepped:Connect(function()
        if not placing or not preview or not equippedTool then
            return
        end

        local character = getCharacter()
        local root = getRoot()
        local camera = Workspace.CurrentCamera

        if not character or not root or not camera
            or not equippedTool.Parent
            or equippedTool.Parent ~= character then
            canPlace = false
            setPreviewColor(false)
            updateStatus("Кот должен быть экипирован")
            return
        end

        local screenPosition
        if UserInputService.TouchEnabled then
            screenPosition = lastTouchPosition
        else
            screenPosition = UserInputService:GetMouseLocation()
        end

        if not screenPosition then
            canPlace = false
            setPreviewColor(false)
            return
        end

        local ray = camera:ScreenPointToRay(screenPosition.X, screenPosition.Y)
        local params = RaycastParams.new()
        params.FilterType = Enum.RaycastFilterType.Exclude
        params.FilterDescendantsInstances = {character, preview}
        params.IgnoreWater = true

        local result = Workspace:Raycast(ray.Origin, ray.Direction * 500, params)
        local ownFloor = getOwnedFloor()

        canPlace = false
        targetFloor = nil
        targetPosition = nil

        if result and ownFloor and result.Instance == ownFloor then
            local distance = (root.Position - result.Position).Magnitude
            if distance <= CONFIG.MAX_DISTANCE then
                local localPos = ownFloor.CFrame:PointToObjectSpace(result.Position)
                local inside = math.abs(localPos.X) <= ownFloor.Size.X / 2
                    and math.abs(localPos.Z) <= ownFloor.Size.Z / 2

                if inside then
                    targetFloor = ownFloor
                    targetPosition = result.Position
                    canPlace = true

                    preview:PivotTo(
                        CFrame.new(result.Position)
                            * CFrame.Angles(0, math.rad(rotationY), 0)
                    )

                    -- Поднимаем предпросмотр так, чтобы его низ совпал с верхом пола.
                    local boxCFrame, boxSize = preview:GetBoundingBox()
                    local floorTop = ownFloor.Position.Y + ownFloor.Size.Y / 2
                    local modelBottom = boxCFrame.Position.Y - boxSize.Y / 2
                    local shiftY = floorTop - modelBottom

                    preview:PivotTo(
                        CFrame.new(result.Position.X, result.Position.Y + shiftY, result.Position.Z)
                            * CFrame.Angles(0, math.rad(rotationY), 0)
                    )
                end
            end
        end

        setPreviewColor(canPlace)

        if canPlace then
            updateStatus("Можно поставить кота")
        elseif not ownFloor then
            updateStatus("База не назначена — проверь BaseManager")
        else
            updateStatus("Наведи на пол своей базы в пределах досягаемости")
        end
    end)
end

local function watchCharacter(character)
    for _, connection in ipairs(characterConnections) do
        connection:Disconnect()
    end
    table.clear(characterConnections)

    local function watchTool(child)
        if not child:IsA("Tool") then
            return
        end

        local connection = child.Equipped:Connect(function()
            task.defer(function()
                if child.Parent == character then
                    beginPlacement(child)
                end
            end)
        end)
        table.insert(characterConnections, connection)

        table.insert(characterConnections, child.Unequipped:Connect(function()
            if equippedTool == child then
                stopPlacement(false)
            end
        end))
    end

    for _, child in ipairs(character:GetChildren()) do
        watchTool(child)
    end

    table.insert(characterConnections, character.ChildAdded:Connect(watchTool))
    table.insert(characterConnections, character.ChildRemoved:Connect(function(child)
        if child == equippedTool then
            stopPlacement(false)
        end
    end))
end

if player.Character then
    watchCharacter(player.Character)
end
player.CharacterAdded:Connect(watchCharacter)

UserInputService.InputBegan:Connect(function(input, processed)
    if processed or not placing then
        return
    end

    if input.KeyCode == Enum.KeyCode.R then
        rotate(CONFIG.ROTATION_STEP)
    elseif input.KeyCode == Enum.KeyCode.T then
        rotate(-CONFIG.ROTATION_STEP)
    elseif input.KeyCode == Enum.KeyCode.Escape then
        stopPlacement(true)
    elseif input.UserInputType == Enum.UserInputType.MouseButton1
        and not UserInputService.TouchEnabled then
        if canPlace and targetFloor and targetPosition then
            remote:FireServer(targetFloor, targetPosition, rotationY)
            updateStatus("Отправлен запрос на размещение…")
        else
            updateStatus("Нельзя поставить кота здесь")
        end
    elseif input.UserInputType == Enum.UserInputType.Touch then
        lastTouchPosition = input.Position
    end
end)

UserInputService.InputChanged:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.Touch then
        lastTouchPosition = input.Position
    end
end)

remote.OnClientEvent:Connect(function(action, message)
    -- На случай будущих серверных ответов; размещение подтверждает сервер.
    if action == "Result" and typeof(message) == "string" then
        updateStatus(message)
    end
end)

player:GetAttributeChangedSignal("BaseName"):Connect(function()
    if placing and not getOwnedFloor() then
        stopPlacement(true)
        warn("[CatPlacementClient] Назначенная база стала недоступна.")
    end
end)

log("Клиент запущен. TouchEnabled =", UserInputService.TouchEnabled)