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


--[[
    PLAYER STATE WATCHER v1
    Delta / Roblox client lightweight build.

    Purpose:
      - Does NOT dump current values.
      - Watches player-related replicated state.
      - Prints ONLY changes in real time.
      - Tracks ValueBase objects and Attributes.
      - Dynamically attaches to newly created objects.
]]

print("[STATE WATCHER] 01 script entered")

local Players = game:GetService("Players")
local player = Players.LocalPlayer

if not player then
    warn("[STATE WATCHER] LocalPlayer unavailable")
    return
end

print("[STATE WATCHER] 02 LocalPlayer=" .. player.Name)

local watchedInstances = setmetatable({}, {__mode = "k"})
local watchedAttributes = setmetatable({}, {__mode = "k"})
local connections = {}

local function safePath(obj)
    local ok, result = pcall(function()
        return obj:GetFullName()
    end)
    return ok and result or tostring(obj)
end

local function formatValue(v)
    local t = typeof(v)

    if t == "string" then
        if #v > 120 then
            return string.format("%q...", v:sub(1, 117))
        end
        return string.format("%q", v)
    elseif t == "number" then
        return tostring(v)
    elseif t == "boolean" then
        return tostring(v)
    elseif t == "Instance" then
        return safePath(v)
    end

    return tostring(v)
end

local function numericDelta(oldValue, newValue)
    if typeof(oldValue) == "number" and typeof(newValue) == "number" then
        local d = newValue - oldValue
        if d > 0 then
            return " (+" .. tostring(d) .. ")"
        elseif d < 0 then
            return " (" .. tostring(d) .. ")"
        end
    end
    return ""
end

local function emit(kind, obj, name, oldValue, newValue)
    local line = string.format(
        "[STATE CHANGE] %s | %s | %s: %s -> %s%s",
        kind,
        safePath(obj),
        tostring(name),
        formatValue(oldValue),
        formatValue(newValue),
        numericDelta(oldValue, newValue)
    )

    print(line)

    if typeof(oldValue) == "number" and typeof(newValue) == "number" then
        local d = newValue - oldValue
        if math.abs(d) > 0 then
            print(string.format(
                "[STATE DELTA] %s | %s | delta=%s",
                tostring(name),
                safePath(obj),
                tostring(d)
            ))
        end
    end
end

local function watchValueBase(obj)
    if watchedInstances[obj] then
        return
    end

    if not obj:IsA("ValueBase") then
        return
    end

    watchedInstances[obj] = true

    local ok, lastValue = pcall(function()
        return obj.Value
    end)

    if not ok then
        return
    end

    table.insert(connections, obj:GetPropertyChangedSignal("Value"):Connect(function()
        local readOk, newValue = pcall(function()
            return obj.Value
        end)

        if not readOk then
            return
        end

        if newValue ~= lastValue then
            local oldValue = lastValue
            lastValue = newValue
            emit("VALUE", obj, obj.Name, oldValue, newValue)
        end
    end))
end

local function watchAttributes(obj)
    if watchedAttributes[obj] then
        return
    end

    watchedAttributes[obj] = true

    local previous = {}

    local ok, attrs = pcall(function()
        return obj:GetAttributes()
    end)

    if ok then
        for name, value in pairs(attrs) do
            previous[name] = value
        end
    end

    table.insert(connections, obj.AttributeChanged:Connect(function(name)
        local readOk, newValue = pcall(function()
            return obj:GetAttribute(name)
        end)

        if not readOk then
            return
        end

        local oldValue = previous[name]

        if oldValue ~= newValue then
            previous[name] = newValue
            emit("ATTRIBUTE", obj, name, oldValue, newValue)
        end
    end))
end

local function attachObject(obj)
    pcall(watchValueBase, obj)
    pcall(watchAttributes, obj)
end

local function attachTree(root)
    attachObject(root)

    local ok, descendants = pcall(function()
        return root:GetDescendants()
    end)

    if not ok then
        return
    end

    for _, obj in ipairs(descendants) do
        attachObject(obj)
    end
end

local roots = {
    player
}

local character = player.Character
if character then
    table.insert(roots, character)
end

local playerGui = player:FindFirstChildOfClass("PlayerGui")
if playerGui then
    table.insert(roots, playerGui)
end

local backpack = player:FindFirstChildOfClass("Backpack")
if backpack then
    table.insert(roots, backpack)
end

for _, root in ipairs(roots) do
    attachTree(root)

    table.insert(connections, root.DescendantAdded:Connect(function(obj)
        attachObject(obj)
    end))
end

table.insert(connections, player.CharacterAdded:Connect(function(char)
    print("[STATE WATCHER] Character changed -> " .. safePath(char))
    attachTree(char)

    table.insert(connections, char.DescendantAdded:Connect(function(obj)
        attachObject(obj)
    end))
end))

-- Catch leaderstats/backpack/gui appearing after watcher startup.
table.insert(connections, player.ChildAdded:Connect(function(child)
    if child.Name == "leaderstats"
        or child:IsA("Backpack")
        or child:IsA("PlayerGui")
    then
        attachTree(child)

        table.insert(connections, child.DescendantAdded:Connect(function(obj)
            attachObject(obj)
        end))
    end
end))

print("[STATE WATCHER] READY")
print("[STATE WATCHER] Watching player ValueBase + Attributes. Console will stay quiet until something changes.")