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


-- CatPlacementServer
-- Помести в ServerScriptService

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local catTools = ServerStorage:WaitForChild("CatTools")

local MAX_CATS = 16
local PLACE_DISTANCE = 35
local PICKUP_DISTANCE = 10
local INCOME_INTERVAL = 1

-- Создаём RemoteEvent автоматически
local placeEvent = ReplicatedStorage:FindFirstChild("PlaceCatEvent")

if not placeEvent then
    placeEvent = Instance.new("RemoteEvent")
    placeEvent.Name = "PlaceCatEvent"
    placeEvent.Parent = ReplicatedStorage
end

-- Папка установленных котов
local placedFolder = Workspace:FindFirstChild("PlacedCats")

if not placedFolder then
    placedFolder = Instance.new("Folder")
    placedFolder.Name = "PlacedCats"
    placedFolder.Parent = Workspace
end

-- Деньги игрока
local function setupPlayer(player)
    local leaderstats = player:FindFirstChild("leaderstats")

    if not leaderstats then
        leaderstats = Instance.new("Folder")
        leaderstats.Name = "leaderstats"
        leaderstats.Parent = player
    end

    local coins = leaderstats:FindFirstChild("Coins")

    if not coins then
        coins = Instance.new("NumberValue")
        coins.Name = "Coins"
        coins.Value = 0
        coins.Parent = leaderstats
    end
end

Players.PlayerAdded:Connect(setupPlayer)

for _, player in ipairs(Players:GetPlayers()) do
    setupPlayer(player)
end

-- Находим базу игрока
local function getOwnedBase(player)
    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 not floor or not floor:IsA("BasePart") then
        return nil
    end

    return base, floor
end

local function countCats(player)
    local count = 0

    for _, cat in ipairs(placedFolder:GetChildren()) do
        if cat:IsA("Model")
            and cat:GetAttribute("OwnerUserId") == player.UserId then
            count += 1
        end
    end

    return count
end

local function getEquippedCat(player)
    local character = player.Character

    if not character then
        return nil
    end

    for _, item in ipairs(character:GetChildren()) do
        if item:IsA("Tool")
            and catTools:FindFirstChild(item.Name)
            and item:GetAttribute("Income") ~= nil
            and item:GetAttribute("Rarity") ~= nil then
            return item
        end
    end

    return nil
end

-- Проверяем, что Tool является одним из наших котов
local function getValidTemplate(tool)
    local template = catTools:FindFirstChild(tool.Name)

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

    local income = template:GetAttribute("Income")
    local rarity = template:GetAttribute("Rarity")
    local handle = template:FindFirstChild("Handle")

    if typeof(income) ~= "number" or income < 0 then
        return nil
    end

    if typeof(rarity) ~= "string" then
        return nil
    end

    if not handle or not handle:IsA("BasePart") then
        return nil
    end

    return template
end

-- Создаём модель кота из деталей Tool
local function createCatModel(template)
    local model = Instance.new("Model")
    model.Name = template.Name

    for _, child in ipairs(template:GetChildren()) do
        if child:IsA("BasePart") then
            local part = child:Clone()
            part.Anchored = true
            part.CanCollide = false
            part.CanTouch = false
            part.CanQuery = false
            part.Parent = model
        end
    end

    local handle = model:FindFirstChild("Handle")

    if not handle or not handle:IsA("BasePart") then
        model:Destroy()
        return nil
    end

    model.PrimaryPart = handle

    return model
end

-- Ставим кота на поверхность, не погружая его в пол
local function positionModel(model, position, yaw, floor)
    model:PivotTo(
        CFrame.new(position) * CFrame.Angles(0, yaw, 0)
    )

    local boxCFrame, boxSize = model:GetBoundingBox()
    local bottomY = boxCFrame.Position.Y - boxSize.Y / 2
    local floorTopY = floor.Position.Y + floor.Size.Y / 2
    local correction = floorTopY - bottomY

    model:PivotTo(model:GetPivot() + Vector3.new(0, correction, 0))
end

-- Доход над котом
local function createIncomeLabel(model, income)
    local handle = model.PrimaryPart
    if not handle then
        return
    end

    local gui = Instance.new("BillboardGui")
    gui.Name = "IncomeGui"
    gui.Adornee = handle
    gui.Size = UDim2.fromOffset(110, 26)
    gui.StudsOffsetWorldSpace = Vector3.new(0, 2.5, 0)
    gui.AlwaysOnTop = true
    gui.MaxDistance = 100
    gui.Parent = model

    local label = Instance.new("TextLabel")
    label.Name = "IncomeText"
    label.Size = UDim2.fromScale(1, 1)
    label.BackgroundTransparency = 1
    label.Text = "+" .. tostring(income) .. " / сек"
    label.TextColor3 = Color3.fromRGB(100, 255, 120)
    label.TextStrokeTransparency = 0.35
    label.Font = Enum.Font.GothamBold
    label.TextScaled = true
    label.Parent = gui
end

-- Возвращаем кота только его владельцу
local function giveCatBack(player, model)
    if not model or not model.Parent then
        return
    end

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

    local toolName = model:GetAttribute("CatToolName")
    local template = catTools:FindFirstChild(toolName)

    if not template or not template:IsA("Tool") then
        warn("Не найден Tool для возврата кота:", toolName)
        return
    end

    local backpack = player:FindFirstChildOfClass("Backpack")

    if not backpack then
        return
    end

    local returnedTool = template:Clone()
    returnedTool.Parent = backpack

    model:Destroy()
end

local function addPickupPrompt(model, owner)
    local handle = model.PrimaryPart
    if not handle then
        return
    end

    local prompt = Instance.new("ProximityPrompt")
    prompt.Name = "PickupCatPrompt"
    prompt.ActionText = "Забрать кота"
    prompt.ObjectText = model.Name
    prompt.KeyboardKeyCode = Enum.KeyCode.E
    prompt.HoldDuration = 1
    prompt.MaxActivationDistance = PICKUP_DISTANCE
    prompt.RequiresLineOfSight = false
    prompt.Parent = handle

    prompt.Triggered:Connect(function(player)
        if player ~= owner then
            return
        end

        if not model.Parent then
            return
        end

        local character = player.Character
        local root = character and character:FindFirstChild("HumanoidRootPart")

        if not root then
            return
        end

        if (root.Position - handle.Position).Magnitude
            > PICKUP_DISTANCE + 2 then
            return
        end

        giveCatBack(player, model)
    end)
end

local function isPositionInsideFloor(floor, position)
    local localPosition = floor.CFrame:PointToObjectSpace(position)

    return math.abs(localPosition.X) <= floor.Size.X / 2
        and math.abs(localPosition.Z) <= floor.Size.Z / 2
        and math.abs(localPosition.Y - floor.Size.Y / 2) <= 5
end

-- Установка кота
placeEvent.OnServerEvent:Connect(function(
    player,
    targetFloor,
    position,
    yaw
)
    if typeof(targetFloor) ~= "Instance"
        or not targetFloor:IsA("BasePart") then
        return
    end

    if typeof(position) ~= "Vector3"
        or typeof(yaw) ~= "number" then
        return
    end

    if yaw ~= yaw or math.abs(yaw) > 100000 then
        return
    end

    local base, floor = getOwnedBase(player)

    if not base or targetFloor ~= floor then
        return
    end

    if not isPositionInsideFloor(floor, position) then
        return
    end

    local character = player.Character
    local root = character and character:FindFirstChild("HumanoidRootPart")

    if not root then
        return
    end

    if (root.Position - position).Magnitude > PLACE_DISTANCE then
        return
    end

    if countCats(player) >= MAX_CATS then
        return
    end

    local equippedTool = getEquippedCat(player)

    if not equippedTool then
        return
    end

    local template = getValidTemplate(equippedTool)

    if not template then
        return
    end

    -- Данные берём с серверного шаблона, а не от клиента
    local income = template:GetAttribute("Income")
    local rarity = template:GetAttribute("Rarity")

    local model = createCatModel(template)

    if not model then
        return
    end

    positionModel(model, position, yaw, floor)

    model:SetAttribute("OwnerUserId", player.UserId)
    model:SetAttribute("CatToolName", template.Name)
    model:SetAttribute("Income", income)
    model:SetAttribute("Rarity", rarity)

    model.Parent = placedFolder

    createIncomeLabel(model, income)
    addPickupPrompt(model, player)

    equippedTool:Destroy()
end)

-- Начисление дохода
task.spawn(function()
    while true do
        task.wait(INCOME_INTERVAL)

        for _, model in ipairs(placedFolder:GetChildren()) do
            if model:IsA("Model") then
                local ownerId = model:GetAttribute("OwnerUserId")
                local income = model:GetAttribute("Income")
                local owner = ownerId and Players:GetPlayerByUserId(ownerId)

                if owner and typeof(income) == "number" then
                    local leaderstats = owner:FindFirstChild("leaderstats")
                    local coins = leaderstats and leaderstats:FindFirstChild("Coins")

                    if coins and coins:IsA("NumberValue") then
                        coins.Value += income
                    end
                end
            end
        end
    end
end)

-- Удаляем котов вышедшего игрока
Players.PlayerRemoving:Connect(function(player)
    for _, model in ipairs(placedFolder:GetChildren()) do
        if model:GetAttribute("OwnerUserId") == player.UserId then
            model:Destroy()
        end
    end
end)

print("CatPlacementServer запущен")