Open-source EvoWorld.io userscript with a configurable GUI, player and object ESP, visual tools, skin and pet customization, keybinds, configs, and optional Auto Hit. Support: https://t.me/evoezsquad
// ==UserScript==
// @name EZSQUAD for EvoWorld.io
// @name:ru EZSQUAD для EvoWorld.io
// @namespace https://t.me/evoezsquad
// @version 1.1.0
// @description Open-source EvoWorld.io userscript with a configurable GUI, player and object ESP, visual tools, skin and pet customization, keybinds, configs, and optional Auto Hit. Support: https://t.me/evoezsquad
// @description:ru Открытый userscript для EvoWorld.io: настраиваемое GUI, ESP игроков и объектов, визуальные функции, Skin/Pet Changer, бинды, конфиги и опциональный Auto Hit. Поддержка: https://t.me/evoezsquad
// @author EZSQUAD
// @license MIT
// @match https://evoworld.io/*
// @match https://www.evoworld.io/*
// @run-at document-idle
// @grant none
// @sandbox raw
// @noframes
// @homepageURL https://t.me/evoezsquad
// @supportURL https://t.me/evoezsquad
// @compatible chrome
// @compatible edge
// @compatible firefox
// ==/UserScript==
;(function () {
"use strict";
if (window.EZSQUAD && window.EZSQUAD.active) {
if (document.getElementById("ez-menu")) return;
try { if (typeof window.EZSQUAD.stop === "function") window.EZSQUAD.stop(); } catch (e) {}
}
var storeKey = "ezsquad.cfg.24";
var legacyStoreKey = "ezsquad.cfg.23";
var placeKey = "ezsquad.pos.7";
var keysPlaceKey = "ezsquad.keys.pos.7";
var raf = 0;
var menu = null;
var layer = null;
var ctx = null;
var keyBox = null;
var keysPanel = null;
var fileBox = null;
var soundFileBox = null;
var bindMode = false;
var dead = false;
var fitDirty = true;
var listAt = 0;
var scanAt = 0;
var scanReadAt = 0;
var fitAt = 0;
var worldAt = 0;
var worldDirty = true;
var players = [];
var hides = [];
var extras = [];
var scans = [];
var live = new Set();
var bank = new Map();
var scanBank = new Map();
var cardBank = new Map();
var envBase = new Map();
var viewBase = null;
var brightContext = null;
var brightDraw = null;
var brightWrap = null;
var canvasFilter = null;
var bindPick = null;
var bindPop = null;
var holdKeys = Object.create(null);
var deathPanel = null;
var deathList = null;
var deathEntries = [];
var deathUiAt = 0;
var deathSocket = null;
var deathHandler = null;
var deathPosKey = "ezsquad.deaths.pos.10";
var spectatorPanel = null;
var spectatorList = null;
var spectatorSocket = null;
var spectatorHandler = null;
var spectatorEntries = new Map();
var spectatorRequestAt = 0;
var spectatorUiAt = 0;
var spectatorPosKey = "ezsquad.spectators.pos.1";
var killPanel = null;
var killList = null;
var killEntries = [];
var killUiAt = 0;
var killSocket = null;
var killHandler = null;
var killPosKey = "ezsquad.kills.pos.1";
var skillTracks = new Map();
var projectileOwners = new Map();
var projectileSkillSeen = new Map();
var staticFoodSpawnCache = new Map();
var soundUploadKind = "hit";
var killSoundFileBox = null;
var foodEvolution = "";
var audio = null;
var customSounds = [];
var soundDbPromise = null;
var soundBuffers = new Map();
var soundLoadToken = 0;
var soundsReady = false;
var hitBank = new Map();
var lastHitSound = 0;
var privacyNames = new Map();
var privacyDom = new Map();
var cosmeticAt = 0;
var catalogAt = 0;
var catalogSign = "";
var skinLoads = new Set();
var spriteBounds = new WeakMap();
var recentPlayers = new Map();
var darknessDraw = null;
var darknessWrap = null;
var attackSocket = null;
var attackHandler = null;
var attackDrawOriginal = null;
var attackDrawWrap = null;
var ownAttackAt = 0;
var ownAttackDirection = 1;
var ownAttackTargets = new Set();
var ownAttackAnimation = "";
var localPet = null;
var localPetOwner = null;
var localPetMe = null;
var cosmeticMe = null;
var appliedSkinSpawn = "";
var appliedPetSpawn = "";
var hiddenPet = null;
var hiddenPetOpacity = 1;
var skinHold = null;
var petAttackSocket = null;
var petAttackHandler = null;
var spriteImages = new WeakMap();
var bladeRegions = new WeakMap();
var attackRegions = new Map();
var foodObjects = [];
var foodSpawns = [];
var foodSpawnState = new Map();
var scanIconBase = null;
var bossSync = null;
var skillSync = null;
var skillSyncAt = 0;
var bossSyncSocket = null;
var bossSyncHandler = null;
var foodSeen = new Map();
var foodGone = new Map();
var foodSyncSocket = null;
var foodSyncHandler = null;
var removeObjectOriginal = null;
var removeObjectWrap = null;
var protectedDom = new Map();
var protectedAt = 0;
var lastKillerName = "";
var skillClock = null;
var skillClockHost = null;
var skillClockHostPosition = "";
var skillLocalEnd = 0;
var skillLocalMe = 0;
var skillUseOriginal = null;
var skillUseWrap = null;
var fightNext = 0;
var fightTry = 0;
var fightLock = null;
var fightLockUntil = 0;
var fightRelease = 0;
var fightAttack = 0;
var defaults = {
esp: {
enabled: false,
tracerOrigin: "bottom"
},
settings: {
bind: "Insert",
bindName: "Insert",
keyList: false
},
all: {
on: false,
box: false,
boxColor: "#b58af0",
fill: false,
fillColor: "#7252a8",
line: false,
lineColor: "#b58af0",
name: false,
nameColor: "#ffffff",
level: false,
levelColor: "#d9c7ff",
distance: false,
distanceColor: "#c3c6cd",
health: false,
healthColor: "#50cf72",
armor: false,
armorColor: "#52a9ff",
arrows: false,
arrowColor: "#b58af0",
effects: false,
skill: false,
hidden: false
},
danger: {
on: false,
box: false,
boxColor: "#ef5365",
fill: false,
fillColor: "#99323e",
line: false,
lineColor: "#ef5365",
name: false,
nameColor: "#ffe0e4",
level: false,
levelColor: "#ff9da8",
distance: false,
distanceColor: "#ffc0c8",
health: false,
healthColor: "#ef5365",
armor: false,
armorColor: "#6db6ff",
arrows: false,
arrowColor: "#ef5365",
effects: false,
skill: false,
hidden: false
},
edible: {
on: false,
box: false,
boxColor: "#45d39b",
fill: false,
fillColor: "#287e60",
line: false,
lineColor: "#45d39b",
name: false,
nameColor: "#dcfff1",
level: false,
levelColor: "#83e8c0",
distance: false,
distanceColor: "#a8efd5",
health: false,
healthColor: "#45d39b",
armor: false,
armorColor: "#68b9ff",
arrows: false,
arrowColor: "#45d39b",
effects: false,
skill: false,
hidden: false
},
mutual: {
on: false,
box: false,
boxColor: "#f0a14a",
fill: false,
fillColor: "#9b652d",
line: false,
lineColor: "#f0a14a",
name: false,
nameColor: "#ffe3bd",
level: false,
levelColor: "#ffc77d",
distance: false,
distanceColor: "#ffd39a",
health: false,
healthColor: "#f0a14a",
armor: false,
armorColor: "#70b7ff",
arrows: false,
arrowColor: "#f0a14a",
effects: false,
skill: false,
hidden: false
},
extra: {
tracerOrigin: "bottom",
hide: false,
hideColor: "#c59af2",
hideBox: true,
hideTracer: false,
hideName: true,
hideDistance: true,
hideImage: false,
hideRange: 2500,
portal: false,
portalColor: "#ffbf69",
portalBox: true,
portalTracer: false,
portalName: true,
portalDistance: true,
portalImage: false,
portalRange: 5000,
food: false,
foodColor: "#77df83",
foodBox: true,
foodTracer: false,
foodName: true,
foodDistance: true,
foodImage: false,
foodRange: 1800,
foodTimer: false,
boss: false,
bossColor: "#ff6a78",
bossBox: true,
bossTracer: true,
bossName: true,
bossDistance: true,
bossImage: false,
bossRange: 8000,
others: false,
othersColor: "#67d9ff",
othersBox: true,
othersTracer: false,
othersName: true,
othersDistance: true,
othersImage: true,
othersRange: 2500,
othersGem: true,
othersArmorPotion: true,
othersWaterDrop: true,
othersRoots: true,
othersFireball: true,
othersPlasmaBall: true,
othersShuriken: true,
othersSnowball: true,
othersBomb: true,
othersBearTrap: true
},
other: {
fullBright: false,
zoom: false,
zoomValue: 1,
cloudAlpha: 100,
bushAlpha: 100,
waterAlpha: 100,
swampAlpha: 100,
lavaAlpha: 100
},
misc: {
petChanger: false,
petId: 0,
skinPick: {},
hitbox: false,
hitboxColor: "#f0a14a",
privacy: false,
privacyServer: true,
privacyLeaderboard: true,
privacyIdentity: true,
privacyPlayers: true,
deathLog: false,
deathTime: 60,
spectatorList: false,
killLog: false,
killTime: 60,
hitSound: false,
hitSoundType: "Click",
hitVolume: 45,
killSound: false,
killSoundType: "Bell",
killVolume: 45,
autoHit: false,
reaperTargets: false
},
binds: {
fullBright: { key: "", name: "None", mode: "toggle" },
zoom: { key: "", name: "None", mode: "toggle" },
autoHit: { key: "", name: "None", mode: "toggle" },
reapersOnly: { key: "", name: "None", mode: "toggle" }
}
};
var drawRows = [
["on", "Enabled"],
["box", "Box", "boxColor"],
["fill", "Fill", "fillColor"],
["line", "Lines", "lineColor"],
["name", "Name", "nameColor"],
["level", "Level", "levelColor"],
["distance", "Distance", "distanceColor"],
["health", "Health bar", "healthColor"],
["armor", "Armor bar", "armorColor"],
["arrows", "Arrows", "arrowColor"],
["effects", "Effects"],
["hidden", "Hidden / Cached"]
];
function clone(value) {
return JSON.parse(JSON.stringify(value));
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function hudScale() {
var scale = 1;
try {
if (window.game && Number.isFinite(Number(game.fontScale))) scale = Number(game.fontScale);
if (cfg.other && cfg.other.zoom) {
var zoom = clamp(Number(cfg.other.zoomValue) || 1, .15, 1);
scale = Math.min(scale, .55 + zoom * .45);
}
} catch (e) {}
return clamp(scale, .55, 1);
}
function hudPx(value, minimum) {
return Math.max(minimum == null ? 0 : minimum, value * hudScale());
}
function merge(base, saved) {
if (!saved || typeof saved !== "object") return base;
if (base && typeof base === "object" && !Array.isArray(base) && !Object.keys(base).length) return clone(saved);
Object.keys(base).forEach(function (name) {
if (!Object.prototype.hasOwnProperty.call(saved, name)) return;
var a = base[name];
var b = saved[name];
if (a && b && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
base[name] = merge(a, b);
} else if (typeof a === typeof b) {
base[name] = b;
}
});
return base;
}
function loadCfg() {
var data = clone(defaults);
try {
var saved = localStorage.getItem(storeKey);
var old = false;
if (!saved) {
saved = localStorage.getItem(legacyStoreKey);
old = !!saved;
}
data = merge(data, JSON.parse(saved || "null"));
if (old) {
data.binds.autoHit = clone(defaults.binds.autoHit);
data.binds.reapersOnly = clone(defaults.binds.reapersOnly);
}
} catch (e) {}
return data;
}
var cfg = loadCfg();
cfg.settings.bind = fixKey(cfg.settings.bind);
Object.keys(cfg.binds).forEach(function (name) { cfg.binds[name].key = fixKey(cfg.binds[name].key); });
["all", "danger", "edible", "mutual"].forEach(function (name) { if (cfg[name]) cfg[name].skill = false; });
function saveCfg() {
try { localStorage.setItem(storeKey, JSON.stringify(cfg)); } catch (e) {}
}
function read(path) {
var bits = path.split(".");
return cfg[bits[0]][bits[1]];
}
function write(path, value) {
var bits = path.split(".");
cfg[bits[0]][bits[1]] = value;
saveCfg();
if (bits[0] === "other" || bits[0] === "misc") worldDirty = true;
if (bits[0] === "extra" || bits[0] === "danger" || bits[0] === "edible" || bits[0] === "mutual" || path.indexOf("misc.privacy") === 0) cardBank.clear();
if (path === "misc.spectatorList") updateSpectatorPanel(Date.now());
if (path === "misc.autoHit" && !value) {
clearTimeout(fightAttack);
clearTimeout(fightRelease);
try { if (typeof gameServer !== "undefined" && gameServer && typeof socketMsgType !== "undefined") gameServer.emit(socketMsgType.SKILL, false); } catch (e) {}
}
updateKeys();
}
function keyCode(event) {
var code = String(event && event.code || "");
if (code) return code;
var num = Number(event && (event.keyCode || event.which));
if (num >= 65 && num <= 90) return "Key" + String.fromCharCode(num);
if (num >= 48 && num <= 57) return "Digit" + String.fromCharCode(num);
if (num >= 96 && num <= 105) return "Numpad" + String(num - 96);
var map = { 13: "Enter", 27: "Escape", 32: "Space", 45: "Insert", 46: "Delete", 8: "Backspace", 9: "Tab", 16: "ShiftLeft", 17: "ControlLeft", 18: "AltLeft", 20: "CapsLock", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "ArrowLeft", 38: "ArrowUp", 39: "ArrowRight", 40: "ArrowDown" };
return map[num] || String(event && event.key || "");
}
function fixKey(value) {
value = String(value || "");
if (/^[a-z]$/i.test(value)) return "Key" + value.toUpperCase();
if (/^[0-9]$/.test(value)) return "Digit" + value;
return value;
}
function sameKey(value, event) {
var saved = fixKey(value);
var code = keyCode(event);
if (saved && code && saved === code) return true;
var num = Number(event && (event.keyCode || event.which));
if (/^Key[A-Z]$/.test(saved) && num >= 65 && num <= 90) return saved.slice(3).charCodeAt(0) === num;
if (/^Digit[0-9]$/.test(saved) && num >= 48 && num <= 57) return Number(saved.slice(5)) === num - 48;
return false;
}
function keyName(event) {
var code = keyCode(event);
if (code === "Space") return "Space";
if (code.indexOf("Key") === 0) return code.slice(3);
if (code.indexOf("Digit") === 0) return code.slice(5);
if (code.indexOf("Numpad") === 0) return "Num " + code.slice(6);
return event.key && event.key.length < 12 ? event.key : code;
}
var bindFeatures = {
fullBright: { path: "other.fullBright", title: "FullBright" },
zoom: { path: "other.zoom", title: "Zoom" },
autoHit: { path: "misc.autoHit", title: "Auto Hit" },
reapersOnly: { path: "misc.reaperTargets", title: "Reapers Only" }
};
function bindText(name) {
var item = cfg.binds[name];
return item && item.name ? item.name : "None";
}
function bindButton(name) {
return name ? '<button class="ez-feature-bind" type="button" data-feature-bind="' + name + '">' + bindText(name) + '</button>' : '';
}
function checkRow(path, title, colorPath, bindName, extra) {
var color = colorPath
? '<input class="ez-color" type="color" data-color="' + colorPath + '" value="' + read(colorPath) + '">'
: '<span class="ez-space"></span>';
return '<label class="ez-row"><span>' + title + '</span><span class="ez-tools">' + (extra || '') + color + bindButton(bindName) +
'<input class="ez-check" type="checkbox" data-check="' + path + '"' + (read(path) ? " checked" : "") + '></span></label>';
}
function bindCheckRow(path, title, name) {
return '<label class="ez-row"><span>' + title + '</span><span class="ez-tools">' +
'<input class="ez-check" type="checkbox" data-check="' + path + '"' + (read(path) ? " checked" : "") + '>' +
bindButton(name) + '</span></label>';
}
function selectRow(path, title, values) {
var html = values.map(function (item) {
return '<option value="' + item[0] + '"' + (String(read(path)) === String(item[0]) ? ' selected' : '') + '>' + item[1] + '</option>';
}).join('');
return '<label class="ez-row"><span>' + title + '</span><select class="ez-select" data-select="' + path + '">' + html + '</select></label>';
}
function cleanFileName(name) {
return String(name || "Custom").replace(/\.[^.]+$/, "").trim() || "Custom";
}
function soundOptionsHtml(kind) {
var path = kind === "kill" ? "killSoundType" : "hitSoundType";
var fallback = kind === "kill" ? "Bell" : "Click";
var selected = String(cfg.misc[path] || fallback);
var list = [["Click", "Click"], ["Bubble", "Bubble"], ["Bell", "Bell"], ["Metal", "Metal"]];
for (var i = 0; i < customSounds.length; i++) list.push(["custom:" + customSounds[i].id, customSounds[i].name]);
return list.map(function (item) {
return '<option value="' + escapeHtml(item[0]) + '"' + (selected === item[0] ? ' selected' : '') + '>' + escapeHtml(item[1]) + '</option>';
}).join("");
}
function soundRow(kind, title) {
var isKill = kind === "kill";
var toggle = isKill ? "misc.killSound" : "misc.hitSound";
var type = isKill ? "misc.killSoundType" : "misc.hitSoundType";
var volume = isKill ? "misc.killVolume" : "misc.hitVolume";
return checkRow(toggle, title) +
'<label class="ez-row ez-sound-row"><span>Sound</span><span class="ez-sound-tools">' +
'<select class="ez-select ez-' + kind + '-select" data-select="' + type + '">' + soundOptionsHtml(kind) + '</select>' +
'<button type="button" class="ez-mini" data-sound-action="upload" data-sound-kind="' + kind + '" title="Upload sound">+</button>' +
'<button type="button" class="ez-mini" data-sound-action="play" data-sound-kind="' + kind + '" title="Play sound">▶</button>' +
'<button type="button" class="ez-mini" data-sound-action="remove" data-sound-kind="' + kind + '" title="Remove custom sound">×</button>' +
'</span></label>' +
'<input class="ez-sound-file" data-sound-file="' + kind + '" type="file" accept="audio/*,.mp3,.wav,.ogg,.oga,.m4a,.aac,.flac,.webm">' +
rangeRow(volume, "Volume", 0, 100, 5, "%");
}
function hitSoundRow() {
return soundRow("hit", "Hitsound");
}
function killSoundRow() {
return soundRow("kill", "Killsound");
}
function rangeRow(path, title, min, max, step, suffix) {
var value = read(path);
return '<label class="ez-range-row"><span>' + title + '</span><span class="ez-range-tools">' +
'<input type="range" min="' + min + '" max="' + max + '" step="' + step + '" value="' + value + '" data-range="' + path + '">' +
'<b data-range-value="' + path + '">' + value + suffix + '</b></span></label>';
}
function rangeSuffix(path) {
if (path === "other.zoomValue") return "x";
if (path === "misc.deathTime" || path === "misc.killTime") return " s";
if (/Range$/.test(path)) return " m";
return "%";
}
function modeText(name) {
var out = [];
if (read("extra." + name + "Box")) out.push("Box");
if (read("extra." + name + "Tracer")) out.push("Tracer");
if (read("extra." + name + "Name")) out.push("Name");
if (read("extra." + name + "Distance")) out.push("Distance");
if (read("extra." + name + "Image")) out.push("Image");
return out.length ? out.join(", ") : "None";
}
var otherKinds = [
["Gem", "Gem"],
["ArmorPotion", "Armor potion"],
["WaterDrop", "Water drop"],
["Roots", "Roots"],
["Fireball", "Fireball"],
["PlasmaBall", "Plasma ball"],
["Shuriken", "Shuriken"],
["Snowball", "Snowball"],
["Bomb", "Bomb"],
["BearTrap", "Bear trap"]
];
function objectText() {
var out = [];
for (var i = 0; i < otherKinds.length; i++) {
if (read("extra.others" + otherKinds[i][0])) out.push(otherKinds[i][1]);
}
if (!out.length) return "None";
if (out.length === otherKinds.length) return "All objects";
return out.join(", ");
}
function objectMenu() {
return otherKinds.map(function (item) {
var path = "extra.others" + item[0];
return '<label><input type="checkbox" data-check="' + path + '"' + (read(path) ? " checked" : "") + '>' + item[1] + '</label>';
}).join("");
}
function extraRow(name, title, color, range) {
return '<div class="ez-extra-item">' +
'<label class="ez-row"><span>' + title + '</span><span class="ez-tools">' +
'<input class="ez-color" type="color" data-color="extra.' + name + 'Color" value="' + color + '">' +
'<input class="ez-check" type="checkbox" data-check="extra.' + name + '"' + (read("extra." + name) ? " checked" : "") + '>' +
'<button class="ez-mode-btn" type="button" data-mode-button="' + name + '">' + modeText(name) + '</button>' +
'</span></label>' +
'<div class="ez-mode-pop" data-mode-menu="' + name + '">' +
'<label><input type="checkbox" data-check="extra.' + name + 'Box"' + (read("extra." + name + "Box") ? " checked" : "") + '>Box</label>' +
'<label><input type="checkbox" data-check="extra.' + name + 'Tracer"' + (read("extra." + name + "Tracer") ? " checked" : "") + '>Tracer</label>' +
'<label><input type="checkbox" data-check="extra.' + name + 'Name"' + (read("extra." + name + "Name") ? " checked" : "") + '>Name</label>' +
'<label><input type="checkbox" data-check="extra.' + name + 'Distance"' + (read("extra." + name + "Distance") ? " checked" : "") + '>Distance</label>' +
'<label><input type="checkbox" data-check="extra.' + name + 'Image"' + (read("extra." + name + "Image") ? " checked" : "") + '>Image</label>' +
'</div>' +
rangeRow("extra." + name + "Range", "Range", 50, range, 50, " m") +
'</div>';
}
function othersRow() {
return '<div class="ez-extra-item">' +
'<label class="ez-row"><span>Others ESP</span><span class="ez-tools">' +
'<input class="ez-color" type="color" data-color="extra.othersColor" value="' + cfg.extra.othersColor + '">' +
'<input class="ez-check" type="checkbox" data-check="extra.others"' + (read("extra.others") ? " checked" : "") + '>' +
'<button class="ez-object-btn" type="button" data-object-button="others">' + objectText() + '</button>' +
'<button class="ez-mode-btn" type="button" data-mode-button="others">' + modeText("others") + '</button>' +
'</span></label>' +
'<div class="ez-object-pop" data-object-menu="others">' + objectMenu() + '</div>' +
'<div class="ez-mode-pop" data-mode-menu="others">' +
'<label><input type="checkbox" data-check="extra.othersBox"' + (read("extra.othersBox") ? " checked" : "") + '>Box</label>' +
'<label><input type="checkbox" data-check="extra.othersTracer"' + (read("extra.othersTracer") ? " checked" : "") + '>Tracer</label>' +
'<label><input type="checkbox" data-check="extra.othersName"' + (read("extra.othersName") ? " checked" : "") + '>Name</label>' +
'<label><input type="checkbox" data-check="extra.othersDistance"' + (read("extra.othersDistance") ? " checked" : "") + '>Distance</label>' +
'<label><input type="checkbox" data-check="extra.othersImage"' + (read("extra.othersImage") ? " checked" : "") + '>Image</label>' +
'</div>' +
rangeRow("extra.othersRange", "Range", 50, 5000, 50, " m") +
'</div>';
}
function tracerOriginSelect(path) {
var values = [["bottom", "Bottom"], ["top", "Top"], ["player", "Player"]];
var value = String(read(path) || "bottom");
return '<span class="ez-tracer-mode"><span>Tracer Mode</span><select class="ez-select ez-tracer-select" data-select="' + path + '">' + values.map(function (item) {
return '<option value="' + item[0] + '"' + (value === item[0] ? ' selected' : '') + '>' + item[1] + '</option>';
}).join("") + '</select></span>';
}
function playerEspHead() {
return '<div class="ez-head-controls">' +
'<label class="ez-head-toggle"><span>Player ESP</span><input class="ez-check" type="checkbox" data-check="esp.enabled"' + (read("esp.enabled") ? " checked" : "") + '></label>' +
tracerOriginSelect("esp.tracerOrigin") +
'</div>';
}
function playerPage(name) {
return '<div class="ez-subpage' + (name === "all" ? " ez-active" : "") + '" data-scope="visual" data-subpage="' + name + '">' +
drawRows.map(function (item) {
return checkRow(name + "." + item[0], item[1], item[2] ? name + "." + item[2] : "");
}).join("") + '</div>';
}
function espPanel() {
return '<div class="ez-page ez-active" data-page="esp">' +
'<div class="ez-work">' +
'<section class="ez-box">' +
'<div class="ez-box-head"><b>Visuals</b>' + playerEspHead() + '</div>' +
'<div class="ez-subtabs" data-scope-tabs="visual">' +
'<button class="ez-subtab ez-active" type="button" data-scope="visual" data-subtab="all">Players</button>' +
'<button class="ez-subtab" type="button" data-scope="visual" data-subtab="danger">Dangerous</button>' +
'<button class="ez-subtab" type="button" data-scope="visual" data-subtab="edible">Edible</button>' +
'<button class="ez-subtab" type="button" data-scope="visual" data-subtab="mutual">Mutual</button>' +
'</div>' +
playerPage("all") + playerPage("danger") + playerPage("edible") + playerPage("mutual") +
'</section>' +
'<section class="ez-box">' +
'<div class="ez-subtabs ez-side-tabs" data-scope-tabs="side">' +
'<button class="ez-subtab ez-active" type="button" data-scope="side" data-subtab="extra">Extra ESP</button>' +
'<span class="ez-side-tracer" data-extra-tracer>' + tracerOriginSelect("extra.tracerOrigin") + '</span>' +
'<button class="ez-subtab" type="button" data-scope="side" data-subtab="other">Other</button>' +
'</div>' +
'<div class="ez-subpage ez-active ez-extra-page" data-scope="side" data-subpage="extra">' +
extraRow("hide", "Hide ESP", cfg.extra.hideColor, 5000) +
extraRow("portal", "Portal ESP", cfg.extra.portalColor, 12000) +
extraRow("food", "Food ESP", cfg.extra.foodColor, 5000) +
extraRow("boss", "Boss ESP", cfg.extra.bossColor, 12000) +
othersRow() +
'</div>' +
'<div class="ez-subpage" data-scope="side" data-subpage="other">' +
checkRow("other.fullBright", "FullBright", "", "fullBright") +
checkRow("other.zoom", "Zoom", "", "zoom") +
rangeRow("other.zoomValue", "Zoom value", 0.15, 1, 0.05, "x") +
'<div class="ez-divider"></div>' +
rangeRow("other.cloudAlpha", "Cloud", 0, 100, 5, "%") +
rangeRow("other.bushAlpha", "Bush", 0, 100, 5, "%") +
rangeRow("other.waterAlpha", "Water", 0, 100, 5, "%") +
rangeRow("other.swampAlpha", "Swamp", 0, 100, 5, "%") +
rangeRow("other.lavaAlpha", "Lava", 0, 100, 5, "%") +
'<div class="ez-divider"></div>' +
'</div>' +
'</section>' +
'</div>' +
'</div>';
}
function privacyText() {
var out = [];
if (cfg.misc.privacyServer) out.push("Server");
if (cfg.misc.privacyLeaderboard) out.push("Leaderboard");
if (cfg.misc.privacyIdentity) out.push("Identity");
if (cfg.misc.privacyPlayers) out.push("Players");
return out.length ? out.join(", ") : "None";
}
function privacyMenu() {
return [
["privacyServer", "Server"],
["privacyLeaderboard", "Leaderboard"],
["privacyIdentity", "Identity"],
["privacyPlayers", "Players"]
].map(function (item) {
return '<label><input type="checkbox" data-check="misc.' + item[0] + '"' + (cfg.misc[item[0]] ? ' checked' : '') + '>' + item[1] + '</label>';
}).join('');
}
function miscPanel() {
var privacyButton = '<button class="ez-object-btn" type="button" data-privacy-button>' + privacyText() + '</button>';
return '<div class="ez-page" data-page="misc">' +
'<div class="ez-misc">' +
'<section class="ez-box ez-skin-box">' +
'<div class="ez-box-head"><b>Skin Changer</b></div>' +
'<div class="ez-pet-wrap">' +
checkRow("misc.petChanger", "Pet Changer", "", "", '<button class="ez-pet-button" type="button" data-pet-button>None</button>') +
'<div class="ez-pet-pop" data-pet-menu><div class="ez-empty">Pet list is loading</div></div>' +
'</div>' +
'<div class="ez-divider"></div>' +
'<div class="ez-skin-list" data-skin-list><div class="ez-empty">Skin list is loading</div></div>' +
'</section>' +
'<section class="ez-box ez-utility-box">' +
'<div class="ez-box-head"><b>Utility</b></div>' +
checkRow("misc.hitbox", "Hitbox", "misc.hitboxColor") +
checkRow("extra.foodTimer", "Food Timer") +
checkRow("misc.spectatorList", "Spectator List") +
'<div class="ez-divider"></div>' +
checkRow("misc.privacy", "Protected", "", "", privacyButton) +
'<div class="ez-object-pop ez-privacy-pop" data-privacy-menu>' + privacyMenu() + '</div>' +
'<div class="ez-divider"></div>' +
bindCheckRow("misc.autoHit", "Auto Hit", "autoHit") +
bindCheckRow("misc.reaperTargets", "Reapers Only", "reapersOnly") +
'<div class="ez-divider"></div>' +
checkRow("misc.deathLog", "Death Log") +
rangeRow("misc.deathTime", "Remove after", 10, 180, 5, " s") +
checkRow("misc.killLog", "Kill Log") +
rangeRow("misc.killTime", "Remove after", 10, 180, 5, " s") +
'<div class="ez-divider"></div>' +
hitSoundRow() +
'<div class="ez-divider"></div>' +
killSoundRow() +
'</section>' +
'</div>' +
'</div>';
}
function settingsPanel() {
return '<div class="ez-page" data-page="settings">' +
'<div class="ez-settings">' +
'<section class="ez-group"><h3>Interface</h3>' +
'<label class="ez-row"><span>Menu key</span><button class="ez-bind" type="button">' + cfg.settings.bindName + '</button></label>' +
checkRow("settings.keyList", "Keybind list") +
'</section>' +
'<section class="ez-group"><h3>Keybinds</h3>' +
'<div class="ez-bind-line"><span>Menu</span><b data-bind-name>' + cfg.settings.bindName + '</b></div>' +
Object.keys(bindFeatures).map(function (name) { return '<div class="ez-bind-line"><span>' + bindFeatures[name].title + '</span><b data-bind-setting="' + name + '">' + bindText(name) + '</b></div>'; }).join('') +
'</section>' +
'<section class="ez-group"><h3>Config</h3>' +
'<div class="ez-actions">' +
'<button type="button" data-action="save">Save</button>' +
'<button type="button" data-action="load">Load</button>' +
'<button type="button" data-action="reset">Reset</button>' +
'</div>' +
'<input class="ez-file" type="file" accept="application/json,.json">' +
'</section>' +
'</div>' +
'</div>';
}
function addStyle() {
var style = document.createElement("style");
style.id = "ez-style";
style.textContent = [
"#ez-menu{position:fixed;left:18px;top:58px;width:820px;max-width:calc(100vw - 16px);z-index:2147483646;background:#151619;border:1px solid #373940;border-radius:5px;color:#d7d9de;font:12px Arial,sans-serif;box-shadow:0 10px 28px rgba(0,0,0,.46);user-select:none}",
"#ez-menu *{box-sizing:border-box}",
"#ez-menu.ez-hide{display:none}",
"#ez-menu .ez-head{height:34px;display:flex;align-items:center;padding:0 7px 0 11px;border-bottom:1px solid #303238;background:#1a1b1f;cursor:move}",
"#ez-menu .ez-head b{font-size:12px;letter-spacing:.8px}",
"#ez-menu .ez-close{margin-left:auto;width:23px;height:23px;border:0;border-radius:3px;background:transparent;color:#858991;font:17px/21px Arial;cursor:pointer}",
"#ez-menu .ez-close:hover{background:#342126;color:#ff7b88}",
"#ez-menu .ez-tabs{height:32px;display:flex;align-items:end;padding:0 8px;border-bottom:1px solid #303238;background:#17181c}",
"#ez-menu .ez-tab{height:27px;min-width:74px;padding:0 13px;border:0;border-bottom:2px solid transparent;background:transparent;color:#858a92;font:11px Arial;cursor:pointer}",
"#ez-menu .ez-tab:hover{color:#c8cad0}",
"#ez-menu .ez-tab.ez-active{color:#e2e3e7;border-bottom-color:#8b6bd1}",
"#ez-menu .ez-page{display:none}",
"#ez-menu .ez-page.ez-active{display:block}",
"#ez-menu .ez-work{display:grid;grid-template-columns:1.12fr .88fr;gap:8px;padding:8px}",
"#ez-menu .ez-box,#ez-menu .ez-group{overflow:hidden;border:1px solid #2e3036;border-radius:4px;background:#18191d}",
"#ez-menu .ez-box-head{height:32px;padding:0 8px;display:flex;align-items:center;border-bottom:1px solid #2e3036;background:#1e1f24}",
"#ez-menu .ez-box-head>b{font-size:10px;color:#c1c3ca}",
"#ez-menu .ez-head-controls{margin-left:auto;display:flex;align-items:center;gap:10px}",
"#ez-menu .ez-head-toggle{display:flex;align-items:center;gap:6px;color:#bfc2c8;font-size:10px;white-space:nowrap;cursor:pointer}",
"#ez-menu .ez-subtabs{height:31px;display:flex;padding:4px 5px 0;border-bottom:1px solid #2e3036;background:#1a1b20}",
"#ez-menu .ez-side-tabs{align-items:center;gap:3px;padding-left:7px;padding-right:6px}",
"#ez-menu .ez-side-tabs .ez-subtab{flex:0 0 auto;min-width:68px}",
"#ez-menu .ez-side-tracer{margin-left:auto;display:flex;align-items:center}",
"#ez-menu .ez-subtab{height:26px;flex:1;padding:0 8px;border:0;border-bottom:2px solid transparent;background:transparent;color:#858990;font:10px Arial;cursor:pointer}",
"#ez-menu .ez-subtab:hover{color:#c7c9ce}",
"#ez-menu .ez-subtab.ez-active{color:#e4e5e8;border-bottom-color:#8d70ca}",
"#ez-menu .ez-subpage{display:none}",
"#ez-menu .ez-subpage.ez-active{display:block}",
"#ez-menu .ez-extra-page{max-height:390px;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin}",
"#ez-menu .ez-extra-item{position:relative;border-bottom:1px solid #303238}",
"#ez-menu .ez-extra-item:last-child{border-bottom:0}",
"#ez-menu .ez-extra-item>.ez-range-row{height:29px;padding-left:18px;background:#16171b}",
"#ez-menu .ez-extra-item>.ez-range-row>span:first-child{color:#858991;font-size:10px}",
"#ez-menu .ez-mode-btn,#ez-menu .ez-object-btn{height:20px;padding:0 6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid #44474e;border-radius:3px;background:#22242a;color:#bfc2c8;font:9px Arial;cursor:pointer}",
"#ez-menu .ez-mode-btn{max-width:86px}",
"#ez-menu .ez-object-btn{max-width:96px}",
"#ez-menu .ez-mode-btn:hover,#ez-menu .ez-mode-btn.ez-open,#ez-menu .ez-object-btn:hover,#ez-menu .ez-object-btn.ez-open{border-color:#7660a8;color:#e0d5f8}",
"#ez-menu .ez-mode-pop,#ez-menu .ez-object-pop{display:none;gap:4px;padding:6px 8px;border-top:1px solid #292b30;background:#141519}",
"#ez-menu .ez-mode-pop{grid-template-columns:repeat(5,1fr)}",
"#ez-menu .ez-object-pop{grid-template-columns:repeat(3,1fr)}",
"#ez-menu .ez-mode-pop.ez-open,#ez-menu .ez-object-pop.ez-open{display:grid}",
"#ez-menu .ez-mode-pop label,#ez-menu .ez-object-pop label{height:22px;display:flex;align-items:center;gap:5px;color:#aeb1b8;font-size:9px;cursor:pointer}",
"#ez-menu .ez-mode-pop input,#ez-menu .ez-object-pop input{accent-color:#8d70ca}",
"#ez-menu .ez-row{height:28px;padding:0 8px 0 9px;display:flex;align-items:center;border-bottom:1px solid #25272c;background:#18191d}",
"#ez-menu .ez-row:last-child{border-bottom:0}",
"#ez-menu .ez-row:hover,#ez-menu .ez-range-row:hover{background:#1b1d21}",
"#ez-menu .ez-tools{margin-left:auto;display:flex;align-items:center;gap:8px}",
"#ez-menu .ez-space{width:18px}",
"#ez-menu .ez-check{appearance:none;width:27px;height:14px;margin:0;border:1px solid #4a4d54;border-radius:10px;background:#292b30;cursor:pointer;position:relative}",
"#ez-menu .ez-check:before{content:'';position:absolute;left:2px;top:2px;width:8px;height:8px;border-radius:50%;background:#858991;transition:.12s}",
"#ez-menu .ez-check:checked{border-color:#765cae;background:#4b3b6b}",
"#ez-menu .ez-check:checked:before{left:15px;background:#d5c3ff}",
"#ez-menu .ez-color{appearance:none;width:18px;height:16px;padding:0;border:1px solid #454850;border-radius:3px;background:none;cursor:pointer}",
"#ez-menu .ez-color::-webkit-color-swatch-wrapper{padding:2px}",
"#ez-menu .ez-color::-webkit-color-swatch{border:0;border-radius:1px}",
"#ez-menu .ez-range-row{height:34px;padding:0 8px 0 9px;display:flex;align-items:center;border-bottom:1px solid #25272c;background:#18191d}",
"#ez-menu .ez-range-tools{margin-left:auto;display:flex;align-items:center;gap:7px}",
"#ez-menu input[type=range]{width:112px;height:3px;accent-color:#8d70ca;cursor:pointer}",
"#ez-menu .ez-range-tools b{min-width:34px;color:#b9a4e8;font-size:9px;text-align:right}",
"#ez-menu .ez-divider{height:8px;border-top:1px solid #303238;border-bottom:1px solid #25272c;background:#141519}",
"#ez-menu .ez-settings,#ez-menu .ez-misc{display:grid;grid-template-columns:1fr 1fr;gap:8px;padding:8px}",
"#ez-menu .ez-misc>.ez-box{min-height:410px}",
"#ez-menu .ez-skin-box{position:relative}",
"#ez-menu .ez-skin-list{max-height:330px;overflow:auto;scrollbar-width:thin}",
"#ez-menu .ez-skin-row{min-height:43px;padding:4px 8px;display:grid;grid-template-columns:1fr 58px 145px;align-items:center;gap:7px;border-bottom:1px solid #25272c}",
"#ez-menu .ez-skin-row>.ez-skin-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#bfc1c7}",
"#ez-menu .ez-skin-preview{height:34px;display:grid;grid-template-columns:28px 1fr;grid-template-rows:17px 13px;align-items:center;column-gap:4px}",
"#ez-menu .ez-skin-preview img,#ez-menu .ez-skin-preview em{grid-row:1/3;width:28px;height:28px;object-fit:contain;border-radius:3px;background:#111216;font-style:normal;font-size:8px;display:flex;align-items:center;justify-content:center;color:#8c9098}",
"#ez-menu .ez-skin-preview small{font-size:7px;line-height:10px;color:#777c85}",
"#ez-menu .ez-skin-preview small.ez-owned{color:#65ce92}",
"#ez-menu .ez-skin-row select{width:145px}",
"#ez-menu .ez-select{height:21px;padding:0 5px;border:1px solid #44474e;border-radius:3px;background:#22242a;color:#c8cad0;font:9px Arial;outline:none}",
"#ez-menu .ez-tracer-mode{display:flex;align-items:center;gap:4px;margin-right:2px;color:#8f939b;font-size:9px;white-space:nowrap}",
"#ez-menu .ez-tracer-select{width:58px;max-width:58px;font-size:9px}",
"#ez-menu .ez-sound-tools{display:flex;align-items:center;gap:4px}",
"#ez-menu .ez-hit-select{width:116px}",
"#ez-menu .ez-mini{width:21px;height:21px;padding:0;border:1px solid #41444b;border-radius:3px;background:#23252a;color:#bbbfc7;font:10px Arial;cursor:pointer}",
"#ez-menu .ez-mini:hover{border-color:#686c75;color:#fff}",
"#ez-menu .ez-sound-file{display:none}",
"#ez-menu .ez-utility-box{max-height:410px;overflow-y:auto;overflow-x:hidden}",
"#ez-menu .ez-empty{padding:14px;color:#767b83;font-size:10px;text-align:center}",
"#ez-menu .ez-pet-wrap{position:relative}",
"#ez-menu .ez-pet-button{width:72px;height:21px;padding:0 5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid #44474e;border-radius:3px;background:#22242a;color:#c8cad0;font:9px Arial;cursor:pointer}",
"#ez-menu .ez-pet-button img{width:18px;height:18px;object-fit:contain;vertical-align:middle;margin-right:4px}",
"#ez-menu .ez-pet-pop{display:none;grid-template-columns:repeat(7,1fr);gap:4px;max-height:235px;padding:7px;overflow:auto;border-top:1px solid #292b30;background:#141519}",
"#ez-menu .ez-pet-pop.ez-open{display:grid}",
"#ez-menu .ez-pet-choice{min-height:50px;padding:2px;border:1px solid #34373e;border-radius:3px;background:#202228;color:#aeb1b8;font:8px Arial;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px}",
"#ez-menu .ez-pet-choice:hover,#ez-menu .ez-pet-choice.ez-active{border-color:#8067b5;background:#282331}",
"#ez-menu .ez-pet-choice img{width:28px;height:28px;object-fit:contain}",
"#ez-menu .ez-pet-choice small{font-size:7px;color:#7f838b}",
"#ez-menu .ez-pet-choice small.ez-owned{color:#65ce92}",
"#ez-menu .ez-settings .ez-group:last-child{grid-column:1/-1}",
"#ez-menu .ez-group h3{height:28px;margin:0;padding:0 9px;display:flex;align-items:center;border-bottom:1px solid #2e3036;background:#1e1f24;font-size:10px;color:#bec1c8}",
"#ez-menu .ez-bind,#ez-menu .ez-feature-bind{min-width:58px;height:21px;padding:0 6px;border:1px solid #44474e;border-radius:3px;background:#23252a;color:#d4d6db;font:9px Arial;cursor:pointer}",
"#ez-menu .ez-feature-bind{max-width:68px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
"#ez-menu .ez-feature-bind.ez-wait{border-color:#8b6bd1;color:#d9c7ff}",
"#ez-menu .ez-bind.ez-wait{border-color:#8b6bd1;color:#d9c7ff}",
"#ez-menu .ez-bind-line{height:30px;padding:0 9px;display:flex;align-items:center}",
"#ez-menu .ez-bind-line b{margin-left:auto;color:#cdb8ff;font-size:10px}",
"#ez-menu .ez-bind-line.ez-on span:before{content:'';display:inline-block;width:6px;height:6px;margin-right:6px;border-radius:50%;background:#62d58c;box-shadow:0 0 6px rgba(98,213,140,.7)}",
"#ez-menu .ez-bind-line:not(.ez-on) span:before{content:'';display:inline-block;width:6px;height:6px;margin-right:6px;border-radius:50%;background:#555961}",
"#ez-menu .ez-actions{padding:9px;display:flex;gap:6px}",
"#ez-menu .ez-actions button{height:25px;min-width:58px;padding:0 10px;border:1px solid #3b3e45;border-radius:3px;background:#202228;color:#d0d2d7;font:10px Arial;cursor:pointer}",
"#ez-menu .ez-actions button:hover{border-color:#6f5a9f;background:#27242f}",
"#ez-menu .ez-file{display:none}",
"#ez-bind-pop{position:fixed;display:none;z-index:2147483647;min-width:104px;padding:4px;border:1px solid #3c3f46;border-radius:4px;background:#17181c;box-shadow:0 7px 20px rgba(0,0,0,.45);font:9px Arial}",
"#ez-bind-pop.ez-open{display:block}",
"#ez-bind-pop button{display:block;width:100%;height:24px;padding:0 7px;border:0;border-radius:2px;background:transparent;color:#c8cad0;text-align:left;font:9px Arial;cursor:pointer}",
"#ez-bind-pop button:hover{background:#282a30}",
"#ez-deaths{position:fixed;left:12px;bottom:72px;width:196px;z-index:2147483644;border:1px solid #34363c;border-radius:4px;background:rgba(18,19,22,.9);color:#c9cbd0;font:10px Arial;user-select:none}",
"#ez-deaths.ez-hide{display:none}",
"#ez-deaths>b{height:25px;padding:0 8px;display:flex;align-items:center;border-bottom:1px solid #303238;cursor:move}",
"#ez-deaths .ez-death-row{height:39px;padding:4px 6px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #292b30}",
"#ez-deaths .ez-death-row:last-child{border-bottom:0}",
"#ez-deaths .ez-death-row img,#ez-deaths .ez-death-avatar{width:30px;height:30px;border-radius:50%;object-fit:contain;background:#25272c}",
"#ez-deaths .ez-death-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}",
"#ez-deaths .ez-death-time{color:#b9a4e8;font-size:9px}",
"#ez-kills{position:fixed;left:12px;bottom:260px;width:196px;z-index:2147483644;border:1px solid #34363c;border-radius:4px;background:rgba(18,19,22,.9);color:#c9cbd0;font:10px Arial;user-select:none}",
"#ez-kills.ez-hide{display:none}",
"#ez-kills>b{height:25px;padding:0 8px;display:flex;align-items:center;border-bottom:1px solid #303238;cursor:move}",
"#ez-kills .ez-kill-row{height:39px;padding:4px 6px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #292b30}",
"#ez-kills .ez-kill-row:last-child{border-bottom:0}",
"#ez-kills .ez-kill-row img,#ez-kills .ez-kill-avatar{width:30px;height:30px;border-radius:50%;object-fit:contain;background:#25272c}",
"#ez-kills .ez-kill-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}",
"#ez-kills .ez-kill-time{color:#7edca7;font-size:9px}",
"#ez-spectators{position:fixed;left:220px;bottom:72px;width:230px;z-index:2147483644;border:1px solid #34363c;border-radius:4px;background:rgba(18,19,22,.92);color:#c9cbd0;font:10px Arial;user-select:none;overflow:hidden}",
"#ez-spectators.ez-hide{display:none}",
"#ez-spectators>b{height:25px;padding:0 8px;display:flex;align-items:center;border-bottom:1px solid #303238;cursor:move}",
"#ez-spectators .ez-spec-row{height:39px;padding:4px 6px;display:flex;align-items:center;gap:7px;border-bottom:1px solid #292b30}",
"#ez-spectators .ez-spec-row:last-child{border-bottom:0}",
"#ez-spectators .ez-spec-row img,#ez-spectators .ez-spec-avatar{width:30px;height:30px;border-radius:50%;object-fit:contain;background:#25272c;flex:0 0 30px}",
"#ez-spectators .ez-spec-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}",
"#ez-spectators .ez-spec-time{color:#b9a4e8;white-space:nowrap;font-size:9px}",
"body.ez-spec-active #spectatorsList{display:none!important;visibility:hidden!important;opacity:0!important;pointer-events:none!important}",
"#ez-layer{position:fixed;display:none;pointer-events:none;z-index:2147483000}",
"#ez-skill-clock{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:20;display:none;min-width:30px;height:21px;padding:0 4px;align-items:center;justify-content:center;pointer-events:none;border:1px solid rgba(255,255,255,.32);border-radius:4px;background:rgba(0,0,0,.62);color:#fff;text-shadow:0 1px 2px #000;font:700 13px/1 Arial,sans-serif}",
"#ez-keys{position:fixed;right:12px;top:72px;width:188px;z-index:2147483645;border:1px solid #34363c;border-radius:4px;background:rgba(18,19,22,.9);color:#c9cbd0;font:10px Arial,sans-serif;pointer-events:auto;user-select:none}",
"#ez-keys.ez-hide{display:none}",
"#ez-keys b{height:24px;padding:0 8px;display:flex;align-items:center;border-bottom:1px solid #303238;font-size:10px;cursor:move}",
"#ez-keys div{height:25px;padding:0 8px;display:flex;align-items:center;pointer-events:none;border-bottom:1px solid rgba(48,50,56,.6)}",
"#ez-keys div span:first-child:before{content:'';display:inline-block;width:6px;height:6px;margin-right:6px;border-radius:50%;background:#555961}",
"#ez-keys div.ez-on span:first-child:before{background:#62d58c;box-shadow:0 0 6px rgba(98,213,140,.65)}",
"#ez-keys span:last-child{margin-left:auto;color:#cdb8ff}",
"@media(max-width:780px){#ez-menu{left:8px;top:8px;width:calc(100vw - 16px);max-height:calc(100vh - 16px);overflow:auto}#ez-menu .ez-work{grid-template-columns:1fr}}"
].join("");
document.head.appendChild(style);
}
function syncMenu() {
if (!menu) return;
menu.querySelectorAll("[data-check]").forEach(function (input) {
input.checked = !!read(input.getAttribute("data-check"));
});
menu.querySelectorAll("[data-color]").forEach(function (input) {
input.value = read(input.getAttribute("data-color"));
});
menu.querySelectorAll("[data-range]").forEach(function (input) {
input.value = read(input.getAttribute("data-range"));
});
menu.querySelectorAll("[data-range-value]").forEach(function (node) {
var path = node.getAttribute("data-range-value");
node.textContent = read(path) + rangeSuffix(path);
});
if (keyBox) keyBox.textContent = cfg.settings.bindName;
menu.querySelectorAll("[data-bind-name]").forEach(function (node) {
node.textContent = cfg.settings.bindName;
});
menu.querySelectorAll("[data-feature-bind]").forEach(function (node) {
node.textContent = bindText(node.getAttribute("data-feature-bind"));
node.classList.remove("ez-wait");
});
menu.querySelectorAll("[data-bind-setting]").forEach(function (node) {
node.textContent = bindText(node.getAttribute("data-bind-setting"));
});
renderSoundOptions();
menu.querySelectorAll("[data-select]").forEach(function (node) {
node.value = read(node.getAttribute("data-select"));
});
var privacyButton = menu.querySelector("[data-privacy-button]");
if (privacyButton) privacyButton.textContent = privacyText();
menu.querySelectorAll("[data-mode-button]").forEach(function (button) {
button.textContent = modeText(button.getAttribute("data-mode-button"));
});
menu.querySelectorAll("[data-object-button]").forEach(function (button) {
button.textContent = objectText();
});
if (window.EZSQUAD) window.EZSQUAD.settings = cfg;
updateKeys();
updateDeathPanel();
updateKillPanel();
updateSpectatorPanel(Date.now());
updatePetButton();
cardBank.clear();
worldDirty = true;
}
function updateKeys() {
if (!keysPanel) return;
keysPanel.classList.toggle("ez-hide", !cfg.settings.keyList);
var holder = keysPanel.querySelector("[data-key-list]");
if (!holder) return;
var menuOpen = !!(menu && !menu.classList.contains("ez-hide"));
var body = '<div class="' + (menuOpen ? 'ez-on' : '') + '"><span>Menu</span><span>' + cfg.settings.bindName + ' · ' + (menuOpen ? 'Open' : 'Closed') + '</span></div>';
Object.keys(bindFeatures).forEach(function (name) {
var item = cfg.binds[name];
if (!item || !item.key) return;
var active = !!read(bindFeatures[name].path);
body += '<div class="' + (active ? 'ez-on' : '') + '"><span>' + bindFeatures[name].title + '</span><span>' + item.name + ' · ' + (item.mode === 'hold' ? 'Hold' : 'Toggle') + ' · ' + (active ? 'ON' : 'OFF') + '</span></div>';
});
if (holder.innerHTML !== body) holder.innerHTML = body;
if (menu) {
menu.querySelectorAll("[data-bind-setting]").forEach(function (node) {
var name = node.getAttribute("data-bind-setting");
var feature = bindFeatures[name];
var line = node.closest(".ez-bind-line");
if (line && feature) line.classList.toggle("ez-on", !!read(feature.path));
});
}
}
function placeData(node, x, y) {
if (!node) return null;
var width = Math.max(0, Number(node.offsetWidth) || 0);
var height = Math.max(0, Number(node.offsetHeight) || 0);
var maxX = Math.max(0, innerWidth - width);
var maxY = Math.max(0, innerHeight - height);
x = clamp(Number(x) || 0, 0, maxX);
y = clamp(Number(y) || 0, 0, maxY);
return {
x: Math.round(x),
y: Math.round(y),
rx: maxX > 0 ? x / maxX : 0,
ry: maxY > 0 ? y / maxY : 0,
vw: innerWidth,
vh: innerHeight,
w: Math.round(width),
h: Math.round(height)
};
}
function readPlace(key) {
if (!key) return null;
try { return JSON.parse(localStorage.getItem(key) || "null"); } catch (e) { return null; }
}
function nodePlace(node, key) {
if (!node) return null;
var x = parseFloat(node.style.left);
var y = parseFloat(node.style.top);
if (!Number.isFinite(x) || !Number.isFinite(y)) {
var rect = null;
try { rect = node.getBoundingClientRect(); } catch (e) {}
if (rect && (rect.width || rect.height || node.offsetWidth || node.offsetHeight)) {
x = rect.left;
y = rect.top;
} else {
var saved = readPlace(key);
if (saved) return saved;
}
}
return Number.isFinite(x) && Number.isFinite(y) ? placeData(node, x, y) : null;
}
function saveNodePlace(node, key) {
if (!node || !key) return;
var pos = nodePlace(node, null);
if (!pos) return;
try { localStorage.setItem(key, JSON.stringify(pos)); } catch (e) {}
}
function resolvedPlace(node, pos) {
if (!node || !pos) return null;
var width = Math.max(0, Number(node.offsetWidth) || Number(pos.w) || 0);
var height = Math.max(0, Number(node.offsetHeight) || Number(pos.h) || 0);
var maxX = Math.max(0, innerWidth - width);
var maxY = Math.max(0, innerHeight - height);
var rx = Number(pos.rx);
var ry = Number(pos.ry);
if (!Number.isFinite(rx) && Number.isFinite(Number(pos.x)) && Number.isFinite(Number(pos.vw))) {
var oldMaxX = Math.max(1, Number(pos.vw) - (Number(pos.w) || width));
rx = Number(pos.x) / oldMaxX;
}
if (!Number.isFinite(ry) && Number.isFinite(Number(pos.y)) && Number.isFinite(Number(pos.vh))) {
var oldMaxY = Math.max(1, Number(pos.vh) - (Number(pos.h) || height));
ry = Number(pos.y) / oldMaxY;
}
var x = Number.isFinite(rx) ? clamp(rx, 0, 1) * maxX : Number(pos.x);
var y = Number.isFinite(ry) ? clamp(ry, 0, 1) * maxY : Number(pos.y);
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
return placeData(node, x, y);
}
function activeValue(selector, attr, fallback) {
if (!menu) return fallback;
var node = menu.querySelector(selector + ".ez-active");
return node ? node.getAttribute(attr) || fallback : fallback;
}
function layoutState() {
return {
viewport: { width: innerWidth, height: innerHeight },
menu: nodePlace(menu, placeKey),
keybinds: nodePlace(keysPanel, keysPlaceKey),
deaths: nodePlace(deathPanel, deathPosKey),
kills: nodePlace(killPanel, killPosKey),
spectators: nodePlace(spectatorPanel, spectatorPosKey),
tabs: {
main: activeValue(".ez-tab", "data-tab", "esp"),
players: activeValue('.ez-subtab[data-scope="visual"]', "data-subtab", "all"),
side: activeValue('.ez-subtab[data-scope="side"]', "data-subtab", "extra")
}
};
}
function putNode(node, pos, key) {
if (!node || !pos) return;
var next = resolvedPlace(node, pos);
if (!next) return;
node.style.right = "auto";
node.style.bottom = "auto";
node.style.left = clamp(next.x, 4, Math.max(4, innerWidth - node.offsetWidth - 4)) + "px";
node.style.top = clamp(next.y, 4, Math.max(4, innerHeight - node.offsetHeight - 4)) + "px";
if (key) saveNodePlace(node, key);
}
function selectTab(scope, value) {
if (!menu || !value) return;
var buttons;
var pages;
var buttonAttr;
var pageAttr;
if (scope === "main") {
buttons = menu.querySelectorAll(".ez-tab");
pages = menu.querySelectorAll(".ez-page");
buttonAttr = "data-tab";
pageAttr = "data-page";
} else {
buttons = menu.querySelectorAll('.ez-subtab[data-scope="' + scope + '"]');
pages = menu.querySelectorAll('.ez-subpage[data-scope="' + scope + '"]');
buttonAttr = "data-subtab";
pageAttr = "data-subpage";
}
buttons.forEach(function (node) { node.classList.toggle("ez-active", node.getAttribute(buttonAttr) === value); });
pages.forEach(function (node) { node.classList.toggle("ez-active", node.getAttribute(pageAttr) === value); });
}
function applyLayout(layout) {
if (!layout || typeof layout !== "object") return;
var viewport = layout.viewport || null;
function withViewport(pos) {
if (!pos || !viewport || Number.isFinite(Number(pos.vw)) || Number.isFinite(Number(pos.rx))) return pos;
var next = {};
Object.keys(pos).forEach(function (key) { next[key] = pos[key]; });
next.vw = Number(viewport.width);
next.vh = Number(viewport.height);
return next;
}
putNode(menu, withViewport(layout.menu), placeKey);
putNode(keysPanel, withViewport(layout.keybinds), keysPlaceKey);
putNode(deathPanel, withViewport(layout.deaths), deathPosKey);
putNode(killPanel, withViewport(layout.kills), killPosKey);
putNode(spectatorPanel, withViewport(layout.spectators), spectatorPosKey);
if (layout.tabs) {
selectTab("main", layout.tabs.main);
selectTab("visual", layout.tabs.players);
selectTab("side", layout.tabs.side);
}
}
function pullSkins() {
if (!cfg.misc.skinPick || typeof cfg.misc.skinPick !== "object") cfg.misc.skinPick = {};
if (!menu) return;
menu.querySelectorAll("[data-skin-select]").forEach(function (select) {
var name = select.getAttribute("data-skin-select");
if (name) cfg.misc.skinPick[name] = Number(select.value) || 0;
});
}
function saveFile() {
pullSkins();
saveCfg();
exportSounds().then(function (sounds) {
try {
var blob = new Blob([JSON.stringify({ version: 30, settings: clone(cfg), layout: layoutState(), sounds: sounds }, null, 2)], { type: "application/json" });
var url = URL.createObjectURL(blob);
var link = document.createElement("a");
link.href = url;
link.download = "ezsquad-config.json";
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(function () { try { URL.revokeObjectURL(url); } catch (e) {} }, 60000);
} catch (e) {}
});
}
function loadFile(file) {
if (!file) return;
var reader = new FileReader();
reader.onload = function () {
try {
var value = JSON.parse(String(reader.result || ""));
var source = value && value.settings ? value.settings : value;
cfg = merge(clone(defaults), source);
saveCfg();
var done = value && value.sounds ? importSounds(value.sounds) : Promise.resolve();
done.then(function () {
syncMenu();
renderSkinPanel(true);
if (value && value.layout) requestAnimationFrame(function () { applyLayout(value.layout); });
});
} catch (e) {}
if (fileBox) fileBox.value = "";
};
reader.readAsText(file);
}
function resetCfg() {
clearTimeout(fightAttack);
clearTimeout(fightRelease);
cfg = clone(defaults);
saveCfg();
syncMenu();
renderSkinPanel(true);
}
function makeKeys() {
keysPanel = document.createElement("div");
keysPanel.id = "ez-keys";
keysPanel.innerHTML = '<b>Keybinds</b><section data-key-list></section>';
document.body.appendChild(keysPanel);
putNode(keysPanel, readPlace(keysPlaceKey), null);
var head = keysPanel.querySelector("b");
var moving = false;
var dx = 0;
var dy = 0;
head.addEventListener("pointerdown", function (event) {
moving = true;
dx = event.clientX - keysPanel.offsetLeft;
dy = event.clientY - keysPanel.offsetTop;
head.setPointerCapture(event.pointerId);
event.preventDefault();
});
head.addEventListener("pointermove", function (event) {
if (!moving) return;
keysPanel.style.right = "auto";
keysPanel.style.left = clamp(event.clientX - dx, 4, innerWidth - keysPanel.offsetWidth - 4) + "px";
keysPanel.style.top = clamp(event.clientY - dy, 4, innerHeight - 24) + "px";
});
head.addEventListener("pointerup", function (event) {
if (!moving) return;
moving = false;
try { head.releasePointerCapture(event.pointerId); } catch (e) {}
saveNodePlace(keysPanel, keysPlaceKey);
});
updateKeys();
}
function makeMenu() {
addStyle();
var node = document.createElement("div");
node.id = "ez-menu";
node.innerHTML = '<div class="ez-head"><b>EZSQUAD | https://t.me/evoezsquad</b><button class="ez-close" type="button" title="Unload">×</button></div>' +
'<div class="ez-tabs"><button class="ez-tab ez-active" type="button" data-tab="esp">Visuals</button><button class="ez-tab" type="button" data-tab="misc">Misc</button><button class="ez-tab" type="button" data-tab="settings">Settings</button></div>' +
espPanel() + miscPanel() + settingsPanel();
document.body.appendChild(node);
keyBox = node.querySelector(".ez-bind");
fileBox = node.querySelector(".ez-file");
soundFileBox = node.querySelector('[data-sound-file="hit"]');
killSoundFileBox = node.querySelector('[data-sound-file="kill"]');
makeBindPop();
makeDeathPanel();
makeKillPanel();
putNode(node, readPlace(placeKey), null);
node.addEventListener("click", function (event) {
var tab = event.target.getAttribute("data-tab");
if (tab) {
node.querySelectorAll(".ez-tab").forEach(function (button) {
button.classList.toggle("ez-active", button.getAttribute("data-tab") === tab);
});
node.querySelectorAll(".ez-page").forEach(function (page) {
page.classList.toggle("ez-active", page.getAttribute("data-page") === tab);
});
return;
}
var sub = event.target.getAttribute("data-subtab");
if (sub) {
var scope = event.target.getAttribute("data-scope");
node.querySelectorAll('.ez-subtab[data-scope="' + scope + '"]').forEach(function (button) {
button.classList.toggle("ez-active", button.getAttribute("data-subtab") === sub);
});
node.querySelectorAll('.ez-subpage[data-scope="' + scope + '"]').forEach(function (page) {
page.classList.toggle("ez-active", page.getAttribute("data-subpage") === sub);
});
if (scope === "side") {
var extraTracer = node.querySelector("[data-extra-tracer]");
if (extraTracer) extraTracer.style.display = sub === "extra" ? "flex" : "none";
}
return;
}
var petButton = event.target.closest && event.target.closest("[data-pet-button]");
if (petButton) {
event.preventDefault();
var petMenu = node.querySelector("[data-pet-menu]");
if (petMenu) petMenu.classList.toggle("ez-open");
return;
}
var petChoice = event.target.closest && event.target.closest("[data-pet-id]");
if (petChoice) {
choosePet(Number(petChoice.getAttribute("data-pet-id")) || 0);
var petList = node.querySelector("[data-pet-menu]");
if (petList) petList.classList.remove("ez-open");
return;
}
if (event.target.hasAttribute && event.target.hasAttribute("data-privacy-button")) {
event.preventDefault();
event.stopPropagation();
var privacyMenu = node.querySelector("[data-privacy-menu]");
if (privacyMenu) privacyMenu.classList.toggle("ez-open");
return;
}
var featureBind = event.target.getAttribute("data-feature-bind");
if (featureBind) {
event.preventDefault();
event.stopPropagation();
beginFeatureBind(featureBind, event.target);
return;
}
var mode = event.target.getAttribute("data-mode-button");
var objectMenuName = event.target.getAttribute("data-object-button");
if (mode || objectMenuName) {
event.preventDefault();
event.stopPropagation();
var selector = mode ? '[data-mode-menu="' + mode + '"]' : '[data-object-menu="' + objectMenuName + '"]';
var box = node.querySelector(selector);
var open = box && !box.classList.contains("ez-open");
node.querySelectorAll(".ez-mode-pop.ez-open,.ez-object-pop.ez-open").forEach(function (item) { item.classList.remove("ez-open"); });
node.querySelectorAll(".ez-mode-btn.ez-open,.ez-object-btn.ez-open").forEach(function (item) { item.classList.remove("ez-open"); });
if (open) {
box.classList.add("ez-open");
event.target.classList.add("ez-open");
}
return;
}
var soundAction = event.target.getAttribute("data-sound-action");
if (soundAction) {
var soundKind = event.target.getAttribute("data-sound-kind") === "kill" ? "kill" : "hit";
if (soundAction === "upload") {
soundUploadKind = soundKind;
var soundInput = soundKind === "kill" ? killSoundFileBox : soundFileBox;
if (soundInput) soundInput.click();
return;
}
if (soundAction === "play") { playSound(soundKind); return; }
if (soundAction === "remove") {
var chosen = selectedSound(soundKind);
if (chosen) {
removeSoundRow(chosen.id).then(function () {
soundBuffers.delete(chosen.id);
if (cfg.misc.hitSoundType === "custom:" + chosen.id) cfg.misc.hitSoundType = "Click";
if (cfg.misc.killSoundType === "custom:" + chosen.id) cfg.misc.killSoundType = "Bell";
saveCfg();
refreshCustomSounds();
});
}
return;
}
}
var action = event.target.getAttribute("data-action");
if (action === "save") saveFile();
else if (action === "load" && fileBox) fileBox.click();
else if (action === "reset") resetCfg();
});
node.addEventListener("input", function (event) {
var color = event.target.getAttribute("data-color");
if (color) write(color, event.target.value);
var range = event.target.getAttribute("data-range");
if (range) {
var value = Number(event.target.value);
write(range, value);
var out = node.querySelector('[data-range-value="' + range + '"]');
if (out) out.textContent = value + rangeSuffix(range);
if (range === "misc.deathTime") updateDeathPanel(Date.now());
if (range === "misc.killTime") updateKillPanel(Date.now());
}
});
node.addEventListener("change", function (event) {
var select = event.target.getAttribute("data-select");
if (select) write(select, event.target.value);
var skinName = event.target.getAttribute("data-skin-select");
if (skinName) chooseSkin(skinName, Number(event.target.value) || 0);
var path = event.target.getAttribute("data-check");
if (path) {
write(path, event.target.checked);
if (path.indexOf("extra.") === 0) {
var match = path.match(/^extra\.([A-Za-z]+)(Box|Tracer|Name|Distance|Image)$/);
if (match) {
var button = node.querySelector('[data-mode-button="' + match[1] + '"]');
if (button) button.textContent = modeText(match[1]);
}
if (/^extra\.others(Gem|ArmorPotion|WaterDrop|Roots|Fireball|PlasmaBall|Shuriken|Snowball|Bomb|BearTrap)$/.test(path)) {
var objectButton = node.querySelector('[data-object-button="others"]');
if (objectButton) objectButton.textContent = objectText();
}
}
if (path.indexOf("misc.privacy") === 0) {
var button = node.querySelector("[data-privacy-button]");
if (button) button.textContent = privacyText();
}
updateDeathPanel();
updateKillPanel();
}
if (event.target === fileBox) loadFile(fileBox.files && fileBox.files[0]);
if (event.target === soundFileBox || event.target === killSoundFileBox) {
var kind = event.target === killSoundFileBox ? "kill" : "hit";
uploadSound(event.target.files && event.target.files[0], kind);
event.target.value = "";
}
});
node.addEventListener("contextmenu", function (event) {
var button = event.target.closest && event.target.closest("[data-feature-bind]");
if (!button) return;
event.preventDefault();
openBindPop(button.getAttribute("data-feature-bind"), event.clientX, event.clientY);
});
node.addEventListener("pointerdown", ensureAudio, { passive: true });
keyBox.addEventListener("click", function () {
bindMode = true;
keyBox.textContent = "Press key";
keyBox.classList.add("ez-wait");
});
node.querySelector(".ez-close").addEventListener("click", stop);
var head = node.querySelector(".ez-head");
var moving = false;
var dx = 0;
var dy = 0;
head.addEventListener("pointerdown", function (event) {
if (event.target.closest("button,input")) return;
moving = true;
dx = event.clientX - node.offsetLeft;
dy = event.clientY - node.offsetTop;
head.setPointerCapture(event.pointerId);
});
head.addEventListener("pointermove", function (event) {
if (!moving) return;
node.style.left = clamp(event.clientX - dx, 4, innerWidth - node.offsetWidth - 4) + "px";
node.style.top = clamp(event.clientY - dy, 4, innerHeight - 34) + "px";
});
head.addEventListener("pointerup", function (event) {
if (!moving) return;
moving = false;
try { head.releasePointerCapture(event.pointerId); } catch (e) {}
saveNodePlace(node, placeKey);
});
return node;
}
function beginFeatureBind(name, button) {
if (!cfg.binds[name]) return;
bindPick = { name: name, button: button };
button.textContent = "Press key";
button.classList.add("ez-wait");
}
function cancelFeatureBind() {
if (!bindPick) return;
if (bindPick.button) {
bindPick.button.textContent = bindText(bindPick.name);
bindPick.button.classList.remove("ez-wait");
}
bindPick = null;
}
function useFeatureKey(name, down, repeat) {
var bind = cfg.binds[name];
var feature = bindFeatures[name];
if (!bind || !feature || !bind.key) return false;
if (bind.mode === "hold") {
if (down && !repeat) {
holdKeys[name] = true;
write(feature.path, true);
syncMenu();
} else if (!down && holdKeys[name]) {
delete holdKeys[name];
write(feature.path, false);
syncMenu();
}
} else if (down && !repeat) {
write(feature.path, !read(feature.path));
syncMenu();
}
return true;
}
function onKey(event) {
var code = keyCode(event);
if (bindMode) {
event.preventDefault();
event.stopPropagation();
if (code === "Escape") {
bindMode = false;
keyBox.textContent = cfg.settings.bindName;
keyBox.classList.remove("ez-wait");
return;
}
cfg.settings.bind = fixKey(code);
cfg.settings.bindName = keyName(event);
bindMode = false;
keyBox.classList.remove("ez-wait");
saveCfg();
syncMenu();
return;
}
if (sameKey(cfg.settings.bind, event) && !event.repeat) {
event.preventDefault();
event.stopPropagation();
if (typeof event.stopImmediatePropagation === "function") event.stopImmediatePropagation();
cancelFeatureBind();
if (bindPop) bindPop.classList.remove("ez-open");
if (document.activeElement && typeof document.activeElement.blur === "function") document.activeElement.blur();
if (menu) menu.classList.toggle("ez-hide");
syncScanIcon();
updateKeys();
return;
}
if (bindPick) {
event.preventDefault();
event.stopPropagation();
if (code === "Escape") {
cancelFeatureBind();
return;
}
cfg.binds[bindPick.name].key = fixKey(code);
cfg.binds[bindPick.name].name = keyName(event);
saveCfg();
cancelFeatureBind();
syncMenu();
return;
}
if (code === "Space" && !event.repeat) {
var active = document.activeElement;
var tag = active && active.tagName ? active.tagName.toLowerCase() : "";
if (tag !== "input" && tag !== "textarea" && tag !== "select") noteSkillUse(performance.now());
}
Object.keys(bindFeatures).some(function (name) {
var bind = cfg.binds[name];
if (!bind || !sameKey(bind.key, event)) return false;
event.preventDefault();
event.stopPropagation();
useFeatureKey(name, true, event.repeat);
return true;
});
}
function onKeyUp(event) {
Object.keys(bindFeatures).some(function (name) {
var bind = cfg.binds[name];
if (!bind || !sameKey(bind.key, event) || bind.mode !== "hold") return false;
event.preventDefault();
useFeatureKey(name, false, false);
return true;
});
}
function makeBindPop() {
bindPop = document.createElement("div");
bindPop.id = "ez-bind-pop";
bindPop.innerHTML = '<button type="button" data-bind-mode="toggle">Toggle</button><button type="button" data-bind-mode="hold">Hold</button><button type="button" data-bind-mode="none">Unload key</button>';
document.body.appendChild(bindPop);
bindPop.addEventListener("click", function (event) {
var mode = event.target.getAttribute("data-bind-mode");
var name = bindPop.getAttribute("data-feature");
if (!mode || !name || !cfg.binds[name]) return;
if (mode === "none") {
cfg.binds[name].key = "";
cfg.binds[name].name = "None";
delete holdKeys[name];
} else {
cfg.binds[name].mode = mode;
}
saveCfg();
bindPop.classList.remove("ez-open");
syncMenu();
});
document.addEventListener("pointerdown", closeBindPop, true);
}
function openBindPop(name, x, y) {
if (!bindPop || !cfg.binds[name]) return;
bindPop.setAttribute("data-feature", name);
bindPop.style.left = clamp(x, 4, innerWidth - 112) + "px";
bindPop.style.top = clamp(y, 4, innerHeight - 82) + "px";
bindPop.classList.add("ez-open");
}
function closeBindPop(event) {
if (!bindPop || !bindPop.classList.contains("ez-open")) return;
if (event && (bindPop.contains(event.target) || (event.target.closest && event.target.closest("[data-feature-bind]")))) return;
bindPop.classList.remove("ez-open");
}
function makeDeathPanel() {
deathPanel = document.createElement("div");
deathPanel.id = "ez-deaths";
deathPanel.innerHTML = '<b>Death log</b><div data-death-list></div>';
document.body.appendChild(deathPanel);
deathList = deathPanel.querySelector("[data-death-list]");
putNode(deathPanel, readPlace(deathPosKey), null);
var head = deathPanel.querySelector("b");
var moving = false, dx = 0, dy = 0;
head.addEventListener("pointerdown", function (event) {
moving = true;
dx = event.clientX - deathPanel.offsetLeft;
dy = event.clientY - deathPanel.offsetTop;
head.setPointerCapture(event.pointerId);
event.preventDefault();
});
head.addEventListener("pointermove", function (event) {
if (!moving) return;
deathPanel.style.bottom = "auto";
deathPanel.style.left = clamp(event.clientX - dx, 4, innerWidth - deathPanel.offsetWidth - 4) + "px";
deathPanel.style.top = clamp(event.clientY - dy, 4, innerHeight - 25) + "px";
});
head.addEventListener("pointerup", function (event) {
if (!moving) return;
moving = false;
try { head.releasePointerCapture(event.pointerId); } catch (e) {}
saveNodePlace(deathPanel, deathPosKey);
});
updateDeathPanel();
}
function updateDeathPanel(now) {
if (!deathPanel) return;
deathPanel.classList.toggle("ez-hide", !cfg.misc.deathLog);
if (!cfg.misc.deathLog || !deathList) return;
now = Number(now) || Date.now();
var life = clamp(Number(cfg.misc.deathTime) || 60, 10, 180) * 1000;
deathEntries = deathEntries.filter(function (item) {
var born = Number(item.at) || Math.max(0, Number(item.until) - life);
item.at = born;
return born + life > now;
}).slice(-6);
var html = deathEntries.map(function (item) {
var left = Math.max(0, Math.ceil((item.at + life - now) / 1000));
var time = left < 60 ? left + "s" : Math.floor(left / 60) + ":" + String(left % 60).padStart(2, "0");
var avatar = item.image ? '<img src="' + item.image + '">' : '<div class="ez-death-avatar"></div>';
var shownName = protectedPlayers() ? "Player" : item.name;
return '<div class="ez-death-row">' + avatar + '<span class="ez-death-name">' + escapeHtml(shownName) + '</span><span class="ez-death-time">(' + time + ')</span></div>';
}).join('');
deathList.innerHTML = html || '<div class="ez-empty">No deaths</div>';
}
function watchTime(seconds) {
seconds = Math.max(0, Math.floor(Number(seconds) || 0));
if (seconds < 60) return seconds + "s";
return Math.floor(seconds / 60) + ":" + String(seconds % 60).padStart(2, "0");
}
function spectatorMatch(item) {
var id = item && item.id != null ? String(item.id) : "";
var nick = String(item && item.nick || "").toLowerCase();
for (var i = 0; i < players.length; i++) {
var obj = players[i];
if (!obj || obj.deleted) continue;
if ((id && String(obj.userId != null ? obj.userId : obj.id) === id) || (nick && String(rawNick(obj)).toLowerCase() === nick)) {
return { object: obj, scan: null };
}
}
for (var j = 0; j < scans.length; j++) {
var scan = scans[j];
if (!scan) continue;
if ((id && String(scan.id) === id) || (nick && String(scan.nick || "").toLowerCase() === nick)) {
return { object: null, scan: scan };
}
}
return { object: null, scan: null };
}
function spectatorImage(item) {
var found = spectatorMatch(item);
var sprite = found.object ? objectSprite(found.object, performance.now()) : (found.scan ? scanSprite(found.scan) : null);
return sprite ? spriteUrl(sprite) : "";
}
function makeKillPanel() {
killPanel = document.createElement("div");
killPanel.id = "ez-kills";
killPanel.innerHTML = '<b>Kill Log</b><div data-kill-list></div>';
document.body.appendChild(killPanel);
killList = killPanel.querySelector("[data-kill-list]");
putNode(killPanel, readPlace(killPosKey), null);
var head = killPanel.querySelector("b");
var moving = false, dx = 0, dy = 0;
head.addEventListener("pointerdown", function (event) {
moving = true; dx = event.clientX - killPanel.offsetLeft; dy = event.clientY - killPanel.offsetTop;
head.setPointerCapture(event.pointerId); event.preventDefault();
});
head.addEventListener("pointermove", function (event) {
if (!moving) return;
killPanel.style.bottom = "auto";
killPanel.style.left = clamp(event.clientX - dx, 4, innerWidth - killPanel.offsetWidth - 4) + "px";
killPanel.style.top = clamp(event.clientY - dy, 4, innerHeight - 25) + "px";
});
head.addEventListener("pointerup", function (event) {
if (!moving) return; moving = false;
try { head.releasePointerCapture(event.pointerId); } catch (e) {}
saveNodePlace(killPanel, killPosKey);
});
updateKillPanel(Date.now());
}
function feedImage(user) {
if (!user) return "";
try {
if (typeof getSystemFeedObjectIconSprite === "function") {
var name = String(user.evolution || user.character || user.name || "");
var id = getSystemFeedObjectIconSprite(name, Number(user.skin) || 0);
var sprite = id && game.sprites ? game.sprites[id] : null;
if (sprite) return spriteUrl(sprite);
}
} catch (e) {}
try {
var evolution = String(user.evolution || user.character || user.name || "");
var def = game.objectsDef && game.objectsDef[evolution];
if (def && def.animations) {
var anim = def.animations[def.animation] || def.animations.idle_right || def.animations.flying_right || def.animations.none;
var sprite = anim && anim.sprites && game.sprites[anim.sprites[0]];
if (sprite) return spriteUrl(sprite);
}
} catch (e) {}
return "";
}
function updateKillPanel(now) {
if (!killPanel) return;
killPanel.classList.toggle("ez-hide", !cfg.misc.killLog);
if (!cfg.misc.killLog || !killList) return;
now = Number(now) || Date.now();
var life = clamp(Number(cfg.misc.killTime) || 60, 10, 180) * 1000;
killEntries = killEntries.filter(function (item) { return item.at + life > now; }).slice(-6);
killList.innerHTML = killEntries.map(function (item) {
var left = Math.max(0, Math.ceil((item.at + life - now) / 1000));
var time = watchTime(left);
var avatar = item.image ? '<img src="' + item.image + '">' : '<div class="ez-kill-avatar"></div>';
var shownName = protectedPlayers() ? "Player" : item.name;
return '<div class="ez-kill-row">' + avatar + '<span class="ez-kill-name">' + escapeHtml(shownName) + '</span><span class="ez-kill-time">(' + time + ')</span></div>';
}).join("") || '<div class="ez-empty">No kills</div>';
}
function onGameFeed(data) {
if (!data || data.type !== "player-kill") return;
var victim = data.victim || {};
var name = String(victim.nick || victim.name || data.nick || victim.id || "Player");
killEntries.push({ name: name, image: feedImage(victim), at: Date.now() });
killEntries = killEntries.slice(-6);
if (cfg.misc.killSound) playKill();
updateKillPanel(Date.now());
}
function attachKillFeed() {
try {
if (typeof gameServer === "undefined" || !gameServer || killSocket === gameServer) return;
if (killSocket && killHandler) {
if (typeof killSocket.off === "function") killSocket.off("game:feed", killHandler);
else if (typeof killSocket.removeListener === "function") killSocket.removeListener("game:feed", killHandler);
}
killSocket = gameServer;
killHandler = onGameFeed;
killSocket.on("game:feed", killHandler);
} catch (e) {}
}
function makeSpectatorPanel() {
spectatorPanel = document.createElement("div");
spectatorPanel.id = "ez-spectators";
spectatorPanel.innerHTML = '<b>Spectators</b><div data-spectator-list></div>';
document.body.appendChild(spectatorPanel);
spectatorList = spectatorPanel.querySelector("[data-spectator-list]");
putNode(spectatorPanel, readPlace(spectatorPosKey), null);
var head = spectatorPanel.querySelector("b");
var moving = false, dx = 0, dy = 0;
head.addEventListener("pointerdown", function (event) {
moving = true;
dx = event.clientX - spectatorPanel.offsetLeft;
dy = event.clientY - spectatorPanel.offsetTop;
head.setPointerCapture(event.pointerId);
event.preventDefault();
});
head.addEventListener("pointermove", function (event) {
if (!moving) return;
spectatorPanel.style.bottom = "auto";
spectatorPanel.style.left = clamp(event.clientX - dx, 4, innerWidth - spectatorPanel.offsetWidth - 4) + "px";
spectatorPanel.style.top = clamp(event.clientY - dy, 4, innerHeight - 25) + "px";
});
head.addEventListener("pointerup", function (event) {
if (!moving) return;
moving = false;
try { head.releasePointerCapture(event.pointerId); } catch (e) {}
saveNodePlace(spectatorPanel, spectatorPosKey);
});
updateSpectatorPanel(Date.now());
}
function onSpectatorList(list) {
if (!cfg.misc.spectatorList || !Array.isArray(list)) return;
var now = Date.now();
var next = new Map();
for (var i = 0; i < list.length; i++) {
var item = list[i];
if (!item) continue;
var key = String(item.socketId || item.id || item.nick || i);
var old = spectatorEntries.get(key);
next.set(key, {
key: key,
nick: String(item.nick || ""),
id: item.id != null ? String(item.id) : "?",
socketId: item.socketId || "",
since: old ? old.since : now,
image: old && old.image ? old.image : spectatorImage(item)
});
}
spectatorEntries = next;
updateSpectatorPanel(now);
setTimeout(function () {
var original = document.getElementById("spectatorsList");
if (original) original.style.setProperty("display", "none", "important");
}, 0);
}
function attachSpectators() {
try {
if (typeof gameServer === "undefined" || !gameServer || typeof socketMsgType === "undefined" || socketMsgType.SPECTATORSLIST == null) return;
if (spectatorSocket === gameServer) return;
if (spectatorSocket && spectatorHandler) {
if (typeof spectatorSocket.off === "function") spectatorSocket.off(socketMsgType.SPECTATORSLIST, spectatorHandler);
else if (typeof spectatorSocket.removeListener === "function") spectatorSocket.removeListener(socketMsgType.SPECTATORSLIST, spectatorHandler);
}
spectatorSocket = gameServer;
spectatorHandler = onSpectatorList;
spectatorSocket.on(socketMsgType.SPECTATORSLIST, spectatorHandler);
} catch (e) {}
}
function requestSpectators(now) {
if (!cfg.misc.spectatorList || now - spectatorRequestAt < 1200) return;
spectatorRequestAt = now;
try {
if (typeof gameServer !== "undefined" && gameServer && window.game && game.me && !game.me.deleted) gameServer.emit("game:spectatorList");
} catch (e) {}
}
function updateSpectatorPanel(now) {
if (!spectatorPanel) return;
var enabled = !!cfg.misc.spectatorList;
spectatorPanel.classList.toggle("ez-hide", !enabled);
document.body.classList.toggle("ez-spec-active", enabled);
if (!enabled || !spectatorList) {
if (!enabled) spectatorEntries.clear();
return;
}
now = Number(now) || Date.now();
var rows = [];
spectatorEntries.forEach(function (item) {
if (!item.image) item.image = spectatorImage(item);
var avatar = item.image ? '<img src="' + item.image + '">' : '<div class="ez-spec-avatar"></div>';
var shownName = protectedPlayers() ? "Player" : String(item.nick || "").trim();
var idText = item.id && item.id !== "?" ? "#" + item.id : "";
var labelText = shownName && shownName !== String(item.id) && shownName !== idText ? shownName + (idText ? " (" + idText + ")" : "") : (idText || "Unknown");
rows.push('<div class="ez-spec-row">' + avatar + '<span class="ez-spec-text">' + escapeHtml(labelText) + '</span><span class="ez-spec-time">' + watchTime((now - item.since) / 1000) + '</span></div>');
});
spectatorList.innerHTML = rows.join("") || '<div class="ez-empty">No spectators</div>';
}
function escapeHtml(value) {
return String(value == null ? "" : value).replace(/[&<>"']/g, function (char) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char];
});
}
function makeLayer() {
layer = document.createElement("canvas");
layer.id = "ez-layer";
document.body.appendChild(layer);
ctx = layer.getContext("2d", { alpha: true });
}
function markFit() {
fitDirty = true;
}
function fit(now) {
if (!window.game || !game.canvas || !game.canvas.width || !game.canvas.height) {
if (layer) layer.style.display = "none";
return false;
}
var canvas = game.canvas;
if (layer.width !== canvas.width) layer.width = canvas.width;
if (layer.height !== canvas.height) layer.height = canvas.height;
if (fitDirty || now - fitAt > 180) {
var rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) {
layer.style.display = "none";
return false;
}
layer.style.left = rect.left + "px";
layer.style.top = rect.top + "px";
layer.style.width = rect.width + "px";
layer.style.height = rect.height + "px";
layer.style.display = "block";
fitAt = now;
fitDirty = false;
}
return true;
}
function playerKeys(obj) {
var out = [];
if (obj && obj.userId != null && obj.userId !== 0) out.push("u:" + obj.userId);
if (obj && displayNick(obj)) out.push("n:" + String(displayNick(obj)).toLowerCase());
return out;
}
function scanKeys(item) {
var out = [];
if (item && item.id != null) out.push("u:" + item.id);
if (item && item.nick) out.push("n:" + String(item.nick).toLowerCase());
return out;
}
var bossNames = {
boss1: 1,
blackWidow: 1,
giantRat: 1,
mummy: 1,
yeti: 1,
tyrannosaurus: 1,
tyrannosaurus_easter: 1,
mammoth: 1,
zombie: 1,
alienBigInsect: 1,
alienEye: 1
};
function bossObject(obj) {
if (!obj || !obj.name || !window.objectType) return false;
try {
var name = String(obj.name);
var def = window.game && game.objectsDef ? game.objectsDef[name] : null;
if (obj.type === objectType.BOSS || (def && def.type === objectType.BOSS)) return true;
if (bossNames[name] || /^boss/i.test(name)) return true;
if (obj.customData && (obj.customData.boss || obj.customData.isBoss)) return true;
} catch (e) {}
return false;
}
function extraKind(obj) {
if (!obj || !obj.name || !window.objectType) return "";
if (bossObject(obj)) return "boss";
if (obj.type === objectType.HIDE) return "hide";
if (obj.type === objectType.TELEPORT || /portal/i.test(obj.name)) return "portal";
if (obj.type === objectType.FOOD) return "food";
return "";
}
function otherKind(obj) {
if (!obj || !obj.name) return "";
if (obj.name === "gem" || obj.name === "gem_moveable") return "Gem";
if (obj.name === "potion_armor") return "ArmorPotion";
if (obj.name === "waterDrop") return "WaterDrop";
if (obj.name === "roots") return "Roots";
if (obj.name === "fireball") return "Fireball";
if (obj.name === "plasmaball") return "PlasmaBall";
if (obj.name === "shuriken") return "Shuriken";
if (obj.name === "snowball") return "Snowball";
if (obj.name === "bomb") return "Bomb";
if (obj.name === "bear_trap") return "BearTrap";
return "";
}
function canEat(obj) {
if (!obj || !window.game || !game.me || !window.objectType || obj.type !== objectType.FOOD || typeof checkFoodChain !== "function") return false;
try {
var result = checkFoodChain(game.me, obj);
return !!result && result.check === 1;
} catch (e) {
return false;
}
}
function canEatName(name) {
try {
var def = game.objectsDef && game.objectsDef[name];
if (!def || def.type !== objectType.FOOD || typeof checkFoodChain !== "function") return false;
var result = checkFoodChain(game.me, def);
return !!result && result.check === 1;
} catch (e) { return false; }
}
function foodSpawn(obj) {
if (!obj || !obj.position || !window.game || !window.objectType) return null;
try {
var def = game.objectsDef && game.objectsDef[obj.name];
if (!def || def.type !== objectType.SPAWN || !def.spawning) return null;
var target = game.objectsDef[def.spawning];
if (!target || target.type !== objectType.FOOD || !canEatName(def.spawning)) return null;
return {
obj: obj,
name: def.spawning,
delay: Math.max(1000, Number(def.delay) || 30000),
key: obj.id != null ? "id:" + obj.id : "pos:" + def.spawning + ":" + Math.round(obj.position.x) + ":" + Math.round(obj.position.y)
};
} catch (e) { return null; }
}
function timerSeconds(value) {
if (value == null) return null;
if (typeof value === "object") {
var fields = ["left", "remaining", "timeLeft", "timer", "time", "b"];
for (var i = 0; i < fields.length; i++) {
if (value[fields[i]] != null) {
var parsed = timerSeconds(value[fields[i]]);
if (parsed != null) return parsed;
}
}
return null;
}
var num = Number(value);
if (!Number.isFinite(num) || num < 0) return null;
if (num > 1e12) return Math.max(0, (num - Date.now()) / 1000);
if (num > 1e9) return Math.max(0, num - Date.now() / 1000);
if (num > 10000) return num / 1000;
return num;
}
function serverSpawnLeft(spawn) {
var raw = null;
try { raw = typeof spawnTimerData !== "undefined" ? spawnTimerData : window.spawnTimerData; } catch (e) {}
if (!raw) return null;
var id = spawn.obj && spawn.obj.id;
var item = null;
try {
if (!Array.isArray(raw) && id != null && raw[id] != null) item = raw[id];
else if (Array.isArray(raw)) {
var best = Infinity;
var sx = Number(spawn.obj.position.x) || 0;
var sy = Number(spawn.obj.position.y) || 0;
for (var i = 0; i < raw.length; i++) {
var row = raw[i];
if (!row || typeof row !== "object") continue;
if (row.c === spawn.name && Number.isFinite(Number(row.a)) && Number.isFinite(Number(row.b))) {
var dx = Number(row.a) - sx;
var dy = Number(row.b) - sy;
var dist = dx * dx + dy * dy;
if (dist < best) { best = dist; item = row.d; }
continue;
}
if ((id != null && (row.id == id || row.spawnId == id)) || row.name === spawn.name || row.spawning === spawn.name) {
item = row;
if (id != null && (row.id == id || row.spawnId == id)) break;
}
}
}
} catch (e) {}
return timerSeconds(item);
}
function worldDistance(a, b) {
if (!a || !b || !a.position || !b.position) return Infinity;
var ax = a.position.x + (Number(a.width) || 0) / 2;
var ay = a.position.y + (Number(a.height) || 0) / 2;
var bx = b.position.x + (Number(b.width) || 0) / 2;
var by = b.position.y + (Number(b.height) || 0) / 2;
var dx = ax - bx, dy = ay - by;
return Math.sqrt(dx * dx + dy * dy);
}
function syncFoodSpawns(now) {
var used = new Set();
for (var i = 0; i < foodSpawns.length; i++) {
var spawn = foodSpawns[i];
var state = foodSpawnState.get(spawn.key) || { bound: null, end: 0, estimated: false, had: false, seen: now, name: spawn.name };
state.seen = now;
state.name = spawn.name;
var current = null;
if (state.bound != null) {
for (var p = 0; p < foodObjects.length; p++) {
if (foodObjects[p].id == state.bound && !foodObjects[p].deleted) { current = foodObjects[p]; break; }
}
}
if (!current) {
var best = Infinity;
for (var f = 0; f < foodObjects.length; f++) {
var food = foodObjects[f];
if (!food || food.deleted || food.name !== spawn.name || used.has(food.id)) continue;
var dist = worldDistance(spawn.obj, food);
var limit = Math.max(90, Math.min(260, (Number(food.width) || 50) * 1.35));
if (dist < limit && dist < best) { best = dist; current = food; }
}
}
if (current) {
used.add(current.id);
state.bound = current.id;
state.end = 0;
state.estimated = false;
state.had = true;
} else {
var exact = serverSpawnLeft(spawn);
if (exact != null) {
state.end = now + exact * 1000;
state.estimated = false;
} else if (!state.end) {
state.end = now + spawn.delay;
state.estimated = !state.had;
}
}
foodSpawnState.set(spawn.key, state);
}
foodSpawnState.forEach(function (state, key) {
if (!state || now - state.seen > 15000) foodSpawnState.delete(key);
});
}
function clearFoodGone(obj) {
if (!obj || !obj.position) return;
var bestKey = null;
var best = Infinity;
var limit = Math.max(28, Math.min(110, Math.max(Number(obj.width) || 0, Number(obj.height) || 0) * .75));
foodGone.forEach(function (item, key) {
if (!item || item.name !== obj.name) return;
var dx = item.x - obj.position.x;
var dy = item.y - obj.position.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= limit && distance < best) {
best = distance;
bestKey = key;
}
});
if (bestKey != null) foodGone.delete(bestKey);
}
function ownPlayer() {
try {
if (!window.game || !game.me || game.me.deleted) return null;
if ((typeof imDead !== "undefined" && imDead) || (typeof spectatorMode !== "undefined" && spectatorMode)) return null;
if (window.user && user.id != null && game.me.userId != null && Number(game.me.userId) !== Number(user.id)) return null;
return game.me;
} catch (e) { return null; }
}
function updateList(now) {
if (now - listAt < 100) return;
listAt = now;
var currentMe = ownPlayer();
var currentEvolution = currentMe ? String(currentMe.name || "") : "";
if (currentEvolution && currentEvolution !== foodEvolution) {
foodEvolution = currentEvolution;
foodGone.forEach(function (item, key) { if (!item || !canEatName(item.name)) foodGone.delete(key); });
foodSpawnState.forEach(function (state, key) {
var keep = false;
for (var si = 0; si < foodSpawns.length; si++) if (foodSpawns[si] && foodSpawns[si].key === key && canEatName(foodSpawns[si].name)) { keep = true; break; }
if (!keep && state && state.name && !canEatName(state.name)) foodSpawnState.delete(key);
});
}
players.length = 0;
hides.length = 0;
extras.length = 0;
foodObjects.length = 0;
foodSpawns.length = 0;
live.clear();
if (!window.game || !game.gameObjects || !window.objectType) return;
var list = game.gameObjects;
for (var id in list) {
if (!Object.prototype.hasOwnProperty.call(list, id)) continue;
var obj = list[id];
if (!obj || obj.deleted || !obj.position) continue;
if (obj.type === objectType.FOOD) {
foodObjects.push(obj);
foodSeen.set(String(obj.id), {
name: obj.name,
x: Number(obj.position.x) || 0,
y: Number(obj.position.y) || 0,
at: now,
sprite: objectSprite(obj, now)
});
clearFoodGone(obj);
}
var spawn = foodSpawn(obj);
if (spawn) foodSpawns.push(spawn);
if (obj.type === objectType.PLAYER && obj !== game.me) {
players.push(obj);
if (obj.id != null && obj.userId != null && obj.userId !== 0) projectileOwners.set(String(obj.id), String(obj.userId));
var recentName = String(rawNick(obj) || "").toLowerCase();
if (recentName) recentPlayers.set(recentName, { obj: obj, sprite: objectSprite(obj, now), at: now });
var keys = playerKeys(obj);
for (var i = 0; i < keys.length; i++) live.add(keys[i]);
} else if (obj.type === objectType.HIDE) {
hides.push(obj);
}
var kind = extraKind(obj);
if (kind && cfg.extra[kind]) {
if (kind !== "food" || canEat(obj)) {
var range = Number(cfg.extra[kind + "Range"]) || 0;
if (!range || meters(obj) <= range + 300) extras.push({ obj: obj, kind: kind, subtype: "" });
}
}
var subtype = otherKind(obj);
if (subtype && cfg.extra.others && cfg.extra["others" + subtype]) {
var otherRange = Number(cfg.extra.othersRange) || 0;
if (!otherRange || meters(obj) <= otherRange + 300) extras.push({ obj: obj, kind: "others", subtype: subtype });
}
}
foodSeen.forEach(function (value, key) { if (!value || now - value.at > 60000) foodSeen.delete(key); });
projectileSkillSeen.forEach(function (at, key) { if (now - at > 30000) projectileSkillSeen.delete(key); });
syncFoodSpawns(now);
recentPlayers.forEach(function (item, name) {
if (!item || now - item.at > 120000) recentPlayers.delete(name);
});
}
function updateScan(now) {
if (now - scanReadAt < 80) return;
scanReadAt = now;
var list = null;
try {
if (typeof scanPlayersArr !== "undefined" && Array.isArray(scanPlayersArr)) list = scanPlayersArr;
else if (Array.isArray(window.scanPlayersArr)) list = window.scanPlayersArr;
} catch (e) {}
scans.length = 0;
if (!list) return;
var meId = window.game && game.me ? game.me.userId : null;
var meNick = window.game && game.me && game.me.nick ? String(game.me.nick).toLowerCase() : "";
for (var i = 0; i < list.length; i++) {
var item = list[i];
if (!item) continue;
if (meId != null && item.id == meId) continue;
if (meNick && item.nick && String(item.nick).toLowerCase() === meNick) continue;
scans.push(item);
}
}
function doScan(now, force) {
if (!force && now - scanAt < 450) return false;
if (!window.game || !game.me || game.me.deleted || game.me.inBattleRoyaleZone) return false;
try {
if (typeof gameServer === "undefined" || typeof socketMsgType === "undefined" || socketMsgType.SCANPLAYERS == null) return false;
gameServer.emit(socketMsgType.SCANPLAYERS);
scanAt = now;
return true;
} catch (e) {
return false;
}
}
function itemBank(obj) {
var key = obj.id != null ? "o:" + obj.id : "n:" + String(displayNick(obj) || "player").toLowerCase();
var item = bank.get(key);
if (!item) {
item = { rel: 0, relAt: 0, sprite: null, spriteAt: 0, spriteName: "", spriteSkin: null };
bank.set(key, item);
}
return item;
}
function pair(target) {
if (!window.game || !game.me || typeof checkFoodChain !== "function") return 0;
var eat = false;
var eaten = false;
try { eat = checkFoodChain(game.me, target).check === 1; } catch (e) {}
try { eaten = checkFoodChain(target, game.me).check === 1; } catch (e) {}
if (eat && eaten) return 2;
if (eaten) return -1;
if (eat) return 1;
return 0;
}
function relation(obj, now) {
var item = itemBank(obj);
if (now - item.relAt < 220) return item.rel;
item.relAt = now;
item.rel = pair(obj);
return item.rel;
}
function scanTarget(item) {
var def = null;
try { def = game.objectsDef && game.objectsDef[item.evolution]; } catch (e) {}
def = def || {};
return {
id: "scan:" + (item.id != null ? item.id : item.nick),
name: item.evolution,
type: window.objectType ? objectType.PLAYER : 1,
eatsSouls: !!def.eatsSouls,
hasSoul: !!def.hasSoul,
immortal: false
};
}
function scanRelation(item, now) {
var key = String(item.id != null ? item.id : item.nick) + ":" + item.evolution;
var data = scanBank.get(key);
if (!data) {
data = { rel: 0, at: 0, sprite: null };
scanBank.set(key, data);
}
if (now - data.at > 600) {
data.at = now;
data.rel = pair(scanTarget(item));
}
return data.rel;
}
function objectSprite(obj, now) {
var item = itemBank(obj);
if (item.sprite && item.spriteName === obj.name && item.spriteSkin === obj.skin && now - item.spriteAt < 1400) return item.sprite;
item.spriteAt = now;
item.spriteName = obj.name;
item.spriteSkin = obj.skin;
try {
if (typeof getObjectSprite === "function") {
var ready = getObjectSprite(obj);
if (ready && ready.width > 0 && ready.height > 0) return item.sprite = ready;
}
} catch (e) {}
try {
if (!obj.animations || !game.sprites) return item.sprite;
var anim = obj.animations[obj.animation] || obj.animations.none || obj.animations.flying_right;
if (!anim || !anim.sprites || !anim.sprites.length) return item.sprite;
var name = anim.sprites[(obj.animationFrame || 0) % anim.sprites.length];
if (obj.skin && game.objectsDef[obj.name] && game.objectsDef[obj.name].skinsLoaded[obj.skin]) name += "_" + obj.skin;
var sprite = game.sprites[name];
if (sprite && sprite.width > 0 && sprite.height > 0) item.sprite = sprite;
} catch (e) {}
return item.sprite;
}
function scanSprite(item) {
try {
if (!item || !item.evolution || !game.objectsDef || !game.sprites) return null;
var key = String(item.id != null ? item.id : item.nick) + ":" + item.evolution + ":" + (Number(item.skin) || 0);
var data = scanBank.get(key);
if (data && data.sprite && data.sprite.width > 0 && data.sprite.height > 0) return data.sprite;
var def = game.objectsDef[item.evolution];
if (!def || !def.animations) return null;
var anim = def.animations.flying_right || def.animations.none;
if (!anim || !anim.sprites) return null;
var list = Array.isArray(anim.sprites) ? anim.sprites : Object.keys(anim.sprites).map(function (name) { return anim.sprites[name]; });
if (!list.length) return null;
var name = list[0];
var skin = Number(item.skin) || 0;
if (skin && def.skinsLoaded && def.skinsLoaded[skin]) name += "_" + skin;
var sprite = game.sprites[name];
if (!data) {
data = { rel: 0, at: 0, sprite: null };
scanBank.set(key, data);
}
if (sprite && sprite.width > 0 && sprite.height > 0) data.sprite = sprite;
return sprite && sprite.width > 0 && sprite.height > 0 ? sprite : null;
} catch (e) {
return null;
}
}
function styleFor(rel) {
if (rel === 2 && cfg.mutual.on) return cfg.mutual;
if (rel < 0 && cfg.danger.on) return cfg.danger;
if (rel > 0 && cfg.edible.on) return cfg.edible;
return cfg.all.on ? cfg.all : null;
}
function hiddenStyle(rel) {
if (rel === 2 && cfg.mutual.on && cfg.mutual.hidden) return cfg.mutual;
if (rel < 0 && cfg.danger.on && cfg.danger.hidden) return cfg.danger;
if (rel > 0 && cfg.edible.on && cfg.edible.hidden) return cfg.edible;
return cfg.all.on && cfg.all.hidden ? cfg.all : null;
}
function relationColor(rel) {
var style = hiddenStyle(rel);
return style ? style.boxColor : null;
}
function middle(obj) {
if (obj.positionCenter && Number.isFinite(obj.positionCenter.x) && Number.isFinite(obj.positionCenter.y)) return obj.positionCenter;
return {
x: obj.position.x + (Number(obj.width) || 0) / 2,
y: obj.position.y + (Number(obj.height) || 0) / 2
};
}
function meters(obj) {
if (!window.game || !game.me || !game.me.position) return 0;
var a = middle(game.me);
var b = middle(obj);
var dx = b.x - a.x;
var dy = b.y - a.y;
return Math.round(Math.sqrt(dx * dx + dy * dy));
}
function rgba(hex, alpha) {
var value = String(hex || "#ffffff").replace("#", "");
if (value.length === 3) value = value.replace(/./g, function (c) { return c + c; });
var number = parseInt(value, 16);
if (!Number.isFinite(number)) return "rgba(255,255,255," + alpha + ")";
return "rgba(" + ((number >> 16) & 255) + "," + ((number >> 8) & 255) + "," + (number & 255) + "," + alpha + ")";
}
function roundPath(g, x, y, w, h, r) {
r = Math.min(r, w / 2, h / 2);
g.beginPath();
g.moveTo(x + r, y);
g.arcTo(x + w, y, x + w, y + h, r);
g.arcTo(x + w, y + h, x, y + h, r);
g.arcTo(x, y + h, x, y, r);
g.arcTo(x, y, x + w, y, r);
g.closePath();
}
function roundRect(x, y, w, h, r) {
roundPath(ctx, x, y, w, h, r);
}
function label(value, x, y, color, size, align) {
ctx.font = "600 " + size + "px Arial,sans-serif";
ctx.textAlign = align || "center";
ctx.textBaseline = "bottom";
ctx.lineJoin = "round";
ctx.lineWidth = Math.max(1, size * .24);
ctx.strokeStyle = "rgba(0,0,0,.84)";
ctx.strokeText(value, x, y);
ctx.fillStyle = color;
ctx.fillText(value, x, y);
}
function effects(obj) {
var list = [];
if (obj.fire) list.push({ text: "Burning", color: "#ff3b30", pulse: true });
if (obj.poisoned) list.push({ text: "Poisoned", color: "#5dde71" });
if (obj.infected) list.push({ text: "Infected", color: "#bd4b4b" });
if (obj.boostLock) list.push({ text: "Boost lock", color: "#bd75e8" });
if (obj.grabbed) list.push({ text: "Grabbed", color: "#b7b7b7" });
if (obj.isProtected) list.push({ text: "Protected", color: "#78a9ff" });
if (obj.inSafeZone) list.push({ text: "Safe zone", color: "#75c8ff" });
if (obj.inHide) list.push({ text: "Hidden", color: "#d7d7d7" });
if (obj.invisibleTime && window.game && obj.invisibleTime > game.time) list.push({ text: "Invisible", color: "#d9c5ff" });
return list;
}
function drawEffects(obj, x, y, now) {
var list = effects(obj);
if (!list.length) return 0;
var scale = hudScale();
var fontSize = Math.max(5, 9 * scale);
var rowHeight = Math.max(7, 13 * scale);
var padX = Math.max(3, 5 * scale);
var gap = Math.max(1, 3 * scale);
var count = Math.min(4, list.length);
var widths = [];
var total = 0;
ctx.font = "600 " + fontSize + "px Arial,sans-serif";
for (var i = 0; i < count; i++) {
var name = i === 3 && list.length > 4 ? "+" + (list.length - 3) : list[i].text;
widths[i] = Math.ceil(ctx.measureText(name).width) + padX * 2;
total += widths[i] + (i ? gap : 0);
}
var left = x - total / 2;
for (i = 0; i < count; i++) {
var effect = list[i];
var textValue = i === 3 && list.length > 4 ? "+" + (list.length - 3) : effect.text;
var alpha = effect.pulse ? 0.45 + 0.55 * (0.5 + 0.5 * Math.sin(now / 80)) : 0.9;
roundRect(left, y - rowHeight, widths[i], rowHeight, Math.max(1.5, 3 * scale));
ctx.fillStyle = "rgba(12,13,16,.84)";
ctx.fill();
ctx.strokeStyle = rgba(effect.color, alpha);
ctx.lineWidth = Math.max(.6, scale);
ctx.stroke();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = rgba(effect.color, Math.min(1, alpha + 0.1));
ctx.fillText(textValue, left + widths[i] / 2, y - rowHeight / 2);
left += widths[i] + gap;
}
return rowHeight + Math.max(1, 3 * scale);
}
function avatarOn(g, sprite, x, y, size, circle) {
g.save();
if (circle) {
g.beginPath();
g.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2);
g.clip();
}
g.fillStyle = "rgba(255,255,255,.07)";
g.fillRect(x, y, size, size);
if (sprite && sprite.width > 0 && sprite.height > 0) {
var scale = Math.min(size / sprite.width, size / sprite.height);
var w = sprite.width * scale;
var h = sprite.height * scale;
try { g.drawImage(sprite, x + (size - w) / 2, y + (size - h) / 2, w, h); } catch (e) {}
}
g.restore();
}
function avatar(sprite, x, y, size) {
avatarOn(ctx, sprite, x, y, size, true);
}
function trimText(g, value, width, font) {
var name = String(value || "Player");
g.font = font || "600 10px Arial,sans-serif";
if (g.measureText(name).width <= width) return name;
while (name.length > 2 && g.measureText(name + "…").width > width) name = name.slice(0, -1);
return name + "…";
}
function skillOf(obj) {
if (!obj) return null;
try {
var skill = obj.skill || (game.objectsDef && game.objectsDef[obj.name] && game.objectsDef[obj.name].skill);
if (!skill || !(Number(skill.cooldown) > 0)) return null;
return skill;
} catch (e) { return null; }
}
function skillTitle(skill) {
if (!skill) return "Skill";
var value = String(skill.shortName || skill.fullName || skill.name || "Skill");
value = value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^skill[_ ]?/i, "");
return value.length > 18 ? value.slice(0, 17) + "…" : value;
}
function trackedSkill(obj, now) {
if (!obj) return null;
var row = skillTracks.get(String(obj.id));
if (!row) return null;
if (row.end <= now) { skillTracks.delete(String(obj.id)); return null; }
return { title: row.title, left: row.end - now, total: row.total };
}
function drawSkillTag(obj, style, x, y, now) {
if (!style.skill) return 0;
var row = trackedSkill(obj, now);
if (!row) return 0;
var text = row.title + " " + (row.left < 10000 ? (row.left / 1000).toFixed(1) : Math.ceil(row.left / 1000)) + "s";
ctx.save();
ctx.font = "700 10.5px Arial,sans-serif";
var w = Math.max(64, ctx.measureText(text).width + 16);
var h = 20;
roundRect(x - w / 2, y, w, h, 4);
ctx.fillStyle = "rgba(13,14,17,.93)"; ctx.fill();
ctx.strokeStyle = rgba(style.boxColor || style.nameColor || "#b58af0", .98); ctx.lineWidth = 1.1; ctx.stroke();
ctx.fillStyle = "#f4f4f6"; ctx.textAlign = "center"; ctx.textBaseline = "middle";
ctx.fillText(text, x, y + h / 2 - 1);
var ratio = clamp(row.left / Math.max(1, row.total), 0, 1);
ctx.fillStyle = style.boxColor || "#b58af0";
ctx.fillRect(x - w / 2 + 2, y + h - 3, (w - 4) * ratio, 2);
ctx.restore();
return h + 3;
}
function startSkillTrack(obj, skill, now) {
if (!obj || !skill || !(Number(skill.cooldown) > 0)) return;
var total = Number(skill.cooldown) || 0;
skillTracks.set(String(obj.id), { end: now + total, total: total, title: skillTitle(skill) });
}
function noteSkillAttack(data) {
if (!data || !window.game || !game.gameObjects) return;
var obj = game.gameObjects[data.objectId];
if (!obj) return;
if (game.me && String(data.objectId) === String(game.me.id)) return;
var skill = skillOf(obj);
if (!skill) return;
var attackEvent = false;
try {
attackEvent = typeof skillEvent === "undefined" || skill.event === skillEvent.onAttack || skill.event === skillEvent.onObtainAttack;
} catch (e) { attackEvent = true; }
if (!attackEvent && String(skill.fullName || "").toLowerCase() !== "scythe attack") return;
startSkillTrack(obj, skill, performance.now());
}
function projectileOwnerObjectId(obj) {
if (!obj) return null;
var raw = obj.customData;
if (raw == null) raw = obj.ownerId != null ? obj.ownerId : (obj.owner != null ? obj.owner : obj.throwerId);
if (raw && typeof raw === "object") {
raw = raw.ownerId != null ? raw.ownerId : (raw.owner != null ? raw.owner : (raw.objectId != null ? raw.objectId : (raw.playerId != null ? raw.playerId : raw.id)));
}
if (Array.isArray(raw)) raw = raw.length ? raw[0] : null;
return raw == null || raw === "" ? null : raw;
}
function noteProjectileSkill(obj, subtype, now) {
if (!obj || !/^(Fireball|PlasmaBall)$/.test(subtype || "")) return;
var key = String(obj.id != null ? obj.id : obj.name + ":" + Math.round(obj.position.x) + ":" + Math.round(obj.position.y));
if (projectileSkillSeen.has(key)) return;
var ownerId = projectileOwnerObjectId(obj);
var owner = ownerId != null && game.gameObjects ? game.gameObjects[ownerId] : null;
if (!owner || (game.me && owner === game.me)) return;
var skill = skillOf(owner);
if (!skill) skill = subtype === "PlasmaBall" && game.skills ? game.skills.flame : (game.skills ? game.skills.fireball : null);
if (!skill) return;
projectileSkillSeen.set(key, now);
startSkillTrack(owner, skill, now);
}
function projectileOwner(obj) {
var raw = projectileOwnerObjectId(obj);
if (raw == null) return "";
try {
var owner = game.gameObjects && game.gameObjects[raw];
if (owner) {
var id = owner.userId != null ? owner.userId : (owner.accountId != null ? owner.accountId : null);
if (id != null && id !== 0) {
projectileOwners.set(String(raw), String(id));
return "#" + String(id).replace(/^#/, "");
}
}
if (projectileOwners.has(String(raw))) return "#" + String(projectileOwners.get(String(raw))).replace(/^#/, "");
} catch (e) {}
return "";
}
function drawArrow(obj, style, targetX, targetY, now) {
if (!style.arrows) return;
var scaleUi = hudScale();
var cx = layer.width / 2;
var cy = layer.height / 2;
var dx = targetX - cx;
var dy = targetY - cy;
if (!dx && !dy) return;
var angle = Math.atan2(dy, dx);
var margin = Math.max(24, 50 * scaleUi);
var edgeX = Math.max(30, layer.width / 2 - margin);
var edgeY = Math.max(30, layer.height / 2 - margin);
var sx = dx === 0 ? Infinity : edgeX / Math.abs(dx);
var sy = dy === 0 ? Infinity : edgeY / Math.abs(dy);
var edgeScale = Math.min(sx, sy);
var x = cx + dx * edgeScale;
var y = cy + dy * edgeScale;
var pulse = 0.45 + 0.55 * (0.5 + 0.5 * Math.sin(now / 80));
var color = obj.fire && style.effects ? rgba("#ff3b30", pulse) : style.arrowColor;
var arrowLen = Math.max(5, 11 * scaleUi);
var arrowWing = Math.max(4, 8.5 * scaleUi);
ctx.save();
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x + Math.cos(angle) * arrowLen, y + Math.sin(angle) * arrowLen);
ctx.lineTo(x + Math.cos(angle + 2.48) * arrowWing, y + Math.sin(angle + 2.48) * arrowWing);
ctx.lineTo(x + Math.cos(angle - 2.48) * arrowWing, y + Math.sin(angle - 2.48) * arrowWing);
ctx.closePath();
ctx.fill();
var ix = -Math.cos(angle);
var iy = -Math.sin(angle);
var cardW = Math.max(72, Math.round(124 * scaleUi));
var cardH = Math.max(24, Math.round(38 * scaleUi));
var cardX = clamp(x + ix * Math.max(38, 72 * scaleUi) - cardW / 2, 5, layer.width - cardW - 5);
var cardY = clamp(y + iy * Math.max(31, 58 * scaleUi) - cardH / 2, 5, layer.height - cardH - 5);
roundRect(cardX, cardY, cardW, cardH, Math.max(3, 5 * scaleUi));
ctx.fillStyle = "rgba(13,14,17,.88)";
ctx.fill();
ctx.strokeStyle = color;
ctx.lineWidth = Math.max(.7, scaleUi);
if (obj.inHide) ctx.setLineDash([3 * scaleUi, 3 * scaleUi]);
ctx.stroke();
ctx.setLineDash([]);
var av = Math.max(16, 28 * scaleUi);
var gap = Math.max(4, 5 * scaleUi);
avatar(objectSprite(obj, now), cardX + gap, cardY + (cardH - av) / 2, av);
var textX = cardX + gap * 2 + av;
ctx.textAlign = "left";
ctx.textBaseline = "top";
var nameSize = Math.max(5.5, 10 * scaleUi);
ctx.font = "600 " + nameSize + "px Arial,sans-serif";
ctx.fillStyle = "#f1f1f3";
ctx.fillText(trimText(ctx, displayNick(obj), Math.max(20, cardW - (textX - cardX) - gap)), textX, cardY + Math.max(3, 6 * scaleUi));
var distSize = Math.max(5, 9 * scaleUi);
ctx.font = distSize + "px Arial,sans-serif";
ctx.fillStyle = style.distanceColor;
ctx.fillText("[" + meters(obj) + " m]", textX, cardY + cardH - distSize - Math.max(2, 5 * scaleUi));
ctx.restore();
}
function bar(x, y, width, value, color) {
var height = hudPx(3.5, 1.4);
ctx.fillStyle = "rgba(0,0,0,.76)";
ctx.fillRect(x, y, width, height);
ctx.fillStyle = color;
ctx.fillRect(x, y, width * clamp(value, 0, 100) / 100, height);
ctx.strokeStyle = "rgba(0,0,0,.9)";
ctx.lineWidth = 1;
ctx.strokeRect(x, y, width, height);
return height;
}
function tracerStart(origin) {
var mode = String(origin || "bottom");
if (mode === "top") return { x: layer.width / 2, y: 10 };
if (mode === "player") {
try {
var me = ownPlayer();
if (me && me.position) {
var pos = game.getRenderPosition(me.position.x, me.position.y);
var width = (Number(me.width) || 50) * game.scaleX * game.zoom;
var height = (Number(me.height) || 50) * game.scaleY * game.zoom;
if (pos && Number.isFinite(pos.x) && Number.isFinite(pos.y)) return { x: pos.x + width / 2, y: pos.y - height / 2 };
}
} catch (e) {}
}
return { x: layer.width / 2, y: layer.height - 10 };
}
function drawPlayer(obj, style, now) {
if (!obj.position || !Number.isFinite(obj.position.x) || !Number.isFinite(obj.position.y)) return;
var pos = game.getRenderPosition(obj.position.x, obj.position.y);
var width = (Number(obj.width) || 50) * game.scaleX * game.zoom;
var height = (Number(obj.height) || 50) * game.scaleY * game.zoom;
var left = pos.x;
var top = pos.y - height;
var right = left + width;
var bottom = pos.y;
var midX = left + width / 2;
var midY = top + height / 2;
var pad = hudPx(2.5, .8);
if (!Number.isFinite(left) || !Number.isFinite(top) || width <= 0 || height <= 0) return;
var visible = right > 6 && left < layer.width - 6 && bottom > 6 && top < layer.height - 6;
if (!visible) {
drawArrow(obj, style, midX, midY, now);
return;
}
ctx.save();
var alpha = clamp(Number(obj.opacity) || 1, 0.3, 1);
if (obj.inHide) alpha *= 0.8;
ctx.globalAlpha = alpha;
if (obj.inHide) ctx.setLineDash([4, 3]);
if (style.fill) {
ctx.fillStyle = rgba(style.fillColor, 0.15);
ctx.fillRect(left - pad, top - pad, width + pad * 2, height + pad * 2);
}
if (style.box) {
ctx.lineWidth = hudPx(1.35, .65);
ctx.strokeStyle = style.boxColor;
ctx.strokeRect(left - pad, top - pad, width + pad * 2, height + pad * 2);
}
if (style.effects && obj.fire) {
var pulse = 0.35 + 0.65 * (0.5 + 0.5 * Math.sin(now / 80));
ctx.setLineDash([]);
ctx.lineWidth = hudPx(2, .9);
ctx.strokeStyle = rgba("#ff3b30", pulse);
ctx.strokeRect(left - pad - 2, top - pad - 2, width + pad * 2 + 4, height + pad * 2 + 4);
}
if (style.line) {
var lineStart = tracerStart(cfg.esp && cfg.esp.tracerOrigin);
ctx.beginPath();
ctx.moveTo(lineStart.x, lineStart.y);
ctx.lineTo(midX, bottom + pad);
ctx.lineWidth = hudPx(1, .6);
ctx.strokeStyle = rgba(style.lineColor, 0.88);
ctx.stroke();
}
ctx.setLineDash([]);
var barY = bottom + pad + 4;
var barW = width + pad * 2;
if (style.health) barY += bar(left - pad, barY, barW, Number.isFinite(obj.hp) ? obj.hp : 100, style.healthColor) + 2;
if (style.armor && Number.isFinite(obj.armor) && obj.armor > 0) barY += bar(left - pad, barY, barW, obj.armor, style.armorColor) + 2;
var size = hudPx(9.7, 5.2);
var textY = top - pad - 2;
if (style.effects) textY -= drawEffects(obj, midX, textY, now);
if (style.distance) {
label("[" + meters(obj) + " m]", midX, textY, style.distanceColor, size);
textY -= size + 1;
}
var hideInfo = cfg.misc.privacy && cfg.misc.privacyPlayers;
if (style.level && !hideInfo) {
label("Lv. " + (Number.isFinite(obj.level) ? obj.level : 0), midX, textY, style.levelColor, size);
textY -= size + 1;
}
if (style.name && !hideInfo) label(displayNick(obj), midX, textY, style.nameColor, size);
ctx.restore();
}
function hideSpot(obj) {
if (!obj || !obj.position) return null;
var pos = game.getRenderPosition(obj.position.x, obj.position.y);
var width = (Number(obj.width) || 50) * game.scaleX * game.zoom;
var height = (Number(obj.height) || 50) * game.scaleY * game.zoom;
var left = pos.x;
var top = pos.y - height;
var right = left + width;
var bottom = pos.y;
if (!Number.isFinite(left) || !Number.isFinite(top)) return null;
if (right < 8 || left > layer.width - 8 || bottom < 8 || top > layer.height - 8) return null;
var dist = 0;
if (game.me && game.me.position) {
var a = middle(game.me);
var b = middle(obj);
var dx = b.x - a.x;
var dy = b.y - a.y;
dist = dx * dx + dy * dy;
}
return {
x: clamp(left + width / 2, 58, layer.width - 58),
y: clamp(top + height / 2, 18, layer.height - 18),
left: left,
top: top,
width: width,
height: height,
dist: dist
};
}
function missingPlayers() {
var out = [];
for (var i = 0; i < scans.length; i++) {
var keys = scanKeys(scans[i]);
var found = false;
for (var k = 0; k < keys.length; k++) {
if (live.has(keys[k])) {
found = true;
break;
}
}
if (!found) out.push(scans[i]);
}
return out;
}
function guessCard(item, chance, color, now) {
var sprite = scanSprite(item);
var scale = 1;
var cw = Math.max(72, Math.round(122 * scale));
var ch = Math.max(18, Math.round(27 * scale));
var key = String(item.id != null ? item.id : item.nick) + ":" + item.evolution + ":" + (item.skin || 0) + ":" + chance + ":" + color + ":" + (cfg.misc.privacy && cfg.misc.privacyPlayers ? 1 : 0) + ":" + cw;
var ready = cardBank.get(key);
if (ready && ready.sprite === sprite && now - ready.at < 1500) return ready.canvas;
var canvas = document.createElement("canvas");
canvas.width = cw;
canvas.height = ch;
var g = canvas.getContext("2d");
roundPath(g, .5, .5, cw - 1, ch - 1, Math.max(2, 4 * scale));
g.fillStyle = "rgba(13,14,17,.92)";
g.fill();
g.strokeStyle = rgba(color, .96);
g.lineWidth = Math.max(.7, scale);
g.setLineDash([Math.max(2, 3 * scale), Math.max(2, 3 * scale)]);
g.stroke();
g.setLineDash([]);
var av = Math.max(12, 19 * scale);
avatarOn(g, sprite, Math.max(2, 4 * scale), (ch - av) / 2, av, true);
var tx = Math.max(18, 28 * scale);
var nameSize = Math.max(5, 9 * scale);
g.textAlign = "left";
g.textBaseline = "top";
g.font = "600 " + nameSize + "px Arial,sans-serif";
g.fillStyle = "#f0f1f3";
g.fillText(trimText(g, cfg.misc.privacy && cfg.misc.privacyPlayers ? "Player" : item.nick, cw - tx - 3, "600 " + nameSize + "px Arial,sans-serif"), tx, Math.max(2, 4 * scale));
var chanceSize = Math.max(4.5, 8 * scale);
g.font = chanceSize + "px Arial,sans-serif";
g.fillStyle = rgba(color, 1);
g.fillText(chance + "% here", tx, ch - chanceSize - Math.max(1, 3 * scale));
cardBank.set(key, { canvas: canvas, sprite: sprite, at: now });
return canvas;
}
function drawCached(now) {
if (!hides.length || !(cfg.all.hidden || cfg.danger.hidden || cfg.edible.hidden || cfg.mutual.hidden)) return;
var spots = [];
var limit = Number(cfg.extra.hideRange) || 0;
for (var i = 0; i < hides.length; i++) {
if (limit > 0 && meters(hides[i]) > limit) continue;
var spot = hideSpot(hides[i]);
if (spot) spots.push(spot);
}
if (!spots.length) return;
spots.sort(function (a, b) { return a.dist - b.dist; });
if (spots.length > 4) spots.length = 4;
var missing = missingPlayers().filter(function (item) {
return !!hiddenStyle(scanRelation(item, now));
});
if (!missing.length) return;
var firstStyle = hiddenStyle(scanRelation(missing[0], now));
var frameColor = firstStyle ? firstStyle.boxColor : cfg.all.boxColor;
var scale = 1;
ctx.save();
ctx.setLineDash([Math.max(2, 5 * scale), Math.max(2, 4 * scale)]);
ctx.lineWidth = Math.max(.7, 1.2 * scale);
ctx.strokeStyle = rgba(frameColor, .88);
ctx.fillStyle = rgba(frameColor, .05);
for (i = 0; i < spots.length; i++) {
ctx.fillRect(spots[i].left, spots[i].top, spots[i].width, spots[i].height);
ctx.strokeRect(spots[i].left, spots[i].top, spots[i].width, spots[i].height);
}
ctx.setLineDash([]);
var chance = Math.max(1, Math.round(100 / spots.length));
var sample = guessCard(missing[0], chance, frameColor, now);
var rowH = sample.height + Math.max(1, Math.round(2 * scale));
var fitCount = Math.max(1, Math.min(missing.length, Math.floor((layer.height - 12) / rowH), 12));
for (i = 0; i < spots.length; i++) {
var start = clamp(spots[i].y - fitCount * rowH / 2, 4, layer.height - fitCount * rowH - 4);
for (var p = 0; p < fitCount; p++) {
var rel = scanRelation(missing[p], now);
var cardStyle = hiddenStyle(rel);
if (!cardStyle) continue;
var color = cardStyle.boxColor;
var card = guessCard(missing[p], chance, color, now);
var left = clamp(spots[i].x - card.width / 2, 4, layer.width - card.width - 4);
ctx.drawImage(card, left, start + p * rowH);
}
if (missing.length > fitCount) label("+" + (missing.length - fitCount), spots[i].x, start + fitCount * rowH + Math.max(5, 10 * scale), frameColor, Math.max(5, 9 * scale));
}
ctx.restore();
}
function objectTitle(obj, fallback) {
try {
var def = game.objectsDef && game.objectsDef[obj.name];
if (def && def.fullName) return def.fullName;
} catch (e) {}
if (fallback === "Food" && obj && obj.name) return String(obj.name).replace(/_/g, " ");
return fallback;
}
function extraStyle(kind, obj, subtype) {
if (!cfg.extra[kind]) return null;
var titles = {
hide: "Hide",
portal: "Portal",
food: "Food",
boss: "Boss"
};
var objectTitles = {
Gem: "Gem",
ArmorPotion: "Armor potion",
WaterDrop: "Water drop",
Roots: "Roots",
Fireball: "Fireball",
PlasmaBall: "Plasma ball",
Shuriken: "Shuriken",
Snowball: "Snowball",
Bomb: "Bomb",
BearTrap: "Bear trap"
};
var base = kind === "others" ? "others" : kind;
var fallback = kind === "others" ? (objectTitles[subtype] || "Object") : (titles[kind] || kind);
return {
kind: kind,
subtype: subtype || "",
color: cfg.extra[base + "Color"],
title: objectTitle(obj, fallback),
image: !!cfg.extra[base + "Image"],
box: !!cfg.extra[base + "Box"],
tracer: !!cfg.extra[base + "Tracer"],
name: !!cfg.extra[base + "Name"],
distance: !!cfg.extra[base + "Distance"],
range: Number(cfg.extra[base + "Range"]) || 0
};
}
function drawExtra(obj, info, now) {
if (!obj || !obj.position) return;
var dist = meters(obj);
if (info.range > 0 && dist > info.range) return;
var pos = game.getRenderPosition(obj.position.x, obj.position.y);
var width = (Number(obj.width) || 45) * game.scaleX * game.zoom;
var height = (Number(obj.height) || 45) * game.scaleY * game.zoom;
var left = pos.x;
var top = pos.y - height;
if (left + width < 4 || left > layer.width - 4 || top + height < 4 || top > layer.height - 4) return;
var x = left + width / 2;
var bottom = top + height;
var scaleUi = hudScale();
var y = top - Math.max(1, 3 * scaleUi);
ctx.save();
ctx.strokeStyle = info.color;
ctx.fillStyle = rgba(info.color, .07);
ctx.lineWidth = Math.max(.65, 1.2 * scaleUi);
if (info.box) {
var boxPad = Math.max(.7, 2 * scaleUi);
ctx.fillRect(left - boxPad, top - boxPad, width + boxPad * 2, height + boxPad * 2);
ctx.strokeRect(left - boxPad, top - boxPad, width + boxPad * 2, height + boxPad * 2);
}
if (info.tracer) {
var extraLineStart = tracerStart(cfg.extra && cfg.extra.tracerOrigin);
ctx.beginPath();
ctx.moveTo(extraLineStart.x, extraLineStart.y);
ctx.lineTo(x, bottom + 3);
ctx.strokeStyle = rgba(info.color, .88);
ctx.stroke();
}
if (info.image) {
var sprite = objectSprite(obj, now);
var imageSize = Math.max(10, 20 * scaleUi);
avatar(sprite, x - imageSize / 2, y - imageSize - Math.max(1, 2 * scaleUi), imageSize);
y -= imageSize + Math.max(2, 4 * scaleUi);
}
if (info.distance) {
var textSize = Math.max(5, 9 * scaleUi);
label("[" + dist + " m]", x, y, info.color, textSize);
y -= textSize + Math.max(1, scaleUi);
}
if (info.name) label(info.title, x, y, info.color, Math.max(5, 9 * scaleUi));
if (info.kind === "boss" && Number.isFinite(obj.hp)) bar(left - 2, bottom + 4, width + 4, obj.hp, info.color);
if (info.kind === "others" && /^(Fireball|PlasmaBall|Shuriken|Snowball|Bomb)$/.test(info.subtype || "")) {
var ownerText = projectileOwner(obj);
if (ownerText) {
ctx.font = "600 9px Arial,sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "top";
var labelY = bottom + Math.max(3, hudPx(4, 2));
var tw = ctx.measureText(ownerText).width + 8;
roundRect(x - tw / 2, labelY, tw, 14, 3);
ctx.fillStyle = "rgba(14,15,18,.88)"; ctx.fill();
ctx.fillStyle = info.color;
ctx.fillText(ownerText, x, labelY + 2);
}
}
ctx.restore();
}
function shortClock(seconds) {
var value = Math.max(0, Math.ceil(Number(seconds) || 0));
if (value < 60) return value + "s";
return Math.floor(value / 60) + ":" + String(value % 60).padStart(2, "0");
}
function timerSprite(name) {
try {
var def = game.objectsDef && game.objectsDef[name];
if (!def || !def.animations) return null;
var anim = def.animations[def.animation] || def.animations.idle_right || def.animations.running_right || def.animations.flying_right || def.animations.none;
if (!anim || !anim.sprites || !anim.sprites.length) return null;
return game.sprites && game.sprites[anim.sprites[0]] || null;
} catch (e) { return null; }
}
function foodClock(seconds) {
var value = Math.max(0, Math.ceil(Number(seconds) || 0));
return String(Math.floor(value / 60)).padStart(2, "0") + ":" + String(value % 60).padStart(2, "0");
}
function opaqueBounds(sprite) {
if (!sprite || !sprite.width || !sprite.height) return null;
try { if (spriteBounds.has(sprite)) return spriteBounds.get(sprite); } catch (e) {}
var result = null;
try {
var sw = Number(sprite.naturalWidth) || Number(sprite.width) || 1;
var sh = Number(sprite.naturalHeight) || Number(sprite.height) || 1;
var scale = Math.min(1, 96 / Math.max(sw, sh));
var w = Math.max(1, Math.round(sw * scale));
var h = Math.max(1, Math.round(sh * scale));
var canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
var g = canvas.getContext("2d", { willReadFrequently: true });
g.drawImage(sprite, 0, 0, w, h);
var data = g.getImageData(0, 0, w, h).data;
var minX = w, minY = h, maxX = -1, maxY = -1;
for (var yy = 0; yy < h; yy++) {
for (var xx = 0; xx < w; xx++) {
if (data[(yy * w + xx) * 4 + 3] < 12) continue;
if (xx < minX) minX = xx;
if (yy < minY) minY = yy;
if (xx > maxX) maxX = xx;
if (yy > maxY) maxY = yy;
}
}
if (maxX >= minX && maxY >= minY) {
result = {
x: minX / scale, y: minY / scale,
width: Math.max(1, (maxX - minX + 1) / scale),
height: Math.max(1, (maxY - minY + 1) / scale)
};
}
} catch (e) {}
try { spriteBounds.set(sprite, result); } catch (e) {}
return result;
}
function drawFoodIcon(sprite, x, y, width, height) {
if (!sprite || !sprite.width || !sprite.height) return;
var bounds = opaqueBounds(sprite);
var sx = bounds ? bounds.x : 0;
var sy = bounds ? bounds.y : 0;
var sw = bounds ? bounds.width : (Number(sprite.naturalWidth) || Number(sprite.width));
var sh = bounds ? bounds.height : (Number(sprite.naturalHeight) || Number(sprite.height));
var scale = Math.min(width / sw, height / sh);
var dw = sw * scale;
var dh = sh * scale;
try { ctx.drawImage(sprite, sx, sy, sw, sh, x + (width - dw) / 2, y + (height - dh) / 2, dw, dh); } catch (e) {}
}
function drawSpawnClock(name, x, y, seconds, estimated, spriteOverride) {
var def = game.objectsDef && game.objectsDef[name];
if (!def) return;
var pos = game.getRenderPosition(x, y);
if (!pos || !Number.isFinite(pos.x) || !Number.isFinite(pos.y)) return;
var scale = hudScale();
var card = Math.max(15, Math.round(36 * scale));
var worldWidth = (Number(def.width) || card) * game.scaleX * game.zoom;
var worldHeight = (Number(def.height) || card) * game.scaleY * game.zoom;
var left = pos.x + (worldWidth - card) / 2;
var top = pos.y - worldHeight / 2 - card / 2;
if (left < -card || left > layer.width + card || top < -card || top > layer.height + card) return;
var sprite = spriteOverride || timerSprite(name);
ctx.save();
ctx.globalAlpha = .58;
drawFoodIcon(sprite, left, top, card, card);
ctx.globalAlpha = 1;
var fontSize = Math.max(5, 9 * scale);
var text = (estimated ? "~" : "") + foodClock(seconds);
ctx.font = "700 " + fontSize + "px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.lineJoin = "round";
ctx.lineWidth = Math.max(1.2, fontSize * .3);
ctx.strokeStyle = "rgba(0,0,0,.95)";
ctx.strokeText(text, left + card / 2, top + card / 2);
ctx.fillStyle = "#ffd166";
ctx.fillText(text, left + card / 2, top + card / 2);
ctx.restore();
}
function drawFoodTimers(now) {
if (!cfg.extra.foodTimer || !ownPlayer()) return;
var maxRange = 0;
var raw = null;
try { raw = typeof spawnTimerData !== "undefined" ? spawnTimerData : window.spawnTimerData; } catch (e) {}
var used = new Set();
if (Array.isArray(raw)) {
for (var i = 0; i < raw.length; i++) {
var row = raw[i];
if (!row) continue;
var x = Number(row.a), y = Number(row.b), name = row.c, left = Number(row.d);
var def = game.objectsDef && game.objectsDef[name];
if (!def || def.type !== objectType.FOOD || bossNames[name] || !staticFoodSpawn(name) || !canEatName(name) || !Number.isFinite(left) || left <= 0) continue;
var pseudo = { position: { x: x, y: y }, width: def.width || 0, height: def.height || 0 };
if (maxRange > 0 && meters(pseudo) > maxRange) continue;
used.add(name + ":" + Math.round(x * 10) + ":" + Math.round(y * 10));
drawSpawnClock(name, x, y, left, false);
}
}
foodGone.forEach(function (item, key) {
if (!item || !canEatName(item.name)) {
foodGone.delete(key);
return;
}
var seconds = (item.end - now) / 1000;
if (seconds <= 0) {
foodGone.delete(key);
return;
}
var posKey = item.name + ":" + Math.round(item.x * 10) + ":" + Math.round(item.y * 10);
if (used.has(posKey)) return;
var def = game.objectsDef && game.objectsDef[item.name];
var pseudo = { position: { x: item.x, y: item.y }, width: def && def.width || 0, height: def && def.height || 0 };
if (maxRange > 0 && meters(pseudo) > maxRange) return;
drawSpawnClock(item.name, item.x, item.y, seconds, false, item.sprite);
used.add(posKey);
});
for (var j = 0; j < foodSpawns.length; j++) {
var spawn = foodSpawns[j];
if (!spawn || !canEatName(spawn.name)) {
if (spawn) foodSpawnState.delete(spawn.key);
continue;
}
var state = foodSpawnState.get(spawn.key);
if (!state || !state.end) continue;
var seconds = (state.end - now) / 1000;
if (seconds <= 0) continue;
var posKey = spawn.name + ":" + Math.round(spawn.obj.position.x * 10) + ":" + Math.round(spawn.obj.position.y * 10);
if (used.has(posKey)) continue;
if (maxRange > 0 && meters(spawn.obj) > maxRange) continue;
drawSpawnClock(spawn.name, spawn.obj.position.x, spawn.obj.position.y, seconds, state.estimated);
}
}
function staticFoodSpawn(name) {
name = String(name || "");
if (!name || !window.game || !game.objectsDef || !window.objectType) return null;
if (staticFoodSpawnCache.has(name)) return staticFoodSpawnCache.get(name);
var found = null;
try {
Object.keys(game.objectsDef).some(function (key) {
var def = game.objectsDef[key];
if (!def || def.type !== objectType.SPAWN || def.spawning !== name || !(Number(def.delay) > 0)) return false;
found = { name: key, delay: Number(def.delay) };
return true;
});
} catch (e) {}
if (found) staticFoodSpawnCache.set(name, found);
return found;
}
function nearestFoodSpawn(item) {
if (!item || !staticFoodSpawn(item.name)) return null;
var best = null;
var bestDistance = Infinity;
for (var i = 0; i < foodSpawns.length; i++) {
var spawn = foodSpawns[i];
if (!spawn || spawn.name !== item.name || !spawn.obj || !spawn.obj.position) continue;
var dx = Number(spawn.obj.position.x) - Number(item.x);
var dy = Number(spawn.obj.position.y) - Number(item.y);
var distance = Math.sqrt(dx * dx + dy * dy);
var limit = Math.max(100, Math.min(320, Math.max(Number(spawn.obj.width) || 0, Number(spawn.obj.height) || 0, 80) * 1.6));
if (distance <= limit && distance < bestDistance) { bestDistance = distance; best = spawn; }
}
return best;
}
function foodDelay(name) {
var spawn = staticFoodSpawn(name);
return spawn ? spawn.delay : 0;
}
function rememberRemoval(data, object) {
if (!data) return;
try {
var id = data.a != null ? data.a : data.id;
var killerId = data.b != null ? data.b : data.killerId;
var obj = object || (window.game && game.gameObjects ? game.gameObjects[id] : null);
if (obj && window.objectType && obj.type === objectType.PLAYER && window.game && game.me && String(killerId) === String(game.me.id)) {
if (cfg.misc.hitSound && performance.now() - lastHitSound > 35) {
lastHitSound = performance.now();
playHit();
}
}
var item = obj && obj.position ? {
name: obj.name, x: Number(obj.position.x) || 0, y: Number(obj.position.y) || 0,
sprite: objectSprite(obj, performance.now())
} : foodSeen.get(String(id));
if (!item || !item.name || bossNames[item.name] || !canEatName(item.name)) return;
var def = game.objectsDef && game.objectsDef[item.name];
if (!def || !window.objectType || def.type !== objectType.FOOD) return;
var spawn = nearestFoodSpawn(item);
var delay = foodDelay(item.name);
if (!spawn || !(delay > 0)) return;
var sx = Number(spawn.obj.position.x) || 0;
var sy = Number(spawn.obj.position.y) || 0;
var key = "spawn:" + spawn.key;
foodGone.set(key, { id: id, name: item.name, x: sx, y: sy, end: performance.now() + delay, sprite: item.sprite || null });
} catch (e) {}
}
function noteFoodRemoval(response) {
try {
if (!response || !Array.isArray(response.c)) return;
for (var i = 0; i < response.c.length; i++) {
var message = response.c[i];
if (!message || message.a != socketMsgType.REMOVEOBJECT || !message.b) continue;
rememberRemoval(message.b, null);
}
} catch (e) {}
}
function attachRemoveHook() {
try {
if (typeof window.removeObject !== "function") return;
if (window.removeObject === removeObjectWrap) return;
if (removeObjectOriginal && window.removeObject !== removeObjectOriginal) removeObjectOriginal = null;
removeObjectOriginal = window.removeObject;
removeObjectWrap = function (data) {
var obj = null;
try {
var id = data && (data.a != null ? data.a : data.id);
obj = window.game && game.gameObjects ? game.gameObjects[id] : null;
rememberRemoval(data, obj);
} catch (e) {}
return removeObjectOriginal.apply(this, arguments);
};
window.removeObject = removeObjectWrap;
} catch (e) {}
}
function attachFoodSync() {
try {
if (typeof gameServer === "undefined" || !gameServer || foodSyncSocket === gameServer) return;
if (foodSyncSocket && foodSyncHandler) {
if (typeof foodSyncSocket.off === "function") foodSyncSocket.off(socketMsgType.SYNC, foodSyncHandler);
else if (typeof foodSyncSocket.removeListener === "function") foodSyncSocket.removeListener(socketMsgType.SYNC, foodSyncHandler);
}
foodSyncSocket = gameServer;
foodSyncHandler = noteFoodRemoval;
foodSyncSocket.on(socketMsgType.SYNC, foodSyncHandler);
} catch (e) {}
}
function ownedSkins() {
var map = Object.create(null);
try {
if (!window.user || typeof user.skins !== "string") return map;
user.skins.split(";").forEach(function (part) {
var bits = part.split(",");
var name = bits.shift();
if (!name) return;
map[name] = new Set(bits.map(function (id) { return Number(id) || 0; }).filter(Boolean));
});
} catch (e) {}
return map;
}
function cosmeticCatalog() {
var out = Object.create(null);
out.pets = new Set();
for (var petId = 1; petId <= 41; petId++) out.pets.add(petId);
var owned = ownedSkins();
Object.keys(owned).forEach(function (name) {
out[name] = out[name] || new Set();
owned[name].forEach(function (id) { out[name].add(id); });
});
try {
if (typeof skinsPreview !== "undefined" && skinsPreview) {
Object.keys(skinsPreview).forEach(function (name) {
var list = skinsPreview[name];
if (!list) return;
Object.keys(list).forEach(function (id) {
id = Number(id) || 0;
if (!id) return;
out[name] = out[name] || new Set();
out[name].add(id);
});
});
}
} catch (e) {}
try {
if (window.game && game.objectsDef && game.sprites) {
var baseToName = Object.create(null);
Object.keys(game.objectsDef).forEach(function (name) {
var def = game.objectsDef[name];
if (!def || !def.animations) return;
if (window.objectType && def.type !== objectType.PLAYER) return;
Object.keys(def.animations).forEach(function (anim) {
var row = def.animations[anim];
if (!row || !row.sprites) return;
var bases = Array.isArray(row.sprites) ? row.sprites : Object.keys(row.sprites).map(function (key) { return row.sprites[key]; });
bases.forEach(function (base) { if (base) baseToName[base] = name; });
});
});
function addCatalogKey(key) {
var pet = key.match(/^pet_(\d+)_/);
if (pet) {
out.pets = out.pets || new Set();
out.pets.add(Number(pet[1]));
}
var skin = key.match(/^(.*)_(\d+)$/);
if (!skin || !baseToName[skin[1]]) return;
var id = Number(skin[2]) || 0;
if (!id) return;
var name = baseToName[skin[1]];
out[name] = out[name] || new Set();
out[name].add(id);
}
[game.spritesToDownload, game.sprites].forEach(function (bank) {
if (!bank) return;
Object.keys(bank).forEach(addCatalogKey);
});
try {
if (typeof skinsPreview !== "undefined" && skinsPreview) {
Object.keys(skinsPreview).forEach(function (name) {
var list = skinsPreview[name];
var base = list && list[0];
if (!base || name === "pets") return;
[game.spritesToDownload, game.sprites].forEach(function (bank) {
if (!bank) return;
Object.keys(bank).forEach(function (key) {
if (key.indexOf(String(base) + "_") !== 0) return;
var id = Number(key.slice(String(base).length + 1));
if (!Number.isFinite(id) || id <= 0) return;
out[name] = out[name] || new Set();
out[name].add(id);
});
});
});
}
} catch (e) {}
Object.keys(game.objectsDef).forEach(function (name) {
var pet = name.match(/^pet_(\d+)$/);
if (!pet) return;
out.pets = out.pets || new Set();
out.pets.add(Number(pet[1]));
});
}
} catch (e) {}
try {
document.querySelectorAll('.offer.skin[data-skin][data-skin-id]').forEach(function (node) {
var name = node.getAttribute('data-skin');
var id = Number(node.getAttribute('data-skin-id')) || 0;
if (!name || !id) return;
out[name] = out[name] || new Set();
out[name].add(id);
});
} catch (e) {}
return out;
}
function prettyName(name) {
try {
var def = game.objectsDef && game.objectsDef[name];
if (def && def.fullName) return def.fullName;
} catch (e) {}
return String(name || "").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, function (c) { return c.toUpperCase(); });
}
function directImage(src) {
var value = String(src || "");
if (/^data:image\//i.test(value)) return value;
if (/^https?:\/\//i.test(value) && /\.(png|webp|gif|jpe?g|svg)(?:[?#]|$)/i.test(value)) return value;
return "";
}
function previewSprite(name, id) {
try {
var previews = typeof skinsPreview !== "undefined" ? skinsPreview : null;
var list = previews && previews[name];
if (!list) return "";
if (name === "pets") {
if (list[id]) return list[id];
if (list[0]) return String(list[0]).replace("(id)", id);
return "";
}
if (list[id]) return list[id];
if (list[0]) return String(list[0]) + "_" + id;
} catch (e) {}
return "";
}
function requestSpriteKey(key) {
if (!key || !window.game) return;
try {
if (game.sprites && game.sprites[key] && game.sprites[key].loaded) return;
var pending = game.spritesToDownload && game.spritesToDownload[key];
if (!pending || typeof game.downloadSprite !== "function") return;
var loadKey = "sprite:" + key;
if (skinLoads.has(loadKey)) return;
skinLoads.add(loadKey);
game.downloadSprite(key, pending.url, pending.params, function () {
skinLoads.delete(loadKey);
if (!dead) refreshCosmeticImages();
});
setTimeout(function () {
skinLoads.delete(loadKey);
if (!dead) refreshCosmeticImages();
}, 2500);
} catch (e) {}
}
function skinImage(name, id) {
try {
var node = document.querySelector('.offer.skin[data-skin="' + CSS.escape(name) + '"][data-skin-id="' + id + '"] .sprite-image');
if (node && node.naturalWidth > 0 && node.naturalHeight > 0) {
var fromNode = spriteUrl(node);
if (fromNode) return fromNode;
}
} catch (e) {}
try {
var previewName = previewSprite(name, id);
if (previewName) {
var preview = game.sprites && game.sprites[previewName];
var previewUrl = spriteUrl(preview);
if (previewUrl) return previewUrl;
requestSpriteKey(previewName);
}
var defName = name;
if (name === "pets" && id > 0) defName = "pet_" + id;
var def = game.objectsDef && game.objectsDef[defName];
if (!def || !def.animations) return "";
var anim = def.animations[def.animation] || def.animations.flying_right || def.animations.none || def.animations.flapping_right || def.animations.idle_right || def.animations.running_right;
if (!anim || !anim.sprites) return "";
var bases = Array.isArray(anim.sprites) ? anim.sprites : Object.keys(anim.sprites).map(function (key) { return anim.sprites[key]; });
if (!bases.length) return "";
var base = bases[0];
var spriteName = base + (name === "pets" ? "" : (id ? "_" + id : ""));
var sprite = game.sprites && game.sprites[spriteName];
var result = spriteUrl(sprite);
if (result) return result;
requestSpriteKey(spriteName);
if (id > 0 && name !== "pets" && game && typeof game.loadSkin === "function") {
var loadKey = name + ":" + id;
if (!skinLoads.has(loadKey)) {
skinLoads.add(loadKey);
try {
def.skinsLoaded = def.skinsLoaded || [];
game.loadSkin(name, id);
} catch (e) {}
setTimeout(function () {
skinLoads.delete(loadKey);
if (!dead) refreshCosmeticImages();
}, 1200);
}
}
} catch (e) {}
return "";
}
function freeSkin() {
if (!skinHold) return;
var row = skinHold;
skinHold = null;
try {
var desc = Object.getOwnPropertyDescriptor(row.obj, "skin");
if (desc && desc.get === row.get) {
delete row.obj.skin;
row.obj.skin = row.last;
}
} catch (e) {}
}
function holdSkin(obj, id) {
id = Number(id) || 0;
if (skinHold && skinHold.obj === obj) {
skinHold.pick = id;
return;
}
freeSkin();
var row = { obj: obj, pick: id, last: obj.skin, get: null, set: null };
row.get = function () { return row.pick; };
row.set = function (value) { row.last = value; };
try {
Object.defineProperty(obj, "skin", { configurable: true, enumerable: true, get: row.get, set: row.set });
skinHold = row;
} catch (e) {
try { obj.skin = id; } catch (x) {}
}
}
function hasPremium() {
try {
if (!window.user) return false;
return user.premium === true || Number(user.premium) > 0 || String(user.premium).toLowerCase() === "true";
} catch (e) { return false; }
}
function isOwned(name, id) {
if (!id) return true;
var map = ownedSkins();
return !!(map[name] && map[name].has(Number(id)));
}
function officialSkin(name, id) {
if (!isOwned(name, id)) return false;
if (name === "pets" && !hasPremium()) return false;
try {
if (typeof selectedSkins !== "undefined") {
if (id > 0) selectedSkins[name] = id;
else delete selectedSkins[name];
if (typeof selectedSkinsToString === "function" && window.user) {
user.selectedSkins = selectedSkinsToString(selectedSkins);
if (typeof setCookie === "function") setCookie("selectedSkins", user.selectedSkins, 365);
}
}
if (id > 0 && name !== "pets" && game && typeof game.loadSkin === "function") game.loadSkin(name, id);
if (typeof joinedGame !== "undefined" && joinedGame && typeof imDead !== "undefined" && !imDead && typeof gameServer !== "undefined" && typeof socketMsgType !== "undefined") {
gameServer.emit(socketMsgType.CHANGESKIN, { skinId: id, skinName: name });
}
return true;
} catch (e) { return false; }
}
function chooseSkin(name, id) {
cfg.misc.skinPick[name] = id;
if (isOwned(name, id)) officialSkin(name, id);
saveCfg();
applyCosmetics(performance.now(), true);
renderSkinPanel(true);
}
function choosePet(id) {
cfg.misc.petId = id;
if (isOwned("pets", id) && hasPremium()) officialSkin("pets", id);
saveCfg();
applyCosmetics(performance.now(), true);
renderPetMenu();
updatePetButton();
}
function cosmeticState(name, id) {
if (!id) return "Default";
if (name === "pets" && !hasPremium()) return "Locked";
return isOwned(name, id) ? "Owned" : "Not owned";
}
function cosmeticPreview(name, id) {
var image = id ? skinImage(name, id) : skinImage(name, 0);
var state = cosmeticState(name, id);
return '<span class="ez-skin-preview">' + (image ? '<img src="' + image + '">' : '<em>#' + (id || 0) + '</em>') + '<small class="' + (state === 'Owned' ? 'ez-owned' : '') + '">' + state + '</small></span>';
}
function refreshCosmeticImages() {
if (!menu) return;
menu.querySelectorAll("[data-skin-select]").forEach(function (select) {
var row = select.closest(".ez-skin-row");
var preview = row && row.querySelector(".ez-skin-preview");
if (!preview) return;
var name = select.getAttribute("data-skin-select");
var id = Number(select.value) || 0;
var image = id ? skinImage(name, id) : skinImage(name, 0);
var state = cosmeticState(name, id);
preview.innerHTML = (image ? '<img src="' + image + '">' : '<em>#' + (id || 0) + '</em>') + '<small class="' + (state === 'Owned' ? 'ez-owned' : '') + '">' + state + '</small>';
});
var petMenu = menu.querySelector("[data-pet-menu]");
if (!petMenu || !petMenu.classList.contains("ez-open")) renderPetMenu();
updatePetButton();
}
function renderSkinPanel(force) {
if (!menu) return;
var holder = menu.querySelector("[data-skin-list]");
if (!holder) return;
var focused = document.activeElement;
if (!force && focused && focused.matches && focused.matches("[data-skin-select]")) return;
var catalog = cosmeticCatalog();
var names = Object.keys(catalog).filter(function (name) {
if (name === "pets" || name.indexOf("_effects") !== -1 || name === "tombstones" || name === "profile_themes") return false;
try { return !!(game.objectsDef[name] && (!window.objectType || game.objectsDef[name].type === objectType.PLAYER)); } catch (e) { return false; }
}).sort(function (a, b) { return prettyName(a).localeCompare(prettyName(b)); });
var sign = names.map(function (name) { return name + ":" + Array.from(catalog[name]).sort(function (a,b) { return a-b; }).join("."); }).join("|");
if (!force && sign === catalogSign) return;
catalogSign = sign;
if (!names.length) {
holder.innerHTML = '<div class="ez-empty">Skin IDs are not ready yet</div>';
return;
}
holder.innerHTML = names.map(function (name) {
var ids = Array.from(catalog[name]).sort(function (a, b) { return a - b; });
var selected = Number(cfg.misc.skinPick[name]);
if (!Number.isFinite(selected)) {
try { selected = typeof selectedSkins !== "undefined" && selectedSkins[name] ? Number(selectedSkins[name]) : 0; } catch (e) { selected = 0; }
}
var opts = '<option value="0"' + (!selected ? ' selected' : '') + '>Default</option>' + ids.map(function (id) {
return '<option value="' + id + '"' + (selected === id ? ' selected' : '') + '>Skin ' + id + ' · ' + cosmeticState(name, id) + '</option>';
}).join('');
return '<label class="ez-skin-row"><span class="ez-skin-name">' + escapeHtml(prettyName(name)) + '</span>' + cosmeticPreview(name, selected) + '<select class="ez-select" data-skin-select="' + escapeHtml(name) + '">' + opts + '</select></label>';
}).join('');
renderPetMenu();
updatePetButton();
}
function renderPetMenu() {
if (!menu) return;
var holder = menu.querySelector("[data-pet-menu]");
if (!holder) return;
var catalog = cosmeticCatalog();
var ids = catalog.pets ? Array.from(catalog.pets) : [];
ids = Array.from(new Set(ids)).filter(function (id) { return id > 0; }).sort(function (a, b) { return a - b; });
var all = [0].concat(ids);
holder.innerHTML = all.map(function (id) {
var image = id ? skinImage("pet_" + id, 0) || skinImage("pets", id) : "";
var state = cosmeticState("pets", id);
var title = id ? "Pet " + id : "None";
return '<button class="ez-pet-choice' + (Number(cfg.misc.petId) === id ? ' ez-active' : '') + '" type="button" data-pet-id="' + id + '" title="' + title + ' · ' + state + '">' + (image ? '<img src="' + image + '">' : '<span>' + title + '</span>') + '<small class="' + (state === 'Owned' ? 'ez-owned' : '') + '">' + state + '</small></button>';
}).join('');
}
function updatePetButton() {
if (!menu) return;
var button = menu.querySelector("[data-pet-button]");
if (!button) return;
var id = Number(cfg.misc.petId) || 0;
var image = id ? skinImage("pet_" + id, 0) || skinImage("pets", id) : "";
var state = cosmeticState("pets", id);
button.innerHTML = image ? '<img src="' + image + '">' + state : (id ? 'Pet ' + id + ' · ' + state : 'None');
button.title = id ? 'Pet ' + id + ' · ' + state : 'No pet';
}
function removeLocalPet() {
if (localPet && !localPet.deleted && window.game) {
try { game.deleteObject(localPet); } catch (e) { localPet.deleted = true; }
}
localPet = null;
localPetOwner = null;
localPetMe = null;
if (hiddenPet && !hiddenPet.deleted) hiddenPet.opacity = hiddenPetOpacity;
hiddenPet = null;
}
function petAttack(data) {
try {
if (!data || !localPet || localPet.deleted || !localPetMe || localPetMe.deleted) return;
if (String(data.attacker) !== String(localPetMe.id)) return;
var victim = game.gameObjects && game.gameObjects[data.victim];
if (!victim || victim.deleted) return;
localPet.victim = victim;
localPet.attackTime = game.time;
} catch (e) {}
}
function attachPetAttack() {
try {
if (typeof gameServer === "undefined" || !gameServer || typeof socketMsgType === "undefined") return;
if (petAttackSocket === gameServer) return;
if (petAttackSocket && petAttackHandler) {
if (typeof petAttackSocket.off === "function") petAttackSocket.off(socketMsgType.DOPETATTACK, petAttackHandler);
else if (typeof petAttackSocket.removeListener === "function") petAttackSocket.removeListener(socketMsgType.DOPETATTACK, petAttackHandler);
}
petAttackSocket = gameServer;
petAttackHandler = petAttack;
petAttackSocket.on(socketMsgType.DOPETATTACK, petAttackHandler);
} catch (e) {}
}
function makeLocalPet(me, id) {
if (!window.game || !game.objectsDef || !game.objectsDef["pet_" + id] || !me || me.deleted) return;
if (localPet && !localPet.deleted && Number(localPet.petID) === id && localPetMe === me) return;
if (localPet && !localPet.deleted) {
try { game.deleteObject(localPet); } catch (e) { localPet.deleted = true; }
}
localPet = null;
localPetOwner = null;
var owner = { petID: id, pet: null };
["deleted", "killed", "inHide", "direction", "position"].forEach(function (name) {
Object.defineProperty(owner, name, { configurable: true, get: function () { return me[name]; } });
});
Object.defineProperty(owner, "petOffset", { configurable: true, get: function () {
return me.petOffset && Number.isFinite(me.petOffset.x) && Number.isFinite(me.petOffset.y) ? me.petOffset : { x: 55, y: 55 };
}});
Object.defineProperty(owner, "moveSpeed", { configurable: true, get: function () {
return me.moveSpeed || { x: 0, y: 0 };
}});
owner.getAllPositions = function () { return me.getAllPositions(); };
var pet = game.newObject(game.objectsDef["pet_" + id]);
var all = me.getAllPositions();
var center = all.center;
pet.id = 0;
pet.petID = id;
pet.position.x = center.x - pet.width / 2;
pet.position.y = center.y - pet.height / 2;
pet.owner = owner;
pet.moveSpeed.x = me.moveSpeed ? me.moveSpeed.x : 0;
pet.moveSpeed.y = me.moveSpeed ? me.moveSpeed.y : 0;
localPet = game.addObject(pet);
localPetOwner = owner;
localPetMe = me;
owner.pet = localPet;
}
function applyCosmetics(now, force) {
if (!force && now - cosmeticAt < 60) return;
cosmeticAt = now;
attachPetAttack();
var me = ownPlayer();
if (!me) {
freeSkin();
removeLocalPet();
cosmeticMe = null;
return;
}
var spawned = cosmeticMe !== me;
if (spawned) {
freeSkin();
cosmeticMe = me;
appliedSkinSpawn = "";
appliedPetSpawn = "";
if (localPet && localPetMe !== me) removeLocalPet();
}
var spawnKey = String(me.id != null ? me.id : "me") + ":" + String(me.name || "");
var canSend = false;
try {
canSend = typeof joinedGame !== "undefined" && joinedGame && typeof imDead !== "undefined" && !imDead && typeof gameServer !== "undefined" && typeof socketMsgType !== "undefined";
} catch (e) {}
var skin = cfg.misc.skinPick && Number(cfg.misc.skinPick[me.name]);
if (Number.isFinite(skin)) {
if (skin > 0 && game.loadSkin && !skinLoads.has(me.name + ":" + skin)) {
skinLoads.add(me.name + ":" + skin);
try { game.loadSkin(me.name, skin); } catch (e) {}
}
holdSkin(me, skin);
var skinSpawnKey = spawnKey + ":" + skin;
if (canSend && isOwned(me.name, skin) && appliedSkinSpawn !== skinSpawnKey) {
officialSkin(me.name, skin);
appliedSkinSpawn = skinSpawnKey;
}
} else if (skinHold && skinHold.obj === me) {
freeSkin();
}
var petId = Number(cfg.misc.petId) || 0;
var realPet = petId > 0 && isOwned("pets", petId) && hasPremium();
if (cfg.misc.petChanger && petId > 0) {
if (realPet) {
removeLocalPet();
if (Number(me.petID) !== petId) me.petID = petId;
var petSpawnKey = spawnKey + ":" + petId;
if (canSend && appliedPetSpawn !== petSpawnKey) {
officialSkin("pets", petId);
appliedPetSpawn = petSpawnKey;
}
} else {
appliedPetSpawn = "";
if (me.pet && me.pet !== localPet && !me.pet.deleted) {
if (hiddenPet !== me.pet) {
if (hiddenPet && !hiddenPet.deleted) hiddenPet.opacity = hiddenPetOpacity;
hiddenPet = me.pet;
hiddenPetOpacity = Number.isFinite(me.pet.opacity) ? me.pet.opacity : 1;
}
me.pet.opacity = 0;
}
makeLocalPet(me, petId);
if (localPet && hiddenPet && !hiddenPet.deleted && hiddenPet.victim && !hiddenPet.victim.deleted) {
localPet.victim = hiddenPet.victim;
localPet.attackTime = hiddenPet.attackTime || game.time;
}
}
} else {
appliedPetSpawn = "";
removeLocalPet();
}
if (now - catalogAt > 1800) {
catalogAt = now;
renderSkinPanel(false);
}
}
function reaperSkill(obj) {
try {
var def = game.objectsDef && game.objectsDef[obj.name];
var skill = obj.skill || (def && def.skill);
return skill && skill.fullName === "scythe attack" ? skill : null;
} catch (e) { return null; }
}
function frameName(obj) {
try {
var row = obj.animations && obj.animations[obj.animation];
if (!row || !row.sprites || !row.sprites.length) return "";
return row.sprites[obj.animationFrame % row.sprites.length];
} catch (e) { return ""; }
}
function sweepRegion(sprite, obj, right) {
var offsets = obj && obj.colliderRectangleOffset || {};
var leftOffset = clamp(Number(offsets.left) || 0, 0, .49);
var rightOffset = clamp(Number(offsets.right) || 0, 0, .49);
var topOffset = clamp(Number(offsets.top) || 0, 0, .49);
var bottomOffset = clamp(Number(offsets.bottom) || 0, 0, .49);
var key = (right ? "r:" : "l:") + [leftOffset, rightOffset, topOffset, bottomOffset].join(":");
if (!sprite || !sprite.width || !sprite.height) {
return right
? { x: 1 - rightOffset - .025, y: .03, width: rightOffset + .025, height: .94 }
: { x: 0, y: .03, width: leftOffset + .025, height: .94 };
}
var saved = bladeRegions.get(sprite);
if (!saved) {
saved = {};
bladeRegions.set(sprite, saved);
}
if (saved[key]) return saved[key];
try {
var sw = Number(sprite.naturalWidth) || Number(sprite.width) || 1;
var sh = Number(sprite.naturalHeight) || Number(sprite.height) || 1;
var scale = Math.min(1, 256 / Math.max(sw, sh));
var cw = Math.max(1, Math.round(sw * scale));
var ch = Math.max(1, Math.round(sh * scale));
var canvas = document.createElement("canvas");
canvas.width = cw;
canvas.height = ch;
var g = canvas.getContext("2d", { willReadFrequently: true });
g.drawImage(sprite, 0, 0, cw, ch);
var data = g.getImageData(0, 0, cw, ch).data;
var bodyEdge = (right ? 1 - rightOffset : leftOffset) * cw;
var overlap = Math.max(2, cw * .025);
var minX = cw, minY = ch, maxX = -1, maxY = -1;
var x, y, a;
for (y = 0; y < ch; y++) {
for (x = 0; x < cw; x++) {
a = data[(y * cw + x) * 4 + 3];
if (a < 30) continue;
if (right ? x < bodyEdge - overlap : x > bodyEdge + overlap) continue;
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}
if (maxX < minX || maxY < minY) throw new Error("no weapon reach");
var padX = Math.max(2, cw * .018);
var padY = Math.max(2, ch * .035);
var startX = right ? Math.min(bodyEdge - overlap, minX) : Math.max(0, minX - padX);
var endX = right ? Math.min(cw, maxX + padX) : Math.max(bodyEdge + overlap, maxX);
if (!right) {
startX = Math.max(0, minX - padX);
endX = Math.min(cw, Math.max(bodyEdge + overlap, maxX + padX));
}
var result = {
x: clamp(startX / cw, 0, 1),
y: clamp((minY - padY) / ch, 0, 1),
width: clamp((endX - startX) / cw, .08, .58),
height: clamp((maxY - minY + padY * 2) / ch, .18, 1)
};
if (result.x + result.width > 1) result.width = 1 - result.x;
if (result.y + result.height > 1) result.height = 1 - result.y;
saved[key] = result;
return result;
} catch (e) {
var fallback = right
? { x: Math.max(0, 1 - rightOffset - .03), y: .03, width: Math.min(.5, rightOffset + .03), height: .94 }
: { x: 0, y: .03, width: Math.min(.5, leftOffset + .03), height: .94 };
saved[key] = fallback;
return fallback;
}
}
function attackSweepRegion(obj, right) {
var def = null;
try { def = game.objectsDef && game.objectsDef[obj.name]; } catch (e) {}
var animations = obj.animations || (def && def.animations) || {};
var row = animations[right ? "attacking_right" : "attacking_left"];
var names = row && row.sprites ? row.sprites : [];
var key = String(obj.name || "reaper") + ":" + (right ? "r" : "l") + ":" + names.length;
var cached = attackRegions.get(key);
if (cached && cached.ready) return cached.region;
var union = null;
var loaded = 0;
for (var i = 0; i < names.length; i++) {
var sprite = null;
try { sprite = game.sprites && game.sprites[names[i]]; } catch (e) {}
if (!sprite || !(sprite.width > 0) || !(sprite.height > 0)) continue;
var region = sweepRegion(sprite, obj, right);
if (!region) continue;
loaded++;
if (!union) {
union = { x: region.x, y: region.y, width: region.width, height: region.height };
} else {
var x1 = Math.min(union.x, region.x);
var y1 = Math.min(union.y, region.y);
var x2 = Math.max(union.x + union.width, region.x + region.width);
var y2 = Math.max(union.y + union.height, region.y + region.height);
union.x = x1;
union.y = y1;
union.width = x2 - x1;
union.height = y2 - y1;
}
}
var offsets = obj.colliderRectangleOffset || (def && def.colliderRectangleOffset) || {};
var leftOffset = clamp(Number(offsets.left) || 0, 0, .49);
var rightOffset = clamp(Number(offsets.right) || 0, 0, .49);
if (!union) {
union = right
? { x: Math.max(0, 1 - rightOffset - .04), y: .04, width: rightOffset + .04, height: .92 }
: { x: 0, y: .04, width: leftOffset + .04, height: .92 };
}
union.y = clamp(union.y - .055, 0, 1);
union.height = clamp(union.height + .11, .45, 1 - union.y);
if (right) {
var bodyRight = 1 - rightOffset;
var outerRight = Math.max(1, union.x + union.width);
union.x = clamp(Math.min(union.x, bodyRight - .035), 0, 1);
union.width = Math.max(.20, outerRight - union.x);
} else {
var bodyLeft = leftOffset;
var outerLeft = Math.min(0, union.x);
var end = Math.max(union.x + union.width, bodyLeft + .035);
union.x = outerLeft;
union.width = Math.max(.20, end - outerLeft);
}
var result = { x: union.x, y: union.y, width: union.width, height: union.height };
attackRegions.set(key, { region: result, ready: names.length > 0 && loaded === names.length });
return result;
}
function weaponBox(obj) {
if (!obj || !obj.position) return null;
var skill = reaperSkill(obj);
if (!skill) return null;
var animName = String(obj.animation || "");
var right = animName.indexOf("_right") !== -1 ? true : (animName.indexOf("_left") !== -1 ? false : Number(obj.direction) === 1);
var attacking = animName.indexOf("attacking_") === 0;
var region = attackSweepRegion(obj, right);
if (!region) return null;
var pos = game.getRenderPosition(obj.position.x, obj.position.y);
var sx = (Number(game.scaleX) || 1) * (Number(game.zoom) || 1);
var sy = (Number(game.scaleY) || 1) * (Number(game.zoom) || 1);
var objectW = (Number(obj.width) || 50) * sx;
var objectH = (Number(obj.height) || 50) * sy;
if (!pos || !Number.isFinite(pos.x) || !Number.isFinite(pos.y) || !(objectW > 0) || !(objectH > 0)) return null;
var offsets = obj.colliderRectangleOffset || {};
var leftOffset = clamp(Number(offsets.left) || 0, 0, .49);
var rightOffset = clamp(Number(offsets.right) || 0, 0, .49);
var reachWorld = Number(skill.params && skill.params[0]);
if (!(reachWorld > 0)) reachWorld = 30;
var reachX = reachWorld * sx;
var y = pos.y - objectH + region.y * objectH;
var height = Math.max(18, region.height * objectH);
var x, width;
if (right) {
var bodyEdgeR = pos.x + (1 - rightOffset) * objectW;
var visualEndR = pos.x + Math.max(1, region.x + region.width) * objectW;
x = Math.min(bodyEdgeR - objectW * .025, pos.x + region.x * objectW);
width = Math.max(24, visualEndR + reachX - x);
} else {
var bodyEdgeL = pos.x + leftOffset * objectW;
var visualStartL = pos.x + Math.min(0, region.x) * objectW;
var endL = Math.max(bodyEdgeL + objectW * .025, pos.x + (region.x + region.width) * objectW);
x = visualStartL - reachX;
width = Math.max(24, endL - x);
}
return {
x: x,
y: y,
width: width,
height: height,
attacking: attacking
};
}
function drawHitboxes() {
if (!cfg.misc.hitbox || !window.game || !game.me) return;
var list = [game.me].concat(players);
for (var i = 0; i < list.length; i++) {
var obj = list[i];
if (!obj || obj.deleted || !obj.position) continue;
var box = weaponBox(obj);
if (!box) continue;
ctx.save();
ctx.fillStyle = rgba(cfg.misc.hitboxColor, box.attacking ? .12 : .055);
ctx.strokeStyle = rgba(cfg.misc.hitboxColor, box.attacking ? 1 : .82);
ctx.lineWidth = box.attacking ? 1.7 : 1.1;
ctx.fillRect(box.x, box.y, box.width, box.height);
ctx.strokeRect(box.x, box.y, box.width, box.height);
ctx.restore();
}
}
function openSoundDb() {
if (soundDbPromise) return soundDbPromise;
soundDbPromise = new Promise(function (resolve) {
try {
if (!window.indexedDB) return resolve(null);
var request = indexedDB.open("ezsquad.audio", 1);
request.onupgradeneeded = function () {
var db = request.result;
if (!db.objectStoreNames.contains("sounds")) db.createObjectStore("sounds", { keyPath: "id" });
};
request.onsuccess = function () { resolve(request.result); };
request.onerror = function () { resolve(null); };
} catch (e) { resolve(null); }
});
return soundDbPromise;
}
function soundTx(mode, callback) {
return openSoundDb().then(function (db) {
if (!db) return null;
return new Promise(function (resolve) {
try {
var tx = db.transaction("sounds", mode);
var store = tx.objectStore("sounds");
var result = callback(store);
tx.oncomplete = function () { resolve(result && result.result !== undefined ? result.result : result); };
tx.onerror = function () { resolve(null); };
tx.onabort = function () { resolve(null); };
} catch (e) { resolve(null); }
});
});
}
function getSoundRows() {
return openSoundDb().then(function (db) {
if (!db) return [];
return new Promise(function (resolve) {
try {
var request = db.transaction("sounds", "readonly").objectStore("sounds").getAll();
request.onsuccess = function () { resolve(Array.isArray(request.result) ? request.result : []); };
request.onerror = function () { resolve([]); };
} catch (e) { resolve([]); }
});
});
}
function renderSoundOptions() {
if (!menu) return;
[["hit", "hitSoundType", "Click"], ["kill", "killSoundType", "Bell"]].forEach(function (row) {
var kind = row[0], path = row[1], fallback = row[2];
var select = menu.querySelector(".ez-" + kind + "-select");
if (!select) return;
var current = String(cfg.misc[path] || fallback);
if (soundsReady && current.indexOf("custom:") === 0) {
var id = current.slice(7);
if (!customSounds.some(function (item) { return item.id === id; })) {
current = fallback;
cfg.misc[path] = current;
saveCfg();
}
}
select.innerHTML = soundOptionsHtml(kind);
select.value = current;
});
}
function refreshCustomSounds() {
var token = ++soundLoadToken;
return getSoundRows().then(function (rows) {
if (token !== soundLoadToken) return;
soundsReady = true;
customSounds = rows.map(function (row) {
return { id: String(row.id), name: cleanFileName(row.name), type: row.type || "audio/mpeg", blob: row.blob };
}).sort(function (a, b) { return a.name.localeCompare(b.name); });
renderSoundOptions();
});
}
function putSoundRow(row) {
return soundTx("readwrite", function (store) { return store.put(row); });
}
function removeSoundRow(id) {
return soundTx("readwrite", function (store) { return store.delete(id); });
}
function soundId() {
try { if (crypto && typeof crypto.randomUUID === "function") return crypto.randomUUID(); } catch (e) {}
return Date.now().toString(36) + Math.random().toString(36).slice(2);
}
function uploadSound(file, kind) {
if (!file) return;
kind = kind === "kill" ? "kill" : "hit";
var ext = String(file.name || "").split(".").pop().toLowerCase();
if (!(file.type && file.type.indexOf("audio/") === 0) && ["mp3", "wav", "ogg", "oga", "m4a", "aac", "flac", "webm"].indexOf(ext) < 0) return;
if (file.size > 12 * 1024 * 1024) return;
var row = { id: soundId(), name: cleanFileName(file.name), type: file.type || "audio/" + ext, blob: file, updated: Date.now() };
putSoundRow(row).then(function () {
customSounds.push({ id: row.id, name: row.name, type: row.type, blob: row.blob });
customSounds.sort(function (a, b) { return a.name.localeCompare(b.name); });
cfg.misc[kind === "kill" ? "killSoundType" : "hitSoundType"] = "custom:" + row.id;
saveCfg();
renderSoundOptions();
playSound(kind);
});
}
function selectedSound(kind) {
var path = kind === "kill" ? "killSoundType" : "hitSoundType";
var fallback = kind === "kill" ? "Bell" : "Click";
var value = String(cfg.misc[path] || fallback);
if (value.indexOf("custom:") !== 0) return null;
var id = value.slice(7);
for (var i = 0; i < customSounds.length; i++) if (customSounds[i].id === id) return customSounds[i];
return null;
}
function decodeSound(item) {
if (!item || !item.blob) return Promise.resolve(null);
if (soundBuffers.has(item.id)) return Promise.resolve(soundBuffers.get(item.id));
ensureAudio();
if (!audio) return Promise.resolve(null);
return item.blob.arrayBuffer().then(function (data) {
return audio.decodeAudioData(data.slice(0));
}).then(function (buffer) {
soundBuffers.set(item.id, buffer);
return buffer;
}).catch(function () { return null; });
}
function playCustom(item, volume) {
decodeSound(item).then(function (buffer) {
if (!buffer || !audio) return;
try {
var source = audio.createBufferSource();
var gain = audio.createGain();
gain.gain.value = clamp(volume, 0, 1);
source.buffer = buffer;
source.connect(gain).connect(audio.destination);
source.start();
} catch (e) {}
});
}
function bytesToBase64(buffer) {
var bytes = new Uint8Array(buffer);
var out = "";
var step = 32768;
for (var i = 0; i < bytes.length; i += step) out += String.fromCharCode.apply(null, bytes.subarray(i, Math.min(bytes.length, i + step)));
return btoa(out);
}
function base64ToBytes(value) {
var raw = atob(String(value || ""));
var out = new Uint8Array(raw.length);
for (var i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
return out;
}
function exportSounds() {
return Promise.all(customSounds.map(function (item) {
if (!item.blob) return null;
return item.blob.arrayBuffer().then(function (data) {
return { id: item.id, name: item.name, type: item.type, data: bytesToBase64(data) };
}).catch(function () { return null; });
})).then(function (rows) { return rows.filter(Boolean); });
}
function importSounds(rows) {
if (!Array.isArray(rows) || !rows.length) return Promise.resolve();
return Promise.all(rows.map(function (row) {
try {
if (!row || !row.id || !row.data) return Promise.resolve();
var blob = new Blob([base64ToBytes(row.data)], { type: row.type || "audio/mpeg" });
return putSoundRow({ id: String(row.id), name: cleanFileName(row.name), type: row.type || blob.type, blob: blob, updated: Date.now() });
} catch (e) { return Promise.resolve(); }
})).then(refreshCustomSounds);
}
function ensureAudio() {
try {
if (!audio) audio = new (window.AudioContext || window.webkitAudioContext)();
if (audio.state === "suspended") audio.resume();
} catch (e) {}
}
function tone(freq, end, duration, type, gain, delay) {
if (!audio) return;
var now = audio.currentTime + (delay || 0);
var osc = audio.createOscillator();
var vol = audio.createGain();
osc.type = type || "sine";
osc.frequency.setValueAtTime(freq, now);
osc.frequency.exponentialRampToValueAtTime(Math.max(30, end), now + duration);
vol.gain.setValueAtTime(gain, now);
vol.gain.exponentialRampToValueAtTime(.0001, now + duration);
osc.connect(vol).connect(audio.destination);
osc.start(now);
osc.stop(now + duration + .01);
}
function playSound(kind) {
kind = kind === "kill" ? "kill" : "hit";
ensureAudio();
if (!audio) return;
var volume = clamp(Number(cfg.misc[kind === "kill" ? "killVolume" : "hitVolume"]) || 0, 0, 100) / 100;
if (!volume) return;
var custom = selectedSound(kind);
if (custom) { playCustom(custom, volume); return; }
var gain = volume * .16;
var type = cfg.misc[kind === "kill" ? "killSoundType" : "hitSoundType"];
if (type === "Bubble") tone(360, 920, .085, "sine", gain);
else if (type === "Bell") { tone(1050, 760, .14, "sine", gain); tone(1580, 1120, .11, "sine", gain * .55, .012); }
else if (type === "Metal") { tone(1700, 260, .075, "triangle", gain); tone(920, 180, .09, "square", gain * .35); }
else tone(900, 420, .045, "square", gain);
}
function playHit() { playSound("hit"); }
function playKill() { playSound("kill"); }
function noteOwnAttack(data) {
try {
if (!window.game || !game.me || !data || String(data.objectId) !== String(game.me.id)) return;
ownAttackAt = performance.now();
ownAttackDirection = Number(data.direction) || Number(game.me.direction) || 1;
ownAttackTargets.clear();
var lag = Number.isFinite(Number(window.latency)) ? Math.max(0, Number(window.latency)) : 35;
fightNext = ownAttackAt + clamp(500 - lag * .5, 455, 500);
} catch (e) {}
}
function attachAttack() {
try {
if (typeof gameServer !== "undefined" && gameServer && attackSocket !== gameServer) {
if (attackSocket && attackHandler) {
if (typeof attackSocket.off === "function") attackSocket.off(socketMsgType.DOATTACK, attackHandler);
else if (typeof attackSocket.removeListener === "function") attackSocket.removeListener(socketMsgType.DOATTACK, attackHandler);
}
attackSocket = gameServer;
attackHandler = noteOwnAttack;
attackSocket.on(socketMsgType.DOATTACK, attackHandler);
}
if (typeof window.doAttack === "function" && window.doAttack !== attackDrawWrap) {
attackDrawOriginal = window.doAttack;
attackDrawWrap = function (object, direction) {
try {
if (window.game && game.me && object === game.me) noteOwnAttack({ objectId: game.me.id, direction: direction });
} catch (e) {}
return attackDrawOriginal.apply(this, arguments);
};
window.doAttack = attackDrawWrap;
}
} catch (e) {}
}
function targetInWeapon(obj) {
if (!window.game || !game.me || !obj || !obj.position) return false;
var box = weaponBox(game.me);
if (box) {
var pos = game.getRenderPosition(obj.position.x, obj.position.y);
var width = (Number(obj.width) || 50) * game.scaleX * game.zoom;
var height = (Number(obj.height) || 50) * game.scaleY * game.zoom;
var cx = pos.x + width / 2;
var cy = pos.y - height / 2;
var growX = Math.max(18, box.width * .35);
var growY = Math.max(18, box.height * .35);
if (cx >= box.x - growX && cx <= box.x + box.width + growX && cy >= box.y - growY && cy <= box.y + box.height + growY) return true;
}
var distance = worldDistance(game.me, obj);
var reach = Math.max(120, (Number(game.me.width) || 50) * .7 + (Number(obj.width) || 50) * .7);
return distance <= reach;
}
function ownContact(obj) {
if (!window.game || !game.me || !obj || typeof checkFoodChain !== "function") return false;
try {
var link = checkFoodChain(game.me, obj);
return !!link && link.check === 1 && targetInWeapon(obj);
} catch (e) { return false; }
}
function watchHits(now) {
if (!window.game || !window.objectType || !game.me) return;
var anim = String(game.me.animation || "");
if (anim.indexOf("attacking_") === 0 && anim !== ownAttackAnimation) {
ownAttackAt = now;
ownAttackDirection = anim.indexOf("_right") !== -1 ? 1 : -1;
ownAttackTargets.clear();
}
ownAttackAnimation = anim;
var seen = new Set();
for (var i = 0; i < players.length; i++) {
var obj = players[i];
if (!obj || obj.deleted || obj.type !== objectType.PLAYER || obj === game.me) continue;
var key = obj.id != null ? "o:" + obj.id : "u:" + obj.userId;
seen.add(key);
var hp = Number.isFinite(obj.hp) ? obj.hp : 100;
var armor = Number.isFinite(obj.armor) ? obj.armor : 0;
var old = hitBank.get(key);
var damaged = old && (hp < old.hp || armor < old.armor);
var manual = now - ownAttackAt < 1050 && targetInWeapon(obj);
var contact = ownContact(obj);
var blocked = manual && ownAttackTargets.has(key);
if (damaged && cfg.misc.hitSound && (manual || contact) && !blocked && now - lastHitSound > 35) {
if (manual) ownAttackTargets.add(key);
lastHitSound = now;
playHit();
}
hitBank.set(key, { hp: hp, armor: armor, at: now });
}
hitBank.forEach(function (value, key) { if (!seen.has(key) && now - value.at > 3000) hitBank.delete(key); });
}
function rawNick(obj) {
if (!obj) return "";
return privacyNames.get(obj) || obj.nick || "";
}
function displayNick(obj) {
var hidden = cfg.misc.privacy && ((window.game && obj === game.me && cfg.misc.privacyIdentity) || (obj !== (window.game && game.me) && cfg.misc.privacyPlayers));
if (hidden) return "Player";
return privacyNames.get(obj) || (obj && obj.nick) || "Player";
}
function protectedPlayers() {
return !!(cfg.misc.privacy && cfg.misc.privacyPlayers);
}
function restoreProtectedDom() {
protectedDom.forEach(function (value, node) {
if (!node || !node.isConnected) return;
if (value.kind === "text") node.nodeValue = value.value;
else node.textContent = value.value;
});
protectedDom.clear();
}
function maskTextNode(node, name) {
if (!node || !name || !node.nodeValue) return;
var source = String(node.nodeValue);
var escaped = String(name).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
var next = source.replace(new RegExp(escaped, "gi"), "Player");
if (next === source) return;
if (!protectedDom.has(node)) protectedDom.set(node, { kind: "text", value: source });
node.nodeValue = next;
}
function protectedNames() {
var names = new Set();
players.forEach(function (obj) { var name = rawNick(obj); if (name) names.add(String(name)); });
scans.forEach(function (item) { if (item && item.nick) names.add(String(item.nick)); });
recentPlayers.forEach(function (item, name) { if (name) names.add(String(name)); });
deathEntries.forEach(function (item) { if (item && item.name) names.add(String(item.name)); });
killEntries.forEach(function (item) { if (item && item.name) names.add(String(item.name)); });
if (lastKillerName) names.add(String(lastKillerName));
return Array.from(names).filter(function (name) { return name && name.toLowerCase() !== "player"; }).sort(function (a, b) { return b.length - a.length; });
}
function useProtectedDom(now) {
if (now - protectedAt < 160) return;
protectedAt = now;
if (!protectedPlayers()) {
restoreProtectedDom();
return;
}
document.querySelectorAll(".system-feed-high-exp-nick,.system-feed-high-exp-killer,.system-feed-player-name,.profile-theme-killer-name").forEach(function (node) {
if (!protectedDom.has(node)) protectedDom.set(node, { kind: "element", value: node.textContent });
node.textContent = "Player";
});
var names = protectedNames();
document.querySelectorAll("#systemFeed,.endGameText,#chat-box").forEach(function (root) {
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
var node;
while ((node = walker.nextNode())) for (var i = 0; i < names.length; i++) maskTextNode(node, names[i]);
});
protectedDom.forEach(function (value, node) { if (!node || !node.isConnected) protectedDom.delete(node); });
}
function restorePrivacyNames() {
privacyNames.forEach(function (name, obj) {
if (obj && !obj.deleted) obj.nick = name;
});
privacyNames.clear();
}
function usePrivacy() {
var enabled = !!cfg.misc.privacy;
var dom = [
["#gameContainer > div.debugInfo > div.server", enabled && cfg.misc.privacyServer, "visibility"],
["#gameContainer > .leaderboard", enabled && cfg.misc.privacyLeaderboard, "display"]
];
dom.forEach(function (item) {
var node = document.querySelector(item[0]);
if (!node) return;
if (item[1]) {
if (!privacyDom.has(node)) privacyDom.set(node, { display: node.style.display, visibility: node.style.visibility });
if (item[2] === "display") node.style.display = "none";
else node.style.visibility = "hidden";
} else if (privacyDom.has(node)) {
var old = privacyDom.get(node);
node.style.display = old.display;
node.style.visibility = old.visibility;
privacyDom.delete(node);
}
});
if (!enabled || (!cfg.misc.privacyIdentity && !cfg.misc.privacyPlayers)) {
restorePrivacyNames();
return;
}
var list = [];
if (cfg.misc.privacyIdentity && window.game && game.me) list.push(game.me);
if (cfg.misc.privacyPlayers) list = list.concat(players);
var active = new Set(list);
privacyNames.forEach(function (name, obj) {
if (!active.has(obj) && obj && !obj.deleted) {
obj.nick = name;
privacyNames.delete(obj);
}
});
list.forEach(function (obj) {
if (!obj || obj.deleted) return;
if (!privacyNames.has(obj) && obj.nick) privacyNames.set(obj, obj.nick);
obj.nick = "";
});
}
function spriteUrl(sprite) {
if (!sprite || !sprite.width || !sprite.height) return "";
var cacheable = typeof sprite === "object" || typeof sprite === "function";
try {
if (cacheable && spriteImages.has(sprite)) return spriteImages.get(sprite);
var fallback = directImage(sprite.currentSrc || sprite.src);
var canvas = document.createElement("canvas");
canvas.width = Math.max(1, Math.min(128, Number(sprite.width) || Number(sprite.naturalWidth) || 1));
canvas.height = Math.max(1, Math.min(128, Number(sprite.height) || Number(sprite.naturalHeight) || 1));
var g = canvas.getContext("2d");
var sw = Number(sprite.naturalWidth) || Number(sprite.width) || 1;
var sh = Number(sprite.naturalHeight) || Number(sprite.height) || 1;
var scale = Math.min(canvas.width / sw, canvas.height / sh);
g.drawImage(sprite, (canvas.width - sw * scale) / 2, (canvas.height - sh * scale) / 2, sw * scale, sh * scale);
var data = canvas.toDataURL("image/png");
if (cacheable) spriteImages.set(sprite, data);
return data;
} catch (e) {
var safe = directImage(sprite.currentSrc || sprite.src);
if (cacheable && safe) spriteImages.set(sprite, safe);
return safe;
}
}
function onDeath(data) {
var killer = data && data.killer != null ? String(data.killer) : "";
lastKillerName = killer;
if (!cfg.misc.deathLog || !killer) return;
var lower = killer.toLowerCase();
var match = null;
var savedSprite = null;
var scanMatch = null;
for (var i = 0; i < players.length; i++) {
if (String(rawNick(players[i])).toLowerCase() === lower) { match = players[i]; break; }
}
if (!match) {
var recent = recentPlayers.get(lower);
if (recent) { match = recent.obj; savedSprite = recent.sprite || null; }
}
if (!match) {
for (var s = 0; s < scans.length; s++) {
if (String(scans[s] && scans[s].nick || "").toLowerCase() === lower) {
scanMatch = scans[s];
break;
}
}
}
var isPlayer = false;
if (match) {
try { isPlayer = match.type === objectType.PLAYER || game.objectsDef[match.name].type === objectType.PLAYER; } catch (e) {}
} else if (scanMatch) {
try { isPlayer = !!(game.objectsDef[scanMatch.evolution] && game.objectsDef[scanMatch.evolution].type === objectType.PLAYER); } catch (e) {}
}
if (!isPlayer) return;
var avatarSprite = match ? objectSprite(match, performance.now()) : (scanMatch ? scanSprite(scanMatch) : savedSprite);
if (!avatarSprite && savedSprite) avatarSprite = savedSprite;
if (!avatarSprite && match && window.game && game.objectsDef && game.objectsDef[match.name]) {
try {
var def = game.objectsDef[match.name];
var anim = def.animations[def.animation] || def.animations.idle_right || def.animations.flying_right || def.animations.none;
avatarSprite = anim && anim.sprites && game.sprites[anim.sprites[0]];
} catch (e) {}
}
var created = Date.now();
deathEntries.push({
name: killer,
image: avatarSprite ? spriteUrl(avatarSprite) : "",
at: created,
until: created + clamp(Number(cfg.misc.deathTime) || 60, 10, 180) * 1000
});
deathEntries = deathEntries.slice(-6);
updateDeathPanel();
}
function attachDeath() {
try {
if (typeof gameServer === "undefined" || !gameServer || deathSocket === gameServer) return;
if (deathSocket && deathHandler) {
if (typeof deathSocket.off === "function") deathSocket.off("game:dead", deathHandler);
else if (typeof deathSocket.removeListener === "function") deathSocket.removeListener("game:dead", deathHandler);
}
deathSocket = gameServer;
deathHandler = onDeath;
deathSocket.on("game:dead", deathHandler);
} catch (e) {}
}
function envKind(obj) {
var name = String(obj && obj.name || "").toLowerCase();
if (!name) return "";
if (/^cloud\d/.test(name)) return "cloudAlpha";
if (name.indexOf("bush") !== -1) return "bushAlpha";
if (name === "water" || name === "water2" || name === "water2_fill" || name === "toxic_water") return "waterAlpha";
if (name === "swamp") return "swampAlpha";
if (name === "lava" || name === "lava_rising") return "lavaAlpha";
return "";
}
function clearScene() {
try {
if (!window.game) return;
if (game.staticContext && game.staticCanvas) game.staticContext.clearRect(0, 0, game.staticCanvas.width, game.staticCanvas.height);
if (typeof game.clearStaticObjects === "function") game.clearStaticObjects();
if (game.staticCanvasRenderOffset) {
game.staticCanvasRenderOffset.x = 0;
game.staticCanvasRenderOffset.y = 0;
game.staticCanvasRenderOffset.restX = 0;
game.staticCanvasRenderOffset.restY = 0;
}
} catch (e) {}
}
function syncScanIcon() {
var icon = document.getElementById("scan-players-icon");
if (!icon) return;
if (!scanIconBase || scanIconBase.node !== icon) {
scanIconBase = { node: icon, display: icon.style.display, visibility: icon.style.visibility };
}
var closed = !!(menu && menu.classList.contains("ez-hide"));
if (closed) {
icon.style.setProperty("display", "none", "important");
icon.style.setProperty("visibility", "hidden", "important");
} else {
icon.style.removeProperty("display");
icon.style.removeProperty("visibility");
if (scanIconBase.display) icon.style.display = scanIconBase.display;
if (scanIconBase.visibility) icon.style.visibility = scanIconBase.visibility;
}
}
function attachBossSync() {
if (bossSyncSocket || typeof gameServer === "undefined" || typeof socketMsgType === "undefined") return;
bossSyncSocket = gameServer;
bossSyncHandler = function (packet) {
try {
if (packet && packet.b) {
if (packet.b.g != null) bossSync = packet.b.g;
if (packet.b.d != null && Number.isFinite(Number(packet.b.d))) {
skillSync = Number(packet.b.d);
skillSyncAt = performance.now();
skillLocalEnd = skillSyncAt + Math.max(0, skillSync);
skillLocalMe = window.game && game.me ? game.me.id : 0;
} else {
skillSync = null;
skillSyncAt = performance.now();
skillLocalEnd = 0;
}
}
} catch (e) {}
};
try { bossSyncSocket.on(socketMsgType.SYNC, bossSyncHandler); } catch (e) { bossSyncSocket = null; bossSyncHandler = null; }
}
function drawRemoteBosses() {
if (!cfg.extra.boss || !window.game || !game.me) return;
var raw = null;
try { raw = typeof spawnTimerData !== "undefined" ? spawnTimerData : window.spawnTimerData; } catch (e) {}
if (!Array.isArray(raw)) return;
var range = Number(cfg.extra.bossRange) || 0;
for (var i = 0; i < raw.length; i++) {
var row = raw[i];
if (!row || !bossNames[row.c]) continue;
var def = game.objectsDef && game.objectsDef[row.c];
if (!def) continue;
var pseudo = { name: row.c, position: { x: Number(row.a), y: Number(row.b) }, width: def.width || 50, height: def.height || 50, hp: 100 };
if (range > 0 && meters(pseudo) > range) continue;
var loaded = false;
for (var j = 0; j < extras.length; j++) {
if (extras[j].kind === "boss" && extras[j].obj && extras[j].obj.name === row.c && worldDistance(extras[j].obj, pseudo) < 500) { loaded = true; break; }
}
if (loaded) continue;
var left = Number(row.d);
var info = extraStyle("boss", pseudo, "");
if (!info) continue;
info.title = objectTitle(def, "Boss") + (Number.isFinite(left) && left > 0 ? " · " + shortClock(left) : " · Active");
drawExtra(pseudo, info, performance.now());
}
}
function useBright() {
try {
if (!darknessDraw && typeof window.drawDarkness === "function") {
darknessDraw = window.drawDarkness;
darknessWrap = function () {
if (cfg.other.fullBright) return;
return darknessDraw.apply(this, arguments);
};
}
if (darknessWrap && window.drawDarkness !== darknessWrap) window.drawDarkness = darknessWrap;
} catch (e) {}
if (!window.game || !game.context) return;
if (brightContext === game.context && brightWrap) return;
if (brightContext && brightDraw && brightContext.drawImage === brightWrap) {
try { brightContext.drawImage = brightDraw; } catch (e) {}
}
brightContext = game.context;
brightDraw = brightContext.drawImage;
brightWrap = function () {
try {
if (cfg.other.fullBright && typeof darknessCanvas !== "undefined" && arguments[0] === darknessCanvas) return;
} catch (e) {}
return brightDraw.apply(this, arguments);
};
try { brightContext.drawImage = brightWrap; } catch (e) { brightWrap = null; }
}
function useZoom() {
if (!window.game || !game.canvas || typeof game.setCanvasSize !== "function") return;
if (!cfg.other.zoom) {
if (!viewBase) return;
game.originalWidth = viewBase.width;
game.originalHeight = viewBase.height;
game.zoom = viewBase.zoom;
try { if (typeof gameZoom !== "undefined") gameZoom = viewBase.gameZoom; } catch (e) {}
game.setCanvasSize(game.canvas.width, game.canvas.height);
viewBase = null;
return;
}
if (!viewBase) {
viewBase = {
width: Number(game.originalWidth) || 1920,
height: Number(game.originalHeight) || 1080,
zoom: Number(game.zoom) || 1,
gameZoom: typeof gameZoom !== "undefined" ? Number(gameZoom) || 1 : 1
};
}
var value = clamp(Number(cfg.other.zoomValue) || 1, .15, 1);
var width = viewBase.width / value;
var height = viewBase.height / value;
var changed = Math.abs(game.originalWidth - width) > .1 || Math.abs(game.originalHeight - height) > .1 || Math.abs(game.zoom - 1) > .001;
game.originalWidth = width;
game.originalHeight = height;
game.zoom = 1;
try { if (typeof gameZoom !== "undefined") gameZoom = 1; } catch (e) {}
if (changed) game.setCanvasSize(game.canvas.width, game.canvas.height);
}
function useWorld(now, force) {
useBright();
useZoom();
if (window.game && game.canvas && canvasFilter == null) canvasFilter = game.canvas.style.filter || "";
var changedEnv = cfg.other.cloudAlpha !== 100 || cfg.other.bushAlpha !== 100 || cfg.other.waterAlpha !== 100 || cfg.other.swampAlpha !== 100 || cfg.other.lavaAlpha !== 100;
if (!force && !worldDirty && now - worldAt < (changedEnv ? 350 : 1000)) return;
worldAt = now;
worldDirty = false;
var changed = false;
if (!changedEnv) {
envBase.forEach(function (value, obj) {
if (obj && !obj.deleted && obj.opacity !== value) {
obj.opacity = value;
changed = true;
}
});
envBase.clear();
} else if (window.game && game.gameObjects) {
var list = game.gameObjects;
for (var id in list) {
if (!Object.prototype.hasOwnProperty.call(list, id)) continue;
var obj = list[id];
if (!obj || obj.deleted) continue;
var key = envKind(obj);
if (!key) continue;
if (!envBase.has(obj)) envBase.set(obj, Number.isFinite(obj.opacity) ? obj.opacity : 1);
var value = envBase.get(obj) * clamp(Number(cfg.other[key]) || 0, 0, 100) / 100;
if (Math.abs((Number(obj.opacity) || 0) - value) > .001) {
obj.opacity = value;
changed = true;
}
}
envBase.forEach(function (value, obj) {
if (!obj || obj.deleted) envBase.delete(obj);
});
}
if (changed) clearScene();
}
function noteSkillUse(now) {
try {
if (!window.game || !game.me || game.me.deleted || !game.me.skill || !game.me.skill.active || !(Number(game.me.skill.cooldown) > 0)) return;
var cooldown = Number(game.me.skill.cooldown) || 0;
if (!(cooldown > 0)) return;
skillLocalEnd = now + cooldown;
skillLocalMe = game.me.id;
} catch (e) {}
}
function onSkillPointer(event) {
try {
if (event.button === 2 && window.game && event.target === game.canvas) noteSkillUse(performance.now());
} catch (e) {}
}
function attachSkillUse() {
try {
if (typeof window.skillUse !== "function" || window.skillUse === skillUseWrap) return;
skillUseOriginal = window.skillUse;
skillUseWrap = function () {
noteSkillUse(performance.now());
return skillUseOriginal.apply(this, arguments);
};
window.skillUse = skillUseWrap;
} catch (e) {
skillUseOriginal = null;
skillUseWrap = null;
}
}
function updateSkillClock(now) {
var host = document.querySelector("#gameContainer > div.status div.skillRight") || document.getElementById("skillButton");
if (host !== skillClockHost) {
if (skillClock && skillClock.parentNode) skillClock.remove();
if (skillClockHost && skillClockHostPosition !== "") skillClockHost.style.position = skillClockHostPosition;
skillClock = null;
skillClockHost = host || null;
skillClockHostPosition = host ? host.style.position : "";
if (host) {
try { if (getComputedStyle(host).position === "static") host.style.position = "relative"; } catch (e) {}
skillClock = document.createElement("span");
skillClock.id = "ez-skill-clock";
host.appendChild(skillClock);
}
}
if (!skillClock || !window.game || !game.me || !game.me.skill || !(Number(game.me.skill.cooldown) > 0)) {
if (skillClock) skillClock.style.display = "none";
return;
}
var left = skillSync == null ? 0 : Math.max(0, skillSync - Math.max(0, now - skillSyncAt));
if (skillLocalMe === game.me.id && skillLocalEnd > now) left = Math.max(left, skillLocalEnd - now);
if (!(left > 20)) {
try {
var barNode = host.querySelector("div.bar") || document.querySelector("#gameContainer > div.status div.skillRight > div.bar");
var total = Number(game.me.skill.cooldown) || 0;
if (barNode && barNode.parentElement && total > 0) {
var barHeight = parseFloat(getComputedStyle(barNode).height) || 0;
var parentHeight = parseFloat(getComputedStyle(barNode.parentElement).height) || 0;
if (parentHeight > 0 && barHeight > 0) left = total * clamp(barHeight / parentHeight, 0, 1);
}
} catch (e) {}
}
if (!(left > 20)) {
skillClock.style.display = "none";
return;
}
var seconds = left / 1000;
skillClock.textContent = seconds < 10 ? seconds.toFixed(1) : String(Math.ceil(seconds));
skillClock.style.display = "flex";
}
var fightReapers = new Set(["grimReaper", "pumpkinGhost", "ghostlyReaper"]);
var fightPass = new Set(["fly", "butterfly", "wasp", "mosquito", "dragonfly"]);
var fightReach = {
grimReaper: { x: 168, y: 88 },
pumpkinGhost: { x: 178, y: 94 },
ghostlyReaper: { x: 158, y: 90 }
};
function fightReady() {
if (!window.game || !game.me || !game.gameObjects || game.me.deleted) return false;
if (typeof gameServer === "undefined" || !gameServer || typeof socketMsgType === "undefined" || typeof objectType === "undefined") return false;
try { if (typeof imDead !== "undefined" && imDead) return false; } catch (e) {}
try { if (typeof joinedGame !== "undefined" && !joinedGame) return false; } catch (e) {}
return true;
}
function fightSelf() {
return fightReady() && fightReapers.has(game.me.name) && game.me.eatsSouls === true;
}
function fightCenter(obj) {
try {
var all = obj && typeof obj.getAllPositions === "function" ? obj.getAllPositions() : null;
if (all && all.center && Number.isFinite(all.center.x) && Number.isFinite(all.center.y)) return all.center;
} catch (e) {}
return {
x: Number(obj && obj.position && obj.position.x || 0) + Number(obj && obj.width || 0) / 2,
y: Number(obj && obj.position && obj.position.y || 0) + Number(obj && obj.height || 0) / 2
};
}
function fightSpeed(obj) {
return {
x: Number.isFinite(obj && obj.moveSpeed && obj.moveSpeed.x) ? obj.moveSpeed.x : 0,
y: Number.isFinite(obj && obj.moveSpeed && obj.moveSpeed.y) ? obj.moveSpeed.y : 0
};
}
function fightParty(obj) {
try { return typeof isPartyMemberObject === "function" && isPartyMemberObject(obj); } catch (e) { return false; }
}
function fightCan(obj) {
if (!obj || obj.type !== objectType.PLAYER || obj.immortal === true) return false;
if (game.me && game.me.eatsSouls === true) return true;
try { return typeof checkFoodChain !== "function" || checkFoodChain(game.me, obj).check === 1; } catch (e) { return true; }
}
function fightValid(obj) {
if (!obj || obj === game.me || obj.deleted || obj.type !== objectType.PLAYER) return false;
if (obj.inHide || obj.inSafeZone || obj.immortal || obj.isProtected || fightParty(obj)) return false;
if (cfg.misc.reaperTargets) {
if (!fightReapers.has(obj.name)) return false;
} else if (fightPass.has(obj.name) || obj.reapersIgnoreCollision === true) return false;
return fightCan(obj);
}
function fightPoint(obj) {
var me = fightCenter(game.me);
var at = fightCenter(obj);
var mv = fightSpeed(game.me);
var tv = fightSpeed(obj);
var dx = at.x - me.x;
var dy = at.y - me.y;
var vx = tv.x - mv.x;
var vy = tv.y - mv.y;
var dist = Math.max(1, Math.hypot(dx, dy));
var radial = (dx * vx + dy * vy) / dist;
var lag = Number.isFinite(Number(window.latency)) ? Math.max(0, Number(window.latency)) : 35;
var span = clamp(42 + lag * .45, 42, 82);
return {
obj: obj,
dx: dx,
dy: dy,
d2: dx * dx + dy * dy,
radial: radial,
px: dx + vx * span / 1000,
py: dy + vy * span / 1000
};
}
function fightNear(item) {
return Math.abs(item.dx) <= 305 && Math.abs(item.dy) <= 205;
}
function fightMetric(item) {
var reach = fightReach[game.me.name] || { x: 168, y: 90 };
var rx = reach.x + Math.min(20, Number(item.obj.width || 0) * .09);
var ry = reach.y + Math.min(14, Number(item.obj.height || 0) * .07);
return Math.max(Math.abs(item.px) / rx, Math.abs(item.py) / ry);
}
function pickFight(now) {
if (fightLock != null) {
var locked = game.gameObjects[fightLock];
if (fightValid(locked)) {
var keep = fightPoint(locked);
if (fightNear(keep) && now <= fightLockUntil) return keep;
}
fightLock = null;
}
var best = null;
var bestScore = Infinity;
var list = game.gameObjects;
for (var id in list) {
if (!Object.prototype.hasOwnProperty.call(list, id)) continue;
var obj = list[id];
if (!fightValid(obj)) continue;
var item = fightPoint(obj);
if (!fightNear(item)) continue;
var metric = fightMetric(item);
var score = metric * 1000 + Math.sqrt(item.d2) - Math.max(0, item.radial) * .18;
if (score < bestScore) {
best = item;
bestScore = score;
}
}
if (best) {
fightLock = best.obj.id;
fightLockUntil = now + (best.radial > 25 ? 760 : 560);
}
return best;
}
function inFightRange(item) {
var reach = fightReach[game.me.name] || { x: 168, y: 90 };
var rx = reach.x + Math.min(20, Number(item.obj.width || 0) * .09);
var ry = reach.y + Math.min(14, Number(item.obj.height || 0) * .07);
var here = Math.max(Math.abs(item.dx) / rx, Math.abs(item.dy) / ry);
var next = Math.max(Math.abs(item.px) / rx, Math.abs(item.py) / ry);
if (item.radial > 28) return here <= .94 && next <= 1.01;
if (item.radial < -28) return next <= .84;
return here <= .88 && next <= .93;
}
function swingFight() {
if (!fightSelf()) return;
try { gameServer.emit(socketMsgType.SKILL, true); } catch (e) {}
clearTimeout(fightRelease);
fightRelease = setTimeout(function () {
try { gameServer.emit(socketMsgType.SKILL, false); } catch (e) {}
}, 38);
}
function fightBusy() {
var anim = String(game.me && game.me.animation || "");
return anim === "attacking_left" || anim === "attacking_right";
}
function runFight(now) {
if (!fightSelf() || !cfg.misc.autoHit) return;
var target = pickFight(now);
if (!target || !inFightRange(target) || fightBusy()) return;
var side = target.dx >= 0 ? 1 : -1;
if (fightSide() !== side) return;
if (now < fightNext || now - fightTry < 34) return;
fightTry = now;
clearTimeout(fightAttack);
fightAttack = setTimeout(swingFight, 0);
}
function paint(now) {
raf = requestAnimationFrame(paint);
if (dead || document.hidden) return;
if (!fit(now)) return;
updateList(now);
updateScan(now);
doScan(now, false);
syncScanIcon();
attachBossSync();
attachSkillUse();
updateSkillClock(now);
attachDeath();
attachKillFeed();
attachSpectators();
requestSpectators(now);
attachAttack();
attachPetAttack();
attachFoodSync();
attachRemoveHook();
applyCosmetics(now, false);
runFight(now);
watchHits(now);
usePrivacy();
useProtectedDom(now);
useWorld(now, false);
ctx.clearRect(0, 0, layer.width, layer.height);
if (!window.game || !game.me || game.me.deleted || !window.objectType) return;
drawHitboxes();
if (cfg.esp.enabled) {
for (var i = 0; i < players.length; i++) {
var obj = players[i];
if (!obj || obj.deleted || obj === game.me || obj.type !== objectType.PLAYER) continue;
var style = styleFor(relation(obj, now));
if (style) drawPlayer(obj, style, now);
}
}
drawCached(now);
for (var j = 0; j < extras.length; j++) {
var item = extras[j];
var info = extraStyle(item.kind, item.obj, item.subtype);
if (info) drawExtra(item.obj, info, now);
}
drawFoodTimers(now);
drawRemoteBosses();
if (now - deathUiAt > 250) {
deathUiAt = now;
updateDeathPanel(Date.now());
}
if (now - killUiAt > 250) {
killUiAt = now;
updateKillPanel(Date.now());
}
if (now - spectatorUiAt > 250) {
spectatorUiAt = now;
updateSpectatorPanel(Date.now());
}
}
function start() {
if (dead) return;
makeLayer();
menu = makeMenu();
makeKeys();
makeSpectatorPanel();
refreshCustomSounds();
window.addEventListener("keydown", onKey, true);
window.addEventListener("keyup", onKeyUp, true);
document.addEventListener("pointerdown", onSkillPointer, true);
window.addEventListener("resize", markFit, true);
window.addEventListener("scroll", markFit, true);
doScan(performance.now(), true);
renderSkinPanel(true);
attachDeath();
attachKillFeed();
attachSpectators();
requestSpectators(performance.now());
attachAttack();
attachFoodSync();
attachRemoveHook();
paint(0);
}
function stop() {
if (dead) return;
dead = true;
clearTimeout(bootTimer);
cancelAnimationFrame(raf);
clearTimeout(fightRelease);
clearTimeout(fightAttack);
try {
if (typeof gameServer !== "undefined" && gameServer && typeof socketMsgType !== "undefined") {
gameServer.emit(socketMsgType.SKILL, false);
}
} catch (e) {}
window.removeEventListener("keydown", onKey, true);
window.removeEventListener("keyup", onKeyUp, true);
document.removeEventListener("pointerdown", onSkillPointer, true);
document.removeEventListener("pointerdown", closeBindPop, true);
window.removeEventListener("resize", markFit, true);
window.removeEventListener("scroll", markFit, true);
if (brightContext && brightDraw && brightContext.drawImage === brightWrap) {
try { brightContext.drawImage = brightDraw; } catch (e) {}
}
brightContext = null;
brightDraw = null;
brightWrap = null;
if (darknessDraw && window.drawDarkness === darknessWrap) {
try { window.drawDarkness = darknessDraw; } catch (e) {}
}
darknessDraw = null;
darknessWrap = null;
if (window.game && game.canvas && canvasFilter != null) game.canvas.style.filter = canvasFilter;
if (window.game && viewBase && game.canvas && typeof game.setCanvasSize === "function") {
game.originalWidth = viewBase.width;
game.originalHeight = viewBase.height;
game.zoom = viewBase.zoom;
try { if (typeof gameZoom !== "undefined") gameZoom = viewBase.gameZoom; } catch (e) {}
try { game.setCanvasSize(game.canvas.width, game.canvas.height); } catch (e) {}
viewBase = null;
}
restorePrivacyNames();
restoreProtectedDom();
privacyDom.forEach(function (old, node) {
if (node) { node.style.display = old.display; node.style.visibility = old.visibility; }
});
privacyDom.clear();
if (deathSocket && deathHandler) {
try {
if (typeof deathSocket.off === "function") deathSocket.off("game:dead", deathHandler);
else if (typeof deathSocket.removeListener === "function") deathSocket.removeListener("game:dead", deathHandler);
} catch (e) {}
}
deathSocket = null;
deathHandler = null;
if (killSocket && killHandler) {
try {
if (typeof killSocket.off === "function") killSocket.off("game:feed", killHandler);
else if (typeof killSocket.removeListener === "function") killSocket.removeListener("game:feed", killHandler);
} catch (e) {}
}
killSocket = null;
killHandler = null;
if (spectatorSocket && spectatorHandler) {
try {
if (typeof spectatorSocket.off === "function") spectatorSocket.off(socketMsgType.SPECTATORSLIST, spectatorHandler);
else if (typeof spectatorSocket.removeListener === "function") spectatorSocket.removeListener(socketMsgType.SPECTATORSLIST, spectatorHandler);
} catch (e) {}
}
spectatorSocket = null;
spectatorHandler = null;
spectatorEntries.clear();
document.body.classList.remove("ez-spec-active");
if (attackSocket && attackHandler) {
try {
if (typeof attackSocket.off === "function") attackSocket.off(socketMsgType.DOATTACK, attackHandler);
else if (typeof attackSocket.removeListener === "function") attackSocket.removeListener(socketMsgType.DOATTACK, attackHandler);
} catch (e) {}
}
attackSocket = null;
attackHandler = null;
if (petAttackSocket && petAttackHandler) {
try {
if (typeof petAttackSocket.off === "function") petAttackSocket.off(socketMsgType.DOPETATTACK, petAttackHandler);
else if (typeof petAttackSocket.removeListener === "function") petAttackSocket.removeListener(socketMsgType.DOPETATTACK, petAttackHandler);
} catch (e) {}
}
petAttackSocket = null;
petAttackHandler = null;
if (skillUseOriginal && window.skillUse === skillUseWrap) {
try { window.skillUse = skillUseOriginal; } catch (e) {}
}
skillUseOriginal = null;
skillUseWrap = null;
skillLocalEnd = 0;
skillLocalMe = 0;
if (attackDrawOriginal && window.doAttack === attackDrawWrap) {
try { window.doAttack = attackDrawOriginal; } catch (e) {}
}
attackDrawOriginal = null;
attackDrawWrap = null;
if (removeObjectOriginal && window.removeObject === removeObjectWrap) {
try { window.removeObject = removeObjectOriginal; } catch (e) {}
}
removeObjectOriginal = null;
removeObjectWrap = null;
if (foodSyncSocket && foodSyncHandler) {
try {
if (typeof foodSyncSocket.off === "function") foodSyncSocket.off(socketMsgType.SYNC, foodSyncHandler);
else if (typeof foodSyncSocket.removeListener === "function") foodSyncSocket.removeListener(socketMsgType.SYNC, foodSyncHandler);
} catch (e) {}
}
foodSyncSocket = null;
foodSyncHandler = null;
if (audio) { try { audio.close(); } catch (e) {} audio = null; }
soundBuffers.clear();
customSounds.length = 0;
if (bossSyncSocket && bossSyncHandler) {
try {
if (typeof bossSyncSocket.off === "function") bossSyncSocket.off(socketMsgType.SYNC, bossSyncHandler);
else if (typeof bossSyncSocket.removeListener === "function") bossSyncSocket.removeListener(socketMsgType.SYNC, bossSyncHandler);
} catch (e) {}
}
bossSyncSocket = null;
bossSyncHandler = null;
skillSync = null;
if (skillClock && skillClock.parentNode) skillClock.remove();
if (skillClockHost && skillClockHostPosition !== "") skillClockHost.style.position = skillClockHostPosition;
skillClock = null;
skillClockHost = null;
skillClockHostPosition = "";
if (scanIconBase && scanIconBase.node) {
scanIconBase.node.style.removeProperty("display");
scanIconBase.node.style.removeProperty("visibility");
scanIconBase.node.style.display = scanIconBase.display || "";
scanIconBase.node.style.visibility = scanIconBase.visibility || "";
}
scanIconBase = null;
freeSkin();
removeLocalPet();
cosmeticMe = null;
appliedSkinSpawn = "";
appliedPetSpawn = "";
bladeRegions = new WeakMap();
attackRegions.clear();
envBase.forEach(function (value, obj) {
if (obj && !obj.deleted) obj.opacity = value;
});
if (envBase.size) clearScene();
["ez-menu", "ez-layer", "ez-style", "ez-keys", "ez-bind-pop", "ez-deaths", "ez-kills", "ez-spectators"].forEach(function (id) {
var node = document.getElementById(id);
if (node) node.remove();
});
players.length = 0;
hides.length = 0;
extras.length = 0;
foodObjects.length = 0;
foodSpawns.length = 0;
scans.length = 0;
live.clear();
bank.clear();
scanBank.clear();
cardBank.clear();
envBase.clear();
hitBank.clear();
deathEntries.length = 0;
killEntries.length = 0;
skillTracks.clear();
projectileOwners.clear();
projectileSkillSeen.clear();
staticFoodSpawnCache.clear();
skinLoads.clear();
recentPlayers.clear();
foodSpawnState.clear();
foodSeen.clear();
foodGone.clear();
protectedDom.clear();
spriteImages = new WeakMap();
if (window.EZSQUAD && window.EZSQUAD.stop === stop) {
try { delete window.EZSQUAD; } catch (e) { window.EZSQUAD = null; }
}
}
window.EZSQUAD = {
active: true,
show: function () { if (menu) menu.classList.remove("ez-hide"); syncScanIcon(); updateKeys(); },
hide: function () { if (menu) menu.classList.add("ez-hide"); syncScanIcon(); updateKeys(); },
toggle: function () { if (menu) menu.classList.toggle("ez-hide"); syncScanIcon(); updateKeys(); },
scan: function () { return doScan(performance.now(), true); },
stop: stop,
settings: cfg
};
var booted = false;
var bootTimer = 0;
var bootBody = null;
var bootCanvas = null;
var bootSince = 0;
function readyGame() {
return !!(document.body && window.game && game.canvas && game.canvas.isConnected && window.objectType && window.socketMsgType);
}
function boot() {
if (dead || booted) return;
if (!readyGame()) {
bootBody = null;
bootCanvas = null;
bootSince = 0;
clearTimeout(bootTimer);
bootTimer = setTimeout(boot, 250);
return;
}
if (bootBody !== document.body || bootCanvas !== game.canvas) {
bootBody = document.body;
bootCanvas = game.canvas;
bootSince = Date.now();
clearTimeout(bootTimer);
bootTimer = setTimeout(boot, 700);
return;
}
if (Date.now() - bootSince < 650) {
clearTimeout(bootTimer);
bootTimer = setTimeout(boot, 100);
return;
}
booted = true;
start();
}
window.addEventListener("load", function () {
clearTimeout(bootTimer);
bootTimer = setTimeout(boot, 250);
}, { once: true });
bootTimer = setTimeout(boot, 250);
})();