Map parsing functions for blackhack
Pada tanggal
Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.greatest.deepsurf.us/scripts/571914/1789001/blackhack-map.js
// ==UserScript==
// @name blackhack-map
// @namespace brofist.io 1st-cheat (FOR ALL MODES)
// @version 1.10
// @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.10;
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 };
}
// ══ FIX 1: measureTextWidth передаётся параметром вместо прямого доступа к BH ══
function processShape(sh, oid, isFakeStatic, al, measureTextWidth) {
const sid = (sh.id || '').toLowerCase();
const oidLower = (oid || '').toLowerCase();
if (sid.includes('mapcredits')) {
return [{ ...sh, alpha: 0.8, collision: false, make: 3 }];
}
const poison = isPoison(sid);
const isPlatform = oidLower.startsWith('platform:');
if (sh.collision === false && !poison && !isPlatform) return null;
const c = { ...sh };
if (isPlatform && sh.make === 3) {
c.alpha = sh.alpha !== undefined ? sh.alpha : 1;
} else {
c.alpha = poison ? al.poison : al.collision;
c.color = poison ? '0x00FF00' : (isFakeStatic ? '0xFFFF00' : '0x000000');
}
const out = [];
if (sh.type === 3 && !oidLower.includes('mapcredits')) {
out.push({ ...c, type: 1, id: '', make: 3, alpha: 0.3,
width: measureTextWidth(sh.fontSize, sh.text), height: sh.fontSize * 1.108 });
}
out.push(c);
return out;
}
function removeCoverFromId(oid) {
return oid.split('|')
.filter(p => !p.trim().toLowerCase().startsWith('cover'))
.join('|');
}
BH.parseMapToLayout = mapData => {
// ══ FIX 2: фолбэки теперь работают — BH всегда truthy, || не спасал ══
const clamp = BH.clamp || ((v, m, x) => Math.max(m, Math.min(x, v)));
const measureTextWidth = BH.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 = {};
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) {
// ══ FIX 3: cover удаляется ДО разбиения на parts — клон тоже чист ══
const cleanOid = removeCoverFromId(oid);
const parts = cleanOid.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 = cleanOid;
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 КЛОН (parts уже без cover)
const cloneObj = JSON.parse(JSON.stringify(origObj));
const cloneParts = [...parts];
cloneParts[0] = `${xrayGateName}:${flippedState}`;
cloneObj.id = cloneParts.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(fontSize, 'XRAY'),
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(fontSize, label),
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, measureTextWidth);
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 = [];
const isPlatform = oidLower.startsWith('platform:');
for (const sh of shapes) {
if (!sh || (sh.make === 3 && !isPlatform)) continue;
const r = processShape(sh, oid, isFakeStatic, al, measureTextWidth);
if (r) fs.push(...r);
}
if (!fs.length) fs.push(makeStub());
obj.shapes = fs; result.push(obj);
}
// ── ПРИВЯЗКА КНОПОК И РЫЧАГОВ К X-RAY КЛОНАМ ──
for (const obj of result) {
// ══ FIX 4: защита от объектов без shapes (пустые объекты из раннего push) ══
if (!obj || !obj.shapes) continue;
const oidLower = (obj.id || '').toLowerCase();
if (oidLower.startsWith('button:') || oidLower.startsWith('leaver:')) {
const colon = obj.id.indexOf(':');
if (colon !== -1) {
const prefix = obj.id.slice(0, colon);
const targets = obj.id.slice(colon + 1).split(',');
const newTargets = [...targets];
for (const t of targets) {
if (gateLinksToAdd[t] && !newTargets.includes(gateLinksToAdd[t]))
newTargets.push(gateLinksToAdd[t]);
}
obj.id = `${prefix}:${newTargets.join(',')}`;
}
}
for (const sh of obj.shapes) {
if (!sh) continue;
const sid = (sh.id || '').toLowerCase();
if (sid.startsWith('button:') || sid.startsWith('leaver:')) {
const colon = sh.id.indexOf(':');
if (colon !== -1) {
const prefix = sh.id.slice(0, colon);
const targets = sh.id.slice(colon + 1).split(',');
const newTargets = [...targets];
for (const t of targets) {
if (gateLinksToAdd[t] && !newTargets.includes(gateLinksToAdd[t]))
newTargets.push(gateLinksToAdd[t]);
}
sh.id = `${prefix}:${newTargets.join(',')}`;
}
}
}
}
return result;
};
})();