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


-- ENGINEER FLOATING SENTRY TEST V1
-- Один ограниченный запрос.

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

local LocalPlayer = Players.LocalPlayer
local Character = LocalPlayer.Character
local Root = Character and Character:FindFirstChild("HumanoidRootPart")
local Humanoid = Character and Character:FindFirstChildOfClass("Humanoid")

-- Проверка на приватный сервер удалена — скрипт работает на любом сервере

assert(Character and Root and Humanoid, "Character is not ready")
assert(Humanoid.Health > 0, "Character is dead")

local gloveName = tostring(LocalPlayer:GetAttribute("Glove"))
local engineerTool =
    Character:FindFirstChild("Engineer")
    or LocalPlayer.Backpack:FindFirstChild("Engineer")

assert(
    gloveName == "Engineer" or engineerTool,
    "Equip Engineer first; current glove: " .. gloveName
)

local arenaValue = Character:FindFirstChild("isInArena")
assert(
    arenaValue and arenaValue.Value == true,
    "Enter the arena first"
)

local SentryRemote = ReplicatedStorage:FindFirstChild("Sentry")
assert(
    SentryRemote and SentryRemote:IsA("RemoteEvent"),
    "ReplicatedStorage.Sentry missing"
)

local report = {}

local function log(...)
    local values = {...}

    for index = 1, #values do
        values[index] = tostring(values[index])
    end

    local line = table.concat(values, " ")
    table.insert(report, line)
    print(line)
end

local function copyReport()
    local output = table.concat(report, "\n")
    local clipboard = setclipboard or toclipboard

    if type(clipboard) == "function" then
        pcall(clipboard, output)
    end
end

local function isTurret(object)
    if not object or not object:IsA("Model") then
        return false
    end

    return object.Name == "Turret"
        or object:HasTag("Sentry")
        or object:FindFirstChild("TurretStandBase", true) ~= nil
end

local function getAllowedGrass()
    local result = {}
    local Arena = Workspace:FindFirstChild("Arena")

    assert(Arena, "Workspace.Arena missing")

    for _, islandName in ipairs({
        "main island",
        "island1",
        "island2",
        "island3",
        "island4"
    }) do
        local island = Arena:FindFirstChild(islandName)
        local grass = island and island:FindFirstChild("Grass")

        if grass and grass:IsA("BasePart") then
            table.insert(result, grass)
        end
    end

    local defaultIsland = Arena:FindFirstChild("default island")

    if defaultIsland then
        for _, object in ipairs(defaultIsland:GetChildren()) do
            if object:IsA("BasePart")
                and object.Material == Enum.Material.Grass then

                table.insert(result, object)
            end
        end
    end

    return result
end

local function findGround()
    local allowedGrass = getAllowedGrass()
    assert(#allowedGrass > 0, "Allowed grass list is empty")

    local forwardPosition =
        Root.Position + Root.CFrame.LookVector * 6

    local origin = Vector3.new(
        forwardPosition.X,
        Root.Position.Y + 60,
        forwardPosition.Z
    )

    local params = RaycastParams.new()
    params.FilterType = Enum.RaycastFilterType.Include
    params.FilterDescendantsInstances = allowedGrass
    params.IgnoreWater = true

    return Workspace:Raycast(
        origin,
        Vector3.new(0, -150, 0),
        params
    )
end

log("=== ENGINEER FLOATING SENTRY TEST V1 ===")
log("Private server:", true)   -- показываем true, так как защита снята
log("Player:", LocalPlayer.Name)
log("Glove:", gloveName)
log("Waiting 14 seconds to clear Engineer cooldown...")
log("Do not move and do not press E")

task.wait(14)

Character = LocalPlayer.Character
Root = Character and Character:FindFirstChild("HumanoidRootPart")
Humanoid = Character and Character:FindFirstChildOfClass("Humanoid")

assert(Character and Root and Humanoid and Humanoid.Health > 0,
    "Character changed during cooldown wait")

local groundHit = findGround()

assert(
    groundHit,
    "No allowed grass ahead; face toward clear arena grass"
)

local FLOAT_HEIGHT = 12
local targetPosition =
    groundHit.Position + Vector3.new(0, FLOAT_HEIGHT, 0)

-- Обычный клиент отправляет identity rotation.
-- Здесь меняется только высота, чтобы результат был однозначным.
local targetCFrame = CFrame.new(targetPosition)
local playerDistance = (Root.Position - targetPosition).Magnitude

log("Ground:", groundHit.Instance:GetFullName())
log("Ground position:", groundHit.Position)
log("Requested position:", targetPosition)
log("Requested grass gap:", FLOAT_HEIGHT)
log("Player distance:", string.format("%.3f", playerDistance))

assert(
    playerDistance <= 15,
    "Target unexpectedly farther than 15 studs"
)

local existing = {}

for _, object in ipairs(Workspace:GetChildren()) do
    if isTurret(object) then
        existing[object] = true
    end
end

for _, object in ipairs(CollectionService:GetTagged("Sentry")) do
    existing[object] = true
end

local newTurret

local addedConnection = Workspace.ChildAdded:Connect(function(object)
    task.defer(function()
        if not existing[object] and isTurret(object) then
            newTurret = object
        end
    end)
end)

local sent, sendError = pcall(function()
    SentryRemote:FireServer(targetCFrame)
end)

log("ONE REQUEST SENT:", sent)
log("SEND ERROR:", sendError)

local deadline = os.clock() + 6

repeat
    task.wait(0.1)

    for _, object in ipairs(CollectionService:GetTagged("Sentry")) do
        if not existing[object] then
            local ownerId = object:GetAttribute("OwnerId")

            if ownerId == nil or ownerId == LocalPlayer.UserId then
                newTurret = object
                break
            end
        end
    end
until newTurret or os.clock() >= deadline

addedConnection:Disconnect()

if not newTurret or not newTurret.Parent then
    log("SERVER TURRET CREATED: false")
    log("RESULT: REJECTED — SERVER BLOCKED FLOATING CFRAME")
    copyReport()
    return
end

task.wait(1)

local actualCFrame = newTurret:GetPivot()
local positionError =
    (actualCFrame.Position - targetPosition).Magnitude

local supportParams = RaycastParams.new()
supportParams.FilterType = Enum.RaycastFilterType.Exclude
supportParams.FilterDescendantsInstances = {
    Character,
    newTurret
}
supportParams.IgnoreWater = true

local supportHit = Workspace:Raycast(
    actualCFrame.Position + Vector3.new(0, 1, 0),
    Vector3.new(0, -50, 0),
    supportParams
)

local actualGap = supportHit
    and actualCFrame.Position.Y - supportHit.Position.Y
    or math.huge

log("SERVER TURRET CREATED: true")
log("Turret:", newTurret:GetFullName())
log("Requested pivot:", targetPosition)
log("Actual pivot:", actualCFrame.Position)
log("Position error:", string.format("%.3f", positionError))
log(
    "Support:",
    supportHit and supportHit.Instance:GetFullName() or "NONE"
)
log(
    "Actual support gap:",
    actualGap == math.huge
        and "INF"
        or string.format("%.3f", actualGap)
)

if positionError <= 4 and actualGap >= 8 then
    log("RESULT: OP PASS")
    log("SERVER ACCEPTED A FLOATING ENGINEER TURRET")
    log("Placement validation only detected grass below")
elseif positionError <= 4 then
    log("RESULT: PARTIAL — CREATED AT REQUEST, BUT SUPPORT IS CLOSE")
else
    log("RESULT: REJECTED — SERVER SNAPPED/RECOMPUTED POSITION")
end

copyReport()