Загрузка данных
-- 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
--------------------------------------------------
-- REMOTE EVENT
--------------------------------------------------
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
--------------------------------------------------
-- СНЯТИЕ ANCHORED С НАСТОЯЩЕГО TOOL
--------------------------------------------------
local function unanchorTool(tool)
for _, descendant in ipairs(tool:GetDescendants()) do
if descendant:IsA("BasePart") then
descendant.Anchored = false
end
end
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
-- Исправление: настоящий Tool не должен быть Anchored.
unanchorTool(item)
return item
end
end
return nil
end
--------------------------------------------------
-- ПРОВЕРКА ШАБЛОНА КОТА
--------------------------------------------------
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
or typeof(rarity) ~= "string"
or not handle
or not handle:IsA("BasePart") then
return nil
end
return template
end
--------------------------------------------------
-- СОЗДАНИЕ МОДЕЛИ УСТАНОВЛЕННОГО КОТА
--------------------------------------------------
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("Не найден шаблон кота:", toolName)
return
end
local backpack = player:FindFirstChildOfClass("Backpack")
if not backpack then
return
end
local returnedTool = template:Clone()
-- Инструмент в инвентаре тоже не должен быть заанкорен.
unanchorTool(returnedTool)
returnedTool.Parent = backpack
model:Destroy()
end
--------------------------------------------------
-- УДЕРЖАНИЕ E ДЛЯ ВОЗВРАТА
--------------------------------------------------
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 or 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")
or 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
or (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 запущен")