X-Ray vision for hidden objects in blackhack
This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/573060/1794202/blackhack-xray.js
// ==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);
})();