blackhack

Cheat for brofist.io

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name           blackhack
// @version        19.25
// @description    Cheat for brofist.io
// @author         CiNoP
// @match          *://*.brofist.io/*
// @require        https://update.greasyfork.org/scripts/571913/1787411/blackhack-utils.js
// @require        https://update.greasyfork.org/scripts/571914/1789017/blackhack-map.js
// @require        https://update.greasyfork.org/scripts/571915/1788773/blackhack-ui.js
// @icon           https://www.google.com/s2/favicons?sz=64&domain=brofist.io
// @grant          none
// @license        GPL-3.0-only
// @namespace      brofist.io 1st-cheat (FOR ALL MODES)
// ==/UserScript==
/* jshint esversion: 11 */
/* jshint asi: true */

(() => {
     'use strict';
    const BH = window.BH;

    console.log(`utilsVer:, ${BH.utilsVer.toFixed(2)}, uiVer: ${BH.uiVer.toFixed(2)}, mapVer: ${BH.mapVer.toFixed(2)}`)

    const _bind = Function.prototype.bind;
    const pathname = location.pathname.toLowerCase();
    const isTwoPlayer = pathname.includes("twoplayer");

	window.hack = {
		mode: null, modeController: null, gp: null, networkHandler: null, client: null,
		originalMakeMeGhost: null, originalSocketEmit: null,
		_isMapReloading: false, _mapSocket: null
	};

	const bm = { processOthers: false, movement: false, _allFound: false, _origPO: null, _origMV: null };
	Function.prototype.bind = function (ctx, ...args) {
		const b = _bind.apply(this, [ctx, ...args]);
		if (!bm._allFound) {
			try {
				const s = this.toString();
				if (!bm.processOthers && s.includes('othersPlayerNetworkData') && s.includes('oldPlayersIndex'))
					{ b.__hk_processOthers = true; bm.processOthers = true; bm._origPO = this; }
				if (!bm.movement && s.includes('moveLeft') && (s.includes('raycast') || s.includes('velocity[1]')))
					{ b.__hk_movement = true; bm.movement = true; bm._origMV = this; }
				if (bm.processOthers && bm.movement) bm._allFound = true;
			} catch (_) {}
		} else {
			if (this === bm._origPO) b.__hk_processOthers = true;
			if (this === bm._origMV) b.__hk_movement = true;
		}
		if (isTwoPlayer && !window.hack.mode && ctx && ctx.playerData && ctx.twoPlayerController) {
			window.hack.mode = ctx; window.hack.modeController = ctx.twoPlayerController;
			window.hack.gp = ctx.gp; window.hack.networkHandler = ctx.networkHandler;
			window.hack.client = ctx.client;
		}
		return b;
	};

    const hackReady = new Promise(resolve => {
        if (isTwoPlayer) {
            const c = () => { if (window.hack.mode) resolve(window.hack); else requestAnimationFrame(c); };
            requestAnimationFrame(c);
        } else {
            const traps = { pingCheckCount: ['networkHandler'], othersPlayerNetworkData: ['mode', 'modeController'] };
            let cap = 0; const tot = Object.keys(traps).length;
            for (const [p, tgts] of Object.entries(traps))
                Object.defineProperty(Object.prototype, p, {
                    set(v) {
                        delete Object.prototype[p]; this[p] = v;
                        for (const t of tgts) window.hack[t] = this;
                        if (++cap >= tot) { window.hack.gp = window.gp; window.hack.client = window.client; resolve(window.hack); }
                    }, configurable: true
                });
        }
    });

    // Запускаем main только когда ОБА готовы
    Promise.all([hackReady, BH.lzmaReady]).then(() => main(isTwoPlayer));

	function main(is2pa) {
		const hack = window.hack;
		Object.defineProperty(hack.client, 'runPhysics', { get: () => true, set: () => {} });
		const st = document.createElement('style'); st.textContent = '#startTime{display:none!important}'; document.head.appendChild(st);

		const getLP = () => is2pa ? hack.modeController.localPlayer : hack.modeController.player?.gpData ?? null;
		const getGPD = () => is2pa ? hack.mode.player?.gpData ?? null : getLP();
		const diff = (a, b) => a.filter(x => !b.includes(x));

		// ─── Socket ───
		function patchSocket(sock) {
			if (!sock || sock.__patched) return; sock.__patched = true;
			hack.originalSocketEmit = sock.emit;
			sock.emit = function (ev, ...a) { if (ev === "rGho" && hack.vars.imm) return; return hack.originalSocketEmit.apply(this, [ev, ...a]); };
			const oo = sock.onevent;
			sock.onevent = function (pkt) {
				if (pkt?.data?.[0] === 'changeMap') {
					const raw = pkt.data[1];
					if (!hack._isMapReloading) hack.vars.mapDataBackup = Array.isArray(raw) ? raw.slice() : raw;
					try {
						let d = raw;
						if (typeof d === 'string') d = JSON.parse(d);
						if (Array.isArray(d) && typeof d[0] === 'number' && window.LZMA) d = JSON.parse(window.LZMA.decompress(d));
						window.mapData = d;
					} catch (_) { window.mapData = raw; }
					if (hack.vars.layoutMode) {
						try {
							const cl = Array.isArray(raw) ? raw.slice() : raw;
							const clean = BH.parseMapToLayout(cl);
							const j = JSON.stringify(clean);
							pkt.data[1] = window.LZMA?.compress ? LZMA.compress(j, 1) : j;
						} catch (_) {}
					}
				}
				return oo.call(this, pkt);
			};
			hack._mapSocket = sock;
		}

		function reloadMap() {
			if (!hack.vars.mapDataBackup || !hack._mapSocket?.onevent) return false;
			hack._isMapReloading = true;
			const bk = Array.isArray(hack.vars.mapDataBackup) ? hack.vars.mapDataBackup.slice() : hack.vars.mapDataBackup;
			hack._mapSocket.onevent({ type: 2, data: ['changeMap', bk] });
			hack._isMapReloading = false; return true;
		}

		// ─── Physics helpers ───
		function applyNoclipState(en) {
			const lp = getLP();
			if (lp?.p) { lp.p.collisionResponse = !en; lp.p.massMultiplier[1] = en ? 0 : hack.vars.playerMass; }
		}
		function applyGravNoclipState(en) {
			const g = getGPD(), lp = getLP();
			if (lp?.p) { lp.p.collisionResponse = !en; lp.p.gravityScale = en ? 0 : 1; if (!en) lp.p.massMultiplier[1] = hack.vars.playerMass; }
			if (g?.p) g.p.gravityScale = en ? 0 : 1;
			if (!en) { g?.setAngle(0); if (lp && lp !== g) lp.setAngle(0); }
		}

		if (hack.networkHandler) {
			patchSocket(hack.networkHandler.gsSocket);
			const oc = hack.networkHandler.connectToGs;
			if (oc) hack.networkHandler.connectToGs = function (...a) { oc.apply(this, a); patchSocket(this.gsSocket); };
		}

		// ─── Settings & state ───
		const cfg = BH.loadSettings();
		hack.vars = {
			imm: false, noclip: false, gravNoclip: false,
			mult: { enabled: false, value: 1, uiValue: cfg.mult },
			gravNoclipGravScale: cfg.gravScale, jumpHeight: cfg.jumpHeight,
			playerMass: cfg.mass, playerDamping: cfg.damping,
			blacklisted: { names: [], data: [] },
			shiftUsedForBlacklist: false, arrowFirstPressed: null,
			layoutMode: false, mapDataBackup: null, layoutSavedPos: null,
			layoutAlpha: { collision: cfg.alphaCollision, poison: cfg.alphaPoison, functional: cfg.alphaFunctional, background: 0xCCCCCC },
			numpadMode: false
		};

		// ─── Keys ───
		hack.keyBinds = { F1:false,F2:false,F3:false,F5:false,SHIFT:false,HOME:false,END:false,ARROW_UP:false,ARROW_DOWN:false,NUMPAD_MINUS:false,DIGIT_1:false,DIGIT_2:false,DIGIT_3:false,DIGIT_5:false };
		hack.prevKeys = {};
		const keyMap = { F1:'F1',F2:'F2',F3:'F3',F5:'F5',Shift:'SHIFT',Home:'HOME',End:'END',ArrowUp:'ARROW_UP',ArrowDown:'ARROW_DOWN' };
		const codeMap = { NumpadSubtract:'NUMPAD_MINUS',Digit1:'DIGIT_1',Numpad1:'DIGIT_1',Digit2:'DIGIT_2',Numpad2:'DIGIT_2',Digit3:'DIGIT_3',Numpad3:'DIGIT_3',Digit5:'DIGIT_5',Numpad5:'DIGIT_5' };

		document.addEventListener('keydown', e => {
			const cm = codeMap[e.code];
			if (cm) { if (cm.startsWith('DIGIT_') && hack.vars.numpadMode) { const t = (e.target.tagName || '').toLowerCase(); if (t !== 'input' && t !== 'textarea' && !e.target.isContentEditable) e.preventDefault(); } if (cm === 'NUMPAD_MINUS') e.preventDefault(); hack.keyBinds[cm] = true; }
			const m = keyMap[e.key]; if (!m) return; e.preventDefault();
			if (m === 'ARROW_UP' && !hack.keyBinds.ARROW_UP) hack.vars.arrowFirstPressed = 'up';
			if (m === 'ARROW_DOWN' && !hack.keyBinds.ARROW_DOWN) hack.vars.arrowFirstPressed = 'down';
			hack.keyBinds[m] = true;
		});
		document.addEventListener('keyup', e => {
			const cm = codeMap[e.code]; if (cm) hack.keyBinds[cm] = false;
			const m = keyMap[e.key]; if (!m) return; hack.keyBinds[m] = false;
			if (m === 'ARROW_UP' || m === 'ARROW_DOWN') {
				if (!hack.keyBinds.ARROW_UP && !hack.keyBinds.ARROW_DOWN) hack.vars.arrowFirstPressed = null;
				else hack.vars.arrowFirstPressed = hack.keyBinds.ARROW_UP ? 'up' : 'down';
			}
		});
		window.addEventListener('blur', () => { for (const k in hack.keyBinds) hack.keyBinds[k] = false; hack.vars.arrowFirstPressed = null; });

		// ─── Blacklist ───
		function getIdxByName(n) { const r = [], o = hack.mode.otherPlayers; for (let i = 0; i < o.length; i++) if (o[i]?.myName === n) r.push(i); return r; }
		function isPlayerOnline(n) { const o = hack.mode.otherPlayers; if (!o) return false; for (let i = 0; i < o.length; i++) if (o[i]?.myName === n) return true; return false; }
		function processBL() {
			const bl = hack.vars.blacklisted;
			for (const n of bl.names) for (const i of getIdxByName(n)) if (!bl.data.some(e => e?.[0] === i && e?.[1] === n)) bl.data.push([i, n]);
			if (bl.data.length) bl.data = bl.data.filter(e => e?.[1] && bl.names.includes(e[1]));
		}
		function isIdxBL(i) { return hack.vars.blacklisted.data.some(e => e?.[0] === i); }
		function enforceBL() {
			const bl = hack.vars.blacklisted; if (!bl.data.length) return;
			const o = hack.mode.otherPlayers;
			for (const [i] of bl.data) { const p = o[i]; if (!p?.gpData) continue; p.gpData.g.visible = false; if (p.gpData.p) p.gpData.p.collisionResponse = false; }
			const msgs = hack.mode.othersPlayerMsgNetworkData;
			if (msgs) { const s = new Set(bl.data.map(e => e[0])); for (let i = 0; i < msgs.length; i++) msgs[i] = msgs[i].filter(m => !s.has(m[0])); }
		}

		// ─── Custom processOthers ───
		const customPO = function () {
			const mode = hack.mode, gp = hack.gp, nh = hack.networkHandler;
			processBL(); if (!mode.othersPlayerNetworkData.length) return;
			var cur = [];
			for (var x = 0; x < mode.othersPlayerNetworkData.length; x++) {
				for (var y = 0; y < mode.othersPlayerNetworkData[x].length; y++) {
					var d = mode.othersPlayerNetworkData[x][y], idx = d[0], xV = d[1], yV = d[2], xA = d[3], yA = d[4];
					if (isIdxBL(idx)) continue;
					if (mode.otherPlayers[idx] == null) {
						mode.oldPlayersIndex.push(idx);
						var p = {}; p.myName = ""; p.mySkin = 0; p.reset = Infinity;
						p.gpData = is2pa ? mode.basePlayer.createPlayer(mode.playerData) : mode.createPlayer();
						p.gpData.g.myIndex = idx; mode.otherPlayers[idx] = p;
						if (nh?.gsSocket) nh.gsSocket.emit("rBio", idx);
						gp.gWorld.removeChild(p.gpData.g); gp.gWorld.mid.addChild(p.gpData.g);
					}
					if (10 < mode.otherPlayers[idx].reset) { mode.otherPlayers[idx].gpData.setX(xA); mode.otherPlayers[idx].gpData.setY(yA); mode.otherPlayers[idx].reset = 0; }
					mode.otherPlayers[idx].reset++;
					mode.otherPlayers[idx].gpData.p.velocity[0] = xV;
					mode.otherPlayers[idx].gpData.p.velocity[1] = yV;
					cur.push(idx);
				}
			}
			var r = diff(mode.oldPlayersIndex, cur);
			for (var i = 0; i < r.length; i++) { var p = mode.otherPlayers[r[i]]; if (p != null) { gp.gWorld.mid.removeChild(p.gpData.g); gp.pWorld.removeBody(p.gpData.p); gp.list[gp.list.indexOf(p.gpData)] = null; gp.deleteCounter++; mode.otherPlayers[r[i]] = null; } }
			mode.oldPlayersIndex = cur; mode.othersPlayerNetworkData = [];
		};

		// ─── Custom movement ───
		function getEV() {
			const u = hack.keyBinds.ARROW_UP, d = hack.keyBinds.ARROW_DOWN;
			if (u && d) { if (hack.vars.arrowFirstPressed === 'up') return 'down'; if (hack.vars.arrowFirstPressed === 'down') return 'up'; return null; }
			return u ? 'up' : d ? 'down' : null;
		}

		const customMV = function () {
			const ctrl = hack.modeController, p = getLP();
			const mult = hack.vars.mult.value, jh = hack.vars.jumpHeight, gs = hack.vars.gravNoclipGravScale;
			if (!p || !ctrl) return;

			if (hack.vars.noclip) {
				p.p.velocity[0] = ctrl.moveRight ? 3 * mult : ctrl.moveLeft ? -3 * mult : 0;
				p.p.velocity[1] = ctrl.moveUp ? 3 * mult : ctrl.moveDown ? -3 * mult : 0;
				return;
			}

			if (hack.vars.gravNoclip) {
				p.p.velocity[0] = ctrl.moveRight ? 3 * mult : ctrl.moveLeft ? -3 * mult : 0;
				const v = getEV();
				if (v === 'up') { p.p.gravityScale = -gs; p.setAngle(180); }
				else if (v === 'down') { p.p.gravityScale = gs; p.setAngle(0); }
				else p.p.gravityScale = 0;
				const gd = p.p.gravityScale;
				let sj = false, jd = 0, rsY, reY;
				const ex = p.getX(), ty = p.getY(), hH = 50, rL = 50, n = ex - 15, r = 30 / 11;
				if (gd > 0 && ctrl.moveUp) { sj = true; jd = jh; rsY = ty + (hH - 1); reY = rsY + rL; }
				else if (gd < 0 && ctrl.moveDown) { sj = true; jd = -jh; rsY = ty - (hH - 1); reY = rsY - rL; }
				if (sj) {
					const ph = is2pa ? ctrl.physics : hack.gp;
					for (let i = 0; i < 12; i++) {
						const ox = n + i * r;
						p.ray.from = [ph.xAxis(ox, 0), ph.yAxis(rsY, 0)]; p.ray.to = [ph.xAxis(ox, 0), ph.yAxis(reY, 0)];
						p.ray.update(); p.ray.result.reset(); p.ray.hitPoint = [Infinity, Infinity];
						if (hack.gp.pWorld.raycast(p.ray.result, p.ray))
							if (p.ray.result.shape.ref.getCollision() && p.ray.result.getHitDistance(p.ray) < 0.15) { p.p.velocity[1] = jd; break; }
					}
				}
				return;
			}

			if (ctrl.moveRight) p.p.velocity[0] = 3;
			if (ctrl.moveLeft) p.p.velocity[0] = -3;
			if (!ctrl.moveUp) return;
			const ex = p.getX(), ty = p.getY(), n = ex - 15, r = 30 / 11;
			const ph = is2pa ? ctrl.physics : hack.gp;
			for (let i = 0; i < 12; i++) {
				const ox = n + i * r, oy1 = ty + 49, oy2 = oy1 + 50;
				p.ray.from = [ph.xAxis(ox, 0), ph.yAxis(oy1, 0)]; p.ray.to = [ph.xAxis(ox, 0), ph.yAxis(oy2, 0)];
				p.ray.update(); p.ray.result.reset(); p.ray.hitPoint = [Infinity, Infinity];
				if (hack.gp.pWorld.raycast(p.ray.result, p.ray))
					if (p.ray.result.shape.ref.getCollision() && p.ray.result.getHitDistance(p.ray) < 0.05) { p.p.velocity[1] = jh; break; }
			}
		};

		// ─── Inject into loop ───
		let poI = false, mvI = false;
		function cih() {
			const lf = hack.client.loopFunctions;
			if (!poI) { const i = lf.findIndex(e => e?.fun?.__hk_processOthers || (e?.fun?.toString().includes('othersPlayerNetworkData') && e.fun.toString().includes('oldPlayersIndex'))); if (i !== -1) { lf[i].fun = customPO; poI = true; } }
			if (!mvI) { const i = lf.findIndex(e => e?.fun?.__hk_movement || (e?.fun?.toString().includes('moveLeft') && (e.fun.toString().includes('raycast') || e.fun.toString().includes('velocity[1]')))); if (i !== -1) { lf[i].fun = customMV; mvI = true; } else if (is2pa && lf.length > 7) { lf[7].fun = customMV; mvI = true; } }
			if (!poI || !mvI) requestAnimationFrame(cih);
		}
		cih();

		const oal = hack.client.addLoopFunction;
		hack.client.addLoopFunction = function (f, p, t, e) {
			if (f) {
				if (f.__hk_processOthers) return oal.call(this, customPO, p, t, e);
				if (f.__hk_movement) return oal.call(this, customMV, p, t, e);
				const s = f.toString();
				if (s.includes('othersPlayerNetworkData') && s.includes('oldPlayersIndex')) return oal.call(this, customPO, p, t, e);
				if (s.includes('moveLeft') && (s.includes('raycast') || s.includes('velocity[1]'))) return oal.call(this, customMV, p, t, e);
			}
			return oal.call(this, f, p, t, e);
		};

		// ─── KickGui patch ───
		if (hack.mode.createKickGui) {
			const _o = hack.mode.createKickGui;
			hack.mode.createKickGui = function (e) {
				const _on = e.on.bind(e);
				e.on = function (ev, fn) {
					return _on(ev, function (...a) {
						if (hack.keyBinds.SHIFT) {
							const nm = this.ref.refP.name.getText();
							if (!hack.vars.blacklisted.names.includes(nm)) {
								hack.vars.blacklisted.names.push(nm);
								hack.vars.blacklisted.data.push([this.ref.refP.g.myIndex, nm]);
								this.ref.refP.g.visible = false;
								if (this.ref.refP.p) this.ref.refP.p.collisionResponse = false;
							}
							hack.vars.shiftUsedForBlacklist = true;
							if (hack._refreshBL) hack._refreshBL();
							return;
						}
						return fn.apply(this, a);
					});
				};
				_o.call(this, e); e.on = _on;
			};
		}

		// ─── Restore after map reload ───
		function restoreAfterReload() {
			let f = 0;
			const r = () => {
				if (++f < 3) { requestAnimationFrame(r); return; }
				if (hack.vars.noclip) applyNoclipState(true);
				if (hack.vars.gravNoclip) applyGravNoclipState(true);
				if (hack.vars.imm) { const g = getGPD(); if (g) g.me = void 0; }
				const pos = hack.vars.layoutSavedPos;
				if (pos) { const lp = getLP(); if (lp) { lp.setX(pos.x); lp.setY(pos.y); if (lp.p) { lp.p.velocity[0] = 0; lp.p.velocity[1] = 0; } } }
			};
			requestAnimationFrame(r);
		}

		// ─── Handlers ───
		hack.funcs = { handlers: {
			toggleImm: en => {
				hack.vars.imm = en; const g = getGPD(); if (g) g.me = en ? void 0 : true;
				if (en) { if (hack.mode.makeMeGhost && !hack.originalMakeMeGhost) hack.originalMakeMeGhost = hack.mode.makeMeGhost; if (hack.mode.makeMeGhost) hack.mode.makeMeGhost = function () {}; }
				else if (hack.originalMakeMeGhost) hack.mode.makeMeGhost = hack.originalMakeMeGhost;
			},
			toggleNoclip: en => {
				hack.vars.noclip = en; applyNoclipState(en);
				if (hack.vars.mult.enabled) hack.vars.mult.value = en ? hack.vars.mult.uiValue : 1;
				if (en && hack.vars.gravNoclip) { hack.vars.gravNoclip = false; applyGravNoclipState(false); }
			},
			toggleGravNoclip: en => {
				if (en && hack.vars.noclip) hack.funcs.handlers.toggleNoclip(false);
				hack.vars.gravNoclip = en; applyGravNoclipState(en);
				if (hack.vars.mult.enabled) hack.vars.mult.value = en ? hack.vars.mult.uiValue : 1;
			},
			toggleMult: en => {
				if (!hack.vars.noclip && !hack.vars.gravNoclip) return;
				hack.vars.mult.enabled = en; hack.vars.mult.value = en ? hack.vars.mult.uiValue : 1;
			},
			toggleLayout: en => {
				hack.vars.layoutMode = en;
				const lp = getLP(); if (lp) hack.vars.layoutSavedPos = { x: lp.getX(), y: lp.getY() };
				if (reloadMap()) restoreAfterReload();
				try {
					const r = hack.gp.renderer || hack.gp.app?.renderer;
					if (r) { if (en) { if (hack._origBg === undefined) hack._origBg = r.backgroundColor; r.backgroundColor = hack.vars.layoutAlpha.background; } else if (hack._origBg !== undefined) r.backgroundColor = hack._origBg; }
				} catch (_) {}
			},
			tpPlayer: (x, y) => { const lp = getLP(); if (lp) { lp.setX(x); lp.setY(y); } },
			toggleNumpadMode: en => { hack.vars.numpadMode = en; },
			blacklistAdd: n => {
				n = (n || '').trim(); if (!n || hack.vars.blacklisted.names.includes(n)) return;
				hack.vars.blacklisted.names.push(n);
				for (const i of getIdxByName(n)) { hack.vars.blacklisted.data.push([i, n]); const p = hack.mode.otherPlayers[i]; if (p?.gpData) { p.gpData.g.visible = false; if (p.gpData.p) p.gpData.p.collisionResponse = false; } }
				if (hack._refreshBL) hack._refreshBL();
			},
			blacklistRemove: n => {
				const ni = hack.vars.blacklisted.names.indexOf(n); if (ni === -1) return;
				hack.vars.blacklisted.names.splice(ni, 1);
				for (const [i] of hack.vars.blacklisted.data.filter(e => e?.[1] === n)) { const p = hack.mode.otherPlayers[i]; if (p?.gpData) { p.gpData.g.visible = true; if (p.gpData.p) p.gpData.p.collisionResponse = true; } }
				hack.vars.blacklisted.data = hack.vars.blacklisted.data.filter(e => e[1] !== n);
				if (hack._refreshBL) hack._refreshBL();
			}
		}};
		window.hackFunctions = hack.funcs.handlers;

		// ─── Expose for UI ───
		hack.getLP = getLP;
		hack.reloadMap = reloadMap;
		hack.restoreAfterReload = restoreAfterReload;
		hack.isPlayerOnline = isPlayerOnline;

		// ─── Update loop ───
		function updateLoop() {
			const binds = hack.keyBinds, prev = hack.prevKeys, fn = hack.funcs.handlers, v = hack.vars;
			const jp = k => binds[k] && !prev[k], jr = k => !binds[k] && prev[k];

			if (jp('NUMPAD_MINUS')) fn.toggleNumpadMode(!v.numpadMode);
			if (is2pa) {
				if (jp('HOME')) { const s = hack.gp.list.find(i => i?.id?.includes('spawn')); if (s) fn.tpPlayer(s.getX(), s.getY()); }
				if (jp('END')) { const d = hack.gp.list.find(i => i?.id?.includes('door')); if (d) fn.tpPlayer(d.getX(), d.getY()); }
			}
			if (jp('F1') || (v.numpadMode && jp('DIGIT_1'))) fn.toggleNoclip(!v.noclip);
			if (jp('F2') || (v.numpadMode && jp('DIGIT_2'))) fn.toggleImm(!v.imm);
			if (jp('F3') || (v.numpadMode && jp('DIGIT_3'))) fn.toggleGravNoclip(!v.gravNoclip);
			if (jp('F5') || (v.numpadMode && jp('DIGIT_5'))) fn.toggleLayout(!v.layoutMode);
			if (jr('SHIFT')) { if (v.shiftUsedForBlacklist) v.shiftUsedForBlacklist = false; else fn.toggleMult(!v.mult.enabled); }

			if (v.noclip) applyNoclipState(true);
			if (v.gravNoclip) { const lp = getLP(); if (lp?.p) lp.p.collisionResponse = false; }
			if (is2pa && v.imm) { const g = getGPD(); if (g?.me !== undefined) g.me = void 0; }
			if (!v.noclip && !v.gravNoclip) { const lp = getLP(); if (lp?.p) { lp.p.massMultiplier[1] = v.playerMass; lp.p.damping = v.playerDamping; } }
			enforceBL();

			const dNc = document.getElementById('hk-d-nc'), dIm = document.getElementById('hk-d-im');
			const dGn = document.getElementById('hk-d-gn'), dLm = document.getElementById('hk-d-lm');
			if (dNc) dNc.style.background = v.noclip ? '#0f0' : '#f44';
			if (dIm) dIm.style.background = v.imm ? '#0f0' : '#f44';
			if (dGn) dGn.style.background = v.gravNoclip ? '#0f0' : '#f44';
			if (dLm) dLm.style.background = v.layoutMode ? '#0f0' : '#f44';

			for (const k in binds) prev[k] = binds[k];
			requestAnimationFrame(updateLoop);
		}
		requestAnimationFrame(updateLoop);

		// ─── Speed fix ───
		const cSS = () => { const lf = hack.client.loopFunctions; for (let i = 0; i < lf.length; i++) if (lf[i] && (lf[i].timeOut === 200 || lf[i].timeOut === 150)) { lf[i].timeOut = 10; return; } requestAnimationFrame(cSS); };
		cSS();

		BH.createZoom();
		BH.createUI();
		console.log("blackhack v19.16 loaded");
	}
})();