blackhack-xray

X-Ray vision for hidden objects in blackhack

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/573060/1794202/blackhack-xray.js

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           blackhack-xray
// @namespace      brofist.io 1st-cheat (FOR ALL MODES)
// @version        2.0
// @description    Safe and Accurate X-Ray vision for hidden objects in blackhack
// @author         CiNoP
// @license        GPL-3.0-only
// @grant          none
// ==/UserScript==

(function() {
    'use strict';

    const DEBUG = false;
    const dbg = (...a) => { if (DEBUG) console.log('%c[BH-XRay]', 'color:#f0f;font-weight:bold', ...a); };

    const BH = window.BH = window.BH || {};
    BH.xrayVer = 2.0;

    // Состояние X-Ray
    const xrayState = {
        enabled: false,
        pulsePhase: 0,
        trackedContainers:[], // Храним наши PIXI.Container
        scanInterval: null,
        renderInterval: null
    };

    // Определяем тип и цвет объекта
    function getXrayInfo(id) {
        const lowerId = id.toLowerCase();
        if (lowerId.includes('gate')) return { isTarget: true, color: 0xFF00FF }; // Пурпурный для гейтов
        if (lowerId.includes('cover')) return { isTarget: true, color: 0xFFFF00 }; // Желтый для укрытий
        return { isTarget: false };
    }

    // Создаем точную геометрическую копию объекта
    function buildXrayContainer(obj, pixi, color) {
        // Создаем корневой контейнер для всего объекта
        const container = new pixi.Container();
        
        // Перебираем все составные части (шейпы) объекта
        obj.shapes.forEach(shape => {
            // Игнорируем текст и декорации (make === 3)
            if (!shape || shape.make === 3) return;

            const gfx = new pixi.Graphics();
            
            // Локальные смещения и поворот шейпа
            gfx.x = shape.x || 0;
            gfx.y = shape.y || 0;
            gfx.rotation = -(shape.angle || 0) * (Math.PI / 180); // Игра использует градусы, инвертированные
            
            const w = shape.width || 100;
            const h = shape.height || 100;

            // Отрисовка в зависимости от типа фигуры
            if (shape.type === 2) {
                // КРУГ (type 2)
                const radius = shape.radius || 50;
                gfx.circle(0, 0, radius);
            } else if (shape.type === 4 && shape.points) {
                // ПОЛИГОН (type 4)
                const pts =[];
                for(let i = 0; i < shape.points.length; i += 2) {
                    pts.push(shape.points[i], shape.points[i+1]);
                }
                gfx.poly(pts);
                gfx.closePath();
            } else {
                // ПРЯМОУГОЛЬНИК (type 1 и fallback)
                gfx.rect(-w / 2, -h / 2, w, h);
            }

            // Применяем стиль (PixiJS v8 API)
            gfx.fill({ color: color, alpha: 0.2 });
            gfx.stroke({ width: 3, color: color, alpha: 1.0 });

            container.addChild(gfx);
        });

        return container;
    }

    // Сканирование карты на наличие новых скрытых объектов
    function scanObjects() {
        const gp = window.hack?.gp;
        const pixi = window.pixi || window.PIXI;

        if (!xrayState.enabled || !gp || !gp.list || !pixi) return;

        gp.list.forEach(obj => {
            // Пропускаем объекты без графики или если мы уже прикрепили к ним X-Ray
            if (!obj || !obj.id || !obj.g || !obj.g.parent || obj.__xray_attached) return;

            const info = getXrayInfo(obj.id);
            if (info.isTarget && obj.shapes && obj.shapes.length > 0) {
                
                // Строим точную копию объекта
                const container = buildXrayContainer(obj, pixi, info.color);
                
                // Добавляем наш контейнер в тот же слой игрового мира, где лежит оригинал
                obj.g.parent.addChild(container);
                
                // Ставим флаг, чтобы не дублировать
                obj.__xray_attached = true;

                // Сохраняем в список для синхронизации
                xrayState.trackedContainers.push({
                    targetObj: obj,
                    container: container
                });

                dbg(`Добавлен X-Ray для ${obj.id}`);
            }
        });
    }

    // Главный цикл синхронизации
    function syncLoop() {
        if (!xrayState.enabled) return;
        
        xrayState.pulsePhase += 0.1;
        const pulse = 0.7 + 0.3 * Math.sin(xrayState.pulsePhase);

        for (let i = xrayState.trackedContainers.length - 1; i >= 0; i--) {
            const item = xrayState.trackedContainers[i];
            const target = item.targetObj;
            const container = item.container;

            // Если оригинальный объект был удален игрой (смена карты или удаление чанка)
            if (!target.g || !target.g.parent || target.g.destroyed) {
                if (!container.destroyed) container.destroy({ children: true });
                target.__xray_attached = false;
                xrayState.trackedContainers.splice(i, 1);
                continue;
            }

            // 1. Синхронизируем глобальную позицию всего объекта
            container.x = target.g.x;
            container.y = target.g.y;
            container.rotation = target.g.rotation;

            // 2. Логика видимости (X-Ray эффект)
            // ЧИТАЕМ оригинальную коллизию и альфу. НИЧЕГО НЕ ПИШЕМ В ИГРУ!
            const isHiddenByGame = target.g.alpha < 0.1 || (target.getCollision && !target.getCollision());
            
            if (isHiddenByGame) {
                // Дверь открыта (исчезла в игре) -> делаем X-Ray ярким и пульсирующим
                container.alpha = 1.0 * pulse;
                container.visible = true;
            } else {
                // Дверь закрыта (видна в игре) -> делаем X-Ray еле заметным, чтобы не мешал
                container.alpha = 0.15;
                container.visible = true;
            }
        }
    }

    // Очистка при выключении
    function clearAll() {
        xrayState.trackedContainers.forEach(item => {
            if (item.targetObj) item.targetObj.__xray_attached = false;
            if (item.container && !item.container.destroyed) {
                item.container.destroy({ children: true });
            }
        });
        xrayState.trackedContainers =[];
    }

    // Публичный API
    BH.xray = {
        enable() {
            if (xrayState.enabled) return;
            xrayState.enabled = true;
            
            // Сканируем новые объекты каждые 500мс (полезно если карта подгружается кусками)
            xrayState.scanInterval = setInterval(scanObjects, 500);
            
            // Синхронизируем позиции 60 раз в секунду
            xrayState.renderInterval = setInterval(syncLoop, 16);
            
            scanObjects(); // Первый скан сразу
            console.log("[BH] X-Ray Включен");
        },

        disable() {
            if (!xrayState.enabled) return;
            xrayState.enabled = false;
            
            clearInterval(xrayState.scanInterval);
            clearInterval(xrayState.renderInterval);
            
            clearAll();
            console.log("[BH] X-Ray Выключен");
        },

        toggle() {
            if (xrayState.enabled) {
                this.disable();
            } else {
                this.enable();
            }
        }
    };

    // Биндим клавишу X
    const initInterval = setInterval(() => {
        if (!window.hack?.gp) return; // Ждем загрузки игры
        clearInterval(initInterval);

        document.addEventListener('keydown', (e) => {
            if (e.key === 'x' || e.key === 'X') {
                const t = (e.target.tagName || '').toLowerCase();
                if (t !== 'input' && t !== 'textarea' && !e.target.isContentEditable) {
                    e.preventDefault();
                    BH.xray.toggle();
                }
            }
        });
        
        dbg('Модуль инициализирован');
    }, 500);

})();