Map parsing functions for blackhack
Per
Dit script moet niet direct worden geïnstalleerd - het is een bibliotheek voor andere scripts om op te nemen met de meta-richtlijn // @require https://update.greatest.deepsurf.us/scripts/571914/1788827/blackhack-map.js
// ==UserScript==
// @name blackhack-map
// @namespace brofist.io 1st-cheat (FOR ALL MODES)
// @version 1.8
// @description Map parsing functions for blackhack
// @author CiNoP
// @license GPL-3.0-only
// ==/UserScript==
/* blackhack-map.js */
(function () {
'use strict';
const BH = window.BH = window.BH || {};
BH.mapVer = 1.8;
const leverParts = new Set(['leftstick', 'leftball', 'rightstick', 'rightball']);
function isPoison(sid) { return sid === 'poision' || sid === 'poison'; }
function getFuncLabel(sid) {
if (sid === 'checkpoint') return 'C';
if (sid === 'playarea') return 'P';
if (sid === 'exitgate') return 'D';
if (sid.startsWith('button:')) return 'B';
if (sid.startsWith('leaver:')) return 'L';
return null;
}
function makeStub() {
return { x: 0, y: 0, type: 1, width: 1, height: 1, alpha: 0, id: '', collision: false, color: '0x000000', make: 3 };
}
function processShape(sh, oid, isFakeStatic, al) {
const sid = (sh.id || '').toLowerCase();
// Защита: если отдельный шейп имеет ID mapCredits, лишаем его коллизии
if (sid.includes('mapcredits')) {
return [{ ...sh, alpha: 0.8, collision: false, make: 3 }];
}
const poison = isPoison(sid);
if (sh.collision === false && !poison) return null;
const c = { ...sh };
c.alpha = poison ? al.poison : al.collision;
c.color = poison ? '0x00FF00' : (isFakeStatic ? '0xFFFF00' : '0x000000');
const out = [];
if (sh.type === 3 && !oid.includes('mapcredits')) {
out.push({ ...c, type: 1, id: '', make: 3, alpha: 0.3,
width: BH.measureTextWidth ? BH.measureTextWidth(sh.fontSize, sh.text) : 30, height: sh.fontSize * 1.108 });
}
out.push(c);
return out;
}
/** Убираем все сегменты cover: из ID объекта */
function removeCoverFromId(oid) {
return oid.split('|')
.filter(p => !p.trim().toLowerCase().startsWith('cover'))
.join('|');
}
BH.parseMapToLayout = mapData => {
const { clamp, measureTextWidth } = BH || { clamp: (v, m, x) => Math.max(m, Math.min(x, v)), measureTextWidth: () => 30 };
const al = window.hack?.vars?.layoutAlpha || { collision: 1, poison: 1, functional: 1, background: 0xCCCCCC };
let parsed = mapData;
try {
if (typeof parsed === 'string') parsed = JSON.parse(parsed);
if (Array.isArray(parsed) && typeof parsed[0] === 'number' && window.LZMA)
parsed = JSON.parse(window.LZMA.decompress(parsed));
} catch (e) { return mapData; }
try { parsed = JSON.parse(JSON.stringify(parsed)); } catch (_) {}
const result = [];
const gateLinksToAdd = {}; // { "gateName": "gateName_xray" }
for (const obj of parsed) {
if (!obj || !obj.shapes || !obj.shapes.length) { result.push(obj || {}); continue; }
const oid = (obj.id || '');
const oidLower = oid.toLowerCase();
// ── ПРОПУСК ГРАНИЦ КАРТЫ И ОБРАБОТКА MAP CREDITS ──
if (oidLower.includes('99999999999') || oidLower.includes('mapcredits')) {
const isCredits = oidLower.includes('mapcredits');
obj.shapes = obj.shapes.map(sh => {
if (!sh) return sh;
return {
...sh,
alpha: isCredits ? 0.8 : 0, // Текст оставляем видимым, невидимые границы прячем полностью
collision: false, // Принудительно отключаем коллизию
make: 3 // Только визуал, никакой физики
};
});
result.push(obj);
continue;
}
const shapes = obj.shapes;
const isFakeStatic = typeof obj.mass === 'number' && obj.mass !== 0 && Math.abs(obj.mass) < 1e-6;
const isGate = /^gate/i.test(oid);
// ── ОБРАБОТКА ВОРОТ ──
if (isGate) {
const parts = oid.split('|');
const gp = parts[0];
const ci = gp.lastIndexOf(':');
if (ci !== -1) {
const gateName = gp.slice(0, ci);
const gateState = gp.slice(ci + 1);
const flippedState = gateState === '1' ? '0' : '1';
const xrayGateName = gateName + '_xray';
gateLinksToAdd[gateName] = xrayGateName;
// 1. ОРИГИНАЛЬНЫЕ ВОРОТА
const origObj = JSON.parse(JSON.stringify(obj));
origObj.id = removeCoverFromId(origObj.id);
origObj.shapes = origObj.shapes.map(sh => {
if (!sh) return sh;
const shPoison = isPoison((sh.id || '').toLowerCase());
return {
...sh,
alpha: 0.8,
color: shPoison ? '0x00FFAA' : '0x00AAAA'
};
});
result.push(origObj);
// 2. X-RAY КЛОН
const cloneObj = JSON.parse(JSON.stringify(origObj));
parts[0] = `${xrayGateName}:${flippedState}`;
cloneObj.id = parts.join('|');
cloneObj.shapes = cloneObj.shapes.map(sh => {
if (!sh) return sh;
const shPoison = isPoison((sh.id || '').toLowerCase());
return {
...sh,
make: 3,
collision: false,
alpha: 0.3,
color: shPoison ? '0x00FFAA' : '0x00AAAA'
};
});
if (cloneObj.shapes.length > 0) {
const sh0 = cloneObj.shapes[0];
const w = Math.abs(sh0.width || 30), h = Math.abs(sh0.height || 30);
const fontSize = clamp(Math.min(w, h) * 0.4, 8, 30);
cloneObj.shapes.push({
x: sh0.x || 0, y: sh0.y || 0, width: measureTextWidth ? measureTextWidth(fontSize, 'XRAY') : 30,
height: fontSize * 1.108, angle: -(obj.angle || 0), radius: 50, alpha: 1,
id: '', collision: false, color: '0xFFFFFF', fontSize, text: 'XRAY', make: 3, type: 3
});
}
result.push(cloneObj);
} else {
result.push(obj);
}
continue;
}
// ── ОБРАБОТКА ФУНКЦИОНАЛЬНЫХ ОБЪЕКТОВ ──
const isFunctional = oidLower === 'spawn' || oidLower === 'door' || oidLower === 'playarea' || oidLower === 'checkpoint' ||
shapes.some(s => {
const sid = (s && s.id) ? s.id.toLowerCase() : '';
return sid === 'spawn' || sid === 'playarea' || sid === 'checkpoint' || sid === 'exitgate' ||
sid === 'egcounter' || sid.startsWith('roundtime:') || sid.startsWith('button:') || sid.startsWith('leaver:');
});
if (isFunctional) {
const fs = [];
const hasLeaver = shapes.some(s => s && s.id && s.id.toLowerCase().startsWith('leaver:'));
for (const sh of shapes) {
if (!sh) continue;
const sid = (sh.id || '').toLowerCase();
const isFunc = sid === 'spawn' || sid === 'playarea' || sid === 'checkpoint' || sid === 'exitgate' ||
sid === 'egcounter' || sid.startsWith('roundtime:') || sid.startsWith('button:') || sid.startsWith('leaver:');
if (isFunc) {
const c = { ...sh };
if (sid === 'egcounter' || (sid.startsWith('roundtime:') && sh.make === 3) || sid === 'spawn') {
c.alpha = 0; fs.push(c); continue;
}
c.alpha = al.functional; c.color = '0x0000FF'; fs.push(c);
const label = getFuncLabel(sid);
if (label) {
const w = Math.abs(sh.width || 30), h = Math.abs(sh.height || 30);
const fontSize = clamp(Math.min(w, h) * 0.6, 8, 30);
fs.push({ x: sh.x || 0, y: sh.y || 0, width: measureTextWidth ? measureTextWidth(fontSize, label) : 30,
height: fontSize * 1.108, angle: -(obj.angle || 0), radius: 50, alpha: 1,
id: '', collision: false, color: '0x000000', fontSize, text: label, make: 3, type: 3 });
}
continue;
}
if (sh.make === 3 && hasLeaver && leverParts.has(sid)) { fs.push({ ...sh }); continue; }
if (sh.make === 3) continue;
const r = processShape(sh, oid, isFakeStatic, al);
if (r) fs.push(...r);
}
if (!fs.length) fs.push(makeStub());
obj.shapes = fs;
result.push(obj);
continue;
}
// ── ОБЫЧНЫЕ ОБЪЕКТЫ ──
if (oidLower.includes('cover')) {
obj.id = removeCoverFromId(obj.id);
}
const fs = [];
for (const sh of shapes) {
if (!sh || sh.make === 3) continue;
const r = processShape(sh, oid, isFakeStatic, al);
if (r) fs.push(...r);
}
if (!fs.length) fs.push(makeStub());
obj.shapes = fs; result.push(obj);
}
// ── ПРИВЯЗКА КНОПОК К X-RAY КЛОНАМ ──
for (const obj of result) {
const oidLower = (obj.id || '').toLowerCase();
if (oidLower.startsWith('button:')) {
const parts = obj.id.split(':');
if (parts.length > 1) {
const targets = parts[1].split(',');
const newTargets = [...targets];
for (const target of targets) {
if (gateLinksToAdd[target]) newTargets.push(gateLinksToAdd[target]);
}
obj.id = `${parts[0]}:${newTargets.join(',')}`;
}
}
for (const sh of obj.shapes) {
if (!sh) continue;
const sid = sh.id || '';
if (sid.toLowerCase().startsWith('button:')) {
const parts = sid.split(':');
if (parts.length > 1) {
const targets = parts[1].split(',');
const newTargets = [...targets];
for (const target of targets) {
if (gateLinksToAdd[target]) newTargets.push(gateLinksToAdd[target]);
}
sh.id = `${parts[0]}:${newTargets.join(',')}`;
}
}
}
}
return result;
};
})();