PidorasiClient — Clean Architecture | Auto-Clan Accept | Custom Kill-Chat | Turret & Bull Insta | Auto-Trap | 4x Mills | Clan ESP
// ==UserScript==
// @name PidorasiClient
// @namespace pidorasi-client
// @version 9.3
// @description PidorasiClient — Clean Architecture | Auto-Clan Accept | Custom Kill-Chat | Turret & Bull Insta | Auto-Trap | 4x Mills | Clan ESP
// @match *://moomoo.io/*
// @match *://*.moomoo.io/*
// @require https://greasyfork.org/scripts/423602-msgpack/code/msgpack.js
// @grant none
// @author xw5yt
// @icon blob:https://gemini.google.com/bc214e5f-738a-4657-b038-4aad20733f22
// @run-at document-start
// @license MIT
// ==/UserScript==
/* global msgpack */
(function () {
'use strict';
// ==========================================
// CONSTANTS & THEMES
// ==========================================
const THEME = {
bg: 'rgba(0, 0, 0, 0.4)',
surface: 'rgba(0, 0, 0, 0.45)',
border: '1.5px solid rgba(255, 42, 42, 0.65)',
borderLight: '1px solid rgba(255, 42, 42, 0.25)',
shadow: '0 0 16px rgba(255, 0, 0, 0.25)',
blur: 'blur(12px)',
accent: '#ff4d6d',
text: '#ffb3c1',
title: '#ff1a1a',
pink: '#ff85a2',
clanLime: '#8ecc51'
};
const BULL = 7;
const BOOSTER = 12;
const MONKEY_TAIL = 11;
const SOLDIER_HELMET = 6;
const WINTER_CAP = 15;
const TURRET_GEAR = 53;
const MUSKET_ID = 15;
const PIT_TRAP_ID = 15;
const TICK_COOLDOWN = 135;
// ==========================================
// STATE VARIABLES
// ==========================================
let bullHat = false;
let monkeyTail = false;
let soldierHelmet = false;
let userActiveHat = 0;
let currentHatId = 0;
let previousHatId = 0;
let lastEquipTime = 0;
let cameraZoom = 1.0;
let predictMe = true;
let predictPlayers = true;
let predictMobs = true;
let showBuildingOwners = true;
let autoAimEnabled = true;
let autoTrapEnabled = false;
let lastTrapTime = 0;
let isRightMouseDown = false;
let isBreakingFast = false;
let autoWindmillsEnabled = false;
let isPlacingMills = false;
let autoClanAccept = true;
let killChatEnabled = true;
let killChatMessage = localStorage.getItem('pcKillMsg') || 'PidorasiClient on top! GG';
let myKills = 0;
let autoHealEnabled = true;
let myHealth = 100;
let lastHealth = 100;
let lastHealTime = 0;
let isHealing = false;
let isMouseDown = false;
let isInstaShooting = false;
// Плавный угол мыши без сетевых задержек
let mouseX = window.innerWidth / 2;
let mouseY = window.innerHeight / 2;
let currentMouseAngle = 0;
let autoBiomeHatEnabled = true;
let isInWinter = false;
window.myPlayer = { x: null, y: null, sid: null, team: null, dir: 0 };
const nearbyEnemies = new Map();
const nearbyMobs = new Map();
const playerNames = new Map();
const playerTeams = new Map();
const placedBuildings = new Map();
let activeSocket = null;
let socketKey = null;
let c2sTable = null;
let s2cDecTable = {};
let globalSeq = 0;
let primaryWeapon = 0;
let secondaryWeapon = 9;
let cachedServers = [];
let overlayCanvas = null;
let overlayCtx = null;
let currentTab = 'keybinds';
// Keybinds Setup
const DEFAULT_BINDS = {
openMenu: 'ShiftRight',
turretInsta: 'KeyT',
instaMusket: 'KeyR',
autoWindmills: 'KeyN',
toggleAutoTrap: 'NONE',
toggleHat: 'KeyV',
toggleSoldier: 'KeyB',
toggleTail: 'KeyC',
toggleHeal: 'KeyH'
};
const userBinds = Object.assign({}, DEFAULT_BINDS, JSON.parse(localStorage.getItem('pcKeybinds') || '{}'));
let rebindingAction = null;
// ==========================================
// DIRECT MOUSE TRACKING
// ==========================================
window.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
currentMouseAngle = Math.atan2(mouseY - (window.innerHeight / 2), mouseX - (window.innerWidth / 2));
}, { capture: true, passive: true });
window.addEventListener('mousedown', (e) => {
if (e.button === 0) isMouseDown = true;
if (e.button === 2) isRightMouseDown = true;
}, { capture: true });
window.addEventListener('mouseup', (e) => {
if (e.button === 0) isMouseDown = false;
if (e.button === 2) isRightMouseDown = false;
}, { capture: true });
// ==========================================
// HELPER FUNCTIONS
// ==========================================
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function saveBinds() {
localStorage.setItem('pcKeybinds', JSON.stringify(userBinds));
}
function formatKey(code) {
if (!code || code === 'NONE') return 'NONE';
if (code.startsWith('Key')) return code.slice(3);
if (code.startsWith('Digit')) return code.slice(5);
if (code === 'ShiftRight') return 'R-Shift';
if (code === 'ShiftLeft') return 'L-Shift';
if (code === 'ControlRight') return 'R-Ctrl';
if (code === 'ControlLeft') return 'L-Ctrl';
if (code === 'AltRight') return 'R-Alt';
if (code === 'AltLeft') return 'L-Alt';
if (code === 'Backquote') return '~';
return code;
}
function isPlayerInGame() {
const gameUI = document.getElementById('gameUI');
const diedText = document.getElementById('diedText');
const mainMenu = document.getElementById('mainMenu');
const isUIActive = gameUI && gameUI.style.display === 'block';
const isDead = diedText && diedText.style.display === 'block';
const isMenuHidden = mainMenu && (mainMenu.style.display === 'none' || mainMenu.classList.contains('hidden'));
return isUIActive && !isDead && isMenuHidden;
}
function equipHat(id) {
if (typeof window.storeEquip === 'function') {
window.storeEquip(id, 0);
}
}
function getFoodIndex() {
const cheese = document.getElementById('actionBarItem18');
if (cheese && cheese.style.display !== 'none') return 2;
const cookie = document.getElementById('actionBarItem17');
if (cookie && cookie.style.display !== 'none') return 1;
return 0;
}
function getActiveSecondaryWeapon() {
if (secondaryWeapon && secondaryWeapon >= 9) return secondaryWeapon;
for (let id = 15; id >= 9; id--) {
const el = document.getElementById('actionBarItem' + id);
if (el && el.style.display !== 'none') return id;
}
return MUSKET_ID;
}
function getWindmillIndex() {
const pm = document.getElementById('actionBarItem28');
if (pm && pm.style.display !== 'none') return 12;
const fm = document.getElementById('actionBarItem27');
if (fm && fm.style.display !== 'none') return 11;
return 10;
}
function getTrapIndex() {
return PIT_TRAP_ID;
}
// ==========================================
// CRYPTOGRAPHY & NETWORK PACKETS
// ==========================================
const bo = ["M", "D", "9", "e", "F", "z", "H", "K", "L", "N", "b", "P", "Q", "c", "6", "S", "0"];
const To = ["A", "B", "C", "D", "E", "a", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "X", "Y", "Z", "g", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
function Co(e) {
return function () {
e |= 0;
e = e + 1831565813 | 0;
let t = Math.imul(e ^ e >>> 15, 1 | e);
return t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t,
((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
function Oi(e, t) {
const i = e.length;
const s = e.map((d, l) => l);
const n = Co(t >>> 0);
for (let d = i - 1; d > 0; d--) {
const l = Math.floor(n() * (d + 1));
const c = s[d];
s[d] = s[l];
s[l] = c;
}
const a = {};
for (let d = 0; d < i; d++) a[e[d]] = s[d];
return a;
}
function createTables(seed) {
const t = (seed ^ Math.imul(1, 2654435761)) >>> 0;
c2sTable = Oi(bo, t);
const s2c_seed = (t ^ 2246822507) >>> 0;
const s2c_len = To.length;
const s = To.map((d, l) => l);
const n = Co(s2c_seed);
for (let d = s2c_len - 1; d > 0; d--) {
const l = Math.floor(n() * (d + 1));
const c = s[d];
s[d] = s[l];
s[l] = c;
}
s2cDecTable = {};
for (let d = 0; d < s2c_len; d++) {
s2cDecTable[s[d]] = To[d];
}
}
function Ro(e) {
const t = new Uint8Array(e.length / 2);
for (let i = 0; i < t.length; i++) t[i] = parseInt(e.substr(i * 2, 2), 16);
return t;
}
const Do = new Uint32Array([1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298]);
function j(e, t) { return e >>> t | e << 32 - t; }
function Vt(e) {
const t = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
const i = e.length, s = i * 8, n = i + 9, a = new Uint8Array(Math.ceil(n / 64) * 64);
a.set(e); a[i] = 128;
const o = new DataView(a.buffer);
o.setUint32(a.length - 4, s >>> 0, !1);
o.setUint32(a.length - 8, Math.floor(s / 4294967296), !1);
const d = new Uint32Array(64);
for (let m = 0; m < a.length; m += 64) {
for (let w = 0; w < 16; w++) d[w] = o.getUint32(m + w * 4, !1);
for (let w = 16; w < 64; w++) {
const T = j(d[w - 15], 7) ^ j(d[w - 15], 18) ^ d[w - 15] >>> 3;
const A = j(d[w - 2], 17) ^ j(d[w - 2], 19) ^ d[w - 2] >>> 10;
d[w] = d[w - 16] + T + d[w - 7] + A | 0;
}
let g = t[0], h = t[1], u = t[2], p = t[3], x = t[4], I = t[5], P = t[6], f = t[7];
for (let w = 0; w < 64; w++) {
const T = j(x, 6) ^ j(x, 11) ^ j(x, 25);
const A = x & I ^ ~x & P;
const V = f + T + A + Do[w] + d[w] | 0;
const W = j(g, 2) ^ j(g, 13) ^ j(g, 22);
const S = g & h ^ g & u ^ h & u;
const H = W + S | 0;
f = P; P = I; I = x; x = p + V | 0; p = u; u = h; h = g; g = V + H | 0;
}
t[0] = t[0] + g | 0; t[1] = t[1] + h | 0; t[2] = t[2] + u | 0; t[3] = t[3] + p | 0;
t[4] = t[4] + x | 0; t[5] = t[5] + I | 0; t[6] = t[6] + P | 0; t[7] = t[7] + f | 0;
}
const l = new Uint8Array(32), c = new DataView(l.buffer);
for (let m = 0; m < 8; m++) c.setUint32(m * 4, t[m], !1);
return l;
}
function Ao(e, t) {
let i = e;
if (i.length > 64) i = Vt(i);
const s = new Uint8Array(64); s.set(i);
const n = new Uint8Array(64 + t.length), a = new Uint8Array(64 + 32);
for (let o = 0; o < 64; o++) { n[o] = s[o] ^ 54; a[o] = s[o] ^ 92; }
n.set(t, 64);
a.set(Vt(n), 64);
return Vt(a);
}
function Eo(e, t) { return Ao(e, t).subarray(0, 6); }
function encodeSeq(seq) {
if (seq < 128) {
return new Uint8Array([seq]);
} else if (seq < 256) {
return new Uint8Array([0xcc, seq]);
} else if (seq < 65536) {
return new Uint8Array([0xcd, (seq >> 8) & 0xff, seq & 0xff]);
} else {
return new Uint8Array([0xce, (seq >> 24) & 0xff, (seq >> 16) & 0xff, (seq >> 8) & 0xff, seq & 0xff]);
}
}
function handleIncomingData(ws, rawData) {
try {
if (rawData && typeof msgpack !== 'undefined') {
const u8 = new Uint8Array(rawData);
const decoded = msgpack.decode(u8);
if (!decoded) return;
const packetCode = decoded[0];
const packetArgs = decoded[1];
if (packetCode === "io-init") {
activeSocket = ws;
nearbyEnemies.clear();
nearbyMobs.clear();
placedBuildings.clear();
playerNames.clear();
playerTeams.clear();
myKills = 0;
const g = packetArgs;
if (g && g[3] === 1) {
socketKey = Ro(g[2]);
createTables(g[1] >>> 0);
globalSeq = 0;
}
return;
}
const opName = typeof packetCode === 'number' ? s2cDecTable[packetCode] : packetCode;
if (opName === "C") {
window.myPlayer.sid = packetArgs[0];
} else if (opName === "D") {
if (packetArgs[1]) {
window.myPlayer.sid = packetArgs[0][1];
}
if (packetArgs[0] && packetArgs[0][1] && packetArgs[0][2]) {
playerNames.set(packetArgs[0][1], packetArgs[0][2]);
}
} else if (opName === "V") {
if (packetArgs[1]) {
const weapons = packetArgs[0];
if (Array.isArray(weapons)) {
if (weapons[0] !== undefined) primaryWeapon = weapons[0];
if (weapons[1] !== undefined) secondaryWeapon = weapons[1];
}
}
} else if (opName === "3") {
window.myPlayer.team = packetArgs[0];
} else if (opName === "2") {
// Auto-Clan Join Request Handler
if (autoClanAccept && packetArgs && packetArgs[0]) {
const requesterSid = packetArgs[0];
sendDirectPacket("P", [requesterSid, 1]);
}
} else if (opName === "N") {
// Resource & Kill Tracker
if (packetArgs && packetArgs[0] === "kills") {
const newKills = packetArgs[1];
if (typeof newKills === 'number' && newKills > myKills) {
myKills = newKills;
if (killChatEnabled && killChatMessage) {
sendDirectPacket("6", [killChatMessage.slice(0, 30)]);
}
}
}
} else if (opName === "P") {
nearbyEnemies.clear();
nearbyMobs.clear();
autoWindmillsEnabled = false;
myKills = 0;
} else if (opName === "a") {
const data = packetArgs[0];
if (Array.isArray(data)) {
const activeSids = new Set();
for (let i = 0; i < data.length; i += 13) {
const sid = data[i];
const x = data[i + 1];
const y = data[i + 2];
const dir = data[i + 3];
const team = data[i + 7];
const skinId = data[i + 9];
activeSids.add(sid);
if (team) playerTeams.set(sid, team);
if (sid === window.myPlayer.sid) {
window.myPlayer.x = x;
window.myPlayer.y = y;
window.myPlayer.dir = dir;
checkBiome();
if (!isInstaShooting && skinId !== BULL && skinId !== TURRET_GEAR && skinId !== WINTER_CAP) {
userActiveHat = skinId;
currentHatId = skinId;
}
} else {
nearbyEnemies.set(sid, {
x, y, dir, team,
isTeammate: Boolean(window.myPlayer.team && team === window.myPlayer.team),
lastSeen: Date.now()
});
}
}
for (const sid of nearbyEnemies.keys()) {
if (!activeSids.has(sid)) nearbyEnemies.delete(sid);
}
}
} else if (opName === "I") {
const data = packetArgs[0];
if (Array.isArray(data)) {
const activeMobSids = new Set();
for (let i = 0; i < data.length; i += 7) {
const sid = data[i];
const x = data[i + 2];
const y = data[i + 3];
const dir = data[i + 4];
activeMobSids.add(sid);
nearbyMobs.set(sid, { x, y, dir, lastSeen: Date.now() });
}
for (const sid of nearbyMobs.keys()) {
if (!activeMobSids.has(sid)) nearbyMobs.delete(sid);
}
}
} else if (opName === "H") {
const data = packetArgs[0];
if (Array.isArray(data)) {
for (let i = 0; i < data.length; i += 8) {
const objSid = data[i];
const x = data[i + 1];
const y = data[i + 2];
const ownerSid = data[i + 7];
if (ownerSid >= 0) {
placedBuildings.set(objSid, { x, y, ownerSid });
}
}
}
} else if (opName === "Q") {
placedBuildings.delete(packetArgs[0]);
} else if (opName === "R") {
const ownerSid = packetArgs[0];
for (const [sid, obj] of placedBuildings.entries()) {
if (obj.ownerSid === ownerSid) placedBuildings.delete(sid);
}
}
}
} catch (e) {}
}
const nativeSend = WebSocket.prototype.send;
WebSocket.prototype.send = function (data) {
if (this.readyState === WebSocket.OPEN) {
activeSocket = this;
}
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
const u8 = new Uint8Array(data);
if (u8.length > 6 && socketKey && c2sTable) {
try {
const payload = u8.subarray(6);
const decoded = msgpack.decode(payload);
if (Array.isArray(decoded) && typeof decoded[2] === 'number') {
if (decoded[2] <= globalSeq) {
globalSeq++;
const oldSeq = decoded[2];
const oldSeqLen = oldSeq < 128 ? 1 : (oldSeq < 256 ? 2 : (oldSeq < 65536 ? 3 : 5));
const basePayload = payload.subarray(0, payload.length - oldSeqLen);
const newSeqBytes = encodeSeq(globalSeq);
const newPayload = new Uint8Array(basePayload.length + newSeqBytes.length);
newPayload.set(basePayload, 0);
newPayload.set(newSeqBytes, basePayload.length);
const newHmac = Eo(socketKey, newPayload);
const newBuf = new Uint8Array(6 + newPayload.length);
newBuf.set(newHmac, 0);
newBuf.set(newPayload, 6);
return nativeSend.call(this, newBuf);
} else {
globalSeq = decoded[2];
}
if (decoded[0] === c2sTable["c"]) {
const args = decoded[1];
if (args && args[0] === 0 && args[2] === 0 && !isInstaShooting) {
userActiveHat = args[1];
currentHatId = args[1];
}
}
if (decoded[0] === c2sTable["z"]) {
const args = decoded[1];
if (args && (args[1] === true || args[1] === 1)) {
if (args[0] < 9) {
primaryWeapon = args[0];
} else {
secondaryWeapon = args[0];
}
}
}
}
} catch (e) {}
}
}
return nativeSend.apply(this, arguments);
};
try {
const desc = Object.getOwnPropertyDescriptor(WebSocket.prototype, 'onmessage');
Object.defineProperty(WebSocket.prototype, 'onmessage', {
configurable: true,
enumerable: true,
get: function () {
return desc ? desc.get.call(this) : this._customOnMessage;
},
set: function (fn) {
const wrapped = (event) => {
handleIncomingData(this, event.data);
if (typeof fn === 'function') fn.call(this, event);
};
this._customOnMessage = wrapped;
if (desc && desc.set) {
desc.set.call(this, wrapped);
} else {
this.addEventListener('message', wrapped);
}
}
});
} catch (e) {}
const nativeAddEventListener = WebSocket.prototype.addEventListener;
WebSocket.prototype.addEventListener = function (type, listener, options) {
if (type === 'message') {
const wrapped = (event) => {
handleIncomingData(this, event.data);
if (typeof listener === 'function') listener.call(this, event);
};
return nativeAddEventListener.call(this, type, wrapped, options);
}
return nativeAddEventListener.apply(this, arguments);
};
function sendDirectPacket(type, argsArray) {
if (!activeSocket || activeSocket.readyState !== WebSocket.OPEN) return;
if (!c2sTable || !socketKey) return;
try {
const opcode = c2sTable[type];
if (opcode === undefined) return;
globalSeq++;
let rawEncoded = msgpack.encode([opcode, argsArray, globalSeq]);
const payload = rawEncoded instanceof Uint8Array ? rawEncoded : new Uint8Array(rawEncoded.buffer || rawEncoded);
const hmac = Eo(socketKey, payload);
const fullBuf = new Uint8Array(6 + payload.length);
fullBuf.set(hmac, 0);
fullBuf.set(payload, 6);
nativeSend.call(activeSocket, fullBuf);
} catch (e) {}
}
// ==========================================
// AUTO-AIM ALGORITHM (REFINED)
// ==========================================
function getAutoAimTargetAngle() {
if (!autoAimEnabled || typeof window.myPlayer.x !== 'number') return currentMouseAngle;
const myX = window.myPlayer.x;
const myY = window.myPlayer.y;
let closestTarget = null;
let closestDist = Infinity;
for (const [sid, p] of nearbyEnemies.entries()) {
if (p.isTeammate || Date.now() - p.lastSeen > 650) continue;
const dx = p.x - myX;
const dy = p.y - myY;
const dist = Math.hypot(dx, dy);
// Радиус поиска цели (до 600 юнитов)
if (dist <= 600 && dist < closestDist) {
closestDist = dist;
closestTarget = p;
}
}
if (closestTarget) {
return Math.atan2(closestTarget.y - myY, closestTarget.x - myX);
}
return currentMouseAngle;
}
// ==========================================
// COMBAT & AUTOMATION ENGINES
// ==========================================
async function executeFastInstaCombo() {
if (isInstaShooting || !isPlayerInGame()) return;
isInstaShooting = true;
try {
const secWep = getActiveSecondaryWeapon();
const hatToRestore = userActiveHat;
const shootAngle = getAutoAimTargetAngle();
equipHat(BULL);
sendDirectPacket("D", [shootAngle]);
sendDirectPacket("z", [secWep, true]);
sendDirectPacket("F", [1, shootAngle]);
await sleep(115);
sendDirectPacket("F", [0, shootAngle]);
sendDirectPacket("z", [primaryWeapon, true]);
sendDirectPacket("F", [1, shootAngle]);
await sleep(35);
sendDirectPacket("F", [0, shootAngle]);
equipHat(isInWinter ? WINTER_CAP : (hatToRestore || 0));
currentHatId = isInWinter ? WINTER_CAP : (hatToRestore || 0);
if (isMouseDown) {
await sleep(15);
sendDirectPacket("F", [1, currentMouseAngle]);
}
} catch (err) {
} finally {
isInstaShooting = false;
}
}
async function executeTurretInstaCombo() {
if (isInstaShooting || !isPlayerInGame()) return;
isInstaShooting = true;
try {
const secWep = getActiveSecondaryWeapon();
const hatToRestore = userActiveHat;
const shootAngle = getAutoAimTargetAngle();
equipHat(TURRET_GEAR);
sendDirectPacket("D", [shootAngle]);
sendDirectPacket("z", [secWep, true]);
sendDirectPacket("F", [1, shootAngle]);
await sleep(115);
sendDirectPacket("F", [0, shootAngle]);
sendDirectPacket("z", [primaryWeapon, true]);
sendDirectPacket("F", [1, shootAngle]);
await sleep(35);
sendDirectPacket("F", [0, shootAngle]);
equipHat(isInWinter ? WINTER_CAP : (hatToRestore || 0));
currentHatId = isInWinter ? WINTER_CAP : (hatToRestore || 0);
if (isMouseDown) {
await sleep(15);
sendDirectPacket("F", [1, currentMouseAngle]);
}
} catch (err) {
} finally {
isInstaShooting = false;
}
}
async function executeAutoTrap() {
if (!autoTrapEnabled || isPlacingMills || isInstaShooting || isHealing || isBreakingFast || !isPlayerInGame()) return;
const now = Date.now();
if (now - lastTrapTime < 300) return;
const myX = window.myPlayer.x;
const myY = window.myPlayer.y;
if (typeof myX !== 'number') return;
const trapId = getTrapIndex();
for (const [sid, p] of nearbyEnemies.entries()) {
if (p.isTeammate || Date.now() - p.lastSeen > 600) continue;
const dx = p.x - myX;
const dy = p.y - myY;
const dist = Math.hypot(dx, dy);
if (dist <= 175 && dist >= 45) {
lastTrapTime = now;
const angle = Math.atan2(dy, dx);
sendDirectPacket("z", [trapId, false]);
sendDirectPacket("F", [1, angle]);
sendDirectPacket("F", [0, angle]);
sendDirectPacket("z", [primaryWeapon, true]);
if (isMouseDown) {
sendDirectPacket("F", [1, null]);
}
break;
}
}
}
async function executeFastBreak() {
if (isBreakingFast || isHealing || isInstaShooting || !isPlayerInGame()) return;
isBreakingFast = true;
try {
sendDirectPacket("D", [currentMouseAngle]);
sendDirectPacket("F", [1, null]);
await sleep(25);
sendDirectPacket("F", [0, null]);
await sleep(25);
} catch (e) {
} finally {
isBreakingFast = false;
}
}
async function placeSingleWindmill(millId, angle) {
sendDirectPacket("z", [millId, false]);
await sleep(12);
sendDirectPacket("F", [1, angle]);
await sleep(12);
sendDirectPacket("F", [0, angle]);
}
async function executeDenseWindmills() {
if (!autoWindmillsEnabled || isPlacingMills || isHealing || isInstaShooting || !isPlayerInGame()) return;
isPlacingMills = true;
try {
const millId = getWindmillIndex();
const back = currentMouseAngle + Math.PI;
const angles = [back - 1.05, back - 0.35, back + 0.35, back + 1.05];
for (const ang of angles) {
await placeSingleWindmill(millId, ang);
await sleep(10);
}
sendDirectPacket("z", [primaryWeapon, true]);
if (isMouseDown) {
await sleep(15);
sendDirectPacket("F", [1, null]);
}
await sleep(100);
} catch (e) {
} finally {
isPlacingMills = false;
}
}
async function executeHeal(count) {
if (isHealing) return;
isHealing = true;
try {
const foodIdx = getFoodIndex();
for (let i = 0; i < count; i++) {
sendDirectPacket("z", [foodIdx, false]);
await sleep(25);
sendDirectPacket("F", [1, currentMouseAngle]);
await sleep(25);
sendDirectPacket("F", [0, currentMouseAngle]);
await sleep(25);
sendDirectPacket("z", [primaryWeapon, true]);
if (isMouseDown) {
await sleep(15);
sendDirectPacket("F", [1, null]);
}
if (i < count - 1) {
await sleep(TICK_COOLDOWN);
}
}
} catch (err) {
} finally {
isHealing = false;
}
}
function triggerHeal(dmg) {
const now = Date.now();
if (now - lastHealTime < TICK_COOLDOWN || isHealing) return;
lastHealTime = now;
const count = Math.min(2, Math.max(1, Math.ceil(dmg / 25)));
executeHeal(count);
}
function onHealthDetected(currentHp) {
myHealth = currentHp;
if (currentHp < lastHealth && currentHp > 0) {
const dmg = lastHealth - currentHp;
if (autoHealEnabled && dmg >= 4) {
triggerHeal(dmg);
}
}
lastHealth = currentHp;
}
// ==========================================
// CANVAS HOOKS
// ==========================================
try {
let _nativeRoundRect = CanvasRenderingContext2D.prototype.roundRect;
Object.defineProperty(CanvasRenderingContext2D.prototype, 'roundRect', {
get: function () {
return function (x, y, w, h, r) {
if (this.canvas && this.canvas.id === 'gameCanvas' && h <= 18) {
const fs = String(this.fillStyle).toLowerCase();
if (fs === '#8ecc51' || fs === 'rgb(142, 204, 81)' || fs === '#4bb6c4') {
const hp = Math.max(0, Math.min(100, Math.round(w)));
onHealthDetected(hp);
}
}
if (typeof _nativeRoundRect === 'function') {
return _nativeRoundRect.apply(this, arguments);
}
};
},
set: function (fn) {
_nativeRoundRect = fn;
},
configurable: true
});
} catch (e) {}
const origSetTransform = CanvasRenderingContext2D.prototype.setTransform;
CanvasRenderingContext2D.prototype.setTransform = function (a, b, c, d, e, f) {
if (this.canvas && this.canvas.id === 'gameCanvas' && arguments.length === 6) {
a *= cameraZoom;
d *= cameraZoom;
e = (this.canvas.width - 1920 * a) / 2;
f = (this.canvas.height - 1080 * d) / 2;
}
return origSetTransform.call(this, a, b, c, d, e, f);
};
function applyZoom() {
const canvas = document.getElementById('gameCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const cw = canvas.width || (window.innerWidth * (window.devicePixelRatio || 1));
const ch = canvas.height || (window.innerHeight * (window.devicePixelRatio || 1));
const De = window.innerWidth;
const Ae = window.innerHeight;
const Pe = window.devicePixelRatio || 1;
const baseScale = Math.max(De / 1920, Ae / 1080) * Pe;
const e = baseScale * cameraZoom;
origSetTransform.call(ctx, e, 0, 0, e, (cw - 1920 * e) / 2, (ch - 1080 * e) / 2);
}
const origClearRect = CanvasRenderingContext2D.prototype.clearRect;
CanvasRenderingContext2D.prototype.clearRect = function (x, y, w, h) {
origClearRect.apply(this, arguments);
if (this.canvas && this.canvas.id === 'mapDisplay') {
const cw = this.canvas.width || 300;
const ch = this.canvas.height || 300;
const snowH = (2400 / 14400) * ch;
this.fillStyle = 'rgba(255, 255, 255, 0.45)';
this.fillRect(0, 0, cw, snowH);
const riverY = (6838 / 14400) * ch;
const riverH = (724 / 14400) * ch;
this.fillStyle = 'rgba(80, 155, 235, 0.55)';
this.fillRect(0, riverY, cw, riverH);
const desertY = (12000 / 14400) * ch;
const desertH = ch - desertY;
this.fillStyle = 'rgba(195, 140, 75, 0.45)';
this.fillRect(0, desertY, cw, desertH);
if (nearbyEnemies.size > 0) {
this.save();
for (const [sid, enemy] of nearbyEnemies.entries()) {
if (Date.now() - enemy.lastSeen > 1200 || enemy.isTeammate) continue;
const ex = (enemy.x / 14400) * cw;
const ey = (enemy.y / 14400) * ch;
this.beginPath();
this.arc(ex, ey, 5.5, 0, 2 * Math.PI);
this.fillStyle = '#ff2a2a';
this.fill();
this.lineWidth = 1.5;
this.strokeStyle = '#ffffff';
this.stroke();
}
this.restore();
}
}
};
function checkBiome() {
if (!autoBiomeHatEnabled || typeof window.myPlayer.y !== 'number' || !isPlayerInGame()) return;
const now = Date.now();
if (now - lastEquipTime < 200) return;
const isSnowZone = window.myPlayer.y < 2400;
if (isSnowZone) {
isInWinter = true;
if (currentHatId !== WINTER_CAP) {
previousHatId = userActiveHat;
currentHatId = WINTER_CAP;
equipHat(WINTER_CAP);
lastEquipTime = now;
updateStatus();
}
} else {
isInWinter = false;
if (currentHatId === WINTER_CAP) {
currentHatId = userActiveHat || previousHatId || 0;
equipHat(currentHatId);
lastEquipTime = now;
updateStatus();
}
}
}
// ==========================================
// OVERLAY ENGINE (PREDICTIONS)
// ==========================================
function resizeOverlay() {
if (!overlayCanvas) return;
overlayCanvas.width = window.innerWidth;
overlayCanvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeOverlay, true);
function initOverlayCanvas() {
if (overlayCanvas) return;
overlayCanvas = document.createElement('canvas');
overlayCanvas.id = 'pc-predict-overlay';
overlayCanvas.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 10;
`;
document.body.appendChild(overlayCanvas);
overlayCtx = overlayCanvas.getContext('2d');
resizeOverlay();
}
function drawPredictLine(ctx, sx, sy, dir, length, color, dotColor) {
const ex = sx + Math.cos(dir) * length;
const ey = sy + Math.sin(dir) * length;
ctx.save();
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.strokeStyle = color;
ctx.lineWidth = 2.5;
ctx.setLineDash([7, 4]);
ctx.stroke();
ctx.setLineDash([]);
ctx.beginPath();
ctx.arc(ex, ey, 4.5, 0, 2 * Math.PI);
ctx.fillStyle = dotColor || color;
ctx.fill();
ctx.lineWidth = 1.5;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.restore();
}
function renderPredictLoop() {
if (overlayCtx && overlayCanvas) {
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
if (isPlayerInGame() && typeof window.myPlayer.x === 'number') {
const cx = overlayCanvas.width / 2;
const cy = overlayCanvas.height / 2;
const myX = window.myPlayer.x;
const myY = window.myPlayer.y;
const baseScale = Math.max(overlayCanvas.width / 1920, overlayCanvas.height / 1080);
const scale = baseScale * cameraZoom;
// Линия игрока
if (predictMe) {
drawPredictLine(overlayCtx, cx, cy, currentMouseAngle, 130 * scale, 'rgba(255, 77, 109, 0.95)', '#27c93f');
}
// Линии врагов
if (predictPlayers) {
for (const [sid, p] of nearbyEnemies.entries()) {
if (Date.now() - p.lastSeen > 800) continue;
const screenX = cx + (p.x - myX) * scale;
const screenY = cy + (p.y - myY) * scale;
const color = p.isTeammate ? 'rgba(75, 182, 196, 0.9)' : 'rgba(255, 42, 42, 0.95)';
drawPredictLine(overlayCtx, screenX, screenY, p.dir, 120 * scale, color, '#ffffff');
}
}
// Линии мобов
if (predictMobs) {
for (const [sid, m] of nearbyMobs.entries()) {
if (Date.now() - m.lastSeen > 800) continue;
const screenX = cx + (m.x - myX) * scale;
const screenY = cy + (m.y - myY) * scale;
drawPredictLine(overlayCtx, screenX, screenY, m.dir, 95 * scale, 'rgba(255, 189, 46, 0.9)', '#ffbd2e');
}
}
// Владельцы построек
if (showBuildingOwners && placedBuildings.size > 0) {
overlayCtx.save();
overlayCtx.font = `bold ${Math.max(10, Math.round(13 * scale))}px 'Hammersmith One', Arial, sans-serif`;
overlayCtx.textAlign = 'center';
overlayCtx.textBaseline = 'middle';
for (const [sid, bldg] of placedBuildings.entries()) {
const screenX = cx + (bldg.x - myX) * scale;
const screenY = cy + (bldg.y - myY) * scale;
if (screenX >= -50 && screenX <= overlayCanvas.width + 50 && screenY >= -50 && screenY <= overlayCanvas.height + 50) {
const isSelf = bldg.ownerSid === window.myPlayer.sid;
const isClan = isSelf || (window.myPlayer.team && playerTeams.get(bldg.ownerSid) === window.myPlayer.team);
const rawName = playerNames.get(bldg.ownerSid) || (isSelf ? 'YOU' : `[ID:${bldg.ownerSid}]`);
if (isClan) {
overlayCtx.fillStyle = THEME.clanLime;
overlayCtx.fillText(`[CLAN MEMBER] ${rawName}`, screenX, screenY - 22 * scale);
} else {
overlayCtx.fillStyle = 'rgba(255, 255, 255, 0.45)';
overlayCtx.fillText(rawName, screenX, screenY - 22 * scale);
}
}
}
overlayCtx.restore();
}
}
}
if (autoTrapEnabled) {
executeAutoTrap();
}
if (autoWindmillsEnabled) {
executeDenseWindmills();
}
if (isRightMouseDown && !isMouseDown) {
executeFastBreak();
}
requestAnimationFrame(renderPredictLoop);
}
// ==========================================
// SERVER FETCHER & UI
// ==========================================
function renderServerListUI() {
const listContainer = document.getElementById('pc-server-list');
if (!listContainer || cachedServers.length === 0) return;
const byPlayers = [...cachedServers].sort((a, b) => b.players - a.players).slice(0, 3);
const playerVals = new Set(byPlayers.map(s => s.val));
const byPing = [...cachedServers]
.filter(s => !playerVals.has(s.val))
.sort((a, b) => a.ping - b.ping)
.slice(0, 3);
const pingVals = new Set(byPing.map(s => s.val));
const others = cachedServers.filter(s => !playerVals.has(s.val) && !pingVals.has(s.val));
const renderItem = (s) => `
<div class="pc-server-item ${s.selected ? 'active' : ''}" data-val="${s.val}" style="
display: flex; justify-content: space-between; align-items: center;
background: ${s.selected ? 'rgba(255, 42, 42, 0.35)' : 'rgba(0, 0, 0, 0.35)'};
border: 1px solid ${s.selected ? '#ff4d6d' : 'rgba(255, 42, 42, 0.2)'};
padding: 6px 8px; border-radius: 6px; cursor: pointer; transition: 0.15s; font-size: 11px;
">
<div style="font-weight: bold; color: ${s.selected ? '#fff' : THEME.text};">${s.name}</div>
<div style="display: flex; gap: 8px; font-size: 10px;">
<span style="color: ${s.players >= s.max ? '#ff4d6d' : '#fff'}">[${s.players}/${s.max}]</span>
<span style="color: ${s.ping < 80 ? '#27c93f' : s.ping < 150 ? '#ffbd2e' : '#ff5f56'}">${s.ping !== 999 ? s.ping + 'ms' : '?'}</span>
</div>
</div>
`;
listContainer.innerHTML = `
<div style="font-size: 10px; font-weight: bold; color: rgba(255,255,255,0.5); letter-spacing: 1px;">// BY PLAYERS</div>
${byPlayers.map(renderItem).join('')}
<div style="font-size: 10px; font-weight: bold; color: rgba(255,255,255,0.5); letter-spacing: 1px; margin-top: 6px;">// LOWEST PING</div>
${byPing.map(renderItem).join('')}
${others.length > 0 ? `
<div style="font-size: 10px; font-weight: bold; color: rgba(255,255,255,0.5); letter-spacing: 1px; margin-top: 6px;">// ALL SERVERS</div>
${others.map(renderItem).join('')}
` : ''}
`;
listContainer.querySelectorAll('.pc-server-item').forEach(item => {
item.addEventListener('click', () => {
const targetVal = item.getAttribute('data-val');
const select = document.querySelector('#serverBrowser select');
if (select) {
select.value = targetVal;
select.dispatchEvent(new Event('change', { bubbles: true }));
}
window.location.href = `/?server=${encodeURIComponent(targetVal)}`;
});
});
}
function parseFromSelectFallback() {
const select = document.querySelector('#serverBrowser select');
if (!select) return;
const options = Array.from(select.options);
if (options.length === 0) return;
cachedServers = options.filter(opt => opt.value && !opt.disabled).map(opt => {
const text = opt.textContent || '';
const match = text.match(/^(.*?)\s*\[(\d+)\/(\d+)\](?:\s*\[(\d+)ms\])?/);
return {
val: opt.value,
name: match ? match[1].trim() : opt.value,
players: match ? parseInt(match[2]) || 0 : 0,
max: match ? parseInt(match[3]) || 40 : 40,
ping: match && match[4] ? parseInt(match[4]) : 999,
selected: opt.selected
};
});
renderServerListUI();
}
async function loadServersFromApi() {
const isSandbox = location.hostname.includes('sandbox');
const apiUrl = isSandbox ? 'https://api-sandbox.moomoo.io/servers?v=1.27' : 'https://api.moomoo.io/servers?v=1.27';
try {
const res = await fetch(apiUrl);
const data = await res.json();
const urlParams = new URLSearchParams(window.location.search);
const currentSelected = urlParams.get('server') || 'frankfurt:AZ';
const parsedList = [];
data.forEach(s => {
let cleanRegion = (s.region || '').replace(/^(vultr|do):/, '');
let totalPlayers = typeof s.playerCount === 'number' ? s.playerCount : 0;
let maxPlayers = s.playerCapacity || 40;
if (Array.isArray(s.games)) {
s.games.forEach(g => {
totalPlayers = Math.max(totalPlayers, g.playerCount || 0);
maxPlayers = g.playerCapacity || maxPlayers;
});
}
parsedList.push({
val: `${s.region}:${s.name}`,
name: `${cleanRegion.toUpperCase()} ${s.name}`,
region: cleanRegion,
players: totalPlayers,
max: maxPlayers,
ping: s.ping || (cleanRegion.includes('frankfurt') ? 45 : (cleanRegion.includes('us') ? 95 : 140)),
selected: currentSelected === `${s.region}:${s.name}`
});
});
cachedServers = parsedList;
renderServerListUI();
} catch (err) {
parseFromSelectFallback();
}
}
function toggleHat() {
if (typeof window.storeEquip !== 'function') return;
soldierHelmet = false;
bullHat = !bullHat;
const target = bullHat ? BULL : BOOSTER;
userActiveHat = target;
currentHatId = target;
previousHatId = target;
equipHat(target);
updateStatus();
}
function toggleSoldierHelmet() {
if (typeof window.storeEquip !== 'function') return;
soldierHelmet = !soldierHelmet;
const target = soldierHelmet ? SOLDIER_HELMET : 0;
userActiveHat = target;
currentHatId = target;
previousHatId = target;
equipHat(target);
updateStatus();
}
function toggleMonkeyTail() {
if (typeof window.storeEquip !== 'function') return;
monkeyTail = !monkeyTail;
const target = monkeyTail ? MONKEY_TAIL : 0;
window.storeEquip(target, 1);
updateStatus();
}
function updateCoordDisplay() {
let element = document.getElementById('pc-coords-box');
const leaderboard = document.getElementById('leaderboard');
if (!isPlayerInGame() || !leaderboard) {
if (element) element.style.display = 'none';
return;
}
if (!element) {
element = document.createElement('div');
element.id = 'pc-coords-box';
element.style.cssText = `
position: fixed;
z-index: 99999;
background: ${THEME.bg};
backdrop-filter: ${THEME.blur};
-webkit-backdrop-filter: ${THEME.blur};
color: #fff;
padding: 6px 10px;
border-radius: 8px;
border: ${THEME.border};
box-shadow: ${THEME.shadow};
font-family: 'Hammersmith One', Arial, sans-serif;
text-align: center;
pointer-events: none;
box-sizing: border-box;
letter-spacing: 0.5px;
`;
document.body.appendChild(element);
}
element.style.display = 'block';
const rect = leaderboard.getBoundingClientRect();
element.style.top = (rect.bottom + 65) + 'px';
element.style.right = (window.innerWidth - rect.right) + 'px';
element.style.width = rect.width + 'px';
const x = typeof window.myPlayer.x === 'number' ? window.myPlayer.x : '—';
const y = typeof window.myPlayer.y === 'number' ? window.myPlayer.y : '—';
let biome = '🌲 PLAINS';
let biomeColor = '#b4db62';
if (typeof window.myPlayer.y === 'number') {
if (window.myPlayer.y < 2400) { biome = '❄️ SNOW'; biomeColor = '#a8e6cf'; }
else if (window.myPlayer.y > 12000) { biome = '🏜️ DESERT'; biomeColor = '#ffd3b6'; }
else if (window.myPlayer.y >= 6838 && window.myPlayer.y <= 7562) { biome = '🌊 RIVER'; biomeColor = '#91b2db'; }
else { biome = '🌲 PLAINS'; biomeColor = '#b4db62'; }
}
element.innerHTML = `
<div style="font-size: 13px; font-weight: normal; color: rgba(255, 255, 255, 0.95); font-family: 'Hammersmith One', Arial, sans-serif;">
X: <span style="color: ${THEME.pink};">${x}</span> | Y: <span style="color: ${THEME.pink};">${y}</span>
</div>
<div style="font-size: 11px; margin-top: 2px; color: ${biomeColor}; text-shadow: 0 0 4px rgba(0,0,0,0.6); font-family: 'Hammersmith One', Arial, sans-serif;">
${biome}
</div>
`;
}
function createStatus() {
if (document.getElementById('pc-status')) return;
const element = document.createElement('div');
element.id = 'pc-status';
element.style.cssText = `
display: none;
position: fixed;
bottom: 220px;
left: 14px;
z-index: 999999;
background: ${THEME.bg};
backdrop-filter: ${THEME.blur};
-webkit-backdrop-filter: ${THEME.blur};
color: ${THEME.text};
font-family: 'Hammersmith One', Arial, sans-serif;
font-size: 13px;
padding: 10px 14px;
border-radius: 8px;
border: ${THEME.border};
box-shadow: ${THEME.shadow};
pointer-events: none;
line-height: 1.5;
min-width: 190px;
`;
document.body.appendChild(element);
updateStatus();
}
function updateStatus() {
const element = document.getElementById('pc-status');
if (!element) return;
if (!isPlayerInGame()) {
element.style.display = 'none';
const macMenu = document.getElementById('pc-macos-window');
if (macMenu) macMenu.style.display = 'none';
return;
}
const zoomPercent = Math.round(cameraZoom * 100);
element.style.display = 'block';
element.innerHTML = `
<div style="color:${THEME.accent};font-size:12px;margin-bottom:4px;letter-spacing:1px;font-family:'Hammersmith One',Arial,sans-serif">PIDORASI CLIENT v9.3</div>
<div style="color:${THEME.pink}">● Auto-Heal (${formatKey(userBinds.toggleHeal)}) <span style="opacity:.8;color:${autoHealEnabled ? '#27c93f' : '#ff5f56'}">[${autoHealEnabled ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Fast-Break <span style="opacity:.8;color:#27c93f">[HOLD ПКМ]</span></div>
<div style="color:${THEME.pink}">● Turret-Insta (${formatKey(userBinds.turretInsta)}) <span style="opacity:.8;color:#ff4d6d">[TURRET+SHOT]</span></div>
<div style="color:${THEME.pink}">● Bull-Insta (${formatKey(userBinds.instaMusket)}) <span style="opacity:.8;color:#27c93f">[BULL+SHOT]</span></div>
<div style="color:${THEME.pink}">● Auto-Trap (${formatKey(userBinds.toggleAutoTrap)}) <span style="opacity:.8;color:${autoTrapEnabled ? '#27c93f' : '#ff5f56'}">[${autoTrapEnabled ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Auto-Aim <span style="opacity:.8;color:${autoAimEnabled ? '#27c93f' : '#ff5f56'}">[${autoAimEnabled ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Auto-Clan Accept <span style="opacity:.8;color:${autoClanAccept ? '#27c93f' : '#ff5f56'}">[${autoClanAccept ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Kill-Chat <span style="opacity:.8;color:${killChatEnabled ? '#27c93f' : '#ff5f56'}">[${killChatEnabled ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Auto-Windmills (${formatKey(userBinds.autoWindmills)}) <span style="opacity:.8;color:${autoWindmillsEnabled ? '#27c93f' : '#ff5f56'}">[${autoWindmillsEnabled ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Hat (${formatKey(userBinds.toggleHat)}) <span style="opacity:.8;color:#fff">[${bullHat ? 'Bull' : 'Booster'}]</span></div>
<div style="color:${THEME.pink}">● Soldier (${formatKey(userBinds.toggleSoldier)}) <span style="opacity:.8;color:#fff">[${soldierHelmet ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Tail (${formatKey(userBinds.toggleTail)}) <span style="opacity:.8;color:#fff">[${monkeyTail ? 'ON' : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Auto-Winter <span style="opacity:.8;color:#fff">[${autoBiomeHatEnabled ? (isInWinter ? 'SNOW' : 'ON') : 'OFF'}]</span></div>
<div style="color:${THEME.pink}">● Zoom (- / +) <span style="opacity:.8;color:#fff">[${zoomPercent}%]</span></div>
`;
}
// ==========================================
// MAC OS GUI
// ==========================================
function renderBindRow(label, action) {
const isRebinding = rebindingAction === action;
return `
<div style="
display: flex; justify-content: space-between; align-items: center; background: rgba(0, 0, 0, 0.3);
padding: 8px 12px; border-radius: 8px; border: 1px solid rgba(255, 42, 42, 0.2);
">
<span style="font-size: 13px; color: #fff;">${label}</span>
<button class="pc-bind-btn" data-action="${action}" title="Left-Click: Rebind | Right-Click: Unbind" style="
padding: 5px 14px; border-radius: 6px; border: 1px solid ${isRebinding ? '#ffbd2e' : 'rgba(255, 42, 42, 0.5)'};
background: ${isRebinding ? 'rgba(255, 189, 46, 0.3)' : 'rgba(0, 0, 0, 0.5)'}; color: ${isRebinding ? '#ffbd2e' : THEME.pink};
font-family: inherit; font-size: 12px; font-weight: bold; cursor: pointer; min-width: 85px; text-align: center;
">${isRebinding ? 'Press key...' : formatKey(userBinds[action])}</button>
</div>
`;
}
function attachBindEvents(content) {
content.querySelectorAll('.pc-bind-btn').forEach(btn => {
btn.addEventListener('click', () => {
const action = btn.getAttribute('data-action');
rebindingAction = action;
btn.textContent = 'Press key...';
btn.style.background = 'rgba(255, 189, 46, 0.4)';
btn.style.borderColor = '#ffbd2e';
});
btn.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
const action = btn.getAttribute('data-action');
userBinds[action] = 'NONE';
saveBinds();
renderTabs();
updateStatus();
});
});
content.querySelector('#gui-aim-btn')?.addEventListener('click', () => {
autoAimEnabled = !autoAimEnabled;
renderTabs();
updateStatus();
});
content.querySelector('#gui-autotrap-btn')?.addEventListener('click', () => {
autoTrapEnabled = !autoTrapEnabled;
renderTabs();
updateStatus();
});
content.querySelector('#gui-clanaccept-btn')?.addEventListener('click', () => {
autoClanAccept = !autoClanAccept;
renderTabs();
updateStatus();
});
content.querySelector('#gui-killchat-btn')?.addEventListener('click', () => {
killChatEnabled = !killChatEnabled;
renderTabs();
updateStatus();
});
content.querySelector('#gui-killchat-msg')?.addEventListener('input', (e) => {
killChatMessage = e.target.value;
localStorage.setItem('pcKillMsg', killChatMessage);
});
content.querySelector('#gui-pred-me')?.addEventListener('click', () => {
predictMe = !predictMe;
renderTabs();
});
content.querySelector('#gui-pred-players')?.addEventListener('click', () => {
predictPlayers = !predictPlayers;
renderTabs();
});
content.querySelector('#gui-esp-owners')?.addEventListener('click', () => {
showBuildingOwners = !showBuildingOwners;
renderTabs();
});
content.querySelector('#gui-heal-btn')?.addEventListener('click', () => {
autoHealEnabled = !autoHealEnabled;
renderTabs();
updateStatus();
});
content.querySelector('#gui-biome-btn')?.addEventListener('click', () => {
autoBiomeHatEnabled = !autoBiomeHatEnabled;
renderTabs();
updateStatus();
});
}
function renderTabs() {
const win = document.getElementById('pc-macos-window');
if (!win) return;
win.querySelectorAll('.pc-tab-btn').forEach(btn => {
const tab = btn.getAttribute('data-tab');
if (tab === currentTab) {
btn.style.background = 'rgba(255, 42, 42, 0.25)';
btn.style.border = '1px solid rgba(255, 42, 42, 0.6)';
btn.style.color = '#fff';
} else {
btn.style.background = 'transparent';
btn.style.border = '1px solid transparent';
btn.style.color = 'rgba(255, 255, 255, 0.6)';
}
});
const content = win.querySelector('#pc-tab-content');
if (!content) return;
if (currentTab === 'keybinds') {
content.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div style="font-size: 14px; font-weight: bold; color: ${THEME.accent}; text-transform: uppercase;">Keybind Configuration</div>
<div style="font-size: 10px; color: rgba(255,255,255,0.45);">[Right-Click = Unbind]</div>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px;">
${renderBindRow('Open GUI Window', 'openMenu')}
${renderBindRow('Turret Insta-Kill', 'turretInsta')}
${renderBindRow('Bull Insta-Kill', 'instaMusket')}
${renderBindRow('Auto-Trap Toggle', 'toggleAutoTrap')}
${renderBindRow('Auto-Windmills (4 Dense Arc)', 'autoWindmills')}
${renderBindRow('Hat Toggle (Bull / Booster)', 'toggleHat')}
${renderBindRow('Soldier Helmet', 'toggleSoldier')}
${renderBindRow('Monkey Tail', 'toggleTail')}
${renderBindRow('Auto-Heal Toggle', 'toggleHeal')}
</div>
<div style="font-size: 14px; font-weight: bold; color: ${THEME.accent}; margin-bottom: 10px; text-transform: uppercase;">Visual & Aim Settings</div>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 16px;">
<button id="gui-aim-btn" style="
padding: 8px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 11px; font-weight: bold;
background: ${autoAimEnabled ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">AUTO-AIM: ${autoAimEnabled ? 'ON' : 'OFF'}</button>
<button id="gui-esp-owners" style="
padding: 8px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 11px; font-weight: bold;
background: ${showBuildingOwners ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">CLAN ESP: ${showBuildingOwners ? 'ON' : 'OFF'}</button>
<button id="gui-pred-me" style="
padding: 8px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 11px; font-weight: bold;
background: ${predictMe ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">PREDICT ME: ${predictMe ? 'ON' : 'OFF'}</button>
<button id="gui-pred-players" style="
padding: 8px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 11px; font-weight: bold;
background: ${predictPlayers ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">PLAYERS: ${predictPlayers ? 'ON' : 'OFF'}</button>
</div>
<div style="font-size: 14px; font-weight: bold; color: ${THEME.accent}; margin-bottom: 10px; text-transform: uppercase;">Automation & Clan Modules</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px;">
<button id="gui-clanaccept-btn" style="
width: 100%; padding: 10px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 13px; font-weight: bold;
background: ${autoClanAccept ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">AUTO-CLAN ACCEPT: ${autoClanAccept ? 'ENABLED' : 'DISABLED'}</button>
<button id="gui-autotrap-btn" style="
width: 100%; padding: 10px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 13px; font-weight: bold;
background: ${autoTrapEnabled ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">AUTO-TRAP: ${autoTrapEnabled ? 'ENABLED' : 'DISABLED'}</button>
<button id="gui-heal-btn" style="
width: 100%; padding: 10px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 13px; font-weight: bold;
background: ${autoHealEnabled ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">Auto-Heal: ${autoHealEnabled ? 'ENABLED' : 'DISABLED'}</button>
<button id="gui-biome-btn" style="
width: 100%; padding: 10px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 13px; font-weight: bold;
background: ${autoBiomeHatEnabled ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">Auto-Biome Hat: ${autoBiomeHatEnabled ? 'ENABLED' : 'DISABLED'}</button>
</div>
<div style="font-size: 14px; font-weight: bold; color: ${THEME.accent}; margin-bottom: 10px; text-transform: uppercase;">Kill-Chat Notification</div>
<div style="display: flex; flex-direction: column; gap: 8px; background: rgba(0,0,0,0.3); padding: 10px; border-radius: 8px; border: 1px solid rgba(255,42,42,0.2);">
<button id="gui-killchat-btn" style="
width: 100%; padding: 8px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 12px; font-weight: bold;
background: ${killChatEnabled ? 'rgba(31,122,58,0.7)' : 'rgba(139,0,0,0.7)'}; color: #fff; border: 1px solid ${THEME.accent};
">KILL-CHAT: ${killChatEnabled ? 'ENABLED' : 'DISABLED'}</button>
<input id="gui-killchat-msg" type="text" maxlength="30" value="${killChatMessage}" placeholder="Kill message (max 30 chars)..." style="
width: 100%; background: rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 42, 42, 0.4); border-radius: 6px;
color: #fff; padding: 7px 10px; font-family: inherit; font-size: 12px; box-sizing: border-box; text-align: center;
" />
</div>
`;
attachBindEvents(content);
} else if (currentTab === 'changelog') {
content.innerHTML = `
<div style="font-size: 14px; font-weight: bold; color: ${THEME.accent}; margin-bottom: 12px; text-transform: uppercase;">Full Version History</div>
<div style="display: flex; flex-direction: column; gap: 12px; font-size: 12px; line-height: 1.4;">
<div style="background: rgba(0,0,0,0.3); padding: 10px; border-radius: 8px; border-left: 3px solid #ff4d6d;">
<div style="font-weight: bold; color: #fff; margin-bottom: 4px;">v9.3: Clan & Kill-Chat Release</div>
<div style="color: ${THEME.text};">● Добавлен модуль Auto-Clan Accept (авто-прием заявок в клан).</div>
<div style="color: ${THEME.text};">● Добавлен кастомный Kill-Chat с настройкой фразы в GUI.</div>
<div style="color: ${THEME.text};">● Полностью вырезан спинбот и лишняя вкладка Fun.</div>
<div style="color: ${THEME.text};">● Переписан и улучшен Auto-Aim для идеальных попаданий комбо.</div>
</div>
</div>
`;
}
}
function createMacGui() {
if (document.getElementById('pc-macos-window')) return;
const win = document.createElement('div');
win.id = 'pc-macos-window';
win.style.cssText = `
display: none;
position: fixed;
top: calc(50% - 175px);
left: calc(50% - 270px);
z-index: 1000000;
width: 550px;
height: 440px;
border-radius: 12px;
background: rgba(15, 0, 0, 0.78);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: ${THEME.border};
box-shadow: 0 8px 32px rgba(255, 0, 0, 0.25);
color: #fff;
font-family: 'Hammersmith One', Arial, sans-serif;
user-select: none;
overflow: hidden;
flex-direction: column;
`;
win.innerHTML = `
<div id="pc-mac-titlebar" style="
height: 38px;
background: rgba(0, 0, 0, 0.5);
border-bottom: ${THEME.borderLight};
display: flex;
align-items: center;
padding: 0 14px;
cursor: grab;
position: relative;
">
<div style="display: flex; gap: 7px; align-items: center;">
<div id="pc-mac-close" style="width: 12px; height: 12px; border-radius: 50%; background: #ff5f56; cursor: pointer; border: 1px solid #e0443e;" title="Close"></div>
<div style="width: 12px; height: 12px; border-radius: 50%; background: #ffbd2e; border: 1px solid #dea123;"></div>
<div style="width: 12px; height: 12px; border-radius: 50%; background: #27c93f; border: 1px solid #1aab29;"></div>
</div>
<div style="
position: absolute;
left: 0;
right: 0;
text-align: center;
font-size: 13px;
letter-spacing: 1px;
color: ${THEME.accent};
font-weight: bold;
pointer-events: none;
">Pidorasi Client // by xw5yt</div>
</div>
<div style="flex: 1; display: flex; overflow: hidden;">
<div style="
width: 150px;
background: rgba(0, 0, 0, 0.25);
border-right: ${THEME.borderLight};
display: flex;
flex-direction: column;
padding: 12px 8px;
gap: 6px;
">
<button class="pc-tab-btn" data-tab="keybinds" style="
padding: 10px 12px; border-radius: 8px; border: none; background: transparent; color: #fff;
font-family: inherit; font-size: 13px; cursor: pointer; text-align: left; transition: 0.2s;
">⌨️ Keybinds</button>
<button class="pc-tab-btn" data-tab="changelog" style="
padding: 10px 12px; border-radius: 8px; border: none; background: transparent; color: #fff;
font-family: inherit; font-size: 13px; cursor: pointer; text-align: left; transition: 0.2s;
">📜 Changelog</button>
</div>
<div id="pc-tab-content" style="flex: 1; padding: 16px 14px 16px 16px; overflow-y: auto;"></div>
</div>
`;
document.body.appendChild(win);
const titlebar = win.querySelector('#pc-mac-titlebar');
let isDragging = false;
let startX = 0;
let startY = 0;
let initLeft = 0;
let initTop = 0;
titlebar.addEventListener('mousedown', e => {
if (e.target.id === 'pc-mac-close') return;
isDragging = true;
titlebar.style.cursor = 'grabbing';
startX = e.clientX;
startY = e.clientY;
const rect = win.getBoundingClientRect();
initLeft = rect.left;
initTop = rect.top;
});
document.addEventListener('mousemove', e => {
if (!isDragging) return;
win.style.left = (initLeft + e.clientX - startX) + 'px';
win.style.top = (initTop + e.clientY - startY) + 'px';
}, true);
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
titlebar.style.cursor = 'grab';
}
}, true);
win.querySelector('#pc-mac-close').addEventListener('click', () => {
win.style.display = 'none';
});
win.querySelectorAll('.pc-tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentTab = btn.getAttribute('data-tab');
renderTabs();
});
});
renderTabs();
}
function setupTriCardLayout() {
const holder = document.getElementById('menuCardHolder');
if (!holder) return;
let skinCard = document.getElementById('pc-skin-card');
if (!skinCard) {
skinCard = document.createElement('div');
skinCard.id = 'pc-skin-card';
skinCard.style.cssText = `
width: 170px;
height: 210px;
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 10px !important;
box-shadow: ${THEME.shadow} !important;
padding: 12px 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
`;
skinCard.innerHTML = `<div style="font-size: 12px; font-weight: bold; color: ${THEME.accent}; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 0.5px;">Skin Color</div><div id="pc-skin-insert"></div>`;
holder.insertBefore(skinCard, holder.firstChild);
}
const origSkinHolder = document.getElementById('skinColorHolder');
const insertTarget = document.getElementById('pc-skin-insert');
if (origSkinHolder && insertTarget && origSkinHolder.parentNode !== insertTarget) {
insertTarget.appendChild(origSkinHolder);
origSkinHolder.style.cssText = `display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; justify-items: center; width: 100%;`;
}
const playCard = document.querySelector('.menuCard:not(#guideCard)');
if (playCard && !playCard._titled) {
playCard._titled = true;
const title = document.createElement('div');
title.style.cssText = `font-size: 12px; font-weight: bold; color: ${THEME.accent}; text-transform: uppercase; letter-spacing: 0.5px; text-align: center;`;
title.textContent = 'Player';
playCard.insertBefore(title, playCard.firstChild);
}
const guideCard = document.getElementById('guideCard');
if (guideCard && !guideCard._rebuilt) {
guideCard._rebuilt = true;
guideCard.style.cssText = `
width: 320px;
height: 280px;
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 10px !important;
box-shadow: ${THEME.shadow} !important;
padding: 12px;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
`;
guideCard.innerHTML = `
<div style="font-size: 12px; font-weight: bold; color: ${THEME.accent}; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px;">Server Browser</div>
<div id="pc-server-list" style="flex: 1; overflow-y: auto; padding-right: 4px; display: flex; flex-direction: column; gap: 5px;"></div>
`;
}
}
function applyVisuals() {
const style = document.createElement('style');
style.innerHTML = `
.material-icons { font-family: 'Material Icons' !important; }
#userscript-warning, #ad-container, #pre-content-container,
div[id*="warning"], div[class*="warning"], div[id*="error"] {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
pointer-events: none !important;
}
#mainMenu:not([style*="display: none"]) {
position: absolute !important;
inset: 0 !important;
width: 100vw !important;
height: 100vh !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
background: transparent !important;
box-sizing: border-box !important;
}
#mainMenu[style*="display: none"] {
display: none !important;
}
#menuCardHolder {
display: flex !important;
flex-direction: row !important;
align-items: flex-start !important;
justify-content: center !important;
margin: 0 auto !important;
gap: 16px !important;
border: none !important;
background: transparent !important;
box-shadow: none !important;
}
.menuCard:not(#guideCard) {
width: 250px !important;
height: 210px !important;
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 10px !important;
box-shadow: ${THEME.shadow} !important;
color: #fff !important;
padding: 12px 14px !important;
display: flex !important;
flex-direction: column !important;
justify-content: space-between !important;
box-sizing: border-box !important;
margin: 0 !important;
}
#nameInput {
background: rgba(0, 0, 0, 0.4) !important;
border: 1px solid rgba(255, 42, 42, 0.4) !important;
border-radius: 8px !important;
color: #fff !important;
font-family: 'Hammersmith One', Arial, sans-serif !important;
font-size: 16px !important;
text-align: center !important;
padding: 7px !important;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.5) !important;
width: 100% !important;
box-sizing: border-box !important;
}
#turnstileWidget {
display: flex !important;
justify-content: center !important;
align-items: center !important;
background: rgba(0, 0, 0, 0.3) !important;
backdrop-filter: blur(5px) !important;
-webkit-backdrop-filter: blur(5px) !important;
border: 1px solid rgba(255, 42, 42, 0.35) !important;
border-radius: 8px !important;
padding: 2px !important;
margin: 2px 0 !important;
overflow: hidden !important;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.4) !important;
height: 52px !important;
width: 100% !important;
box-sizing: border-box !important;
}
#turnstileWidget > div, #turnstileWidget iframe {
transform: scale(0.72) !important;
transform-origin: center center !important;
border-radius: 6px !important;
}
#enterGame {
background: linear-gradient(90deg, #8b0000, #ff1a1a) !important;
border: 1.5px solid ${THEME.accent} !important;
border-radius: 8px !important;
box-shadow: 0 0 14px rgba(255, 0, 0, 0.45) !important;
color: #fff !important;
font-weight: bold !important;
font-family: 'Hammersmith One', Arial, sans-serif !important;
letter-spacing: 1.5px !important;
padding: 10px !important;
cursor: pointer !important;
transition: 0.15s !important;
width: 100% !important;
}
#enterGame:hover {
transform: scale(1.02) !important;
box-shadow: 0 0 22px rgba(255, 0, 0, 0.75) !important;
}
#serverBrowser {
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
}
#guideCard h2, #guideCard h3, #guideCard p { display: none !important; }
.skinColorItem {
border: 2px solid rgba(255, 255, 255, 0.2) !important;
border-radius: 50% !important;
width: 26px !important;
height: 26px !important;
transition: 0.15s !important;
}
.skinColorItem:hover, .activeSkin {
border-color: ${THEME.accent} !important;
box-shadow: 0 0 10px rgba(255, 77, 109, 0.8) !important;
transform: scale(1.15) !important;
}
#leaderboard {
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 8px !important;
box-shadow: ${THEME.shadow} !important;
color: ${THEME.text} !important;
}
#allianceButton, #storeButton, #chatButton, #settingsButton, #leaderboardButton {
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 8px !important;
box-shadow: ${THEME.shadow} !important;
}
#mapDisplay {
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 8px !important;
box-shadow: ${THEME.shadow} !important;
}
#scoreDisplay, #foodDisplay, #woodDisplay, #stoneDisplay, #killCounter, .resourceDisplay {
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 6px !important;
box-shadow: ${THEME.shadow} !important;
padding: 4px 10px !important;
color: #fff !important;
}
.actionBarItem {
background-color: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 8px !important;
box-shadow: ${THEME.shadow} !important;
}
#ageBarContainer {
background: transparent !important;
border: none !important;
box-shadow: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
#ageBar {
background: ${THEME.bg} !important;
backdrop-filter: ${THEME.blur} !important;
-webkit-backdrop-filter: ${THEME.blur} !important;
border: ${THEME.border} !important;
border-radius: 6px !important;
box-shadow: ${THEME.shadow} !important;
overflow: hidden !important;
}
#ageBarBody { background: linear-gradient(90deg, #8b0000, #ff1a1a) !important; }
#pc-server-list::-webkit-scrollbar, #pc-macos-window *::-webkit-scrollbar { width: 5px; }
#pc-server-list::-webkit-scrollbar-track, #pc-macos-window *::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.2); border-radius: 8px; }
#pc-server-list::-webkit-scrollbar-thumb, #pc-macos-window *::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgba(255, 77, 109, 0.7), rgba(255, 26, 26, 0.5));
border-radius: 8px;
}
`;
document.head.appendChild(style);
const gameName = document.getElementById('gameName');
if (gameName) {
gameName.innerHTML = 'PidorasiClient';
gameName.style.cssText = `
color: ${THEME.title};
text-shadow: 0 0 18px #ff0000, 0 0 35px #ff4d6d;
font-size: 90px;
text-align: center;
font-family: 'Hammersmith One', Arial, sans-serif;
margin-bottom: 24px;
width: 100%;
`;
}
const enterBtn = document.getElementById('enterGame');
if (enterBtn) {
enterBtn.innerHTML = '<span>→ PLAY ←</span>';
}
const died = document.getElementById('diedText');
if (died) {
died.innerHTML = "You have been хвайт'ед";
died.style.color = THEME.accent;
died.style.textShadow = '0 0 25px #ff0000, 0 0 45px #ff4d6d';
died.style.fontFamily = "'Hammersmith One', Arial, sans-serif";
died.style.letterSpacing = '1px';
}
document.getElementById('promoImgHolder')?.remove();
}
// ==========================================
// KEYDOWN LISTENER
// ==========================================
document.addEventListener('keydown', event => {
if (rebindingAction) {
event.preventDefault();
if (event.code === 'Escape') {
rebindingAction = null;
} else {
userBinds[rebindingAction] = event.code;
saveBinds();
rebindingAction = null;
}
renderTabs();
updateStatus();
return;
}
if (event.code === userBinds.openMenu && userBinds.openMenu !== 'NONE') {
event.preventDefault();
if (!isPlayerInGame()) return;
const win = document.getElementById('pc-macos-window');
if (win) {
const isHidden = win.style.display === 'none' || !win.style.display;
win.style.display = isHidden ? 'flex' : 'none';
}
return;
}
const tag = document.activeElement?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
if (!isPlayerInGame()) return;
const isZoomOut = event.code === 'Minus' || event.key === '-' || event.key === '_' || event.keyCode === 189 || event.keyCode === 173;
const isZoomIn = event.code === 'Equal' || event.key === '=' || event.key === '+' || event.keyCode === 187 || event.keyCode === 61;
if (isZoomOut) {
event.preventDefault();
event.stopPropagation();
cameraZoom = Math.max(0.25, Math.round((cameraZoom - 0.1) * 100) / 100);
applyZoom();
updateStatus();
return;
}
if (isZoomIn) {
event.preventDefault();
event.stopPropagation();
cameraZoom = Math.min(2.5, Math.round((cameraZoom + 0.1) * 100) / 100);
applyZoom();
updateStatus();
return;
}
if (userBinds.turretInsta !== 'NONE' && event.code === userBinds.turretInsta) {
executeTurretInstaCombo();
}
if (userBinds.instaMusket !== 'NONE' && event.code === userBinds.instaMusket) {
executeFastInstaCombo();
}
if (userBinds.toggleAutoTrap !== 'NONE' && event.code === userBinds.toggleAutoTrap) {
autoTrapEnabled = !autoTrapEnabled;
updateStatus();
renderTabs();
}
if (userBinds.autoWindmills !== 'NONE' && event.code === userBinds.autoWindmills) {
autoWindmillsEnabled = !autoWindmillsEnabled;
updateStatus();
}
if (userBinds.toggleHat !== 'NONE' && event.code === userBinds.toggleHat) toggleHat();
if (userBinds.toggleSoldier !== 'NONE' && event.code === userBinds.toggleSoldier) toggleSoldierHelmet();
if (userBinds.toggleTail !== 'NONE' && event.code === userBinds.toggleTail) toggleMonkeyTail();
if (userBinds.toggleHeal !== 'NONE' && event.code === userBinds.toggleHeal) {
autoHealEnabled = !autoHealEnabled;
renderTabs();
updateStatus();
}
}, true);
// ==========================================
// INITIALIZATION
// ==========================================
function init() {
if (!document.body) return setTimeout(init, 40);
applyVisuals();
createStatus();
createMacGui();
initOverlayCanvas();
requestAnimationFrame(renderPredictLoop);
loadServersFromApi();
setInterval(() => {
document.querySelectorAll('div').forEach(el => {
if (el.textContent && el.textContent.includes('disabling all of your browser extensions')) {
el.remove();
}
});
const mainMenu = document.getElementById('mainMenu');
const gameUI = document.getElementById('gameUI');
if (gameUI && gameUI.style.display === 'block') {
if (mainMenu && mainMenu.style.display !== 'none') {
mainMenu.style.display = 'none';
}
}
setupTriCardLayout();
if (cachedServers.length === 0) {
loadServersFromApi();
}
updateStatus();
updateCoordDisplay();
const died = document.getElementById('diedText');
if (died && died.innerHTML !== "You have been хвайт'ед") {
died.innerHTML = "You have been хвайт'ед";
}
}, 150);
setInterval(loadServersFromApi, 5000);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();