Загрузка данных
-- 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 запущен")