blackhack

Cheat for brofist.io

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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");
	}
})();