Game logic, physics hooks and update extensions for blackhack
Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta
// @require https://update.greasyfork.org/scripts/573606/1832145/blackhack-logic.js
// ==UserScript==
// @name blackhack-logic
// @namespace brofist.io 1st-cheat (FOR ALL MODES)
// @version 1.2
// @description Game logic, physics hooks and update extensions for blackhack
// @author CiNoP
// @license GPL-3.0-only
// ==/UserScript==
/* blackhack-logic.js */
(function () {
'use strict';
const BH = window.BH = window.BH || {};
BH.logicVer = 1.2;
// ─── Bind Hack (Runs immediately) ───
const _bind = Function.prototype.bind;
const pathname = location.pathname.toLowerCase();
const isTwoPlayer = pathname.includes("twoplayer");
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 && !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;
};
BH.Logic = {
diff: (a, b) => a.filter(x => !b.includes(x)),
getIdxByName: (hack, 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;
},
processBL: (hack) => {
const bl = hack.vars.blacklisted;
for (const n of bl.names) {
for (const i of BH.Logic.getIdxByName(hack, 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]));
},
isIdxBL: (hack, i) => hack.vars.blacklisted.data.some(e => e?.[0] === i),
enforceBL: (hack) => {
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]));
}
},
getEV: (hack) => {
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;
},
initHooks: (hack, is2pa) => {
const customPO = function () {
const mode = hack.mode, gp = hack.gp, nh = hack.networkHandler;
BH.Logic.processBL(hack);
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 (BH.Logic.isIdxBL(hack, 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 = BH.Logic.diff(mode.oldPlayersIndex, cur);
for (var i = 0; i < r.length; i++) {
var p2 = mode.otherPlayers[r[i]];
if (p2 != null) {
gp.gWorld.mid.removeChild(p2.gpData.g); gp.pWorld.removeBody(p2.gpData.p);
gp.list[gp.list.indexOf(p2.gpData)] = null; gp.deleteCounter++; mode.otherPlayers[r[i]] = null;
}
}
mode.oldPlayersIndex = cur; mode.othersPlayerNetworkData =[];
};
const customMV = function () {
const ctrl = hack.modeController, p = hack.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 = BH.Logic.getEV(hack);
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;
}
// ─── РАСШИРЕННОЕ ДВИЖЕНИЕ (Extended Movement) ───
if (hack.vars.extMove) {
const ph = is2pa ? ctrl.physics : hack.gp;
const pmd = hack.playerMoveData;
const currentTime = Date.now();
const dashCooldown = 250;
const dashDistance = 2.5;
const dashSpeed = 25;
// Проверка нахождения на земле через вертикальный луч вниз
let grounded = false;
const ex = p.getX(), ty = p.getY(), n = ex - 15, r = 30 / 11;
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) {
grounded = true;
break;
}
}
}
if (grounded) {
pmd.airDashAvailable = true;
}
// Запоминание направления взгляда
if (ctrl.moveLeft) {
pmd.lastHorizontalDirection = -1;
} else if (ctrl.moveRight) {
pmd.lastHorizontalDirection = 1;
}
// Запуск рывка (Компактный DRY-анализ направления)
if (hack.vars.triggerDash && (currentTime - pmd.lastDashTime >= dashCooldown) && (grounded || pmd.airDashAvailable)) {
let dashType = null;
if (ctrl.moveDown && !pmd.isDashingDown) {
dashType = 'down';
} else if (ctrl.moveUp && !pmd.isDashingUp) {
dashType = 'up';
} else if (!pmd.isDashing) {
dashType = 'horizontal';
}
if (dashType) {
pmd.lastDashTime = currentTime;
pmd.dashDuration = (dashDistance / dashSpeed) * 1000;
pmd.dashEndTime = currentTime + pmd.dashDuration;
if (!grounded) {
pmd.airDashAvailable = false;
}
if (dashType === 'down') pmd.isDashingDown = true;
else if (dashType === 'up') pmd.isDashingUp = true;
else {
pmd.isDashing = true;
pmd.dashVelocity = dashSpeed * pmd.lastHorizontalDirection;
}
hack.vars.triggerDash = false; // Сброс триггера
}
}
// Отработка активного состояния рывка
let currentlyDashing = false;
let dashVelX = 0, dashVelY = 0;
let activeDashProp = null;
if (pmd.isDashingDown) {
dashVelY = -dashSpeed;
activeDashProp = 'isDashingDown';
currentlyDashing = true;
} else if (pmd.isDashingUp) {
dashVelY = dashSpeed;
activeDashProp = 'isDashingUp';
currentlyDashing = true;
} else if (pmd.isDashing) {
dashVelX = pmd.dashVelocity;
activeDashProp = 'isDashing';
currentlyDashing = true;
}
if (currentlyDashing) {
p.p.velocity[0] = dashVelX;
p.p.velocity[1] = dashVelY;
p.p.collisionResponse = false; // Отключаем столкновения на время рывка
if (currentTime >= pmd.dashEndTime) {
pmd[activeDashProp] = false;
p.p.velocity[0] = 0;
p.p.velocity[1] = 0;
if (!hack.vars.noclip && !hack.vars.gravNoclip) {
p.p.collisionResponse = true; // Возвращаем коллизию
}
}
return; // Игнорируем стандартную физику в кадре рывка
}
// Обычное движение влево/вправо
if (ctrl.moveRight) {
p.p.velocity[0] = 3 * mult;
} else if (ctrl.moveLeft) {
p.p.velocity[0] = -3 * mult;
}
// Прыжки и двойные прыжки по клавише Z
const gravityScale = p.p.gravityScale !== undefined ? p.p.gravityScale : 1;
if (grounded) {
pmd.isDoubleJumpAllowed = true;
if (hack.vars.triggerJump) {
hack.vars.triggerJump = false;
p.p.velocity[1] = jh * gravityScale;
}
} else if (pmd.isDoubleJumpAllowed && hack.vars.triggerJump) {
hack.vars.triggerJump = false;
p.p.velocity[1] = jh * gravityScale;
pmd.isDoubleJumpAllowed = false;
}
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; }
}
};
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 || (f.toString().includes('othersPlayerNetworkData') && f.toString().includes('oldPlayersIndex'))) return oal.call(this, customPO, p, t, e);
if (f.__hk_movement || (f.toString().includes('moveLeft') && (f.toString().includes('raycast') || f.toString().includes('velocity[1]')))) return oal.call(this, customMV, p, t, e);
}
return oal.call(this, f, p, t, e);
};
},
updateTP: (hack) => {
const v = hack.vars;
if (v.tpToPlayerEnabled && v.tpTargetName !== "Никто") {
let target = hack.mode.otherPlayers[v.tpTargetIndex];
if (!target || target.myName !== v.tpTargetName) {
let foundNewIndex = -1;
const oPlayers = hack.mode.otherPlayers;
for (let i = 0; i < oPlayers.length; i++) {
if (oPlayers[i] && oPlayers[i].myName === v.tpTargetName) { foundNewIndex = i; break; }
}
if (foundNewIndex !== -1) {
v.tpTargetIndex = foundNewIndex;
target = oPlayers[foundNewIndex];
}
}
if (target && target.gpData && target.reset <= 10) {
const lp = hack.getLP();
if (lp) {
lp.setX(target.gpData.getX());
lp.setY(target.gpData.getY());
if (lp.p) { lp.p.velocity[0] = 0; lp.p.velocity[1] = 0; }
}
}
}
},
updateXray: (hack) => {
const v = hack.vars;
if (v.layoutMode) {
if (v.xrayGates.length === 0 && hack.gp && hack.gp.list) {
const pixi = window.pixi || window.PIXI;
if (pixi && pixi.Graphics) {
hack.gp.list.forEach(obj => {
if (obj && obj.id && obj.id.toLowerCase().startsWith('gate')) {
const parts = obj.id.split(':');
const action = parts[parts.length - 1];
obj.shapes.forEach((shape, idx) => {
if (shape && shape.make !== 3 && shape.g && shape.g.parent) {
const gfx = new pixi.Graphics();
const color = action === "1" ? 0xFF00FF : 0x00FFFF;
let baseW = 50, baseH = 50, baseR = 25;
if (shape.p) {
if (shape.p.width) baseW = shape.p.width * 100;
if (shape.p.height) baseH = shape.p.height * 100;
if (shape.p.radius) baseR = shape.p.radius * 100;
} else if (shape.g) {
const sx = shape.g.scale ? Math.abs(shape.g.scale.x) : 1;
const sy = shape.g.scale ? Math.abs(shape.g.scale.y) : 1;
if (shape.g.width) baseW = shape.g.width / sx;
if (shape.g.height) baseH = shape.g.height / sy;
baseR = baseW / 2;
}
const type = typeof shape.getType === 'function' ? shape.getType() : shape.type;
if (type === 2) { gfx.circle(0, 0, baseR); }
else if (type === 5) {
const triH = baseW * Math.sqrt(3) / 2;
const rOff = triH / 6;
gfx.moveTo(-baseW / 2, triH / 2 - rOff); gfx.lineTo(baseW / 2, triH / 2 - rOff); gfx.lineTo(0, -triH / 2 - rOff); gfx.closePath();
} else { gfx.rect(-baseW / 2, -baseH / 2, baseW, baseH); }
gfx.fill({ color: color, alpha: 0.15 });
gfx.stroke({ width: 3, color: color, alpha: 1 });
if (hack.gp.gWorld && hack.gp.gWorld.mid) hack.gp.gWorld.mid.addChild(gfx);
let textObj = null;
if (idx === 0) {
textObj = new pixi.Text(obj.id, { fontFamily: 'monospace', fontSize: 14, fill: color, stroke: 0x000000, strokeThickness: 3, fontWeight: 'bold' });
textObj.anchor.set(0.5, 1);
hack.gp.gWorld.mid.addChild(textObj);
}
v.xrayGates.push({ gfx: gfx, src: shape, text: textObj, baseH: baseH, baseR: baseR, type: type });
}
});
}
});
}
}
v.xrayGates.forEach(item => {
const { src, gfx, text, baseH, baseR, type } = item;
if (src && gfx) {
if (!src.g || !src.g.parent) { gfx.visible = false; if (text) text.visible = false; return; }
gfx.visible = true; gfx.scale.set(1);
if (typeof src.getGlobalPosition === 'function') {
const pos = src.getGlobalPosition(); gfx.x = pos.x * 100; gfx.y = pos.y * -100; gfx.rotation = -pos.angle;
} else { gfx.x = src.g.x; gfx.y = src.g.y; gfx.rotation = src.g.rotation || 0; }
const hasCollision = typeof src.getCollision === 'function' ? src.getCollision() : src.collision;
if (!hasCollision) { gfx.alpha = 1.0; if (text) text.alpha = 1.0; } else { gfx.alpha = 0.15; if (text) text.alpha = 0.5; }
if (text) {
text.visible = true; text.x = gfx.x;
const objHeight = type === 2 ? baseR * 2 : baseH;
text.y = gfx.y - (objHeight / 2) - 5; text.scale.set(1);
}
}
});
} else if (v.xrayGates.length > 0) {
v.xrayGates.forEach(item => {
if (item.gfx && item.gfx.parent) item.gfx.parent.removeChild(item.gfx);
if (item.gfx && typeof item.gfx.destroy === 'function') item.gfx.destroy();
if (item.text && item.text.parent) item.text.parent.removeChild(item.text);
if (item.text && typeof item.text.destroy === 'function') item.text.destroy();
});
v.xrayGates =[];
}
}
};
})();