// ==UserScript==
// @name Aibm recode
// @namespace https://dsc.gg/Aibm
// @author prince finn.
// @description An excellent Moomoo.io hack for a comfortable gaming experience
// @icon https://i.pinimg.com/1200x/7b/fb/12/7bfb12e9d8dfb7989f45c482196e39c4.jpg
// @version 2.3.9.6
// @match *://moomoo.io/
// @match *://moomoo.io/?server*
// @match *://*.moomoo.io/
// @match *://*.moomoo.io/?server*
// @run-at document-start
// @grant GM_webRequest
// @grant GM.fetch
// @connect generativelanguage.googleapis.com
// @license MIT
// @namespace
// @downloadURL
// @updateURL
// @downloadURL https://update.greasyfork.icu/scripts/557442/Aibm%20recode.user.js
// @updateURL https://update.greasyfork.icu/scripts/557442/Aibm%20recode.meta.js
// ==/UserScript==
/* jshint esversion:6 */
/* AIBM_INFO: Aibm recode by prince finn. */
/*
Author: Murka
Github: https://github.com/Murka007/Glotus-client
Greasyfork: https://greasyfork.org/users/919633
Discord: https://discord.gg/cPRFdcZkeD
MURKA REAL AUTHOR OF THE MOD!!!!!!!!!!!!!! (I only slightly modified the bots.)
I slightly modified the bots in this version. Now the bots follow the player and look towards the cursor. They place windmills during AUTO-ATTACK (KEY E), which is done so that bots do not have to be collected all over the map and they level up by themselves. Windmills are not placed during regular attacks (for convenience). Made by Prince Finn.
Feel free to use and distribute it, but don't forget about special credits.
*/
GM_webRequest([
{ selector: { include: ["*cookie*", "*cloudflare*", "*ads*", "*jquery*", "*howler*", "*frvr-channel-web*", "*securepubads*"] }, action: "cancel" },
]);
Function("(" + (() => {
"use strict";
var __webpack_exports__, code, Navbar_code, Keybinds_code, Combat_code, Visuals_code, Misc_code, Devtool_code, Bots_code, Credits_code, __webpack_require__ = {};
(() => {
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key]
});
}
}
};
})();
(() => {
__webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
})();
__webpack_exports__ = {};
__webpack_require__.d(__webpack_exports__, {
ZI: () => connection,
mq: () => myClient
});
const Config_Config = {
maxScreenWidth: 1920,
maxScreenHeight: 1080,
serverUpdateRate: 9,
collisionDepth: 6,
minimapRate: 3e3,
colGrid: 10,
clientSendRate: 5,
barWidth: 50,
barHeight: 17,
barPad: 4.5,
iconPadding: 15,
iconPad: .9,
deathFadeout: 3e3,
crownIconScale: 60,
crownPad: 35,
chatCountdown: 3e3,
chatCooldown: 500,
maxAge: 100,
gatherAngle: Math.PI / 2.6,
gatherWiggle: 10,
hitReturnRatio: .25,
hitAngle: Math.PI / 2,
playerScale: 35,
playerSpeed: .0016,
playerDecel: .993,
nameY: 34,
animalCount: 7,
aiTurnRandom: .06,
shieldAngle: Math.PI / 3,
resourceTypes: [ "wood", "food", "stone", "points" ],
areaCount: 7,
treesPerArea: 9,
bushesPerArea: 3,
totalRocks: 32,
goldOres: 7,
riverWidth: 724,
riverPadding: 114,
waterCurrent: .0011,
waveSpeed: 1e-4,
waveMax: 1.3,
treeScales: [ 150, 160, 165, 175 ],
bushScales: [ 80, 85, 95 ],
rockScales: [ 80, 85, 90 ],
snowBiomeTop: 2400,
snowSpeed: .75,
maxNameLength: 15,
mapScale: 14400,
mapPingScale: 40,
mapPingTime: 2200,
skinColors: [ "#bf8f54", "#cbb091", "#896c4b", "#fadadc", "#ececec", "#c37373", "#4c4c4c", "#ecaff7", "#738cc3", "#8bc373", "#91B2DB" ]
};
const constants_Config = Config_Config;
const Weapons = [ {
id: 0,
itemType: 0,
upgradeType: 0,
type: 0,
age: 0,
name: "tool hammer",
description: "tool for gathering all resources",
src: "hammer_1",
length: 140,
width: 140,
xOffset: -3,
yOffset: 18,
damage: 25,
range: 65,
gather: 1,
speed: 300
}, {
id: 1,
itemType: 0,
upgradeType: 1,
type: 0,
age: 2,
name: "hand axe",
description: "gathers resources at a higher rate",
src: "axe_1",
length: 140,
width: 140,
xOffset: 3,
yOffset: 24,
damage: 30,
spdMult: 1,
range: 70,
gather: 2,
speed: 400
}, {
id: 2,
itemType: 0,
upgradeOf: 1,
upgradeType: 1,
type: 0,
age: 8,
pre: 1,
name: "great axe",
description: "deal more damage and gather more resources",
src: "great_axe_1",
length: 140,
width: 140,
xOffset: -8,
yOffset: 25,
damage: 35,
spdMult: 1,
range: 75,
gather: 4,
speed: 400
}, {
id: 3,
itemType: 0,
upgradeType: 2,
type: 0,
age: 2,
name: "short sword",
description: "increased attack power but slower move speed",
src: "sword_1",
iPad: 1.3,
length: 130,
width: 210,
xOffset: -8,
yOffset: 46,
damage: 35,
spdMult: .85,
range: 110,
gather: 1,
speed: 300
}, {
id: 4,
itemType: 0,
upgradeOf: 3,
upgradeType: 2,
type: 0,
age: 8,
pre: 3,
name: "katana",
description: "greater range and damage",
src: "samurai_1",
iPad: 1.3,
length: 130,
width: 210,
xOffset: -8,
yOffset: 59,
damage: 40,
spdMult: .8,
range: 118,
gather: 1,
speed: 300
}, {
id: 5,
itemType: 0,
upgradeType: 3,
isUpgrade: false,
type: 0,
age: 2,
name: "polearm",
description: "long range melee weapon",
src: "spear_1",
iPad: 1.3,
length: 130,
width: 210,
xOffset: -8,
yOffset: 53,
damage: 45,
knock: .2,
spdMult: .82,
range: 142,
gather: 1,
speed: 700
}, {
id: 6,
itemType: 0,
upgradeType: 4,
isUpgrade: false,
type: 0,
age: 2,
name: "bat",
description: "fast long range melee weapon",
src: "bat_1",
iPad: 1.3,
length: 110,
width: 180,
xOffset: -8,
yOffset: 53,
damage: 20,
knock: .7,
range: 110,
gather: 1,
speed: 300
}, {
id: 7,
itemType: 0,
upgradeType: 5,
isUpgrade: false,
type: 0,
age: 2,
name: "daggers",
description: "really fast short range weapon",
src: "dagger_1",
iPad: .8,
length: 110,
width: 110,
xOffset: 18,
yOffset: 0,
damage: 20,
knock: .1,
range: 65,
gather: 1,
hitSlow: .1,
spdMult: 1.13,
speed: 100
}, {
id: 8,
itemType: 0,
upgradeType: 6,
isUpgrade: false,
type: 0,
age: 2,
name: "stick",
description: "great for gathering but very weak",
src: "stick_1",
length: 140,
width: 140,
xOffset: 3,
yOffset: 24,
damage: 1,
spdMult: 1,
range: 70,
gather: 7,
speed: 400
}, {
id: 9,
itemType: 1,
upgradeType: 7,
projectile: 0,
type: 1,
age: 6,
name: "hunting bow",
description: "bow used for ranged combat and hunting",
src: "bow_1",
cost: {
food: 0,
wood: 4,
stone: 0,
gold: 0
},
length: 120,
width: 120,
xOffset: -6,
yOffset: 0,
spdMult: .75,
speed: 600,
range: 2200
}, {
id: 10,
itemType: 1,
upgradeType: 8,
isUpgrade: false,
type: 1,
age: 6,
name: "great hammer",
description: "hammer used for destroying structures",
src: "great_hammer_1",
length: 140,
width: 140,
xOffset: -9,
yOffset: 25,
damage: 10,
spdMult: .88,
range: 75,
sDmg: 7.5,
gather: 1,
speed: 400
}, {
id: 11,
itemType: 1,
upgradeType: 9,
isUpgrade: false,
type: 1,
age: 6,
name: "wooden shield",
description: "blocks projectiles and reduces melee damage",
src: "shield_1",
length: 120,
width: 120,
shield: .2,
xOffset: 6,
yOffset: 0,
spdMult: .7,
speed: 1
}, {
id: 12,
itemType: 1,
upgradeType: 7,
projectile: 2,
upgradeOf: 9,
type: 1,
age: 8,
pre: 9,
name: "crossbow",
description: "deals more damage and has greater range",
src: "crossbow_1",
cost: {
food: 0,
wood: 5,
stone: 0,
gold: 0
},
aboveHand: true,
armS: .75,
length: 120,
width: 120,
xOffset: -4,
yOffset: 0,
spdMult: .7,
speed: 700,
range: 2200
}, {
id: 13,
itemType: 1,
upgradeType: 7,
projectile: 3,
upgradeOf: 12,
type: 1,
age: 9,
pre: 12,
name: "repeater crossbow",
description: "high firerate crossbow with reduced damage",
src: "crossbow_2",
cost: {
food: 0,
wood: 10,
stone: 0,
gold: 0
},
aboveHand: true,
armS: .75,
length: 120,
width: 120,
xOffset: -4,
yOffset: 0,
spdMult: .7,
speed: 230,
range: 2200
}, {
id: 14,
itemType: 1,
upgradeType: 10,
isUpgrade: false,
type: 1,
age: 6,
name: "mc grabby",
description: "steals resources from enemies",
src: "grab_1",
length: 130,
width: 210,
xOffset: -8,
yOffset: 53,
damage: 0,
steal: 250,
knock: .2,
spdMult: 1.05,
range: 125,
gather: 0,
speed: 700
}, {
id: 15,
itemType: 1,
upgradeType: 7,
projectile: 5,
upgradeOf: 12,
type: 1,
age: 9,
pre: 12,
name: "musket",
description: "slow firerate but high damage and range",
src: "musket_1",
cost: {
food: 0,
wood: 0,
stone: 10,
gold: 0
},
aboveHand: true,
rec: .35,
armS: .6,
hndS: .3,
hndD: 1.6,
length: 205,
width: 205,
xOffset: 25,
yOffset: 0,
hideProjectile: true,
spdMult: .6,
speed: 1500,
range: 2200
} ];
const ItemGroups = {
[1]: {
name: "Wall",
limit: 30,
layer: 0
},
[2]: {
name: "Spike",
limit: 15,
layer: 0
},
[3]: {
name: "Windmill",
limit: 7,
sandboxLimit: 299,
layer: 1
},
[4]: {
name: "Mine",
limit: 1,
layer: 0
},
[5]: {
name: "Trap",
limit: 6,
layer: -1
},
[6]: {
name: "Boost",
limit: 12,
sandboxLimit: 299,
layer: -1
},
[7]: {
name: "Turret",
limit: 2,
layer: 1
},
[8]: {
name: "Plaftorm",
limit: 12,
layer: -1
},
[9]: {
name: "Healing pad",
limit: 4,
layer: -1
},
[10]: {
name: "Spawn",
limit: 1,
layer: -1
},
[11]: {
name: "Sapling",
limit: 2,
layer: 0
},
[12]: {
name: "Blocker",
limit: 3,
layer: -1
},
[13]: {
name: "Teleporter",
limit: 2,
sandboxLimit: 299,
layer: -1
}
};
const Items = [ {
id: 0,
itemType: 2,
name: "apple",
description: "restores 20 health when consumed",
age: 0,
cost: {
food: 10,
wood: 0,
stone: 0,
gold: 0
},
restore: 20,
scale: 22,
holdOffset: 15
}, {
id: 1,
itemType: 2,
upgradeOf: 0,
name: "cookie",
description: "restores 40 health when consumed",
age: 3,
cost: {
food: 15,
wood: 0,
stone: 0,
gold: 0
},
restore: 40,
scale: 27,
holdOffset: 15
}, {
id: 2,
itemType: 2,
upgradeOf: 1,
name: "cheese",
description: "restores 30 health and another 50 over 5 seconds",
age: 7,
cost: {
food: 25,
wood: 0,
stone: 0,
gold: 0
},
restore: 30,
scale: 27,
holdOffset: 15
}, {
id: 3,
itemType: 3,
itemGroup: 1,
name: "wood wall",
description: "provides protection for your village",
age: 0,
cost: {
food: 0,
wood: 10,
stone: 0,
gold: 0
},
projDmg: true,
health: 380,
scale: 50,
holdOffset: 20,
placeOffset: -5
}, {
id: 4,
itemType: 3,
itemGroup: 1,
upgradeOf: 3,
name: "stone wall",
description: "provides improved protection for your village",
age: 3,
cost: {
food: 0,
wood: 0,
stone: 25,
gold: 0
},
health: 900,
scale: 50,
holdOffset: 20,
placeOffset: -5
}, {
pre: 1,
id: 5,
itemType: 3,
itemGroup: 1,
upgradeOf: 4,
name: "castle wall",
description: "provides powerful protection for your village",
age: 7,
cost: {
food: 0,
wood: 0,
stone: 35,
gold: 0
},
health: 1500,
scale: 52,
holdOffset: 20,
placeOffset: -5
}, {
id: 6,
itemType: 4,
itemGroup: 2,
name: "spikes",
description: "damages enemies when they touch them",
age: 0,
cost: {
food: 0,
wood: 20,
stone: 5,
gold: 0
},
health: 400,
damage: 20,
scale: 49,
spritePadding: -23,
holdOffset: 8,
placeOffset: -5
}, {
id: 7,
itemType: 4,
itemGroup: 2,
upgradeOf: 6,
name: "greater spikes",
description: "damages enemies when they touch them",
age: 5,
cost: {
food: 0,
wood: 30,
stone: 10,
gold: 0
},
health: 500,
damage: 35,
scale: 52,
spritePadding: -23,
holdOffset: 8,
placeOffset: -5
}, {
id: 8,
itemType: 4,
itemGroup: 2,
upgradeOf: 7,
name: "poison spikes",
description: "poisons enemies when they touch them",
age: 9,
pre: 1,
cost: {
food: 0,
wood: 35,
stone: 15,
gold: 0
},
health: 600,
damage: 30,
poisonDamage: 5,
scale: 52,
spritePadding: -23,
holdOffset: 8,
placeOffset: -5
}, {
id: 9,
itemType: 4,
itemGroup: 2,
upgradeOf: 7,
name: "spinning spikes",
description: "damages enemies when they touch them",
age: 9,
pre: 2,
cost: {
food: 0,
wood: 30,
stone: 20,
gold: 0
},
health: 500,
damage: 45,
turnSpeed: .003,
scale: 52,
spritePadding: -23,
holdOffset: 8,
placeOffset: -5
}, {
id: 10,
itemType: 5,
itemGroup: 3,
name: "windmill",
description: "generates gold over time",
age: 0,
cost: {
food: 0,
wood: 50,
stone: 10,
gold: 0
},
health: 400,
pps: 1,
turnSpeed: .0016,
spritePadding: 25,
iconLineMult: 12,
scale: 45,
holdOffset: 20,
placeOffset: 5
}, {
id: 11,
itemType: 5,
itemGroup: 3,
upgradeOf: 10,
name: "faster windmill",
description: "generates more gold over time",
age: 5,
pre: 1,
cost: {
food: 0,
wood: 60,
stone: 20,
gold: 0
},
health: 500,
pps: 1.5,
turnSpeed: .0025,
spritePadding: 25,
iconLineMult: 12,
scale: 47,
holdOffset: 20,
placeOffset: 5
}, {
id: 12,
itemType: 5,
itemGroup: 3,
upgradeOf: 11,
name: "power mill",
description: "generates more gold over time",
age: 8,
pre: 1,
cost: {
food: 0,
wood: 100,
stone: 50,
gold: 0
},
health: 800,
pps: 2,
turnSpeed: .005,
spritePadding: 25,
iconLineMult: 12,
scale: 47,
holdOffset: 20,
placeOffset: 5
}, {
id: 13,
itemType: 6,
itemGroup: 4,
name: "mine",
description: "allows you to mine stone",
age: 5,
type: 2,
cost: {
food: 0,
wood: 20,
stone: 100,
gold: 0
},
iconLineMult: 12,
scale: 65,
holdOffset: 20,
placeOffset: 0
}, {
id: 14,
itemType: 6,
itemGroup: 11,
name: "sapling",
description: "allows you to farm wood",
age: 5,
type: 0,
cost: {
food: 0,
wood: 150,
stone: 0,
gold: 0
},
iconLineMult: 12,
colDiv: .5,
scale: 110,
holdOffset: 50,
placeOffset: -15
}, {
id: 15,
itemType: 7,
itemGroup: 5,
name: "pit trap",
description: "pit that traps enemies if they walk over it",
age: 4,
cost: {
food: 0,
wood: 30,
stone: 30,
gold: 0
},
trap: true,
ignoreCollision: true,
hideFromEnemy: true,
health: 500,
colDiv: .2,
scale: 50,
holdOffset: 20,
placeOffset: -5
}, {
id: 16,
itemType: 7,
itemGroup: 6,
name: "boost pad",
description: "provides boost when stepped on",
age: 4,
cost: {
food: 0,
wood: 5,
stone: 20,
gold: 0
},
ignoreCollision: true,
boostSpeed: 1.5,
health: 150,
colDiv: .7,
scale: 45,
holdOffset: 20,
placeOffset: -5
}, {
id: 17,
itemType: 8,
itemGroup: 7,
name: "turret",
description: "defensive structure that shoots at enemies",
age: 7,
doUpdate: true,
cost: {
food: 0,
wood: 200,
stone: 150,
gold: 0
},
health: 800,
projectile: 1,
shootRange: 700,
shootRate: 2200,
scale: 43,
holdOffset: 20,
placeOffset: -5
}, {
id: 18,
itemType: 8,
itemGroup: 8,
name: "platform",
description: "platform to shoot over walls and cross over water",
age: 7,
cost: {
food: 0,
wood: 20,
stone: 0,
gold: 0
},
ignoreCollision: true,
zIndex: 1,
health: 300,
scale: 43,
holdOffset: 20,
placeOffset: -5
}, {
id: 19,
itemType: 8,
itemGroup: 9,
name: "healing pad",
description: "standing on it will slowly heal you",
age: 7,
cost: {
food: 10,
wood: 30,
stone: 0,
gold: 0
},
ignoreCollision: true,
healCol: 15,
health: 400,
colDiv: .7,
scale: 45,
holdOffset: 20,
placeOffset: -5
}, {
id: 20,
itemType: 9,
itemGroup: 10,
name: "spawn pad",
description: "you will spawn here when you die but it will dissapear",
age: 9,
cost: {
food: 0,
wood: 100,
stone: 100,
gold: 0
},
health: 400,
ignoreCollision: true,
spawnPoint: true,
scale: 45,
holdOffset: 20,
placeOffset: -5
}, {
id: 21,
itemType: 8,
itemGroup: 12,
name: "blocker",
description: "blocks building in radius",
age: 7,
cost: {
food: 0,
wood: 30,
stone: 25,
gold: 0
},
ignoreCollision: true,
blocker: 300,
health: 400,
colDiv: .7,
scale: 45,
holdOffset: 20,
placeOffset: -5
}, {
id: 22,
itemType: 8,
itemGroup: 13,
name: "teleporter",
description: "teleports you to a random point on the map",
age: 7,
cost: {
food: 0,
wood: 60,
stone: 60,
gold: 0
},
ignoreCollision: true,
teleport: true,
health: 200,
colDiv: .7,
scale: 45,
holdOffset: 20,
placeOffset: -5
} ];
const WeaponVariants = [ {
id: 0,
src: "",
xp: 1,
needXP: 0,
val: 1,
color: "#7e7e90"
}, {
id: 1,
src: "_g",
xp: 3e3,
needXP: 3e3,
val: 1.1,
color: "#f7cf45"
}, {
id: 2,
src: "_d",
xp: 7e3,
needXP: 4e3,
val: 1.18,
color: "#6d91cb"
}, {
id: 3,
src: "_r",
poison: true,
xp: 12e3,
needXP: 5e3,
val: 1.18,
color: "#be5454"
} ];
const Projectiles = [ {
id: 0,
name: "Hunting bow",
index: 0,
layer: 0,
src: "arrow_1",
damage: 25,
scale: 103,
range: 1e3,
speed: 1.6
}, {
id: 1,
name: "Turret",
index: 1,
layer: 1,
damage: 25,
scale: 20,
speed: 1.5,
range: 700
}, {
id: 2,
name: "Crossbow",
index: 0,
layer: 0,
src: "arrow_1",
damage: 35,
scale: 103,
range: 1200,
speed: 2.5
}, {
id: 3,
name: "Repeater crossbow",
index: 0,
layer: 0,
src: "arrow_1",
damage: 30,
scale: 103,
range: 1200,
speed: 2
}, {
id: 4,
index: 1,
layer: 1,
damage: 16,
scale: 20,
range: 0,
speed: 0
}, {
id: 5,
name: "Musket",
index: 0,
layer: 0,
src: "bullet_1",
damage: 50,
scale: 160,
range: 1400,
speed: 3.6
} ];
class Vector_Vector {
x;
y;
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
static fromAngle(angle, length = 1) {
return new Vector_Vector(Math.cos(angle) * length, Math.sin(angle) * length);
}
add(vec) {
if (vec instanceof Vector_Vector) {
this.x += vec.x;
this.y += vec.y;
} else {
this.x += vec;
this.y += vec;
}
return this;
}
sub(vec) {
if (vec instanceof Vector_Vector) {
this.x -= vec.x;
this.y -= vec.y;
} else {
this.x -= vec;
this.y -= vec;
}
return this;
}
mult(scalar) {
this.x *= scalar;
this.y *= scalar;
return this;
}
div(scalar) {
this.x /= scalar;
this.y /= scalar;
return this;
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
normalize() {
return this.length > 0 ? this.div(this.length) : this;
}
dot(vec) {
return this.x * vec.x + this.y * vec.y;
}
proj(vec) {
const k = this.dot(vec) / vec.dot(vec);
return vec.copy().mult(k);
}
setXY(x, y) {
this.x = x;
this.y = y;
return this;
}
setVec(vec) {
return this.setXY(vec.x, vec.y);
}
setLength(value) {
return this.normalize().mult(value);
}
copy() {
return new Vector_Vector(this.x, this.y);
}
distance(vec) {
return this.copy().sub(vec).length;
}
angle(vec) {
const copy = vec.copy().sub(this);
return Math.atan2(copy.y, copy.x);
}
direction(angle, length) {
return this.copy().add(Vector_Vector.fromAngle(angle, length));
}
isEqual(vec) {
return this.x === vec.x && this.y === vec.y;
}
stringify() {
return this.x + ":" + this.y;
}
}
const modules_Vector = Vector_Vector;
const getAngle = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
const getAngleDist = (a, b) => {
const p = Math.abs(b - a) % (2 * Math.PI);
return p > Math.PI ? 2 * Math.PI - p : p;
};
const removeFast = (array, index) => {
if (index < 0 || index >= array.length) {
throw new RangeError("removeFast: Index out of range");
}
if (index === array.length - 1) {
array.pop();
} else {
array[index] = array.pop();
}
};
let uniqueID = 0;
const getUniqueID = () => uniqueID++;
const isActiveInput = () => {
const active = document.activeElement || document.body;
return active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement;
};
const getAngleFromBitmask = (bitmask, rotate) => {
const vec = {
x: 0,
y: 0
};
if (1 & bitmask) {
vec.y--;
}
if (2 & bitmask) {
vec.y++;
}
if (4 & bitmask) {
vec.x--;
}
if (8 & bitmask) {
vec.x++;
}
if (rotate) {
vec.x *= -1;
vec.y *= -1;
}
return 0 === vec.x && 0 === vec.y ? null : Math.atan2(vec.y, vec.x);
};
const formatCode = code => {
code += "";
if ("Backspace" === code) {
return code;
}
if ("Escape" === code) {
return "ESC";
}
if ("Delete" === code) {
return "DEL";
}
if ("Minus" === code) {
return "-";
}
if ("Equal" === code) {
return "=";
}
if ("BracketLeft" === code) {
return "[";
}
if ("BracketRight" === code) {
return "]";
}
if ("Slash" === code) {
return "/";
}
if ("Backslash" === code) {
return "\\";
}
if ("Quote" === code) {
return "'";
}
if ("Backquote" === code) {
return "`";
}
if ("Semicolon" === code) {
return ";";
}
if ("Comma" === code) {
return ",";
}
if ("Period" === code) {
return ".";
}
if ("CapsLock" === code) {
return "CAPS";
}
if ("ContextMenu" === code) {
return "CTXMENU";
}
if ("NumLock" === code) {
return "LOCK";
}
return code.replace(/^Page/, "PG").replace(/^Digit/, "").replace(/^Key/, "").replace(/^(Shift|Control|Alt)(L|R).*$/, "$2$1").replace(/Control/, "CTRL").replace(/^Arrow/, "").replace(/^Numpad/, "NUM").replace(/Decimal/, "DEC").replace(/Subtract/, "SUB").replace(/Divide/, "DIV").replace(/Multiply/, "MULT").toUpperCase();
};
const formatButton = button => {
if (0 === button) {
return "LBTN";
}
if (1 === button) {
return "MBTN";
}
if (2 === button) {
return "RBTN";
}
if (3 === button) {
return "BBTN";
}
if (4 === button) {
return "FBTN";
}
throw new Error(`formatButton Error: "${button}" is not valid button`);
};
const removeClass = (target, name) => {
if (target instanceof HTMLElement) {
target.classList.remove(name);
return;
}
for (const element of target) {
element.classList.remove(name);
}
};
const pointInRiver = position => {
const y = position.y;
const below = y >= constants_Config.mapScale / 2 - constants_Config.riverWidth / 2;
const above = y <= constants_Config.mapScale / 2 + constants_Config.riverWidth / 2;
return below && above;
};
const pointInDesert = position => position.y >= constants_Config.mapScale - constants_Config.snowBiomeTop;
const inView = (x, y, radius) => {
const maxScreenWidth = Math.min(1920, modules_ZoomHandler.scale.current.w);
const maxScreenHeight = Math.min(1080, modules_ZoomHandler.scale.current.h);
const visibleHorizontally = x + radius > 0 && x - radius < maxScreenWidth;
const visibleVertically = y + radius > 0 && y - radius < maxScreenHeight;
return visibleHorizontally && visibleVertically;
};
const findPlacementAngles = angles => {
const output = new Set;
for (let i = 0; i < angles.length; i++) {
const {angle, offset} = angles[i];
const start = angle - offset;
const end = angle + offset;
let startIntersects = false;
let endIntersects = false;
for (let j = 0; j < angles.length; j++) {
if (startIntersects && endIntersects) {
break;
}
if (i !== j) {
const {angle, offset} = angles[j];
if (getAngleDist(start, angle) <= offset) {
startIntersects = true;
}
if (getAngleDist(end, angle) <= offset) {
endIntersects = true;
}
}
}
if (!startIntersects) {
output.add(start);
}
if (!endIntersects) {
output.add(end);
}
}
return output;
};
const cursorPosition = () => {
const {ModuleHandler, myPlayer} = myClient;
const {w, h} = modules_ZoomHandler.scale.current;
const scale = Math.max(innerWidth / w, innerHeight / h);
const cursorX = (ModuleHandler.mouse.lockX - innerWidth / 2) / scale;
const cursorY = (ModuleHandler.mouse.lockY - innerHeight / 2) / scale;
const pos = myPlayer.position.current;
return new modules_Vector(pos.x + cursorX, pos.y + cursorY);
};
const Hooker = new class Hooker {
createRecursiveHook(target, prop, callback) {
(function recursiveHook() {
Object.defineProperty(target, prop, {
set(value) {
delete target[prop];
this[prop] = value;
if (callback(this, value)) {
return;
}
recursiveHook();
},
configurable: true
});
})();
}
createHook(target, prop, callback) {
const symbol = Symbol(prop);
Object.defineProperty(target, prop, {
get() {
return this[symbol];
},
set(value) {
callback(this, value, symbol);
},
configurable: true
});
}
linker(value) {
const hook = [ value ];
hook.valueOf = () => hook[0];
return hook;
}
};
const utility_Hooker = Hooker;
const resizeEvent = new Event("resize");
const ZoomHandler = new class ZoomHandler {
scale={
Default: {
w: 1920,
h: 1080
},
current: {
w: 1920,
h: 1080
},
smooth: {
w: utility_Hooker.linker(1920),
h: utility_Hooker.linker(1080)
}
};
wheels=3;
scaleFactor=250;
getScale() {
const dpr = 1;
return Math.max(innerWidth / this.scale.Default.w, innerHeight / this.scale.Default.h) * dpr;
}
getMinScale(scale) {
const {w, h} = this.scale.Default;
const min = Math.min(w, h);
const count = Math.floor(min / scale);
return {
w: w - scale * count,
h: h - scale * count
};
}
handler(event) {
if (!(event.target instanceof HTMLCanvasElement) || event.ctrlKey || event.shiftKey || event.altKey || isActiveInput()) {
return;
}
const {Default, current, smooth} = this.scale;
if (Default.w === current.w && Default.h === current.h && 0 !== (this.wheels = (this.wheels + 1) % 4)) {
return;
}
const {w, h} = this.getMinScale(this.scaleFactor);
const zoom = Math.sign(event.deltaY) * -this.scaleFactor;
current.w = Math.max(w, current.w + zoom);
current.h = Math.max(h, current.h + zoom);
smooth.w[0] = current.w;
smooth.h[0] = current.h;
window.dispatchEvent(resizeEvent);
}
};
const modules_ZoomHandler = ZoomHandler;
class Storage {
static get(key) {
const value = localStorage.getItem(key);
return null === value ? null : JSON.parse(value);
}
static set(key, value, stringify = true) {
const data = stringify ? JSON.stringify(value) : value;
localStorage.setItem(key, data);
}
static delete(key) {
const has = localStorage.hasOwnProperty(key) && key in localStorage;
localStorage.removeItem(key);
return has;
}
}
class Cookie {
static get(key) {
const cookies = document.cookie.split(";");
for (const cookie of cookies) {
const match = cookie.trim().match(/^(.+?)=(.+?)$/);
if (null !== match && match[1] === key) {
try {
return JSON.parse(decodeURIComponent(match[2]));
} catch (err) {}
}
}
return null;
}
static set(name, value, days) {
const date = new Date;
date.setTime(date.getTime() + 24 * days * 60 * 60 * 1e3);
const expires = "; expires=" + date.toUTCString();
const domain = "; domain=.moomoo.io";
const path = "; path=/";
const cookieString = `${name}=${encodeURIComponent(value)}${expires}${domain}${path}`;
document.cookie = cookieString;
}
}
const defaultSettings = {
primary: "Digit1",
secondary: "Digit2",
food: "KeyQ",
wall: "Digit4",
spike: "KeyC",
windmill: "KeyR",
syncshot: "KeyL",
farm: "KeyT",
trap: "Space",
turret: "KeyF",
spawn: "KeyG",
up: "KeyW",
left: "KeyA",
down: "KeyS",
right: "KeyD",
autoattack: "KeyE",
lockrotation: "KeyX",
lockBotPosition: "KeyZ",
toggleChat: "Enter",
toggleShop: "ShiftLeft",
toggleClan: "ControlLeft",
toggleMenu: "Escape",
instakill: "KeyK",
biomehats: true,
distanceHitEnabled: true,
instakillCheckSolidHat: false,
instakillCrossForBots: false,
instakillForBots: true,
botWeaponSync: false,
botHatSync: true,
botAccSync: true,
syncShotWeaponSlot: 0,
autoemp: true,
antienemy: true,
antianimal: true,
antispike: true,
autoheal: true,
healingSpeed: 25,
automill: true,
autoplacer: true,
autobreak: true,
autoreload: false,
fastWeaponSwitch: true,
autoShield: false,
autoShieldDebug: true,
enemyTracers: false,
enemyTracersColor: "#cc5151",
teammateTracers: false,
teammateTracersColor: "#8ecc51",
animalTracers: false,
animalTracersColor: "#518ccc",
notificationTracers: true,
notificationTracersColor: "#f5d951",
arrows: true,
itemMarkers: true,
itemMarkersColor: "#84bd4b",
teammateMarkers: true,
teammateMarkersColor: "#bdb14b",
enemyMarkers: true,
enemyMarkersColor: "#ba4949",
weaponXPBar: true,
playerTurretReloadBar: true,
playerTurretReloadBarColor: "#cf7148",
weaponReloadBar: true,
weaponReloadBarColor: "#5155cc",
renderHP: true,
objectTurretReloadBar: false,
objectTurretReloadBarColor: "#66d9af",
itemHealthBar: false,
itemHealthBarColor: "#6b449e",
botLidarViz: true,
botBreakModeViz: true,
itemCounter: true,
renderGrid: false,
windmillRotation: false,
entityDanger: true,
displayPlayerAngle: false,
projectileHitbox: false,
possibleShootTarget: false,
weaponHitbox: false,
collisionHitbox: false,
placementHitbox: false,
turretHitbox: false,
possiblePlacement: true,
autospawn: true,
autoaccept: false,
menuTransparency: false,
storeItems: [ [ 15, 31, 6, 7, 22, 12, 26, 11, 53, 20, 40, 56 ], [ 11, 17, 16, 13, 19, 18, 21 ] ]
};
defaultSettings.storeItems;
const settings = {
...defaultSettings,
...Cookie.get("Aibm")
};
for (const iterator in settings) {
const key = iterator;
if (!defaultSettings.hasOwnProperty(key)) {
delete settings[key];
}
}
const SaveSettings = () => {
Cookie.set("Aibm", JSON.stringify(settings), 365);
};
SaveSettings();
const Settings = settings;
const Hats = {
[0]: {
index: 0,
id: 0,
name: "Unequip",
dontSell: true,
price: 0,
scale: 0,
description: "None"
},
[45]: {
index: 1,
id: 45,
name: "Shame!",
dontSell: true,
price: 0,
scale: 120,
description: "hacks are for losers"
},
[51]: {
index: 2,
id: 51,
name: "Moo Cap",
price: 0,
scale: 120,
description: "coolest mooer around"
},
[50]: {
index: 3,
id: 50,
name: "Apple Cap",
price: 0,
scale: 120,
description: "apple farms remembers"
},
[28]: {
index: 4,
id: 28,
name: "Moo Head",
price: 0,
scale: 120,
description: "no effect"
},
[29]: {
index: 5,
id: 29,
name: "Pig Head",
price: 0,
scale: 120,
description: "no effect"
},
[30]: {
index: 6,
id: 30,
name: "Fluff Head",
price: 0,
scale: 120,
description: "no effect"
},
[36]: {
index: 7,
id: 36,
name: "Pandou Head",
price: 0,
scale: 120,
description: "no effect"
},
[37]: {
index: 8,
id: 37,
name: "Bear Head",
price: 0,
scale: 120,
description: "no effect"
},
[38]: {
index: 9,
id: 38,
name: "Monkey Head",
price: 0,
scale: 120,
description: "no effect"
},
[44]: {
index: 10,
id: 44,
name: "Polar Head",
price: 0,
scale: 120,
description: "no effect"
},
[35]: {
index: 11,
id: 35,
name: "Fez Hat",
price: 0,
scale: 120,
description: "no effect"
},
[42]: {
index: 12,
id: 42,
name: "Enigma Hat",
price: 0,
scale: 120,
description: "join the enigma army"
},
[43]: {
index: 13,
id: 43,
name: "Blitz Hat",
price: 0,
scale: 120,
description: "hey everybody i'm blitz"
},
[49]: {
index: 14,
id: 49,
name: "Bob XIII Hat",
price: 0,
scale: 120,
description: "like and subscribe"
},
[57]: {
index: 15,
id: 57,
name: "Pumpkin",
price: 50,
scale: 120,
description: "Spooooky"
},
[8]: {
index: 16,
id: 8,
name: "Bummle Hat",
price: 100,
scale: 120,
description: "no effect"
},
[2]: {
index: 17,
id: 2,
name: "Straw Hat",
price: 500,
scale: 120,
description: "no effect"
},
[15]: {
index: 18,
id: 15,
name: "Winter Cap",
price: 600,
scale: 120,
description: "allows you to move at normal speed in snow",
coldM: 1
},
[5]: {
index: 19,
id: 5,
name: "Cowboy Hat",
price: 1e3,
scale: 120,
description: "no effect"
},
[4]: {
index: 20,
id: 4,
name: "Ranger Hat",
price: 2e3,
scale: 120,
description: "no effect"
},
[18]: {
index: 21,
id: 18,
name: "Explorer Hat",
price: 2e3,
scale: 120,
description: "no effect"
},
[31]: {
index: 22,
id: 31,
name: "Flipper Hat",
price: 2500,
scale: 120,
description: "have more control while in water",
watrImm: true
},
[1]: {
index: 23,
id: 1,
name: "Marksman Cap",
price: 3e3,
scale: 120,
description: "increases arrow speed and range",
aMlt: 1.3
},
[10]: {
index: 24,
id: 10,
name: "Bush Gear",
price: 3e3,
scale: 160,
description: "allows you to disguise yourself as a bush"
},
[48]: {
index: 25,
id: 48,
name: "Halo",
price: 3e3,
scale: 120,
description: "no effect"
},
[6]: {
index: 26,
id: 6,
name: "Soldier Helmet",
price: 4e3,
scale: 120,
description: "reduces damage taken but slows movement",
spdMult: .94,
dmgMult: .75
},
[23]: {
index: 27,
id: 23,
name: "Anti Venom Gear",
price: 4e3,
scale: 120,
description: "makes you immune to poison",
poisonRes: 1
},
[13]: {
index: 28,
id: 13,
name: "Medic Gear",
price: 5e3,
scale: 110,
description: "slowly regenerates health over time",
healthRegen: 3
},
[9]: {
index: 29,
id: 9,
name: "Miners Helmet",
price: 5e3,
scale: 120,
description: "earn 1 extra gold per resource",
extraGold: 1
},
[32]: {
index: 30,
id: 32,
name: "Musketeer Hat",
price: 5e3,
scale: 120,
description: "reduces cost of projectiles",
projCost: .5
},
[7]: {
index: 31,
id: 7,
name: "Bull Helmet",
price: 6e3,
scale: 120,
description: "increases damage done but drains health",
healthRegen: -5,
dmgMultO: 1.5,
spdMult: .96
},
[22]: {
index: 32,
id: 22,
name: "Emp Helmet",
price: 6e3,
scale: 120,
description: "turrets won't attack but you move slower",
antiTurret: 1,
spdMult: .7
},
[12]: {
index: 33,
id: 12,
name: "Booster Hat",
price: 6e3,
scale: 120,
description: "increases your movement speed",
spdMult: 1.16
},
[26]: {
index: 34,
id: 26,
name: "Barbarian Armor",
price: 8e3,
scale: 120,
description: "knocks back enemies that attack you",
dmgK: .6
},
[21]: {
index: 35,
id: 21,
name: "Plague Mask",
price: 1e4,
scale: 120,
description: "melee attacks deal poison damage",
poisonDmg: 5,
poisonTime: 6
},
[46]: {
index: 36,
id: 46,
name: "Bull Mask",
price: 1e4,
scale: 120,
description: "bulls won't target you unless you attack them",
bullRepel: 1
},
[14]: {
index: 37,
id: 14,
name: "Windmill Hat",
topSprite: true,
price: 1e4,
scale: 120,
description: "generates points while worn",
pps: 1.5
},
[11]: {
index: 38,
id: 11,
name: "Spike Gear",
topSprite: true,
price: 1e4,
scale: 120,
description: "deal damage to players that damage you",
dmg: .45
},
[53]: {
index: 39,
id: 53,
name: "Turret Gear",
topSprite: true,
price: 1e4,
scale: 120,
description: "you become a walking turret",
turret: {
projectile: 1,
range: 700,
rate: 2500
},
spdMult: .7
},
[20]: {
index: 40,
id: 20,
name: "Samurai Armor",
price: 12e3,
scale: 120,
description: "increased attack speed and fire rate",
atkSpd: .78
},
[58]: {
index: 41,
id: 58,
name: "Dark Knight",
price: 12e3,
scale: 120,
description: "restores health when you deal damage",
healD: .4
},
[27]: {
index: 42,
id: 27,
name: "Scavenger Gear",
price: 15e3,
scale: 120,
description: "earn double points for each kill",
kScrM: 2
},
[40]: {
index: 43,
id: 40,
name: "Tank Gear",
price: 15e3,
scale: 120,
description: "increased damage to buildings but slower movement",
spdMult: .3,
bDmg: 3.3
},
[52]: {
index: 44,
id: 52,
name: "Thief Gear",
price: 15e3,
scale: 120,
description: "steal half of a players gold when you kill them",
goldSteal: .5
},
[55]: {
index: 45,
id: 55,
name: "Bloodthirster",
price: 2e4,
scale: 120,
description: "Restore Health when dealing damage. And increased damage",
healD: .25,
dmgMultO: 1.2
},
[56]: {
index: 46,
id: 56,
name: "Assassin Gear",
price: 2e4,
scale: 120,
description: "Go invisible when not moving. Can't eat. Increased speed",
noEat: true,
spdMult: 1.1,
invisTimer: 1e3
}
};
const Accessories = {
[0]: {
index: 0,
id: 0,
name: "Unequip",
dontSell: true,
price: 0,
scale: 0,
xOffset: 0,
description: "None"
},
[12]: {
index: 1,
id: 12,
name: "Snowball",
price: 1e3,
scale: 105,
xOffset: 18,
description: "no effect"
},
[9]: {
index: 2,
id: 9,
name: "Tree Cape",
price: 1e3,
scale: 90,
description: "no effect"
},
[10]: {
index: 3,
id: 10,
name: "Stone Cape",
price: 1e3,
scale: 90,
description: "no effect"
},
[3]: {
index: 4,
id: 3,
name: "Cookie Cape",
price: 1500,
scale: 90,
description: "no effect"
},
[8]: {
index: 5,
id: 8,
name: "Cow Cape",
price: 2e3,
scale: 90,
description: "no effect"
},
[11]: {
index: 6,
id: 11,
name: "Monkey Tail",
price: 2e3,
scale: 97,
xOffset: 25,
description: "Super speed but reduced damage",
spdMult: 1.35,
dmgMultO: .2
},
[17]: {
index: 7,
id: 17,
name: "Apple Basket",
price: 3e3,
scale: 80,
xOffset: 12,
description: "slowly regenerates health over time",
healthRegen: 1
},
[6]: {
index: 8,
id: 6,
name: "Winter Cape",
price: 3e3,
scale: 90,
description: "no effect"
},
[4]: {
index: 9,
id: 4,
name: "Skull Cape",
price: 4e3,
scale: 90,
description: "no effect"
},
[5]: {
index: 10,
id: 5,
name: "Dash Cape",
price: 5e3,
scale: 90,
description: "no effect"
},
[2]: {
index: 11,
id: 2,
name: "Dragon Cape",
price: 6e3,
scale: 90,
description: "no effect"
},
[1]: {
index: 12,
id: 1,
name: "Super Cape",
price: 8e3,
scale: 90,
description: "no effect"
},
[7]: {
index: 13,
id: 7,
name: "Troll Cape",
price: 8e3,
scale: 90,
description: "no effect"
},
[14]: {
index: 14,
id: 14,
name: "Thorns",
price: 1e4,
scale: 115,
xOffset: 20,
description: "no effect"
},
[15]: {
index: 15,
id: 15,
name: "Blockades",
price: 1e4,
scale: 95,
xOffset: 15,
description: "no effect"
},
[20]: {
index: 16,
id: 20,
name: "Devils Tail",
price: 1e4,
scale: 95,
xOffset: 20,
description: "no effect"
},
[16]: {
index: 17,
id: 16,
name: "Sawblade",
price: 12e3,
scale: 90,
spin: true,
xOffset: 0,
description: "deal damage to players that damage you",
dmg: .15
},
[13]: {
index: 18,
id: 13,
name: "Angel Wings",
price: 15e3,
scale: 138,
xOffset: 22,
description: "slowly regenerates health over time",
healthRegen: 3
},
[19]: {
index: 19,
id: 19,
name: "Shadow Wings",
price: 15e3,
scale: 138,
xOffset: 22,
description: "increased movement speed",
spdMult: 1.1
},
[18]: {
index: 20,
id: 18,
name: "Blood Wings",
price: 2e4,
scale: 178,
xOffset: 26,
description: "restores health when you deal damage",
healD: .2
},
[21]: {
index: 21,
id: 21,
name: "Corrupt X Wings",
price: 2e4,
scale: 178,
xOffset: 26,
description: "deal damage to players that damage you",
dmg: .25
}
};
const store = [ Hats, Accessories ];
class DataHandler {
static isWeaponType(type) {
return type <= 1;
}
static isItemType(type) {
return type >= 2;
}
static getStore(type) {
return store[type];
}
static getStoreItem(type, id) {
switch (type) {
case 0:
return Hats[id];
case 1:
return Accessories[id];
default:
throw new Error(`getStoreItem Error: type "${type}" is not defined`);
}
}
static getProjectile(id) {
return Projectiles[Weapons[id].projectile];
}
static isWeapon(id) {
return void 0 !== Weapons[id];
}
static isItem(id) {
return void 0 !== Items[id];
}
static isPrimary(id) {
return null !== id && 0 === Weapons[id].itemType;
}
static isSecondary(id) {
return null !== id && 1 === Weapons[id].itemType;
}
static isMelee(id) {
return null !== id && "damage" in Weapons[id];
}
static isAttackable(id) {
return null !== id && "range" in Weapons[id];
}
static isShootable(id) {
return null !== id && "projectile" in Weapons[id];
}
static isPlaceable(id) {
return -1 !== id && "itemGroup" in Items[id];
}
static isHealable(id) {
return "restore" in Items[id];
}
static isDestroyable(id) {
return "health" in Items[id];
}
}
const utility_DataHandler = DataHandler;
const StoreHandler = new class StoreHandler {
isOpened=false;
store=[ {
previous: -1,
current: -1,
list: new Map
}, {
previous: -1,
current: -1,
list: new Map
} ];
currentType=0;
isRightStore(type) {
return this.isOpened && this.currentType === type;
}
createStore(type) {
const storeContainer = document.createElement("div");
storeContainer.id = "storeContainer";
storeContainer.style.display = "none";
const button = document.createElement("div");
button.id = "toggleStoreType";
button.textContent = 0 === type ? "Hats" : "Accessories";
button.onmousedown = () => {
this.currentType = 0 === this.currentType ? 1 : 0;
button.textContent = 0 === this.currentType ? "Hats" : "Accessories";
if (this.isOpened) {
this.fillStore(this.currentType);
}
};
storeContainer.appendChild(button);
const itemHolder = document.createElement("div");
itemHolder.id = "itemHolder";
storeContainer.appendChild(itemHolder);
itemHolder.addEventListener("wheel", (event => {
event.preventDefault();
const scale = 50 * Math.sign(event.deltaY);
itemHolder.scroll(0, itemHolder.scrollTop + scale);
}));
const {gameUI} = UI_GameUI.getElements();
gameUI.appendChild(storeContainer);
}
getTextEquip(type, id, price) {
const {list, current} = this.store[type];
if (current === id) {
return "Unequip";
}
if (list.has(id) || 0 === price) {
return "Equip";
}
return "Buy";
}
generateStoreElement(type, id, name, price, isTop) {
const srcType = [ "hats/hat", "accessories/access" ];
const src = [ srcType[type], id ];
if (isTop) {
src.push("p");
}
const html = `\n
\n

\n
${name}\n
${this.getTextEquip(type, id, price)}
\n
\n `;
const div = document.createElement("div");
div.innerHTML = html;
const equipButton = div.querySelector(".equipButton");
equipButton.onmousedown = () => {
myClient.ModuleHandler.equip(type, id, true, true);
};
return div.firstElementChild;
}
fillStore(type) {
const {itemHolder} = UI_GameUI.getElements();
itemHolder.innerHTML = "";
const items = Settings.storeItems[type];
for (const id of items) {
const item = utility_DataHandler.getStoreItem(type, id);
const element = this.generateStoreElement(type, id, item.name, item.price, "topSprite" in item);
itemHolder.appendChild(element);
}
}
handleEquipUpdate(type, prev, curr, isBuy) {
if (!this.isRightStore(type)) {
return;
}
const current = document.querySelector(`.equipButton[data-id="${curr}"]`);
if (null !== current) {
current.textContent = isBuy ? "Equip" : "Unequip";
}
if (!isBuy && -1 !== prev) {
const previous = document.querySelector(`.equipButton[data-id="${prev}"]`);
if (null !== previous) {
previous.textContent = "Equip";
}
}
}
updateStoreState(type, action, id) {
const store = this.store[type];
if (0 === action) {
store.previous = store.current;
store.current = id;
const {previous, current, list} = store;
list.set(previous, 0);
list.set(current, 1);
this.handleEquipUpdate(type, store.previous, id, false);
} else {
store.list.set(id, 0);
this.handleEquipUpdate(type, store.previous, id, true);
}
}
closeStore() {
const {storeContainer, itemHolder} = UI_GameUI.getElements();
itemHolder.innerHTML = "";
storeContainer.style.display = "none";
this.isOpened = false;
}
openStore() {
UI_GameUI.closePopups();
const {storeContainer} = UI_GameUI.getElements();
this.fillStore(this.currentType);
storeContainer.style.display = "";
storeContainer.classList.remove("closedItem");
this.isOpened = true;
}
toggleStore() {
const {storeContainer, itemHolder} = UI_GameUI.getElements();
if (this.isOpened) {
itemHolder.innerHTML = "";
} else {
UI_GameUI.closePopups();
this.fillStore(this.currentType);
}
storeContainer.style.display = "none" === storeContainer.style.display ? "" : "none";
this.isOpened = !this.isOpened;
}
init() {
this.createStore(0);
}
};
const UI_StoreHandler = StoreHandler;
const styles = '@import"https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;600;800&display=swap";@keyframes shine{0%{background-position:-200% 0}100%{background-position:200% 0}}@keyframes ripple{from{opacity:1;transform:scale(0)}to{opacity:0;transform:scale(2)}}@keyframes toclose{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(0)}}@keyframes toopen{from{opacity:0;transform:scale(0)}to{opacity:1;transform:scale(1)}}@keyframes appear{from{opacity:0}to{opacity:1}}header{display:flex;justify-content:space-between;align-items:center;height:45px;background:#1A0000!important;padding:10px;border-radius:12px;border:1px solid #ff6b6b;box-shadow:0 0 15px rgba(255,107,107,0.7);background-image:linear-gradient(90deg, #1A0000 0%, #330000 15%, rgba(255,107,107,0.5) 45%, #ff6b6b 50%, rgba(255,107,107,0.5) 55%, #330000 85%, #1A0000 100%)!important;background-size:200% 100%!important;animation:shine 8s linear infinite!important}header h1{font-size:2.3em;color:#D4AF37}header #credits{display:flex;justify-content:space-between;gap:10px;height:45px}header #credits p{margin-top:auto;color:#E0D1B7}header #logo{display:block;width:auto;height:100%;scale:1.2}header #close-button{display:block;fill:#B00000;cursor:pointer;width:auto;height:100%}header #close-button:hover{fill:#FF6B6B}#navbar-container{display:flex;flex-direction:column;background:#120000;padding:10px;border-radius:12px;border:1px solid #330000}#navbar-container .open-menu{position:relative;width:8.5em;height:3.5em;background:#280808;font-weight:800;font-size:1.3em;overflow:hidden;transition:background 100ms}#navbar-container .open-menu:hover{background:#551515}#navbar-container .open-menu.active{background:#551515;pointer-events:none}#navbar-container .open-menu.bottom-align{margin-top:auto}#navbar-container .open-menu .ripple{position:absolute;z-index:5;background:rgba(255,107,107,.4);top:0;left:0;border-radius:50%;opacity:0;animation:ripple 1100ms;pointer-events:none}#page-container{width:100%;height:100%;overflow-y:scroll}.menu-page{background:#120000;padding:10px;border-radius:8px;display:none;border:1px solid #330000}.menu-page.opened{display:block}.menu-page h1{font-size:2.8em;color:#E83030;text-shadow: 0 0 5px rgba(255, 255, 255, 0.1)}.menu-page>.section{margin-top:20px;background:#0D0000;padding:15px;border-radius:12px;border:1px solid #330000}.menu-page>.section .section-title{font-weight:800;font-size:1.8em;color:#E0D1B7;margin-bottom:10px}.menu-page>.section .section-content.split{display:flex;column-gap:30px}.menu-page>.section .section-content .content-split{width:100%;display:flex;flex-direction:column;row-gap:10px}.menu-page>.section .section-content .content-option{display:flex;justify-content:space-between;align-items:center}.menu-page>.section .section-content .content-option.centered{justify-content:center}.menu-page>.section .section-content .content-option .option-title{font-weight:800;font-size:1.4em;color:#E0D1B7;transition:color 100ms}.menu-page>.section .section-content .content-option .option-content{display:flex;justify-content:center;align-items:center;column-gap:10px}.menu-page>.section .section-content .content-option .disconnect-button{width:30px;height:30px;cursor:pointer;fill:rgba(176,0,0,0.5);transition:fill 100ms}.menu-page>.section .section-content .content-option:hover .option-title{color:#FF6B6B}.menu-page>.section .section-content .content-option:hover .disconnect-button{fill:#FF4444}.menu-page>.section .section-content .content-option:hover .disconnect-button:hover{fill:#FF6B6B}.menu-page>.section .section-content .text{display:flex;justify-content:left;gap:10px}.menu-page>.section .section-content .text .text-value{color:#E0D1B7;font-weight:800;font-size:1.5em}.menu-page>.section .section-content .option-button{width:117px;height:52px;background:#280808;border:5px solid #550000;border-radius:8px;font-weight:800;font-size:1.1em;color:#E0D1B7;transition:background 100ms,border-color 100ms}.menu-page>.section .section-content .option-button:hover{background:#401010;border-color:#7A0000}.menu-page>.section .section-content .hotkeyInput{width:90px;height:40px;background:#280808;border:5px solid #550000;border-radius:8px;font-weight:800;font-size:1.1em;color:#E0D1B7;display:flex;justify-content:center;align-items:center;transition:background 100ms,border-color 100ms,color 100ms}.menu-page>.section .section-content .hotkeyInput:hover{background:#401010;border-color:#7A0000}.menu-page>.section .section-content .hotkeyInput.active{background:#751b1b;border-color:#ff6b6b;box-shadow:0 0 10px rgba(255,107,107,0.8);}.menu-page>.section .section-content .hotkeyInput.red{background:#7a3131;border-color:#672929;color:#E0D1B7}.menu-page>.section .section-content .hotkeyInput.red:hover{background:#893333;border-color:#712d2d}.menu-page>.section .section-content .hotkeyInput.red.active{background:#923939;border-color:#712d2d}.menu-page>.section .section-content .switch-checkbox{position:relative;width:90px;height:40px}.menu-page>.section .section-content .switch-checkbox input{width:0;height:0;opacity:0}.menu-page>.section .section-content .switch-checkbox input:checked+span{background:#B00000;box-shadow:0px -20px 0px 0px #900000 inset, 0 0 8px rgba(255,107,107,0.6);}.menu-page>.section .section-content .switch-checkbox input:checked+span:before{transform:translateX(50px) scale(0.7);background:#E0D1B7}.menu-page>.section .section-content .switch-checkbox span{position:absolute;cursor:pointer;top:0;left:0;bottom:0;right:0;width:100%;height:100%;display:flex;align-items:center;background:#280808;border-radius:8px;box-shadow:0px -20px 0px 0px #1A0505 inset}.menu-page>.section .section-content .switch-checkbox span:before{position:absolute;content:"";transform:scale(0.7);transition:transform 300ms;width:40px;height:40px;border-radius:8px;background:#E0D1B7}.menu-page>.section .section-content input[id][type=color]{width:60px;height:33.3333333333px;outline:none;border:none;padding:3px;margin:0;background:#280808;border-radius:8px;cursor:pointer}.menu-page>.section .section-content .reset-color{background:var(--data-color);width:10px;height:10px;border-radius:50%}.menu-page>.section .section-content .slider{position:relative;display:flex;align-items:center;justify-content:space-between;gap:10px}.menu-page>.section .section-content .slider input{appearance:none;outline:none;cursor:pointer;padding:0;margin:0;border:none;width:144px;height:30px;background:#550000;box-shadow:0px -15px 0px 0px #330000 inset;border-radius:8px}.menu-page>.section .section-content .slider input::-webkit-slider-thumb{-webkit-appearance:none;transform:scale(0.7);width:30px;height:30px;background:#FF4444;border-radius:8px}.menu-page>.section .section-content .slider .slider-value{color:#E0D1B7;font-weight:800;font-size:1.4em;opacity:1}html,body{margin:0;padding:0;scrollbar-width:thin;scrollbar-track-color:#280808;scrollbar-face-color:#550000}*{font-family:"Noto Sans",sans-serif;color:#f1f1f1}h1{font-weight:800;margin:0}h2{margin:0}p{font-weight:800;font-size:1.1rem;margin:0;color:#E0D1B7}button{border:none;outline:none;cursor:pointer}#menu-container{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:1280px;height:720px;display:flex;justify-content:center;align-items:center}#menu-container.transparent #menu-wrapper{background:rgba(8,0,0,.5882352941)}#menu-container.transparent header,#menu-container.transparent .menu-page,#menu-container.transparent #navbar-container{background:rgba(18,0,0,.5882352941)}#menu-container.transparent .section{background:rgba(13,0,0,.462745098)}#menu-container.transparent .open-menu{background:rgba(13,0,0,.462745098)}#menu-container.transparent .open-menu:hover,#menu-container.transparent .open-menu.active{background:rgba(85,21,21,.6039215686)}#menu-wrapper{position:relative;display:flex;flex-direction:column;row-gap:10px;width:85%;height:85%;padding:10px;box-shadow:0 0 40px rgba(255,107,107,0.5), inset 0 0 15px rgba(255,107,107,0.2);border-radius:18px;background:linear-gradient(135deg, #100000 0%, #080000 100%)}#menu-wrapper.toclose{animation:250ms cubic-bezier(.2, .8, .2, 1) toclose forwards}#menu-wrapper.toopen{animation:250ms cubic-bezier(.2, .8, .2, 1) toopen forwards}main{display:flex;column-gap:10px;width:100%;height:calc(100% - 75px)}::-webkit-scrollbar{width:12px}::-webkit-scrollbar-track{background:#280808;border-radius:8px}::-webkit-scrollbar-thumb{background:#550000;border-radius:8px}.icon{width:50px;height:50px} .gmx-grid {display: grid;grid-template-columns: repeat(3, 1fr);gap: 6px;margin-bottom: 10px;}'; const Game = "#iframe-page-container{position:absolute;z-index:10;top:0;left:0;bottom:0;right:0;width:100%;height:100%;border:none}#promoImgHolder,#gameName,#rightCardHolder,#mobileDownloadButtonContainer,#touch-controls-left,#touch-controls-right,#touch-controls-fullscreen,#partyButton,#adCard,#joinPartyButton,#guideCard>*:not(#serverBrowser,#altServer,#skinColorHolder,.settingRadio){display:none !important}#setupCard{display:flex;flex-direction:column;row-gap:10px;background:rgba(189,189,189,.2666666667);box-shadow:none}#linksContainer2{background:#505050}#setupCard>*{margin:0 !important}.actionBarItem{position:relative}.itemCounter{position:absolute;top:3px;right:3px;font-size:.95em;color:#fff;text-shadow:#3d3f42 2px 0px 0px,#3d3f42 1.75517px .958851px 0px,#3d3f42 1.0806px 1.68294px 0px,#3d3f42 .141474px 1.99499px 0px,#3d3f42 -0.832294px 1.81859px 0px,#3d3f42 -1.60229px 1.19694px 0px,#3d3f42 -1.97998px .28224px 0px,#3d3f42 -1.87291px -0.701566px 0px,#3d3f42 -1.30729px -1.5136px 0px,#3d3f42 -0.421592px -1.95506px 0px,#3d3f42 .567324px -1.91785px 0px,#3d3f42 1.41734px -1.41108px 0px,#3d3f42 1.92034px -0.558831px 0px}.itemCounter{position:absolute;top:3px;right:3px;font-size:.95em;color:#fff;text-shadow:#3d3f42 2px 0px 0px,#3d3f42 1.75517px .958851px 0px,#3d3f42 1.0806px 1.68294px 0px,#3d3f42 .141474px 1.99499px 0px,#3d3f42 -0.832294px 1.81859px 0px,#3d3f42 -1.60229px 1.19694px 0px,#3d3f42 -1.97998px .28224px 0px,#3d3f42 -1.87291px -0.701566px 0px,#3d3f42 -1.30729px -1.5136px 0px,#3d3f42 -0.421592px -1.95506px 0px,#3d3f42 .567324px -1.91785px 0px,#3d3f42 1.41734px -1.41108px 0px,#3d3f42 1.92034px -0.558831px 0px}.itemCounter.hidden{display:none}#onetrust-consent-sdk{display:none !important}#topInfoHolder{display:flex;flex-direction:column;justify-content:right;align-items:flex-end;gap:10px}#topInfoHolder>div:not([class]):not([id]){display:none}#killCounter,#totalKillCounter{position:static;margin:0;background-image:url(../img/icons/skull.png)}.closedItem{display:none !important}#storeContainer{display:flex;flex-direction:column;gap:10px;max-width:400px;width:100%;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}#toggleStoreType{display:flex;justify-content:center;align-items:center;padding:10px;background-color:rgba(0,0,0,.15);color:#fff;border-radius:4px;cursor:pointer;font-size:20px;pointer-events:all}#itemHolder{background-color:rgba(0,0,0,.15);max-height:200px;height:100%;padding:10px;overflow-y:scroll;border-radius:4px;pointer-events:all}#itemHolder::-webkit-scrollbar{display:none;width:0;height:0;background:rgba(0,0,0,0)}.storeItemContainer{display:flex;align-items:center;gap:10px;padding:5px;height:50px;box-sizing:border-box;overflow:hidden}.storeHat{display:flex;justify-content:center;align-items:center;width:45px;height:45px;margin-top:-5px;pointer-events:none}.storeItemName{color:#fff;font-size:20px}.equipButton{margin-left:auto;color:#80eefc;cursor:pointer;font-size:35px}#bottomContainer{bottom:10px}#scriptName{position:absolute;top:0;left:0}";
code = '';
const Header = code;
Navbar_code = '';
const Navbar = Navbar_code;
Keybinds_code = '';
const Keybinds = Keybinds_code;
Combat_code = '';
const Combat = Combat_code;
Visuals_code = '';
const Visuals = Visuals_code;
Misc_code = '';
const Misc = Misc_code;
Devtool_code = '';
const Devtool = Devtool_code;
Bots_code = '';
const Bots = Bots_code;
Credits_code = ``;
const Credits = Credits_code;
class Logger {
static log=console.log;
static error=console.error;
static timers=new Map;
static start(label) {
this.timers.set(label, performance.now());
}
static end(label, ...args) {
if (this.timers.has(label)) {
this.log(`${label}: ${performance.now() - this.timers.get(label)}`, ...args);
}
this.timers.delete(label);
}
}
const toBytes = b64 => Uint8Array.from(atob(b64), (c => c.charCodeAt(0)));
class Altcha {
code=null;
coreCount=Math.min(16, navigator.hardwareConcurrency || 4);
tokenEncode="IWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2xldCBlPW5ldyBUZXh0RW5jb2Rlcjthc3luYyBmdW5jdGlvbiB0KHQsbixyKXt2YXIgbDtyZXR1cm4gbD1hd2FpdCBjcnlwdG8uc3VidGxlLmRpZ2VzdChyLnRvVXBwZXJDYXNlKCksZS5lbmNvZGUodCtuKSksWy4uLm5ldyBVaW50OEFycmF5KGwpXS5tYXAoZT0+ZS50b1N0cmluZygxNikucGFkU3RhcnQoMiwiMCIpKS5qb2luKCIiKX1mdW5jdGlvbiBuKGUsdD0xMil7bGV0IG49bmV3IFVpbnQ4QXJyYXkodCk7Zm9yKGxldCByPTA7cjx0O3IrKyluW3JdPWUlMjU2LGU9TWF0aC5mbG9vcihlLzI1Nik7cmV0dXJuIG59YXN5bmMgZnVuY3Rpb24gcih0LHI9IiIsbD0xZTYsbz0wKXtsZXQgYT0iQUVTLUdDTSIsYz1uZXcgQWJvcnRDb250cm9sbGVyLGk9RGF0ZS5ub3coKSx1PShhc3luYygpPT57Zm9yKGxldCBlPW87ZTw9bCYmIWMuc2lnbmFsLmFib3J0ZWQmJnMmJnc7ZSsrKXRyeXtsZXQgdD1hd2FpdCBjcnlwdG8uc3VidGxlLmRlY3J5cHQoe25hbWU6YSxpdjpuKGUpfSxzLHcpO2lmKHQpcmV0dXJue2NsZWFyVGV4dDpuZXcgVGV4dERlY29kZXIoKS5kZWNvZGUodCksdG9vazpEYXRlLm5vdygpLWl9fWNhdGNoe31yZXR1cm4gbnVsbH0pKCkscz1udWxsLHc9bnVsbDt0cnl7dz1mdW5jdGlvbiBlKHQpe2xldCBuPWF0b2IodCkscj1uZXcgVWludDhBcnJheShuLmxlbmd0aCk7Zm9yKGxldCBsPTA7bDxuLmxlbmd0aDtsKyspcltsXT1uLmNoYXJDb2RlQXQobCk7cmV0dXJuIHJ9KHQpO2xldCBmPWF3YWl0IGNyeXB0by5zdWJ0bGUuZGlnZXN0KCJTSEEtMjU2IixlLmVuY29kZShyKSk7cz1hd2FpdCBjcnlwdG8uc3VidGxlLmltcG9ydEtleSgicmF3IixmLGEsITEsWyJkZWNyeXB0Il0pfWNhdGNoe3JldHVybntwcm9taXNlOlByb21pc2UucmVqZWN0KCksY29udHJvbGxlcjpjfX1yZXR1cm57cHJvbWlzZTp1LGNvbnRyb2xsZXI6Y319bGV0IGw7b25tZXNzYWdlPWFzeW5jIGU9PntsZXR7dHlwZTpuLHBheWxvYWQ6byxzdGFydDphLG1heDpjfT1lLmRhdGEsaT1udWxsO2lmKCJhYm9ydCI9PT1uKWwmJmwuYWJvcnQoKSxsPXZvaWQgMDtlbHNlIGlmKCJ3b3JrIj09PW4pe2lmKCJvYmZ1c2NhdGVkImluIG8pe2xldHtrZXk6dSxvYmZ1c2NhdGVkOnN9PW98fHt9O2k9YXdhaXQgcihzLHUsYyxhKX1lbHNle2xldHthbGdvcml0aG06dyxjaGFsbGVuZ2U6ZixzYWx0OmR9PW98fHt9O2k9ZnVuY3Rpb24gZShuLHIsbD0iU0hBLTI1NiIsbz0xZTYsYT0wKXtsZXQgYz1uZXcgQWJvcnRDb250cm9sbGVyLGk9RGF0ZS5ub3coKSx1PShhc3luYygpPT57Zm9yKGxldCBlPWE7ZTw9byYmIWMuc2lnbmFsLmFib3J0ZWQ7ZSsrKXtsZXQgdT1hd2FpdCB0KHIsZSxsKTtpZih1PT09bilyZXR1cm57bnVtYmVyOmUsdG9vazpEYXRlLm5vdygpLWl9fXJldHVybiBudWxsfSkoKTtyZXR1cm57cHJvbWlzZTp1LGNvbnRyb2xsZXI6Y319KGYsZCx3LGMsYSl9bD1pLmNvbnRyb2xsZXIsaS5wcm9taXNlLnRoZW4oZT0+e3NlbGYucG9zdE1lc3NhZ2UoZSYmey4uLmUsd29ya2VyOiEwfSl9KX19fSgpOw==";
workerBlob=new Blob([ toBytes(this.tokenEncode) ], {
type: "text/javascript;charset=utf-8"
});
static createPayload(data, result) {
return btoa(JSON.stringify({
algorithm: data.algorithm,
challenge: data.challenge,
number: result.number,
salt: data.salt,
signature: data.signature,
test: !!data || void 0,
took: result.took
}));
}
createWorker(name = "alt_worker") {
try {
const url = URL.createObjectURL(this.workerBlob);
const worker = new Worker(url, {
name
});
worker.addEventListener("error", (() => URL.revokeObjectURL(url)));
return worker;
} catch (e) {
return new Worker(`data:text/javascript;base64,${this.tokenEncode}`, {
name
});
}
}
async fetchChallenge() {
const res = await fetch("https://api.moomoo.io/verify");
if (!res.ok) {
throw new Error("Failed to fetch challenge.");
}
return res.json();
}
async getWorkerSolution(task, total, count = this.coreCount) {
const workerCount = Math.min(16, Math.max(1, count));
const workers = Array.from({
length: workerCount
}, (() => this.createWorker()));
const chunkSize = Math.ceil(total / workerCount);
const results = await Promise.all(workers.map(((worker, index) => {
const start = index * chunkSize;
return new Promise((resolve => {
worker.onmessage = msg => {
if (msg.data) {
workers.forEach((w => {
if (w !== worker) {
w.postMessage({
type: "abort"
});
}
}));
}
resolve(msg.data ?? null);
};
worker.onerror = () => resolve(null);
const message = {
type: "work",
payload: task,
start,
max: start + chunkSize
};
worker.postMessage(message);
}));
})));
workers.forEach((worker => worker.terminate()));
return results.find((r => !!r)) ?? null;
}
async validateChallenge(data) {
const solution = await this.getWorkerSolution(data, data.maxnumber);
if (!solution) {
throw new Error("Failed to solve challenge.");
}
return {
challengeData: data,
solution
};
}
async generate() {
try {
const challengeData = await this.fetchChallenge();
const {solution} = await this.validateChallenge(challengeData);
const encoded = Altcha.createPayload(challengeData, solution);
this.code = `alt:${encoded}`;
return encodeURIComponent(this.code);
} catch (error) {
console.error("Token generation failed:", error);
throw error;
}
}
}
const altcha = new Altcha;
const createSocket = async () => {
const token = await altcha.generate();
const socket = myClient.connection.socket;
const origin = new URL(socket.url).origin;
const url = origin + "/?token=" + token;
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";
return ws;
};
const modules_createSocket = createSocket;
class ObjectItem {
id;
position;
angle;
scale;
constructor(id, x, y, angle, scale) {
this.id = id;
this.position = {
current: new modules_Vector(x, y)
};
this.angle = angle;
this.scale = scale;
}
get hitScale() {
return this.scale;
}
}
class Resource extends ObjectItem {
type;
layer;
constructor(id, x, y, angle, scale, type) {
super(id, x, y, angle, scale);
this.type = type;
this.layer = 0 === type ? 3 : 2 === type ? 0 : 2;
}
formatScale(scaleMult = 1) {
const reduceScale = 0 === this.type || 1 === this.type ? .6 * scaleMult : 1;
return this.scale * reduceScale;
}
get collisionScale() {
return this.formatScale();
}
get placementScale() {
return this.formatScale(.6);
}
get isCactus() {
return 1 === this.type && pointInDesert(this.position.current);
}
}
class PlayerObject extends ObjectItem {
type;
ownerID;
collisionDivider;
health;
maxHealth;
reload=-1;
maxReload=-1;
isDestroyable;
seenPlacement=false;
layer;
itemGroup;
constructor(id, x, y, angle, scale, type, ownerID) {
super(id, x, y, angle, scale);
this.type = type;
this.ownerID = ownerID;
const item = Items[type];
this.collisionDivider = "colDiv" in item ? item.colDiv : 1;
this.health = "health" in item ? item.health : Infinity;
this.maxHealth = this.health;
this.isDestroyable = Infinity !== this.maxHealth;
if (17 === item.id) {
this.reload = item.shootRate;
this.maxReload = this.reload;
}
this.layer = ItemGroups[item.itemGroup].layer;
this.itemGroup = item.itemGroup;
}
formatScale(placeCollision = false) {
return this.scale * (placeCollision ? 1 : this.collisionDivider);
}
get collisionScale() {
return this.formatScale();
}
get placementScale() {
const item = Items[this.type];
if (21 === item.id) {
return item.blocker;
}
return this.scale;
}
}
class SpatialHashGrid {
cellSize;
cells;
constructor(cellSize) {
this.cellSize = cellSize;
this.cells = [];
}
hashPosition(x, y) {
const cellX = Math.floor(x / this.cellSize);
const cellY = Math.floor(y / this.cellSize);
return [ cellX, cellY ];
}
clear() {
this.cells.length = 0;
}
insert(object) {
const {x, y} = object.position.current;
const [cellX, cellY] = this.hashPosition(x, y);
if (!this.cells[cellX]) {
this.cells[cellX] = [];
}
if (!this.cells[cellX][cellY]) {
this.cells[cellX][cellY] = [];
}
this.cells[cellX][cellY].push(object);
}
retrieve(position, radius) {
const {x, y} = position;
const [startX, startY] = this.hashPosition(x - radius, y - radius);
const [endX, endY] = this.hashPosition(x + radius, y + radius);
const results = [];
for (let cellX = startX - 1; cellX <= endX + 1; cellX++) {
for (let cellY = startY - 1; cellY <= endY + 1; cellY++) {
if (this.cells[cellX] && this.cells[cellX][cellY]) {
const objects = this.cells[cellX][cellY];
for (const object of objects) {
results.push(object);
}
}
}
}
return results;
}
remove(object) {
const {x, y} = object.position.current;
const [cellX, cellY] = this.hashPosition(x, y);
if (this.cells[cellX] && this.cells[cellX][cellY]) {
const objects = this.cells[cellX][cellY];
const index = objects.indexOf(object);
if (-1 !== index) {
const lastIndex = objects.length - 1;
if (index === lastIndex) {
objects.pop();
} else {
objects[index] = objects.pop();
}
return true;
}
}
return false;
}
}
const modules_SpatialHashGrid = SpatialHashGrid;
class EnemyManager {
client;
enemiesGrid=new modules_SpatialHashGrid(100);
enemies=[];
trappedEnemies=new Set;
dangerousEnemies=[];
_nearestEnemy=[ null, null ];
nearestMeleeReloaded=null;
nearestDangerAnimal=null;
nearestTrap=null;
nearestCollideSpike=null;
nearestTurretEntity=null;
detectedEnemy=false;
constructor(client) {
this.client = client;
}
reset() {
this.enemiesGrid.clear();
this.enemies.length = 0;
this.trappedEnemies.clear();
this.dangerousEnemies.length = 0;
this._nearestEnemy[0] = null;
this._nearestEnemy[1] = null;
this.nearestMeleeReloaded = null;
this.nearestDangerAnimal = null;
this.nearestTrap = null;
this.nearestCollideSpike = null;
this.nearestTurretEntity = null;
this.detectedEnemy = false;
}
get nearestEnemy() {
return this._nearestEnemy[0];
}
get nearestAnimal() {
return this._nearestEnemy[1];
}
isNear(enemy, nearest) {
if (null === nearest) {
return true;
}
const {myPlayer} = this.client;
const a0 = myPlayer.position.current;
const distance1 = a0.distance(enemy.position.current);
const distance2 = a0.distance(nearest.position.current);
return distance1 < distance2;
}
get nearestEntity() {
const target1 = this.nearestEnemy;
const target2 = this.nearestAnimal;
if (null === target1) {
return target2;
}
return this.isNear(target1, target2) ? target1 : target2;
}
nearestEnemyInRangeOf(range, target) {
const enemy = target || this.nearestEnemy;
return null !== enemy && this.client.myPlayer.collidingEntity(enemy, range);
}
handleNearestDanger(enemy) {
const {myPlayer, ModuleHandler} = this.client;
const extraRange = enemy.usingBoost && !enemy.isTrapped ? 400 : 100;
const range = enemy.getMaxWeaponRange() + myPlayer.hitScale + extraRange;
if (myPlayer.collidingEntity(enemy, range)) {
if (enemy.danger >= 3) {
ModuleHandler.needToHeal = true;
}
this.detectedEnemy = true;
}
}
handleDanger(enemy) {
if (enemy.dangerList.length >= 2) {
enemy.dangerList.shift();
}
const danger = enemy.canPossiblyInstakill();
enemy.dangerList.push(danger);
enemy.danger = Math.max(...enemy.dangerList);
if (0 !== enemy.danger) {
this.dangerousEnemies.push(enemy);
this.handleNearestDanger(enemy);
}
}
checkCollision(target, isOwner = false) {
target.isTrapped = false;
target.onPlatform = false;
const {ObjectManager, PlayerManager} = this.client;
const objects = ObjectManager.retrieveObjects(target.position.current, target.collisionScale);
for (const object of objects) {
if (object instanceof Resource) {
continue;
}
if (!target.collidingObject(object, 5)) {
continue;
}
const isEnemyObject = PlayerManager.isEnemyByID(object.ownerID, target);
if (15 === object.type && isEnemyObject) {
this.trappedEnemies.add(target);
target.isTrapped = true;
if (isOwner && this.isNear(target, this.nearestTrap)) {
this.nearestTrap = object;
}
} else if (18 === object.type) {
target.onPlatform = true;
} else if (2 === object.itemGroup && isEnemyObject) {
if (!isOwner && this.isNear(target, this.nearestCollideSpike)) {
const pos1 = target.position.future;
const pos2 = object.position.current;
const distance = pos1.distance(pos2);
const range = object.collisionScale + target.collisionScale;
const willCollide = distance <= range;
if (willCollide) {
this.nearestCollideSpike = target;
}
}
}
}
}
handleNearest(type, enemy) {
if (this.isNear(enemy, this._nearestEnemy[type])) {
this._nearestEnemy[type] = enemy;
if (enemy.canUseTurret && this.client.myPlayer.collidingEntity(enemy, 700)) {
this.nearestTurretEntity = enemy;
}
}
}
handleNearestMelee(enemy) {
const {myPlayer, ModuleHandler} = this.client;
const range = enemy.getMaxWeaponRange() + myPlayer.hitScale + 60;
const angle = ModuleHandler.getMoveAngle();
if (!enemy.meleeReloaded()) {
return;
}
if (!myPlayer.collidingEntity(enemy, range)) {
return;
}
if (!myPlayer.runningAwayFrom(enemy, angle)) {
return;
}
if (!this.isNear(enemy, this.nearestMeleeReloaded)) {
return;
}
this.nearestMeleeReloaded = enemy;
}
handleNearestDangerAnimal(animal) {
const {myPlayer} = this.client;
if (!animal.isDanger) {
return;
}
if (!myPlayer.collidingEntity(animal, animal.collisionRange)) {
return;
}
if (!this.isNear(animal, this.nearestDangerAnimal)) {
return;
}
this.nearestDangerAnimal = animal;
}
handleEnemies(players, animals) {
this.reset();
const {myPlayer} = this.client;
this.checkCollision(myPlayer, true);
for (let i = 0; i < players.length; i++) {
const player = players[i];
if (myPlayer.isEnemyByID(player.id)) {
this.enemiesGrid.insert(player);
this.enemies.push(player);
this.checkCollision(player);
this.handleDanger(player);
this.handleNearest(0, player);
this.handleNearestMelee(player);
}
}
for (let i = 0; i < animals.length; i++) {
const animal = animals[i];
this.handleNearest(1, animal);
this.handleNearestDangerAnimal(animal);
}
}
}
const Managers_EnemyManager = EnemyManager;
class LeaderboardManager {
client;
list=new Set;
constructor(client) {
this.client = client;
}
updatePlayer(id, nickname, gold) {
const owner = this.client.PlayerManager.playerData.get(id) || this.client.PlayerManager.createPlayer({
id,
nickname
});
this.list.add(owner);
owner.totalGold = gold;
owner.inLeaderboard = true;
}
update(data) {
for (const player of this.list) {
player.inLeaderboard = false;
}
this.list.clear();
for (let i = 0; i < data.length; i += 3) {
const id = data[i + 0];
const nickname = data[i + 1];
const gold = data[i + 2];
this.updatePlayer(id, nickname, gold);
}
}
}
const Managers_LeaderboardManager = LeaderboardManager;
const WeaponTypeString = [ "primary", "secondary" ];
class Entity {
id=-1;
position={
previous: new modules_Vector,
current: new modules_Vector,
future: new modules_Vector
};
angle=0;
scale=0;
setFuturePosition() {
const {previous, current, future} = this.position;
const distance = previous.distance(current);
const angle = previous.angle(current);
future.setVec(current.direction(angle, distance));
}
get collisionScale() {
return this.scale;
}
get hitScale() {
return 1.8 * this.scale;
}
client;
constructor(client) {
this.client = client;
}
colliding(object, radius) {
const {previous: a0, current: a1, future: a2} = this.position;
const b0 = object.position.current;
return a0.distance(b0) <= radius || a1.distance(b0) <= radius || a2.distance(b0) <= radius;
}
collidingObject(object, addRadius = 0, checkPrevious = true) {
const {previous: a0, current: a1, future: a2} = this.position;
const b0 = object.position.current;
const radius = this.collisionScale + object.collisionScale + addRadius;
return checkPrevious && a0.distance(b0) <= radius || a1.distance(b0) <= radius || a2.distance(b0) <= radius;
}
collidingEntity(entity, range, checkBased = false, prev = true) {
const {previous: a0, current: a1, future: a2} = this.position;
const {previous: b0, current: b1, future: b2} = entity.position;
if (checkBased) {
return prev && a0.distance(b0) <= range || a1.distance(b1) <= range || a2.distance(b2) <= range;
}
return a0.distance(b0) <= range || a0.distance(b1) <= range || a0.distance(b2) <= range || a1.distance(b0) <= range || a1.distance(b1) <= range || a1.distance(b2) <= range || a2.distance(b0) <= range || a2.distance(b1) <= range || a2.distance(b2) <= range;
}
checkCollision(itemGroup, addRadius = 0, checkEnemy = false, checkPrevious = true) {
const {ObjectManager} = this.client;
const objects = ObjectManager.retrieveObjects(this.position.current, this.collisionScale);
for (const object of objects) {
const matchItem = object instanceof PlayerObject && object.itemGroup === itemGroup;
const isCactus = object instanceof Resource && 2 === itemGroup && object.isCactus;
if (matchItem || isCactus) {
if (checkEnemy && !ObjectManager.isEnemyObject(object)) {
continue;
}
if (this.collidingObject(object, addRadius, checkPrevious)) {
return true;
}
}
}
return false;
}
runningAwayFrom(entity, angle) {
if (null === angle) {
return false;
}
const pos1 = this.position.current;
const pos2 = entity.position.current;
const angleTo = pos1.angle(pos2);
if (getAngleDist(angle, angleTo) <= Math.PI / 2) {
return false;
}
return true;
}
}
const data_Entity = Entity;
class Player extends data_Entity {
socketID="";
currentItem=-1;
clanName=null;
isLeader=false;
nickname="unknown";
skinID=0;
scale=35;
hatID=0;
accessoryID=0;
totalStorePrice=0;
storeList=[ new Set, new Set ];
previousHealth=100;
currentHealth=100;
tempHealth=100;
maxHealth=100;
globalInventory={};
weapon={};
variant={};
reload={
primary: {},
secondary: {},
turret: {}
};
objects=new Set;
totalGold=0;
inLeaderboard=false;
newlyCreated=true;
usingBoost=false;
isTrapped=false;
onPlatform=false;
isFullyUpgraded=false;
potentialDamage=0;
foundProjectiles=new Map;
dangerList=[];
danger=0;
constructor(client) {
super(client);
this.init();
}
hasFound(projectile) {
const key = projectile.type;
return this.foundProjectiles.has(key);
}
addFound(projectile) {
const key = projectile.type;
if (!this.foundProjectiles.has(key)) {
this.foundProjectiles.set(key, []);
}
const list = this.foundProjectiles.get(key);
list.push(projectile);
}
resetReload() {
const {primary, secondary} = this.weapon;
const primarySpeed = null !== primary ? this.getWeaponSpeed(primary) : -1;
const secondarySpeed = null !== secondary ? this.getWeaponSpeed(secondary) : -1;
const reload = this.reload;
reload.primary.current = primarySpeed;
reload.primary.max = primarySpeed;
reload.secondary.current = secondarySpeed;
reload.secondary.max = secondarySpeed;
reload.turret.current = 2500;
reload.turret.max = 2500;
}
resetGlobalInventory() {
this.globalInventory[0] = null;
this.globalInventory[1] = null;
this.globalInventory[2] = null;
this.globalInventory[3] = null;
this.globalInventory[4] = null;
this.globalInventory[5] = null;
this.globalInventory[6] = null;
this.globalInventory[7] = null;
this.globalInventory[8] = null;
this.globalInventory[9] = null;
}
init() {
this.weapon.current = 0;
this.weapon.oldCurrent = 0;
this.weapon.primary = null;
this.weapon.secondary = null;
this.variant.current = 0;
this.variant.primary = 0;
this.variant.secondary = 0;
this.resetReload();
this.resetGlobalInventory();
this.newlyCreated = true;
this.usingBoost = false;
this.isFullyUpgraded = false;
this.foundProjectiles.clear();
}
get canUseTurret() {
return 22 !== this.hatID;
}
update(id, x, y, angle, currentItem, currentWeapon, weaponVariant, clanName, isLeader, hatID, accessoryID, isSkull) {
this.id = id;
this.position.previous.setVec(this.position.current);
this.position.current.setXY(x, y);
this.setFuturePosition();
this.angle = angle;
this.currentItem = currentItem;
this.weapon.oldCurrent = this.weapon.current;
this.weapon.current = currentWeapon;
this.variant.current = weaponVariant;
this.clanName = clanName;
this.isLeader = Boolean(isLeader);
this.hatID = hatID;
this.accessoryID = accessoryID;
if (!this.storeList[0].has(hatID)) {
this.storeList[0].add(hatID);
this.totalStorePrice += Hats[hatID].price;
}
if (!this.storeList[1].has(accessoryID)) {
this.storeList[1].add(accessoryID);
this.totalStorePrice += Accessories[accessoryID].price;
}
this.newlyCreated = false;
this.potentialDamage = 0;
this.predictItems();
this.predictWeapons();
this.updateReloads();
}
updateHealth(health) {
this.previousHealth = this.currentHealth;
this.currentHealth = health;
this.tempHealth = health;
}
predictItems() {
if (-1 === this.currentItem) {
return;
}
const item = Items[this.currentItem];
this.globalInventory[item.itemType] = this.currentItem;
}
increaseReload(reload) {
reload.current = Math.min(reload.current + this.client.PlayerManager.step, reload.max);
}
updateTurretReload() {
const reload = this.reload.turret;
this.increaseReload(reload);
if (53 !== this.hatID) {
return;
}
const {ProjectileManager} = this.client;
const speed = Projectiles[1].speed;
const list = ProjectileManager.projectiles.get(speed);
if (void 0 === list) {
return;
}
const current = this.position.current;
for (let i = 0; i < list.length; i++) {
const projectile = list[i];
const distance = current.distance(projectile.position.current);
if (distance < 2) {
if (this.hasFound(projectile)) {
this.foundProjectiles.clear();
}
this.addFound(projectile);
projectile.owner = this;
reload.current = 0;
removeFast(list, i);
break;
}
}
}
updateReloads() {
this.updateTurretReload();
if (-1 !== this.currentItem) {
return;
}
const weapon = Weapons[this.weapon.current];
const type = WeaponTypeString[weapon.itemType];
const reload = this.reload[type];
this.increaseReload(reload);
if ("projectile" in weapon) {
const {ProjectileManager} = this.client;
const speedMult = this.getWeaponSpeedMult();
const type = weapon.projectile;
const speed = Projectiles[type].speed * speedMult;
const list = ProjectileManager.projectiles.get(speed);
if (void 0 === list) {
return;
}
const current = this.position.current;
for (let i = 0; i < list.length; i++) {
const projectile = list[i];
const distance = current.distance(projectile.position.current);
if (distance < 2 && this.angle === projectile.angle) {
if (this.hasFound(projectile)) {
this.foundProjectiles.clear();
}
this.addFound(projectile);
projectile.owner = this;
reload.current = 0;
reload.max = this.getWeaponSpeed(weapon.id);
removeFast(list, i);
break;
}
}
}
}
handleObjectPlacement(object) {
this.objects.add(object);
const {myPlayer, ObjectManager} = this.client;
const item = Items[object.type];
if (object.seenPlacement) {
if (17 === object.type) {
ObjectManager.resetTurret(object.id);
} else if (16 === object.type && !this.newlyCreated) {
this.usingBoost = true;
}
this.updateInventory(object.type);
}
if (myPlayer.isMyPlayerByID(this.id) && 5 === item.itemType) {
myPlayer.totalGoldAmount += item.pps;
}
}
handleObjectDeletion(object) {
this.objects.delete(object);
const {myPlayer} = this.client;
const item = Items[object.type];
if (myPlayer.isMyPlayerByID(this.id) && 5 === item.itemType) {
myPlayer.totalGoldAmount -= item.pps;
}
}
updateInventory(type) {
const item = Items[type];
const inventoryID = this.globalInventory[item.itemType];
const shouldUpdate = null === inventoryID || item.age > Items[inventoryID].age;
if (shouldUpdate) {
this.globalInventory[item.itemType] = item.id;
}
}
detectFullUpgrade() {
const inventory = this.globalInventory;
const primary = inventory[0];
const secondary = inventory[1];
const spike = inventory[4];
if (primary && secondary) {
if ("isUpgrade" in Weapons[primary] && "isUpgrade" in Weapons[secondary]) {
return true;
}
}
return primary && 8 === Weapons[primary].age || secondary && 9 === Weapons[secondary].age || spike && 9 === Items[spike].age || 12 === inventory[5] || 20 === inventory[9];
}
predictPrimary(id) {
if (11 === id) {
return 4;
}
return 5;
}
predictSecondary(id) {
if (0 === id) {
return null;
}
if (2 === id || 4 === id) {
return 10;
}
return 15;
}
predictWeapons() {
const {current, oldCurrent} = this.weapon;
const weapon = Weapons[current];
const type = WeaponTypeString[weapon.itemType];
const reload = this.reload[type];
const upgradedWeapon = current !== oldCurrent && weapon.itemType === Weapons[oldCurrent].itemType;
if (-1 === reload.max || upgradedWeapon) {
reload.current = weapon.speed;
reload.max = weapon.speed;
}
this.globalInventory[weapon.itemType] = current;
this.variant[type] = this.variant.current;
const currentType = this.weapon[type];
if (null === currentType || weapon.age > Weapons[currentType].age) {
this.weapon[type] = current;
}
const primary = this.globalInventory[0];
const secondary = this.globalInventory[1];
const notPrimaryUpgrade = null === primary || !("isUpgrade" in Weapons[primary]);
const notSecondaryUpgrade = null === secondary || !("isUpgrade" in Weapons[secondary]);
if (utility_DataHandler.isSecondary(current) && notPrimaryUpgrade) {
const predicted = this.predictPrimary(current);
if (null === primary || Weapons[predicted].upgradeType === Weapons[primary].upgradeType) {
this.weapon.primary = predicted;
}
} else if (utility_DataHandler.isPrimary(current) && notSecondaryUpgrade) {
const predicted = this.predictSecondary(current);
if (null === predicted || null === secondary || Weapons[predicted].upgradeType === Weapons[secondary].upgradeType) {
this.weapon.secondary = predicted;
}
}
this.isFullyUpgraded = this.detectFullUpgrade();
if (this.isFullyUpgraded) {
if (null !== primary) {
this.weapon.primary = primary;
}
if (null !== secondary) {
this.weapon.secondary = secondary;
}
}
}
getWeaponVariant(id) {
const type = Weapons[id].itemType;
const variant = this.variant[WeaponTypeString[type]];
return {
current: variant,
next: Math.min(variant + 1, 3)
};
}
getBuildingDamage(id) {
const weapon = Weapons[id];
const variant = WeaponVariants[this.getWeaponVariant(id).current];
let damage = weapon.damage * variant.val;
if ("sDmg" in weapon) {
damage *= weapon.sDmg;
}
const hat = Hats[this.hatID];
if ("bDmg" in hat) {
damage *= hat.bDmg;
}
return damage;
}
canDealPoison(weaponID) {
const variant = this.getWeaponVariant(weaponID).current;
const isRuby = 3 === variant;
const hasPlague = 21 === this.hatID;
return {
isAble: isRuby || hasPlague,
count: isRuby ? 5 : hasPlague ? 6 : 0
};
}
getWeaponSpeed(id, hat = this.hatID) {
const reloadSpeed = 20 === hat ? Hats[hat].atkSpd : 1;
return Weapons[id].speed * reloadSpeed;
}
getWeaponSpeedMult() {
if (1 === this.hatID) {
return Hats[this.hatID].aMlt;
}
return 1;
}
getMaxWeaponRange() {
const {primary, secondary} = this.weapon;
const primaryRange = Weapons[primary].range;
if (utility_DataHandler.isMelee(secondary)) {
const range = Weapons[secondary].range;
if (range > primaryRange) {
return range;
}
}
return primaryRange;
}
getMaxWeaponDamage(id, lookingShield) {
if (utility_DataHandler.isMelee(id)) {
const BULL_HAT_ID = 7;
const bull = Hats[BULL_HAT_ID];
const variant = this.getWeaponVariant(id).current;
let damage = Weapons[id].damage;
// НОВОЕ (Требование 3): Применяем множитель Bull Hat только если она КУПЛЕНА
const hatIsOwned = this.storeList && this.storeList[0] && this.storeList[0].has(BULL_HAT_ID);
if (hatIsOwned && bull && bull.dmgMultO) {
damage *= bull.dmgMultO;
}
damage *= WeaponVariants[variant].val;
if (lookingShield) {
// Это множитель урона по щиту
damage *= Weapons[11].shield;
}
return damage;
} else if (utility_DataHandler.isShootable(id) && !lookingShield) {
const projectile = utility_DataHandler.getProjectile(id);
return projectile.damage;
}
return 0;
}
getItemPlaceScale(itemID) {
const item = Items[itemID];
return this.scale + item.scale + item.placeOffset;
}
isReloaded(type, tick = 2 * this.client.SocketManager.TICK) {
const reload = this.reload[type].current;
const max = this.reload[type].max - tick;
return reload >= max;
}
meleeReloaded() {
const {TICK} = this.client.SocketManager;
return this.isReloaded("primary", TICK) || utility_DataHandler.isMelee(this.weapon.secondary) && this.isReloaded("secondary", TICK);
}
detectSpikeInsta() {
const {myPlayer, ObjectManager} = this.client;
const spikeID = this.globalInventory[4] || 9;
const placeLength = this.getItemPlaceScale(spikeID);
const pos1 = this.position.current;
const pos2 = myPlayer.position.current;
const angleTo = pos1.angle(pos2);
const angles = ObjectManager.getBestPlacementAngles(pos1, spikeID, angleTo);
const spike = Items[spikeID];
for (const angle of angles) {
const spikePos = pos1.direction(angle, placeLength);
const distance = pos2.distance(spikePos);
const range = this.collisionScale + spike.scale;
if (distance <= range) {
this.potentialDamage += spike.damage;
break;
}
}
}
canPossiblyInstakill() {
const {PlayerManager, myPlayer} = myClient;
const lookingShield = PlayerManager.lookingShield(myPlayer, this);
const {primary, secondary} = this.weapon;
const primaryDamage = this.getMaxWeaponDamage(primary, lookingShield);
const secondaryDamage = this.getMaxWeaponDamage(secondary, lookingShield);
if (this.isReloaded("primary")) {
this.potentialDamage += primaryDamage;
}
if (this.isReloaded("secondary")) {
const turrets = this.foundProjectiles.get(1);
this.foundProjectiles.clear();
if (void 0 !== turrets) {
this.foundProjectiles.set(1, turrets);
}
this.potentialDamage += secondaryDamage;
}
if (this.isReloaded("turret") && !lookingShield) {
this.potentialDamage += 25;
}
this.detectSpikeInsta();
if (this.potentialDamage * Hats[6].dmgMult >= 100) {
return 3;
}
if (this.potentialDamage >= 100) {
return 2;
}
return 0;
}
}
const data_Player = Player;
class Renderer {
static objects=[];
static rect(ctx, pos, scale, color, lineWidth = 4) {
ctx.save();
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.translate(-myClient.myPlayer.offset.x, -myClient.myPlayer.offset.y);
ctx.translate(pos.x, pos.y);
ctx.rect(-scale, -scale, 2 * scale, 2 * scale);
ctx.stroke();
ctx.closePath();
ctx.restore();
}
static roundRect(ctx, x, y, w, h, r) {
if (w < 2 * r) {
r = w / 2;
}
if (h < 2 * r) {
r = h / 2;
}
if (r < 0) {
r = 0;
}
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
static circle(ctx, x, y, radius, color, opacity = 1, lineWidth = 4) {
ctx.save();
ctx.globalAlpha = opacity;
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.translate(-myClient.myPlayer.offset.x, -myClient.myPlayer.offset.y);
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.restore();
}
static fillCircle(ctx, x, y, radius, color, opacity = 1) {
ctx.save();
ctx.globalAlpha = opacity;
ctx.fillStyle = color;
ctx.beginPath();
ctx.translate(-myClient.myPlayer.offset.x, -myClient.myPlayer.offset.y);
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
ctx.restore();
}
static line(ctx, start, end, color, opacity = 1, lineWidth = 4) {
ctx.save();
ctx.translate(-myClient.myPlayer.offset.x, -myClient.myPlayer.offset.y);
ctx.globalAlpha = opacity;
ctx.strokeStyle = color;
ctx.lineCap = "round";
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
ctx.restore();
}
static arrow(ctx, length, x, y, angle, color) {
ctx.save();
ctx.translate(-myClient.myPlayer.offset.x, -myClient.myPlayer.offset.y);
ctx.translate(x, y);
ctx.rotate(Math.PI / 4);
ctx.rotate(angle);
ctx.globalAlpha = .75;
ctx.strokeStyle = color;
ctx.lineCap = "round";
ctx.lineWidth = 8;
ctx.beginPath();
ctx.moveTo(-length, -length);
ctx.lineTo(length, -length);
ctx.lineTo(length, length);
ctx.stroke();
ctx.restore();
}
static cross(ctx, x, y, size, lineWidth, color) {
ctx.save();
ctx.globalAlpha = 1;
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.translate(x - myClient.myPlayer.offset.x, y - myClient.myPlayer.offset.y);
const halfSize = size / 2;
ctx.beginPath();
ctx.moveTo(-halfSize, -halfSize);
ctx.lineTo(halfSize, halfSize);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(halfSize, -halfSize);
ctx.lineTo(-halfSize, halfSize);
ctx.stroke();
ctx.restore();
}
static getTracerColor(entity) {
if (entity instanceof Notification) {
return Settings.notificationTracersColor;
}
if (Settings.animalTracers && entity.isAI) {
return Settings.animalTracersColor;
}
if (Settings.teammateTracers && entity.isPlayer && myClient.myPlayer.isTeammateByID(entity.sid)) {
return Settings.teammateTracersColor;
}
if (Settings.enemyTracers && entity.isPlayer && myClient.myPlayer.isEnemyByID(entity.sid)) {
return Settings.enemyTracersColor;
}
return null;
}
static renderTracer(ctx, entity, player) {
const color = this.getTracerColor(entity);
if (null === color) {
return;
}
const pos1 = new modules_Vector(player.x, player.y);
const pos2 = new modules_Vector(entity.x, entity.y);
if (Settings.arrows) {
const w = 8;
const distance = Math.min(100 + 2 * w, pos1.distance(pos2) - 2 * w);
const angle = pos1.angle(pos2);
const pos = pos1.direction(angle, distance);
this.arrow(ctx, w, pos.x, pos.y, angle, color);
} else {
this.line(ctx, pos1, pos2, color, .75);
}
}
static getMarkerColor(object) {
const id = object.owner?.sid;
if (void 0 === id) {
return null;
}
if (Settings.itemMarkers && myClient.myPlayer.isMyPlayerByID(id)) {
return Settings.itemMarkersColor;
}
if (Settings.teammateMarkers && myClient.myPlayer.isTeammateByID(id)) {
return Settings.teammateMarkersColor;
}
if (Settings.enemyMarkers && myClient.myPlayer.isEnemyByID(id)) {
return Settings.enemyMarkersColor;
}
return null;
}
static renderMarker(ctx, object) {
const color = this.getMarkerColor(object);
if (null === color) {
return;
}
const x = object.x + object.xWiggle - myClient.myPlayer.offset.x;
const y = object.y + object.yWiggle - myClient.myPlayer.offset.y;
ctx.save();
ctx.strokeStyle = "#3b3b3b";
ctx.lineWidth = 4;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore();
}
static barContainer(ctx, x, y, w, h, r = 8) {
ctx.fillStyle = "#3d3f42";
this.roundRect(ctx, x, y, w, h, r);
ctx.fill();
}
static barContent(ctx, x, y, w, h, fill, color) {
const barPad = constants_Config.barPad;
ctx.fillStyle = color;
this.roundRect(ctx, x + barPad, y + barPad, (w - 2 * barPad) * fill, h - 2 * barPad, 7);
ctx.fill();
}
static getNameY(target) {
let nameY = 34;
const height = 5;
if (target === myClient.myPlayer && Settings.weaponXPBar) {
nameY += height;
}
if (Settings.playerTurretReloadBar) {
nameY += height;
}
if (Settings.weaponReloadBar) {
nameY += height;
}
return nameY;
}
static getContainerHeight(entity) {
const {barHeight, barPad} = constants_Config;
let height = barHeight;
if (entity.isPlayer) {
const smallBarHeight = barHeight - 4;
const player = myClient.PlayerManager.playerData.get(entity.sid);
if (void 0 === player) {
return height;
}
if (player === myClient.myPlayer && Settings.weaponXPBar) {
height += smallBarHeight - barPad;
}
if (Settings.playerTurretReloadBar) {
height += smallBarHeight - barPad;
}
if (Settings.weaponReloadBar) {
height += barHeight - barPad;
}
}
return height;
}
static renderBar(ctx, entity) {
const {barWidth, barHeight, barPad} = constants_Config;
const smallBarHeight = barHeight - 4;
const totalWidth = barWidth + barPad;
const scale = entity.scale + 34;
const {myPlayer, PlayerManager} = myClient;
let x = entity.x - myPlayer.offset.x - totalWidth;
let y = entity.y - myPlayer.offset.y + scale;
ctx.save();
if (entity.isPlayer && entity !== myClient.myPlayer) {
const botId = (entity.client && entity.client.id !== undefined)
? entity.client.id // если это бот
: (entity.sid ?? entity.id); // иначе sid или id
ctx.fillStyle = "#ff0";
ctx.strokeStyle = "#000";
ctx.lineWidth = 4;
ctx.font = "16px Hammersmith One";
// рисуем ID + ник
ctx.strokeText(`[${botId}]`, x - -55, y - 20);
ctx.fillText(`[${botId}]`, x - -55, y - 20);
}
ctx.restore();
const player = entity.isPlayer && PlayerManager.playerData.get(entity.sid);
const animal = entity.isAI && PlayerManager.animalData.get(entity.sid);
let height = 0;
if (player instanceof data_Player) {
const {primary, secondary, turret} = player.reload;
if (player === myPlayer && Settings.weaponXPBar) {
const weapon = Weapons[myPlayer.weapon.current];
const current = WeaponVariants[myPlayer.getWeaponVariant(weapon.id).current].color;
const next = WeaponVariants[myPlayer.getWeaponVariant(weapon.id).next].color;
const XP = myPlayer.weaponXP[weapon.itemType];
this.barContainer(ctx, x, y, 2 * totalWidth, smallBarHeight);
this.barContent(ctx, x, y, 2 * totalWidth, smallBarHeight, 1, current);
this.barContent(ctx, x, y, 2 * totalWidth, smallBarHeight, clamp(XP.current / XP.max, 0, 1), next);
height += smallBarHeight - barPad;
}
if (Settings.playerTurretReloadBar) {
this.barContainer(ctx, x, y + height, 2 * totalWidth, smallBarHeight);
this.barContent(ctx, x, y + height, 2 * totalWidth, smallBarHeight, turret.current / turret.max, Settings.playerTurretReloadBarColor);
height += smallBarHeight - barPad;
}
if (Settings.weaponReloadBar) {
const extraPad = 2.25;
this.barContainer(ctx, x, y + height, 2 * totalWidth, barHeight);
this.barContent(ctx, x, y + height, totalWidth + extraPad, barHeight, primary.current / primary.max, Settings.weaponReloadBarColor);
this.barContent(ctx, x + totalWidth - extraPad, y + height, totalWidth + extraPad, barHeight, secondary.current / secondary.max, Settings.weaponReloadBarColor);
height += barHeight - barPad;
}
}
const target = player || animal;
if (target) {
window.config.nameY = this.getNameY(target);
const {currentHealth, maxHealth} = target;
const health = animal ? maxHealth : 100;
const color = PlayerManager.isEnemyTarget(myPlayer, target) ? "#cc5151" : "#8ecc51";
this.barContainer(ctx, x, y + height, 2 * totalWidth, barHeight);
this.barContent(ctx, x, y + height, 2 * totalWidth, barHeight, currentHealth / health, color);
height += barHeight;
}
ctx.restore();
}
static renderHP(ctx, entity) {
if (!Settings.renderHP) {
return;
}
const {barPad, nameY} = constants_Config;
const containerHeight = this.getContainerHeight(entity);
let text = `HP ${Math.floor(entity.health)}/${entity.maxHealth} `;
const offset = entity.scale + nameY + barPad + containerHeight;
const {myPlayer} = myClient;
const x = entity.x - myPlayer.offset.x;
const y = entity.y - myPlayer.offset.y + offset;
if (entity.isPlayer && myPlayer.isMyPlayerByID(entity.sid)) {
text += `Shame ${myPlayer.shameCount}/8`;
}
ctx.save();
ctx.fillStyle = "#fff";
ctx.strokeStyle = "#3d3f42";
ctx.lineWidth = 8;
ctx.lineJoin = "round";
ctx.textBaseline = "top";
ctx.font = `19px Hammersmith One`;
ctx.strokeText(text, x, y);
ctx.fillText(text, x, y);
ctx.restore();
}
static circularBar(ctx, object, perc, angle, color, offset = 0) {
const x = object.x + object.xWiggle - myClient.myPlayer.offset.x;
const y = object.y + object.yWiggle - myClient.myPlayer.offset.y;
const height = .7 * constants_Config.barHeight;
const defaultScale = 10 + height / 2;
const scale = defaultScale + 3 + offset;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.lineCap = "round";
ctx.strokeStyle = "#3b3b3b";
ctx.lineWidth = height;
ctx.beginPath();
ctx.arc(0, 0, scale, 0, 2 * perc * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.strokeStyle = color;
ctx.lineWidth = height / 3;
ctx.beginPath();
ctx.arc(0, 0, scale, 0, 2 * perc * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.restore();
return defaultScale - 3;
}
}
const rendering_Renderer = Renderer;
const Animals = [ {
id: 0,
src: "cow_1",
hostile: false,
killScore: 150,
health: 500,
weightM: .8,
speed: 95e-5,
turnSpeed: .001,
scale: 72,
drop: [ "food", 50 ]
}, {
id: 1,
src: "pig_1",
hostile: false,
killScore: 200,
health: 800,
weightM: .6,
speed: 85e-5,
turnSpeed: .001,
scale: 72,
drop: [ "food", 80 ]
}, {
id: 2,
name: "Bull",
src: "bull_2",
hostile: true,
dmg: 20,
killScore: 1e3,
health: 1800,
weightM: .5,
speed: 94e-5,
turnSpeed: 74e-5,
scale: 78,
viewRange: 800,
chargePlayer: true,
drop: [ "food", 100 ]
}, {
id: 3,
name: "Bully",
src: "bull_1",
hostile: true,
dmg: 20,
killScore: 2e3,
health: 2800,
weightM: .45,
speed: .001,
turnSpeed: 8e-4,
scale: 90,
viewRange: 900,
chargePlayer: true,
drop: [ "food", 400 ]
}, {
id: 4,
name: "Wolf",
src: "wolf_1",
hostile: true,
dmg: 8,
killScore: 500,
health: 300,
weightM: .45,
speed: .001,
turnSpeed: .002,
scale: 84,
viewRange: 800,
chargePlayer: true,
drop: [ "food", 200 ]
}, {
id: 5,
name: "Quack",
src: "chicken_1",
hostile: false,
dmg: 8,
killScore: 2e3,
noTrap: true,
health: 300,
weightM: .2,
speed: .0018,
turnSpeed: .006,
scale: 70,
drop: [ "food", 100 ]
}, {
id: 6,
name: "MOOSTAFA",
nameScale: 50,
src: "enemy",
hostile: true,
dontRun: true,
fixedSpawn: true,
spawnDelay: 6e4,
noTrap: true,
colDmg: 100,
dmg: 40,
killScore: 8e3,
health: 18e3,
weightM: .4,
speed: 7e-4,
turnSpeed: .01,
scale: 80,
spriteMlt: 1.8,
leapForce: .9,
viewRange: 1e3,
hitRange: 210,
hitDelay: 1e3,
chargePlayer: true,
drop: [ "food", 100 ]
}, {
id: 7,
name: "Treasure",
hostile: true,
nameScale: 35,
src: "crate_1",
fixedSpawn: true,
spawnDelay: 12e4,
colDmg: 200,
killScore: 5e3,
health: 2e4,
weightM: .1,
speed: 0,
turnSpeed: 0,
scale: 70,
spriteMlt: 1
}, {
id: 8,
name: "MOOFIE",
src: "wolf_2",
hostile: true,
fixedSpawn: true,
dontRun: true,
hitScare: 4,
spawnDelay: 3e4,
noTrap: true,
nameScale: 35,
dmg: 10,
colDmg: 100,
killScore: 3e3,
health: 7e3,
weightM: .45,
speed: .0015,
turnSpeed: .002,
scale: 90,
viewRange: 800,
chargePlayer: true,
drop: [ "food", 1e3 ]
}, {
id: 9,
name: "💀MOOFIE",
src: "wolf_2",
hostile: !0,
fixedSpawn: !0,
dontRun: !0,
hitScare: 50,
spawnDelay: 6e4,
noTrap: !0,
nameScale: 35,
dmg: 12,
colDmg: 100,
killScore: 3e3,
health: 9e3,
weightM: .45,
speed: .0015,
turnSpeed: .0025,
scale: 94,
viewRange: 1440,
chargePlayer: !0,
drop: [ "food", 3e3 ],
minSpawnRange: .85,
maxSpawnRange: .9
}, {
id: 10,
name: "💀Wolf",
src: "wolf_1",
hostile: !0,
fixedSpawn: !0,
dontRun: !0,
hitScare: 50,
spawnDelay: 3e4,
dmg: 10,
killScore: 700,
health: 500,
weightM: .45,
speed: .00115,
turnSpeed: .0025,
scale: 88,
viewRange: 1440,
chargePlayer: !0,
drop: [ "food", 400 ],
minSpawnRange: .85,
maxSpawnRange: .9
}, {
id: 11,
name: "💀Bully",
src: "bull_1",
hostile: !0,
fixedSpawn: !0,
dontRun: !0,
hitScare: 50,
dmg: 20,
killScore: 5e3,
health: 5e3,
spawnDelay: 1e5,
weightM: .45,
speed: .00115,
turnSpeed: .0025,
scale: 94,
viewRange: 1440,
chargePlayer: !0,
drop: [ "food", 800 ],
minSpawnRange: .85,
maxSpawnRange: .9
} ];
const constants_Animals = Animals;
const colors = [ [ "orange", "red" ], [ "aqua", "blue" ] ];
const EntityRenderer = new class EntityRenderer {
start=Date.now();
step=0;
drawWeaponHitbox(ctx, player) {
if (!Settings.weaponHitbox) {
return;
}
const {myPlayer, ModuleHandler} = myClient;
const current = myPlayer.getItemByType(ModuleHandler.weapon);
if (utility_DataHandler.isMelee(current)) {
const weapon = Weapons[current];
rendering_Renderer.circle(ctx, player.x, player.y, weapon.range, "#f5cb42", 1, 1);
}
}
drawPlacement(ctx) {
if (!Settings.possiblePlacement) {
return;
}
const {myPlayer, ModuleHandler, ObjectManager} = myClient;
const id = myPlayer.getItemByType(7);
if (null === id) {
return;
}
const angles = ObjectManager.getBestPlacementAngles(myPlayer.position.current, id);
const dist = myPlayer.getItemPlaceScale(id);
const item = Items[id];
for (const angle of angles) {
const pos = myPlayer.position.current.direction(angle, dist);
rendering_Renderer.circle(ctx, pos.x, pos.y, item.scale, "purple", 1, 1);
}
}
drawEntityHP(ctx, entity) {
if (entity.isPlayer) {
if (Settings.turretHitbox && 53 === myClient.myPlayer.hatID) {
rendering_Renderer.circle(ctx, entity.x, entity.y, 700, "#3e2773", 1, 1);
}
}
rendering_Renderer.renderBar(ctx, entity);
rendering_Renderer.renderHP(ctx, entity);
}
drawHitScale(ctx, entity) {
if (!Settings.weaponHitbox) {
return;
}
const {PlayerManager} = myClient;
const type = entity.isPlayer ? PlayerManager.playerData : PlayerManager.animalData;
const target = type.get(entity.sid);
if (void 0 !== target) {
rendering_Renderer.circle(ctx, entity.x, entity.y, target.hitScale, "#3f4ec4", 1, 1);
}
if (entity.isAI && 6 === entity.index) {
const moostafa = constants_Animals[6];
rendering_Renderer.circle(ctx, entity.x, entity.y, moostafa.hitRange, "#f5cb42", 1, 1);
}
}
drawDanger(ctx, entity) {
if (!Settings.entityDanger) {
return;
}
const {PlayerManager} = myClient;
if (entity.isPlayer) {
const player = PlayerManager.playerData.get(entity.sid);
if (void 0 !== player && 0 !== player.danger) {
const isBoost = Number(player.usingBoost);
const isDanger = Number(player.danger >= 3);
rendering_Renderer.fillCircle(ctx, entity.x, entity.y, player.scale, colors[isBoost][isDanger], .35);
}
}
if (entity.isAI) {
const animal = PlayerManager.animalData.get(entity.sid);
const color = animal.isDanger ? "red" : "green";
rendering_Renderer.fillCircle(ctx, entity.x, entity.y, animal.attackRange, color, .3);
}
}
render(ctx, entity, player) {
const now = Date.now();
this.step = now - this.start;
this.start = now;
const {myPlayer, EnemyManager} = myClient;
const isMyPlayer = entity === player;
if (isMyPlayer) {
const pos = new modules_Vector(player.x, player.y);
if (Settings.displayPlayerAngle) {
rendering_Renderer.line(ctx, pos, pos.direction(myClient.myPlayer.angle, 70), "#e9adf0");
}
this.drawWeaponHitbox(ctx, player);
this.drawPlacement(ctx);
const secondary = myPlayer.weapon.current;
const enemy = EnemyManager.nearestEnemy;
if (Settings.projectileHitbox && utility_DataHandler.isShootable(secondary) && enemy) {
rendering_Renderer.circle(ctx, entity.x, entity.y, 700, "#3e2773", 1, 1);
}
if (myPlayer.isTrapped) {
rendering_Renderer.fillCircle(ctx, pos.x, pos.y, 35, "yellow", .5);
}
}
this.drawEntityHP(ctx, entity);
if (Settings.collisionHitbox) {
rendering_Renderer.circle(ctx, entity.x, entity.y, entity.scale, "#c7fff2", 1, 1);
}
if (!isMyPlayer) {
const willCollide = EnemyManager.nearestCollideSpike;
if (willCollide && !entity.isAI && myPlayer.isEnemyByID(entity.sid) && entity.sid === willCollide.id) {
rendering_Renderer.circle(ctx, entity.x, entity.y, entity.scale, "#691313", 1, 13);
}
// В Renderer.js внутри drawName(ctx, player) или drawPlayer(...)
// где-то в инициализации ModuleHandler (после создания myClient)
if (myClient && myClient.ModuleHandler && !myClient.ModuleHandler.oneTick) {
myClient.ModuleHandler.oneTick = new OneTick(myClient);
myClient.ModuleHandler.modules = myClient.ModuleHandler.modules || [];
myClient.ModuleHandler.modules.push(myClient.ModuleHandler.oneTick);
}
this.drawHitScale(ctx, entity);
this.drawDanger(ctx, entity);
rendering_Renderer.renderTracer(ctx, entity, player);
}
if (isMyPlayer) {
rendering_NotificationRenderer.render(ctx, player);
}
}
};
const rendering_EntityRenderer = EntityRenderer;
class Notification {
x;
y;
timeout={
value: 0,
max: 1500
};
constructor(x, y) {
this.x = x;
this.y = y;
}
animate() {
const {value, max} = this.timeout;
if (value >= max) {
NotificationRenderer.remove(this);
return;
}
this.timeout.value += rendering_EntityRenderer.step;
}
render(ctx, player) {
this.animate();
rendering_Renderer.renderTracer(ctx, this, player);
}
}
const NotificationRenderer = new class NotificationRenderer {
notifications=new Set;
remove(notify) {
this.notifications.delete(notify);
}
add(object) {
const {x, y} = object.position.current;
const notify = new Notification(x, y);
this.notifications.add(notify);
}
render(ctx, player) {
for (const notification of this.notifications) {
notification.render(ctx, player);
}
}
};
const rendering_NotificationRenderer = NotificationRenderer;
class ObjectManager {
objects=new Map;
grid=new modules_SpatialHashGrid(100);
reloadingTurrets=new Map;
attackedObjects=new Map;
client;
constructor(client) {
this.client = client;
}
insertObject(object) {
this.grid.insert(object);
this.objects.set(object.id, object);
if (object instanceof PlayerObject) {
const {PlayerManager} = this.client;
const owner = PlayerManager.playerData.get(object.ownerID) || PlayerManager.createPlayer({
id: object.ownerID
});
object.seenPlacement = this.inPlacementRange(object);
owner.handleObjectPlacement(object);
}
}
createObjects(buffer) {
for (let i = 0; i < buffer.length; i += 8) {
const isResource = null === buffer[i + 6];
const data = [ buffer[i + 0], buffer[i + 1], buffer[i + 2], buffer[i + 3], buffer[i + 4] ];
this.insertObject(isResource ? new Resource(...data, buffer[i + 5]) : new PlayerObject(...data, buffer[i + 6], buffer[i + 7]));
}
}
removeObject(object) {
this.grid.remove(object);
this.objects.delete(object.id);
if (object instanceof PlayerObject) {
const player = this.client.PlayerManager.playerData.get(object.ownerID);
if (void 0 !== player) {
player.handleObjectDeletion(object);
}
}
}
removeObjectByID(id) {
const object = this.objects.get(id);
if (void 0 !== object) {
this.removeObject(object);
if (this.client.isOwner) {
const pos = object.position.current.copy().sub(this.client.myPlayer.offset);
if (Settings.notificationTracers && !inView(pos.x, pos.y, object.scale)) {
rendering_NotificationRenderer.add(object);
}
}
}
}
removePlayerObjects(player) {
for (const object of player.objects) {
this.removeObject(object);
}
}
resetTurret(id) {
const object = this.objects.get(id);
if (object instanceof PlayerObject) {
object.reload = 0;
this.reloadingTurrets.set(id, object);
}
}
isEnemyObject(object) {
if (object instanceof PlayerObject && !this.client.myPlayer.isEnemyByID(object.ownerID)) {
return false;
}
return true;
}
isTurretReloaded(object) {
const turret = this.reloadingTurrets.get(object.id);
if (void 0 === turret) {
return true;
}
const tick = this.client.SocketManager.TICK;
return turret.reload > turret.maxReload - tick;
}
postTick() {
for (const [id, turret] of this.reloadingTurrets) {
turret.reload += this.client.PlayerManager.step;
if (turret.reload >= turret.maxReload) {
turret.reload = turret.maxReload;
this.reloadingTurrets.delete(id);
}
}
}
retrieveObjects(pos, radius) {
return this.grid.retrieve(pos, radius);
}
canPlaceItem(id, position, addRadius = 0) {
if (18 !== id && pointInRiver(position)) {
return false;
}
const item = Items[id];
const objects = this.retrieveObjects(position, item.scale);
for (const object of objects) {
const scale = item.scale + object.placementScale + addRadius;
if (position.distance(object.position.current) < scale) {
return false;
}
}
return true;
}
inPlacementRange(object) {
const owner = this.client.PlayerManager.playerData.get(object.ownerID);
if (void 0 === owner || !this.client.PlayerManager.players.includes(owner)) {
return false;
}
const {previous: a0, current: a1, future: a2} = owner.position;
const b0 = object.position.current;
const item = Items[object.type];
const range = 2 * owner.scale + item.scale + item.placeOffset;
return a0.distance(b0) <= range || a1.distance(b0) <= range || a2.distance(b0) <= range;
}
getAngleOffset(angle, distance, scale) {}
getBestPlacementAngles(position, id, sortAngle = 0) {
const item = Items[id];
const length = this.client.myPlayer.getItemPlaceScale(id);
const objects = this.retrieveObjects(position, length + item.scale);
const angles = [];
for (const object of objects) {
const angle = position.angle(object.position.current);
const distance = position.distance(object.position.current);
const a = object.placementScale + item.scale;
const b = distance;
const c = length;
const offset = Math.acos((a ** 2 - b ** 2 - c ** 2) / (-2 * b * c));
if (!isNaN(offset)) {
angles.push({
angle,
offset
});
}
}
return findPlacementAngles(angles);
}
}
const Managers_ObjectManager = ObjectManager;
class Animal extends data_Entity {
type=-1;
currentHealth=0;
_maxHealth=0;
nameIndex=0;
isDanger=false;
isHostile=false;
constructor(client) {
super(client);
}
canBeTrapped() {
return !("noTrap" in constants_Animals[this.type]);
}
update(id, type, x, y, angle, health, nameIndex) {
this.id = id;
this.type = type;
this.position.previous.setVec(this.position.current);
this.position.current.setXY(x, y);
this.setFuturePosition();
const animal = constants_Animals[type];
this.angle = angle;
this.currentHealth = health;
this._maxHealth = animal.health;
this.nameIndex = nameIndex;
this.scale = animal.scale;
const isHostile = animal.hostile && 7 !== type;
const isTrapped = this.canBeTrapped() && this.checkCollision(5);
this.isHostile = animal.hostile;
this.isDanger = isHostile && !isTrapped;
}
get attackRange() {
if (6 === this.type) {
return constants_Animals[this.type].hitRange + constants_Config.playerScale;
}
return this.scale;
}
get collisionRange() {
if (6 === this.type) {
return constants_Animals[this.type].hitRange + constants_Config.playerScale;
}
return this.scale + 60;
}
get canUseTurret() {
return this.isHostile;
}
}
const data_Animal = Animal;
class ClientPlayer extends data_Player {
inventory={};
weaponXP=[ {}, {} ];
itemCount=new Map;
resources={};
tempGold=0;
deathPosition=new modules_Vector;
offset=new modules_Vector;
inGame=false;
wasDead=true;
diedOnce=false;
platformActivated=false;
receivedDamage=null;
timerCount=1e3 / 9;
shameActive=false;
shameTimer=0;
shameCount=0;
teammates=new Set;
totalGoldAmount=0;
age=1;
upgradeAge=1;
poisonCount=0;
underTurretAttack=false;
upgradeOrder=[];
upgradeIndex=0;
joinRequests=[];
// ==================== [ КОНСТАНТЫ И ЛОГИКА ДЛЯ ФИКСИРОВАННЫХ ИМЕН БОТОВ ] ====================
// Список имен, которые будут циклически присваиваться ботам
// Новый расширенный список для ClientPlayer.BOT_NAMES
static BOT_NAMES = [
"AllItBloodMy", "Blood??", "Murka", "AllItBloodMy", "prince_finn", "knax", "AibMinyon",
];
static BOT_COLORS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
// Метод для получения фиксированного имени на основе ID клиента
// Метод для получения СЛУЧАЙНОГО имени (исправленный код)
// Метод для получения СЛУЧАЙНОГО имени (только из списка BOT_NAMES)
getFixedBotName(botId) {
const names = ClientPlayer.BOT_NAMES;
// Выбираем СЛУЧАЙНОЕ имя из списка
const index = Math.floor(Math.random() * names.length);
const randomName = names[index]; // Получаем имя, например, "Nova"
// Возвращаем имя БЕЗ каких-либо суффиксов, цифр или приписок
return randomName;
}
// ===========================================================================================
constructor(client) {
super(client);
this.reset(true);
}
isMyPlayerByID(id) {
return id === this.id;
}
isTeammateByID(id) {
return this.teammates.has(id);
}
isEnemyByID(id) {
return !this.isMyPlayerByID(id) && !this.isTeammateByID(id);
}
get isSandbox() {
return true;
}
getItemByType(type) {
return this.inventory[type];
}
hasResourcesForType(type) {
if (this.isSandbox) {
return true;
}
const res = this.resources;
const {food, wood, stone, gold} = Items[this.getItemByType(type)].cost;
return res.food >= food && res.wood >= wood && res.stone >= stone && res.gold >= gold;
}
getItemCount(group) {
const item = ItemGroups[group];
return {
count: this.itemCount.get(group) || 0,
limit: this.isSandbox ? "sandboxLimit" in item ? item.sandboxLimit : 99 : item.limit
};
}
hasItemCountForType(type) {
if (2 === type) {
return true;
}
const item = Items[this.getItemByType(type)];
const {count, limit} = this.getItemCount(item.itemGroup);
return count < limit;
}
canPlace(type) {
return null !== type && null !== this.getItemByType(type) && this.hasResourcesForType(type) && this.hasItemCountForType(type);
}
getBestDestroyingWeapon() {
const secondaryID = this.getItemByType(1);
if (10 === secondaryID) {
return 1;
}
const primary = Weapons[this.getItemByType(0)];
if (1 !== primary.damage) {
return 0;
}
return null;
}
/**
* Находит ID оружия с лучшим соотношением gather/speed.
* Предполагается, что Weapons глобально доступен.
* @returns {number} ID лучшего оружия для добычи (по умолчанию 8 - 'stick').
*/
getBestGatheringWeaponId() {
let bestWeaponId = 8;
let maxGatherRate = 7 / 400; // Базовый rate для 'stick'
for (const weapon of Weapons) {
// Проверяем, что это инструмент для сбора (gather > 0)
if ((weapon.gather || 0) > 0) {
// Рассчитываем эффективность: gather / speed
const rate = (weapon.gather || 0) / (weapon.speed || 1);
if (rate > maxGatherRate) {
maxGatherRate = rate;
bestWeaponId = weapon.id;
}
}
}
return bestWeaponId;
}
getDmgOverTime() {
const hat = Hats[this.hatID];
const accessory = Accessories[this.accessoryID];
let damage = 0;
if ("healthRegen" in hat) {
damage += hat.healthRegen;
}
if ("healthRegen" in accessory) {
damage += accessory.healthRegen;
}
if (0 !== this.poisonCount) {
damage += -5;
}
return Math.abs(damage);
}
getBestCurrentHat() {
const {current, future} = this.position;
const {ModuleHandler, EnemyManager} = this.client;
const {actual} = ModuleHandler.getHatStore();
const useFlipper = ModuleHandler.canBuy(0, 31);
const useSoldier = ModuleHandler.canBuy(0, 6);
const useWinter = ModuleHandler.canBuy(0, 15);
const useActual = ModuleHandler.canBuy(0, actual);
if (Settings.biomehats && useFlipper) {
const inRiver = pointInRiver(current) || pointInRiver(future);
if (inRiver) {
const platformActivated = this.checkCollision(8, -30);
const stillStandingOnPlatform = this.checkCollision(8, 15);
if (!this.platformActivated && platformActivated) {
this.platformActivated = true;
}
if (this.platformActivated && !stillStandingOnPlatform) {
this.platformActivated = false;
}
if (!this.platformActivated) {
return 31;
}
}
}
if (useSoldier) {
if (Settings.antienemy && (EnemyManager.detectedEnemy || EnemyManager.nearestEnemyInRangeOf(275))) {
return 6;
}
if (Settings.antispike && this.checkCollision(2, 35, true)) {
return 6;
}
if (Settings.antianimal && null !== EnemyManager.nearestDangerAnimal) {
return 6;
}
}
if (Settings.biomehats && useWinter) {
const inWinter = current.y <= 2400 || future.y <= 2400;
if (inWinter) {
return 15;
}
}
if (useActual) {
return actual;
}
return 0;
}
getBestCurrentAcc() {
const {ModuleHandler, EnemyManager} = this.client;
const {actual} = ModuleHandler.getAccStore();
const useCorrupt = ModuleHandler.canBuy(1, 21);
const useShadow = ModuleHandler.canBuy(1, 19);
const useTail = ModuleHandler.canBuy(1, 11);
const useActual = ModuleHandler.canBuy(1, actual);
if (EnemyManager.detectedEnemy || EnemyManager.nearestEnemyInRangeOf(275, EnemyManager.nearestEntity)) {
const isEnemy = EnemyManager.nearestEnemyInRangeOf(275, EnemyManager.nearestEnemy);
if (isEnemy && useCorrupt) {
return 21;
}
if (useShadow) {
return 19;
}
if (useActual && 11 !== actual) {
return actual;
}
return 0;
}
if (useTail) {
return 11;
}
return 0;
}
getBestCurrentID(type) {
switch (type) {
case 0:
return this.getBestCurrentHat();
case 1:
return this.getBestCurrentAcc();
}
}
getBestUtilityHat() {
const {ModuleHandler, EnemyManager, ObjectManager, myPlayer} = this.client;
const {autoBreak, spikeTick} = ModuleHandler.staticModules;
const id = this.getItemByType(ModuleHandler.weapon);
if (11 === id) {
return null;
}
if (utility_DataHandler.isShootable(id)) {
return; // было 20
}
const weapon = Weapons[id];
const range = weapon.range + 60;
if (spikeTick.isActive && 1 === spikeTick.tickAction) {
return 53;
}
if (1 === ModuleHandler.attackingState || spikeTick.isActive) {
const nearest = EnemyManager.nearestEntity;
if (null !== nearest && this.collidingEntity(nearest, range + nearest.hitScale, true)) {
ModuleHandler.canHitEntity = true;
if (weapon.damage <= 1) {
return 20;
}
return 7;
}
}
if (0 !== ModuleHandler.attackingState || autoBreak.isActive) {
if (weapon.damage <= 1) {
return null;
}
const pos = myPlayer.position.current;
const objects = ObjectManager.retrieveObjects(pos, range);
for (const object of objects) {
if (object instanceof PlayerObject && object.isDestroyable && this.colliding(object, range + object.hitScale)) {
return 40;
}
}
}
return null;
}
getBestUtilityAcc() {
return null;
}
getBestUtilityID(type) {
switch (type) {
case 0:
return this.getBestUtilityHat();
case 1:
return this.getBestUtilityAcc();
}
}
getMaxWeaponRangeClient() {
const primary = this.inventory[0];
const secondary = this.inventory[1];
const primaryRange = Weapons[primary].range;
if (utility_DataHandler.isMelee(secondary)) {
const range = Weapons[secondary].range;
if (range > primaryRange) {
return range;
}
}
return primaryRange;
}
getPlacePosition(start, itemID, angle) {
return start.direction(angle, this.getItemPlaceScale(itemID));
}
tickUpdate() {
if (this.inGame && this.wasDead) {
this.wasDead = false;
this.onFirstTickAfterSpawn();
}
if (45 === this.hatID && !this.shameActive) {
this.shameActive = true;
this.shameTimer = 0;
this.shameCount = 8;
}
const {PlayerManager, ModuleHandler} = this.client;
this.shameTimer += PlayerManager.step;
if (this.shameTimer >= 3e4 && this.shameActive) {
this.shameActive = false;
this.shameTimer = 0;
this.shameCount = 0;
}
this.timerCount += PlayerManager.step;
if (this.timerCount >= 1e3) {
this.timerCount = 0;
this.poisonCount = Math.max(this.poisonCount - 1, 0);
}
ModuleHandler.postTick();
}
updateHealth(health) {
super.updateHealth(health);
if (this.shameActive) {
return;
}
if (this.currentHealth < this.previousHealth) {
this.receivedDamage = Date.now();
} else if (null !== this.receivedDamage) {
const step = Date.now() - this.receivedDamage;
this.receivedDamage = null;
if (step <= 120) {
this.shameCount += 1;
} else {
this.shameCount -= 2;
}
this.shameCount = clamp(this.shameCount, 0, 7);
}
if (health < 100) {
const {ModuleHandler} = this.client;
ModuleHandler.staticModules.shameReset.healthUpdate();
}
}
playerInit(id) {
this.id = id;
const {PlayerManager} = this.client;
if (!PlayerManager.playerData.has(id)) {
PlayerManager.playerData.set(id, this);
}
}
onFirstTickAfterSpawn() {
const {ModuleHandler, SocketManager, isOwner} = this.client;
const {mouse, staticModules} = ModuleHandler;
ModuleHandler.updateAngle(mouse.sentAngle, true);
if (myClient.ModuleHandler.autoattack) {
ModuleHandler.autoattack = true;
SocketManager.autoAttack();
}
if (!isOwner) {
UI_UI.updateBotOption(this.client, "title");
myClient.clientIDList.add(this.id);
const owner = myClient.ModuleHandler;
staticModules.tempData.setWeapon(owner.weapon);
staticModules.tempData.setAttacking(owner.attacking);
staticModules.tempData.setStore(0, owner.store[0].actual);
staticModules.tempData.setStore(1, owner.store[1].actual);
}
}
playerSpawn() {
this.inGame = true;
}
isUpgradeWeapon(id) {
const weapon = Weapons[id];
if ("upgradeOf" in weapon) {
return this.inventory[weapon.itemType] === weapon.upgradeOf;
}
return true;
}
newUpgrade(points, age) {
this.upgradeAge = age;
if (0 === points || 10 === age) {
return;
}
const ids = [];
for (const weapon of Weapons) {
if (weapon.age === age && this.isUpgradeWeapon(weapon.id)) {
ids.push(weapon.id);
}
}
for (const item of Items) {
if (item.age === age) {
ids.push(item.id + 16);
}
}
if (!this.client.isOwner) {
const id = myClient.myPlayer.upgradeOrder[this.upgradeIndex];
if (void 0 !== id && ids.includes(id)) {
this.upgradeIndex += 1;
this.client.ModuleHandler.upgradeItem(id);
}
}
}
updateAge(age) {
this.age = age;
}
upgradeItem(id) {
this.upgradeOrder.push(id);
const {isOwner, clients} = this.client;
if (isOwner) {
for (const client of clients) {
const {age, upgradeAge} = client.myPlayer;
if (age > this.upgradeAge) {
client.myPlayer.newUpgrade(1, upgradeAge);
}
}
}
if (id < 16) {
const weapon = Weapons[id];
this.inventory[weapon.itemType] = id;
const XP = this.weaponXP[weapon.itemType];
XP.current = 0;
XP.max = -1;
} else {
id -= 16;
const item = Items[id];
this.inventory[item.itemType] = id;
}
}
updateClanMembers(teammates) {
this.teammates.clear();
for (let i = 0; i < teammates.length; i += 2) {
const id = teammates[i + 0];
if (!this.isMyPlayerByID(id)) {
this.teammates.add(id);
}
}
}
updateItemCount(group, count) {
this.itemCount.set(group, count);
if (this.client.isOwner) {
UI_GameUI.updateItemCount(group);
}
}
updateResources(type, amount) {
const previousAmount = this.resources[type];
this.resources[type] = amount;
if ("gold" === type) {
this.tempGold = amount;
return;
}
if (amount < previousAmount) {
return;
}
const difference = amount - previousAmount;
// ===== KillChat: everyone (owner + bots) says a Prince Finn line on each kill =====
const _KILLCHAT_PHRASES = [
"For Prince Finn!",
"Prince Finn reigns!",
"Glory to Prince Finn!",
"Prince Finn's blade strikes true!",
"By the will of Prince Finn!",
"Bow to Prince Finn!",
"Prince Finn commands victory!",
"Prince Finn's glory everlasting!",
"Prince Finn's wrath is righteous!",
"Prince Finn's reign continues!",
"Prince Finn's legend grows!",
"For the glory of Prince Finn!",
];
if ("kills" === type) {
// update internal counter and UI (как раньше)
myClient.totalKills += difference;
UI_GameUI.updateTotalKill();
// если увеличилось — отправляем сообщения
if (amount > previousAmount) {
try {
// список клиентов, от которых нужно отправить сообщение:
// - глобальный owner myClient
// - все клиент-боты в myClient.clients (если есть)
const clientsToSpeak = [];
if (typeof myClient !== "undefined" && myClient !== null) {
clientsToSpeak.push(myClient);
if (myClient.clients && myClient.clients.size) {
for (const c of myClient.clients) {
if (c) clientsToSpeak.push(c);
}
}
} else if (this.client) {
// fallback: используем текущий клиент (на случай, если myClient не доступен)
clientsToSpeak.push(this.client);
}
const ts = Date.now();
for (let i = 0; i < clientsToSpeak.length; i++) {
const c = clientsToSpeak[i];
try {
if (!c || !c.SocketManager || typeof c.SocketManager.chat !== "function") continue;
// берём разную фразу для каждого клиента (по времени + индексу)
const idx = Math.abs((ts + i) % _KILLCHAT_PHRASES.length);
const msg = _KILLCHAT_PHRASES[idx];
// отправка через socket manager — это честная, надёжная отправка чата
c.SocketManager.chat(msg);
} catch (e) {
// не критично — пропускаем проблемный клиент
console.error("killchat: client send error", e);
}
}
} catch (e) {
console.error("killchat outer error", e);
}
}
return;
}
this.updateWeaponXP(difference);
}
updateWeaponXP(amount) {
const {next} = this.getWeaponVariant(this.weapon.current);
const XP = this.weaponXP[Weapons[this.weapon.current].itemType];
const maxXP = WeaponVariants[next].needXP;
XP.current += amount;
if (-1 !== XP.max && XP.current >= XP.max) {
XP.current -= XP.max;
XP.max = maxXP;
return;
}
if (-1 === XP.max) {
XP.max = maxXP;
}
if (XP.current >= XP.max) {
XP.current -= XP.max;
XP.max = -1;
}
}
resetResources() {
this.resources.food = 100;
this.resources.wood = 100;
this.resources.stone = 100;
this.resources.gold = 100;
this.resources.kills = 0;
}
resetInventory() {
this.inventory[0] = 0;
this.inventory[1] = null;
this.inventory[2] = 0;
this.inventory[3] = 3;
this.inventory[4] = 6;
this.inventory[5] = 10;
this.inventory[6] = null;
this.inventory[7] = null;
this.inventory[8] = null;
this.inventory[9] = null;
}
resetWeaponXP() {
for (const XP of this.weaponXP) {
XP.current = 0;
XP.max = -1;
}
}
// ==================== МОДИФИЦИРОВАННЫЙ МЕТОД spawn() ====================
spawn() {
let finalName;
let finalSkin; // Переменная для финального ID цвета
const client = this.client;
if (client.isOwner) {
// Главный игрок: берет имя и скин из localStorage
finalName = localStorage.getItem("moo_name") || "Player";
const ownerSkin = Number(localStorage.getItem("skin_color")) || 0;
// Проверяем на "constructor" skin (ID 10), иначе используем числовой ID
finalSkin = 10 === ownerSkin ? "constructor" : ownerSkin;
} else {
// Бот: генерирует случайное имя (как раньше)
finalName = this.getFixedBotName(client.id);
// Бот: выбирает СЛУЧАЙНЫЙ цвет из BOT_COLORS
const colors = ClientPlayer.BOT_COLORS;
const index = Math.floor(Math.random() * colors.length);
finalSkin = colors[index];
}
// Сохраняем имя в свойстве объекта, прежде чем отправить на сервер
this.name = finalName;
// Отправляем на сервер: имя, тип (1), цвет (finalSkin)
this.client.SocketManager.spawn(finalName, 1, finalSkin);
}
// =======================================================================
handleDeath() {
if (Settings.autospawn) {
this.spawn();
return true;
}
return false;
}
handleJoinRequest(id, name) {
this.joinRequests.push([ id, name ]);
}
reset(first = false) {
this.resetResources();
this.resetInventory();
this.resetWeaponXP();
const {ModuleHandler, PlayerManager} = this.client;
ModuleHandler.reset();
this.inGame = false;
this.wasDead = true;
this.shameTimer = 0;
this.shameCount = 0;
this.upgradeOrder.length = 0;
this.upgradeIndex = 0;
if (first) {
return;
}
for (const player of PlayerManager.players) {
player.resetReload();
}
this.deathPosition.setVec(this.position.current);
this.diedOnce = true;
if (this.client.isOwner) {
UI_GameUI.reset();
} else {
this.spawn();
}
}
}
const data_ClientPlayer = ClientPlayer;
class PlayerManager {
playerData=new Map;
players=[];
animalData=new Map;
animals=[];
start=Date.now();
step=0;
client;
constructor(client) {
this.client = client;
}
get timeSinceTick() {
return Date.now() - this.start;
}
createPlayer({socketID, id, nickname, health, skinID}) {
const player = this.playerData.get(id) || new data_Player(this.client);
if (!this.playerData.has(id)) {
this.playerData.set(id, player);
}
player.socketID = socketID || "";
player.id = id;
player.nickname = nickname || "";
player.currentHealth = health || 100;
player.skinID = "undefined" === typeof skinID ? -1 : skinID;
player.init();
const {myPlayer} = this.client;
if (myPlayer.isMyPlayerByID(id)) {
myPlayer.playerSpawn();
}
return player;
}
canHitTarget(player, weaponID, target) {
const pos = target.position.current;
const distance = player.position.current.distance(pos);
const angle = player.position.current.angle(pos);
const range = Weapons[weaponID].range + target.hitScale;
return distance <= range && getAngleDist(angle, player.angle) <= constants_Config.gatherAngle;
}
attackPlayer(id, gathering, weaponID) {
const player = this.playerData.get(id);
if (void 0 === player) {
return;
}
const {hatID, reload} = player;
const {myPlayer, ObjectManager} = this.client;
if (myPlayer.isMyPlayerByID(id) && !myPlayer.inGame) {
return;
}
const weapon = Weapons[weaponID];
const type = WeaponTypeString[weapon.itemType];
reload[type].current = 0;
reload[type].max = player.getWeaponSpeed(weaponID);
if (myPlayer.isEnemyByID(id) && this.canHitTarget(player, weaponID, myPlayer)) {
const {isAble, count} = player.canDealPoison(weaponID);
if (isAble) {
myPlayer.poisonCount = count;
}
}
if (1 === gathering) {
const objects = ObjectManager.attackedObjects;
for (const [id, data] of objects) {
const [hitAngle, object] = data;
if (this.canHitTarget(player, weaponID, object) && getAngleDist(hitAngle, player.angle) <= 1.25) {
objects.delete(id);
if (object instanceof PlayerObject) {
const damage = player.getBuildingDamage(weaponID);
object.health = Math.max(0, object.health - damage);
} else if (player === myPlayer) {
let amount = 9 === hatID ? 1 : 0;
if (3 === object.type) {
amount += weapon.gather + 4;
}
myPlayer.updateWeaponXP(amount);
}
}
}
}
}
updatePlayer(buffer) {
this.players.length = 0;
const now = Date.now();
this.step = now - this.start;
this.start = now;
for (let i = 0; i < buffer.length; i += 13) {
const id = buffer[i];
const player = this.playerData.get(id);
if (!player) {
continue;
}
this.players.push(player);
player.update(id, buffer[i + 1], buffer[i + 2], buffer[i + 3], buffer[i + 4], buffer[i + 5], buffer[i + 6], buffer[i + 7], buffer[i + 8], buffer[i + 9], buffer[i + 10], buffer[i + 11]);
}
}
updateAnimal(buffer) {
this.animals.length = 0;
for (let i = 0; i < buffer.length; i += 7) {
const id = buffer[i];
if (!this.animalData.has(id)) {
this.animalData.set(id, new data_Animal(this.client));
}
const animal = this.animalData.get(id);
this.animals.push(animal);
animal.update(id, buffer[i + 1], buffer[i + 2], buffer[i + 3], buffer[i + 4], buffer[i + 5], buffer[i + 6]);
}
}
postTick() {
const {EnemyManager, ProjectileManager, ObjectManager, myPlayer} = this.client;
EnemyManager.handleEnemies(this.players, this.animals);
ProjectileManager.postTick();
ObjectManager.postTick();
if (myPlayer.inGame) {
myPlayer.tickUpdate();
}
}
isEnemy(target1, target2) {
return target1 !== target2 && (null === target1.clanName || null === target2.clanName || target1.clanName !== target2.clanName);
}
isEnemyByID(ownerID, target) {
const player = this.playerData.get(ownerID);
if (player instanceof data_ClientPlayer) {
return player.isEnemyByID(target.id);
}
if (target instanceof data_ClientPlayer) {
return target.isEnemyByID(player.id);
}
return this.isEnemy(player, target);
}
isEnemyTarget(owner, target) {
if (target instanceof data_Animal) {
return true;
}
return this.isEnemyByID(owner.id, target);
}
canShoot(ownerID, target) {
return target instanceof data_Animal || this.isEnemyByID(ownerID, target);
}
lookingShield(owner, target) {
const weapon = owner.weapon.current;
if (11 !== weapon) {
return false;
}
const {myPlayer, ModuleHandler} = this.client;
const pos1 = owner.position.current;
const pos2 = target.position.current;
const angle = pos1.angle(pos2);
const ownerAngle = myPlayer.isMyPlayerByID(owner.id) ? ModuleHandler.mouse.sentAngle : owner.angle;
return getAngleDist(angle, ownerAngle) <= constants_Config.shieldAngle;
}
getEntities() {
return [ ...this.players, ...this.animals ];
}
}
const Managers_PlayerManager = PlayerManager;
class Projectile {
position={};
angle;
range;
speed;
type;
onPlatform;
id;
isTurret;
scale;
maxRange;
owner=null;
constructor(angle, range, speed, type, onPlatform, id, maxRange) {
this.isTurret = 1 === type;
this.angle = angle;
this.range = range;
this.speed = speed;
this.type = type;
this.onPlatform = onPlatform;
this.id = id;
this.scale = Projectiles[type].scale;
this.maxRange = maxRange || 0;
}
formatFromCurrent(pos, increase) {
if (this.isTurret) {
return pos;
}
return pos.direction(this.angle, increase ? 70 : -70);
}
}
const data_Projectile = Projectile;
class ProjectileManager {
client;
projectiles=new Map;
ignoreCreation=new Set;
constructor(client) {
this.client = client;
}
createProjectile(projectile) {
const key = projectile.speed;
if (!this.projectiles.has(key)) {
this.projectiles.set(key, []);
}
const list = this.projectiles.get(key);
list.push(projectile);
}
shootingAt(owner, target) {}
postTick() {
this.projectiles.clear();
}
getProjectile(owner, projectile, onPlatform, angle, range) {
const bullet = Projectiles[projectile];
const isTurret = 1 === projectile;
const {previous: a0, current: a1, future: a2} = owner.position;
const arrow = new data_Projectile(angle, bullet.range, bullet.speed, projectile, onPlatform || isTurret ? 1 : 0, -1, range);
arrow.position.previous = arrow.formatFromCurrent(a0, true);
arrow.position.current = arrow.formatFromCurrent(a1, true);
arrow.position.future = arrow.formatFromCurrent(a2, true);
return arrow;
}
}
const Managers_ProjectileManager = ProjectileManager;
class SocketManager {
client;
PacketQueue=[];
startPing=Date.now();
ping=0;
pong=0;
TICK=1e3 / 9;
packetCount=0;
tickTimeout;
constructor(client) {
this.message = this.message.bind(this);
this.client = client;
const attachMessage = socket => {
socket.addEventListener("message", this.message);
socket.onclose = () => {
socket.removeEventListener("message", this.message);
};
};
const connection = client.connection;
if (void 0 === connection.socket) {
Object.defineProperty(connection, "socket", {
set(value) {
delete connection.socket;
connection.socket = value;
attachMessage(value);
},
configurable: true
});
return;
}
attachMessage(connection.socket);
}
handlePing() {
this.pong = Date.now() - this.startPing;
this.ping = this.pong / 2;
if (this.client.isOwner) {
UI_GameUI.updatePing(this.pong);
}
setTimeout((() => {
this.pingRequest();
}), 3e3);
}
message(event) {
const decoder = this.client.connection.Decoder;
if (null === decoder) {
return;
}
const data = event.data;
const decoded = decoder.decode(new Uint8Array(data));
const temp = [ decoded[0], ...decoded[1] ];
const {myPlayer, PlayerManager, ObjectManager, ProjectileManager, LeaderboardManager} = this.client;
switch (temp[0]) {
case "0":
this.handlePing();
break;
case "io-init":
this.pingRequest();
this.client.stableConnection = true;
if (this.client.isOwner) {
UI_GameUI.loadGame();
} else {
this.client.myPlayer.spawn();
this.client.connection.socket.dispatchEvent(new Event("connected"));
}
break;
case "C":
myPlayer.playerInit(temp[1]);
break;
case "P":
myPlayer.reset();
break;
case "N":
this.PacketQueue.push((() => {
const type = "points" === temp[1] ? "gold" : temp[1];
myPlayer.updateResources(type, temp[2]);
}));
break;
case "D":
{
const data = temp[1];
PlayerManager.createPlayer({
socketID: data[0],
id: data[1],
nickname: data[2],
health: data[6],
skinID: data[9]
});
break;
}
case "O":
{
const player = PlayerManager.playerData.get(temp[1]);
if (void 0 !== player) {
player.updateHealth(temp[2]);
}
break;
}
case "a":
PlayerManager.updatePlayer(temp[1]);
for (let i = 0; i < this.PacketQueue.length; i++) {
this.PacketQueue[i]();
}
this.PacketQueue.length = 0;
ObjectManager.attackedObjects.clear();
break;
case "I":
PlayerManager.updateAnimal(temp[1] || []);
clearTimeout(this.tickTimeout);
this.tickTimeout = setTimeout((() => {
PlayerManager.postTick();
}), 5);
break;
case "H":
ObjectManager.createObjects(temp[1]);
break;
case "Q":
ObjectManager.removeObjectByID(temp[1]);
break;
case "R":
{
const player = PlayerManager.playerData.get(temp[1]);
if (void 0 !== player) {
ObjectManager.removePlayerObjects(player);
}
break;
}
case "L":
{
const object = ObjectManager.objects.get(temp[2]);
if (object instanceof Resource || object && object.isDestroyable) {
ObjectManager.attackedObjects.set(getUniqueID(), [ temp[1], object ]);
}
break;
}
case "K":
this.PacketQueue.push((() => PlayerManager.attackPlayer(temp[1], temp[2], temp[3])));
break;
case "M":
{
const id = temp[1];
const angle = temp[2];
const turret = ObjectManager.objects.get(id);
if (void 0 !== turret) {
const creations = ProjectileManager.ignoreCreation;
const pos = turret.position.current.stringify();
creations.add(pos + ":" + angle);
}
this.PacketQueue.push((() => ObjectManager.resetTurret(id)));
break;
}
case "X":
{
const x = temp[1];
const y = temp[2];
const angle = temp[3];
const key = `${x}:${y}:${angle}`;
if (ProjectileManager.ignoreCreation.delete(key)) {
return;
}
const projectile = new data_Projectile(angle, temp[4], temp[5], temp[6], temp[7], temp[8]);
projectile.position.current = projectile.formatFromCurrent(new modules_Vector(x, y), false);
ProjectileManager.createProjectile(projectile);
break;
}
case "4":
myPlayer.updateClanMembers(temp[1]);
break;
case "3":
if ("string" !== typeof temp[1]) {
myPlayer.teammates.clear();
}
break;
case "2":
myPlayer.handleJoinRequest(temp[1], temp[2]);
break;
case "T":
if (4 === temp.length) {
myPlayer.updateAge(temp[3]);
}
break;
case "U":
myPlayer.newUpgrade(temp[1], temp[2]);
break;
case "S":
myPlayer.updateItemCount(temp[1], temp[2]);
break;
case "G":
LeaderboardManager.update(temp[1]);
break;
case "5":
{
const action = 0 === temp[1] ? 1 : 0;
UI_StoreHandler.updateStoreState(temp[3], action, temp[2]);
break;
}
}
}
send(data) {
const connection = this.client.connection;
if (void 0 === connection.socket || connection.socket.readyState !== connection.socket.OPEN || null === connection.Encoder) {
return;
}
const [type, ...args] = data;
const encoded = connection.Encoder.encode([ type, args ]);
connection.socket.send(encoded);
}
clanRequest(id, accept) {
this.send([ "P", id, Number(accept) ]);
}
kick(id) {
this.send([ "Q", id ]);
}
joinClan(name) {
this.send([ "b", name ]);
}
createClan(name) {
this.send([ "L", name ]);
}
leaveClan() {
this.client.myPlayer.joinRequests.length = 0;
this.send([ "N" ]);
}
equip(type, id) {
this.send([ "c", 0, id, type ]);
}
buy(type, id) {
this.send([ "c", 1, id, type ]);
}
chat(message) {
this.send([ "6", message ]);
}
attack(angle) {
this.send([ "F", 1, angle ]);
}
stopAttack() {
this.send([ "F", 0, null ]);
}
resetMoveDir() {
this.send([ "e" ]);
}
move(angle) {
this.send([ "9", angle ]);
}
autoAttack() {
this.send([ "K", 1 ]);
}
lockRotation() {
this.send([ "K", 0 ]);
}
pingMap() {
this.send([ "S" ]);
}
selectItemByID(id, type) {
this.send([ "z", id, type ]);
}
spawn(name, moofoll, skin) {
this.send([ "M", {
name,
moofoll,
skin
} ]);
}
upgradeItem(id) {
this.send([ "H", id ]);
}
updateAngle(radians) {
this.send([ "D", radians ]);
}
pingRequest() {
this.startPing = Date.now();
this.send([ "0" ]);
}
}
const Managers_SocketManager = SocketManager;
class ActionPlanner {
actionKeys=[];
actionValues=[];
createAction(key, value) {
this.actionKeys.push(key);
this.actionValues.push(value);
}
createActions(key, value, amount) {
if (1 === amount) {
return this.createAction(key, value);
}
for (let i = 0; i < amount; i++) {
this.createAction(key, value);
}
}
getActions() {
const keys = [ ...this.actionKeys ];
const values = [ ...this.actionValues ];
const uniqueItems = [ ...new Set(keys) ];
const output = [];
while (keys.length > 0) {
for (const item of uniqueItems) {
const index = keys.indexOf(item);
if (index >= 0) {
output.push([ item, values[index] ]);
removeFast(keys, index);
removeFast(values, index);
}
}
}
this.actionKeys.length = 0;
this.actionValues.length = 0;
return output;
}
}
const modules_ActionPlanner = ActionPlanner;
/* === AutoChat Module (С ГЛИТЧ-ЭФФЕКТОМ И РОТАЦИЕЙ) === */
class AutoChat {
name = "autoChat";
client;
// ⭐ СООБЩЕНИЯ ДЛЯ ЦИКЛИЧЕСКОЙ РОТАЦИИ ПО УМОЛЧАНИЮ
static DEFAULT_MESSAGES = [
"Aibm recode Free now!",
"dsc.gg/Aibm",
"Download the bot mod now"
];
constructor(client) {
this.client = client;
this._enabled = false;
this._messageIndex = 0; // Индекс для циклического переключения
this._lastMsg = "All Is Bloody Mine!"; // Оставлено для совместимости
this._lastSent = 0;
}
toggle(msg) {
this._enabled = !this._enabled;
if (this._enabled) {
this._lastMsg = msg && msg.trim() ? msg : "All Is Bloody Mine!";
}
}
// НОВАЯ ФУНКЦИЯ: Применяет глитч-эффект к сообщению
applyGlitchToMessage(msg, glitchChars) {
if (!glitchChars || glitchChars.length === 0) {
return msg; // Нет глитча - возвращаем оригинал
}
let glitchedMsg = "";
for (let i = 0; i < msg.length; i++) {
// Шанс глитча 30% для каждого символа
if (Math.random() < 0.30) {
glitchedMsg += glitchChars[Math.floor(Math.random() * glitchChars.length)];
} else {
glitchedMsg += msg[i];
}
}
return glitchedMsg;
}
postTick() {
if (!this._enabled) return;
// AutoChat работает только на основном клиенте, который управляет ботами
if (this.client !== myClient) return;
if (Date.now() - this._lastSent < 2500) return; // каждые 2.5 сек
try {
// 1. Чтение настроек из ownerCommander
let messageBase = readOwnerCommanderField('all', 'autochatMessage');
// ⭐ 2. ЛОГИКА ЦИКЛИЧЕСКОГО ПЕРЕКЛЮЧЕНИЯ: Если поле пустое, используем ротацию
// Проверяем, что сообщение не пустое или не состоит только из пробелов
if (!messageBase || (typeof messageBase === 'string' && messageBase.trim() === '')) {
const messages = AutoChat.DEFAULT_MESSAGES;
// Получаем текущее сообщение
messageBase = messages[this._messageIndex];
// Обновляем индекс для следующего сообщения: 0 -> 1 -> 2 -> 0 -> ...
this._messageIndex = (this._messageIndex + 1) % messages.length;
}
const glitchChars = readOwnerCommanderField('all', 'acGlitchSelect') || ''; // Читаем выбор глитч-символов
// 3. Применяем глитч
const finalMessage = this.applyGlitchToMessage(messageBase, glitchChars);
// 4. Отправка
const clientsToSpeak = [];
// Берем только клиентов-ботов (всех, кроме myClient)
if (myClient.clients && myClient.clients.size) {
for (const c of myClient.clients) {
if (c && c !== myClient) clientsToSpeak.push(c);
}
}
for (const c of clientsToSpeak) {
if (!c || !c.SocketManager || typeof c.SocketManager.chat !== "function") continue;
c.SocketManager.chat(finalMessage);
}
this._lastSent = Date.now();
this._enabled = !!readOwnerCommanderField('all', 'autochatEnabled');
} catch (e) {
// Оставляем пустым, чтобы не засорять консоль
}
}
}
const modules_AutoChat = AutoChat;
/* === AutoClan Module (ФИНАЛЬНАЯ ВЕРСИЯ: АВТОНОМНОЕ МЕРЦАНИЕ НА ВСЕХ КЛИЕНТАХ) === */
class AutoClan {
name = "autoClan";
client;
_glitchStep = 0; // 0: создать, 1: удалить
_clanName = "AIBM"; // База, используем только 4 символа
_lastSent = 0;
_enabled = false;
constructor(client) {
this.client = client;
}
toggle(clanName) {
this._enabled = !this._enabled;
if (this._enabled) {
this._clanName = clanName && clanName.trim() ? clanName.slice(0, 4) : "AIBM";
this._glitchStep = 0;
} else {
// При отключении - удаляем клан
if (this.client.SocketManager && typeof this.client.SocketManager.leaveClan === 'function') {
this.client.SocketManager.leaveClan();
}
}
}
setClanName(clanName) {
this._clanName = clanName && clanName.trim() ? clanName.slice(0, 4) : "AIBM";
}
// Функция для создания УНИКАЛЬНОГО глитч-имени (строго 4 символа)
generateGlitchName(baseName, glitchChars) {
const base = baseName.slice(0, 4);
// НАБОР СИМВОЛОВ ДЛЯ ЦИКЛА:
// Если выбрано None, используем набор, который выглядит "статично" (.,_~)
// но обеспечивает уникальность для сервера.
const charsForCycle = glitchChars.length > 0 ? glitchChars : " .,_~";
const isGlitching = glitchChars.length > 0;
let newName = "";
for (let i = 0; i < 4; i++) {
// Логика для режима с глитчем
if (isGlitching && Math.random() < 0.5) {
newName += charsForCycle[Math.floor(Math.random() * charsForCycle.length)];
}
// Логика для режима Static (None):
// Принудительная уникальность на 4-м символе ИЛИ небольшой шанс на смену
else if (!isGlitching && (i === 3 || Math.random() < 0.15)) {
newName += charsForCycle[Math.floor(Math.random() * charsForCycle.length)];
}
else {
// Используем базовый символ или заполняем первым символом для цикла
newName += base[i] || charsForCycle[0];
}
}
return newName;
}
// Главная логика: теперь действует на всех (Owner + Bots)
postTick() {
if (!this._enabled) return;
// Устанавливаем задержку 800 мс
if (Date.now() - this._lastSent < 800) return;
try {
// === ЧТЕНИЕ НАСТРОЕК МЕНЮ ===
const baseName = (readOwnerCommanderField('all', 'autoclanName') || this._clanName).slice(0, 4);
const glitchCharsToUse = readOwnerCommanderField('all', 'aclGlitchSelect') || '';
// ============================
const allClients = [this.client];
if (myClient && myClient.clients) {
myClient.clients.forEach(c => c && allClients.push(c));
}
if (this._glitchStep === 0) {
// === ШАГ 1: СОЗДАНИЕ УНИКАЛЬНОГО КЛАНА ДЛЯ КАЖДОГО КЛИЕНТА ===
for (const c of allClients) {
if (!c.SocketManager || typeof c.SocketManager.createClan !== 'function') continue;
const glitchName = this.generateGlitchName(baseName, glitchCharsToUse);
c.SocketManager.createClan(glitchName);
}
this._glitchStep = 1; // Переход к удалению
} else {
// === ШАГ 2: УДАЛЕНИЕ КЛАНА ДЛЯ КАЖДОГО КЛИЕНТА ===
for (const c of allClients) {
if (!c.SocketManager || typeof c.SocketManager.leaveClan !== 'function') continue;
c.SocketManager.leaveClan();
}
this._glitchStep = 0; // Переход к созданию
}
this._lastSent = Date.now();
this._enabled = !!readOwnerCommanderField('all', 'autoclanEnabled');
} catch (e) {
// Оставим только логи ошибок
}
}
}
const modules_AutoClan = AutoClan;
class AntiInsta {
name="antiInsta";
client;
toggleAnti=false;
constructor(client) {
this.client = client;
}
get isSaveHeal() {
const {myPlayer, SocketManager} = this.client;
const startHit = myPlayer.receivedDamage || 0;
const timeSinceHit = Date.now() - startHit + SocketManager.pong;
return timeSinceHit >= 120;
}
get canHeal() {
const {myPlayer} = this.client;
return Settings.autoheal && myPlayer.tempHealth < 100 && !myPlayer.shameActive && this.isSaveHeal;
}
postTick() {
const {myPlayer, ModuleHandler} = this.client;
const foodID = myPlayer.getItemByType(2);
const restore = Items[foodID].restore;
const maxTimes = Math.ceil(myPlayer.maxHealth / restore);
const needTimes = Math.ceil((myPlayer.maxHealth - myPlayer.tempHealth) / restore);
let healingTimes = null;
if (ModuleHandler.needToHeal || this.toggleAnti) {
ModuleHandler.needToHeal = false;
if (myPlayer.shameActive) {
return;
}
ModuleHandler.didAntiInsta = true;
healingTimes = Math.min(maxTimes, 3);
} else if (this.canHeal) {
healingTimes = needTimes;
myPlayer.tempHealth += clamp(restore * healingTimes, 0, 100);
}
if (null !== healingTimes) {
ModuleHandler.healedOnce = true;
ModuleHandler.actionPlanner.createActions(2, (last => ModuleHandler.heal(last)), healingTimes);
}
}
}
const modules_AntiInsta = AntiInsta;
class AutoPlacer {
name="autoPlacer";
client;
placeAngles=[ null, new Set ];
constructor(client) {
this.client = client;
}
postTick() {
this.placeAngles[0] = null;
this.placeAngles[1].clear();
if (!Settings.autoplacer) {
return;
}
const {myPlayer, ObjectManager, ModuleHandler, EnemyManager} = this.client;
const {currentType} = ModuleHandler;
const pos = myPlayer.position.current;
const nearestEnemy = EnemyManager.nearestEnemy;
if (null === nearestEnemy) {
return;
}
if (!myPlayer.collidingEntity(nearestEnemy, 450)) {
return;
}
const nearestAngle = pos.angle(nearestEnemy.position.current);
let itemType = null;
const spike = myPlayer.getItemByType(4);
const spikeAngles = ObjectManager.getBestPlacementAngles(pos, spike, nearestAngle);
let angles = new Set;
const length = myPlayer.getItemPlaceScale(spike);
for (const angle of spikeAngles) {
const newPos = pos.direction(angle, length);
let shouldPlaceSpike = false;
for (const enemy of EnemyManager.trappedEnemies) {
const distance = newPos.distance(enemy.position.current);
const range = 2 * Items[spike].scale + enemy.collisionScale;
if (distance <= range) {
shouldPlaceSpike = true;
break;
}
}
if (shouldPlaceSpike) {
angles = spikeAngles;
itemType = 4;
break;
}
}
if (0 === angles.size) {
const type = currentType && 2 !== currentType ? currentType : 7;
if (!myPlayer.canPlace(type)) {
return;
}
const id = myPlayer.getItemByType(type);
angles = ObjectManager.getBestPlacementAngles(pos, id, nearestAngle);
itemType = type;
}
if (null === itemType) {
return;
}
this.placeAngles[0] = itemType;
this.placeAngles[1] = angles;
for (const angle of angles) {
ModuleHandler.actionPlanner.createAction(itemType, (last => ModuleHandler.place(itemType, {
angle,
priority: 1,
last
})));
ModuleHandler.placedOnce = true;
}
}
}
const modules_AutoPlacer = AutoPlacer;
class Autohat {
name = "autoHat";
client;
utilitySize = [0, 0];
// 🛡️ ID ШАПОК (Tank Gear/Soldier Helmet) и (Bull Hat/Barbarian Hat)
TANK_GEAR_ID = 40;
BULL_HAT_ID = 7;
HAT_TYPE = 0;
constructor(client) {
this.client = client;
}
handleUtility(type) {
const {ModuleHandler, myPlayer} = this.client;
const store = ModuleHandler.store[type];
// 1. Сброс предыдущей утилиты (если была)
if (null !== store.lastUtility) {
store.utility.delete(store.lastUtility);
store.lastUtility = null;
}
// 2. ⚔️ НОВАЯ ЛОГИКА: ПРИНУДИТЕЛЬНАЯ ЭКИПИРОВКА ПО КНОПКЕ ⚔️
if (ModuleHandler.canAttack && 0 === store.utility.size) {
let requiredHatId = null;
const attackingState = ModuleHandler.attackingState; // 1 (ЛКМ) или 2 (ПКМ)
if (1 === attackingState) {
// ЛКМ нажат (Attack) -> Bull Hat
requiredHatId = this.BULL_HAT_ID;
} else if (2 === attackingState) {
// ПКМ нажат (Place/Utility) -> Tank Gear
requiredHatId = this.TANK_GEAR_ID;
}
if (null !== requiredHatId) {
// Принудительная экипировка нужной шапки, если мы атакуем
if (ModuleHandler.equip(type, requiredHatId)) {
store.lastUtility = requiredHatId;
store.utility.set(requiredHatId, true);
return; // Успешно сменили шапку, выходим.
}
}
// 3. 🛡️ ОРИГИНАЛЬНАЯ ЛОГИКА (Если принудительно не экипировали)
const id = myPlayer.getBestUtilityID(type);
if (null === id) {
return;
}
if (ModuleHandler.equip(type, id)) {
store.lastUtility = id;
store.utility.set(id, true);
}
}
}
// ОСТАЛЬНЫЕ МЕТОДЫ ОСТАВЛЯЕМ БЕЗ ИЗМЕНЕНИЙ
handleEquip(type) {
const {ModuleHandler} = this.client;
const store = ModuleHandler.store[type];
const size = store.utility.size;
const oldSize = this.utilitySize[type];
if (0 === size && (size !== oldSize || store.best !== store.current)) {
if (ModuleHandler.equip(type, store.current)) {
store.best = store.current;
}
}
this.utilitySize[type] = size;
}
postTick() {
const {ModuleHandler} = this.client;
// Если Autobreak ломает — не менять шляпы/аксессуары. Autobreak управляет шапкой в этот момент.
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
if (!ModuleHandler.sentHatEquip) {
this.handleUtility(0);
this.handleEquip(0);
}
if (!ModuleHandler.sentAccEquip && !ModuleHandler.sentHatEquip) {
this.handleEquip(1);
}
}
}
const modules_Autohat = Autohat;
class OwnerAccLoop {
name = "ownerAccLoop";
client;
_index = 0;
_cooldown = 0;
constructor(client) {
this.client = client;
// Используем те же списки, что и для ботов
this._accessoriesAll = [12, 9, 10, 3, 8, 11, 17, 6, 4, 5, 2, 1, 7, 14, 15, 20, 16, 13, 19, 18, 21];
this._accessoriesWings = [21, 18, 19, 13];
}
postTick() {
// ⭐ ИСПРАВЛЕНО: Обращаемся к глобальному объекту конфигурации
const oc = window.OWNER_COMMANDER_CONFIG;
if (!this.client.isOwner || !oc || !oc.ownerAccLoop) return;
const { ModuleHandler } = this.client;
if (!ModuleHandler) return;
// Кульдаун для ограничения скорости переключения
if (this._cooldown > 0) {
this._cooldown--;
return;
}
if (ModuleHandler.sentAccEquip) return;
// ВЫБОР СПИСКА В ЗАВИСИМОСТИ ОТ РЕЖИМА
const mode = oc.ownerAccLoopMode || 'all';
let accessories;
if (mode === 'wings') {
accessories = this._accessoriesWings;
} else { // 'all'
accessories = this._accessoriesAll;
}
if (accessories.length === 0) return;
const accId = accessories[this._index % accessories.length];
this._index++;
try {
// Экипируем аксессуар. Индекс слота для аксессуара — 1.
const ok = ModuleHandler.equip(1, accId);
if (ok) {
this._cooldown = 1; // Короткий кулдаун
} else {
this._cooldown = 0;
}
} catch (e) {
if (e.name === 'TypeError' && e.message.includes("reading 'price'")) {
this._cooldown = 1;
} else {
console.error("OwnerAccLoop equip error:", e);
this._cooldown = 10;
}
}
}
}
const modules_OwnerAccLoop = OwnerAccLoop;
class OwnerHatLoop {
name = "ownerHatLoop";
client;
_index = 0;
_cooldown = 0;
constructor(client) {
this.client = client;
// Используем те же списки, что и для ботов
this._hatsFree = [51, 50, 28, 29, 30, 36, 37, 38, 44, 35, 42, 43, 49];
this._hatsAll = [
1, 2, 3, 4, 5, 6, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27,
31, 32, 33, 34,
39, 41,
46, 47, 48
];
this._hatsFree = this._hatsFree.filter((id, index, self) => self.indexOf(id) === index);
this._hatsAll = this._hatsAll.filter((id, index, self) => self.indexOf(id) === index);
}
postTick() {
// ⭐ ИСПРАВЛЕНО: Обращаемся к глобальному объекту конфигурации
const oc = window.OWNER_COMMANDER_CONFIG;
if (!this.client.isOwner || !oc || !oc.ownerHatLoop) return;
const { ModuleHandler } = this.client;
if (!ModuleHandler) return;
// КУЛДАУН
if (this._cooldown > 0) {
this._cooldown--;
return;
}
// Если Autobreak ломает — не менять шляпы (Autobreak управляет шляпой).
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
// Если Autobreak ломает — не менять шляпы (Autobreak управляет шляпой).
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
if (ModuleHandler.sentHatEquip) return;
const mode = oc.ownerHatLoopMode || 'free';
let hats = mode === 'all' ? this._hatsAll : this._hatsFree;
if (hats.length === 0) return;
const hatId = hats[this._index % hats.length];
this._index++;
try {
const ok = ModuleHandler.equip(0, hatId); // Слот для шапки — 0
if (ok) {
this._cooldown = 1;
} else {
this._cooldown = 0;
}
} catch (e) {
if (e.name === 'TypeError' && e.message.includes("reading 'price'")) {
this._cooldown = 1;
} else {
console.error("OwnerHatLoop equip error:", e);
this._cooldown = 10;
}
}
}
}
const modules_OwnerHatLoop = OwnerHatLoop;
class AccLoop {
name = "accLoop";
client;
_index = 0;
_cooldown = 0;
constructor(client) {
this.client = client;
// Все аксессуары (ID 0-21)
this._accessoriesAll = [0, 12, 9, 10, 3, 8, 11, 17, 6, 4, 5, 2, 1, 7, 14, 15, 20, 16, 13, 19, 18, 21];
// ⭐ Только крылья (ID 21, 18, 19, 13)
// Добавляем 0 (Unequip) в начало
this._accessoriesWings = [0, 21, 18, 19, 13];
}
postTick() {
// Проверка активности модуля
const oc = getOwnerCommanderFor(this.client);
if (!oc || !oc.accLoop) return; // Используем accLoop для проверки
// Хозяина не трогаем (если это бот)
if (this.client.isOwner) return;
const { ModuleHandler } = this.client;
if (!ModuleHandler) return;
// Кульдаун для ограничения скорости переключения
if (this._cooldown > 0) {
this._cooldown--;
return;
}
// Проверка, что ModuleHandler не занят отправкой другого снаряжения
if (ModuleHandler.sentAccEquip) return;
// ⭐ ВЫБОР СПИСКА В ЗАВИСИМОСТИ ОТ РЕЖИМА
const mode = oc.accLoopMode || 'all';
let accessories;
if (mode === 'wings') {
accessories = this._accessoriesWings;
} else { // 'all'
accessories = this._accessoriesAll;
}
// Если вдруг список пуст, выходим
if (accessories.length === 0) return;
// Выбираем следующий аксессуар по кругу
const accId = accessories[this._index % accessories.length];
this._index++;
try {
// Экипируем аксессуар. Индекс слота для аксессуара — 1.
const ok = ModuleHandler.equip(1, accId);
if (ok) {
this._cooldown = 1; // Короткий кулдаун после успешной отправки
} else {
this._cooldown = 0; // Попробуем снова немедленно, если не отправилось
}
} catch (e) {
console.error("AccLoop equip error:", e);
this._cooldown = 10; // Длинный кулдаун в случае ошибки
}
}
}
const modules_AccLoop = AccLoop;
// ======= new module: HatLoop =======
// ...
// ======= new module: HatLoop =======
class HatLoop {
name = "hatLoop";
client;
_index = 0;
_cooldown = 0;
constructor(client) {
this.client = client;
this._hatsFree = [0, 51, 50, 28, 29, 30, 36, 37, 38, 44, 35, 42, 43, 49];
// Жестко заданный отфильтрованный список (платные/желаемые)
this._hatsAll = [
0, // Unequip
1, 2, 3, 4, 5, 6, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27,
31, 32, 33, 34,
39,
41,
46, 47, 48
];
this._hatsFree = this._hatsFree.filter((id, index, self) => self.indexOf(id) === index);
this._hatsAll = this._hatsAll.filter((id, index, self) => self.indexOf(id) === index);
}
postTick() {
const oc = getOwnerCommanderFor(this.client);
if (!oc || !oc.hatLoop) return;
if (this.client.isOwner) return;
const { ModuleHandler } = this.client;
if (!ModuleHandler) return;
// КУЛДАУН
if (this._cooldown > 0) {
this._cooldown--;
return;
}
// Если Autobreak ломает — не менять шляпы (Autobreak управляет шапкой).
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
if (ModuleHandler.sentHatEquip) return;
const mode = oc.hatLoopMode || 'free';
let hats = mode === 'all' ? this._hatsAll : this._hatsFree;
if (hats.length === 0) return;
const hatId = hats[this._index % hats.length];
this._index++;
// ⭐ НОВАЯ ПРОВЕРКА: Пропускаем, если предмет не существует (чтобы избежать ошибки 'price')
// ВАЖНО: замените "getHatItemData" на реальную функцию в вашем клиенте,
// которая возвращает объект предмета или undefined/null.
/* if (hatId !== 0 && !getHatItemData(hatId)) {
// Если предмет не Unequip (ID 0) и данные отсутствуют
this._cooldown = 0; // Попробуем снова на следующем тике
return;
}
*/
try {
// Экипируем шапку. Индекс слота для шапки — 0.
const ok = ModuleHandler.equip(0, hatId);
if (ok) {
// Устанавливаем короткий кулдаун (1 тик) после успешной отправки
this._cooldown = 1;
} else {
// Если не отправилось (например, пакеты еще обрабатываются)
this._cooldown = 0;
}
} catch (e) {
// ⭐ Улучшенная обработка ошибки:
// Если возникла ошибка 'price', мы увеличиваем кулдаун, чтобы не спамить.
if (e.name === 'TypeError' && e.message.includes("reading 'price'")) {
console.warn(`HatLoop: Skipped hat ID ${hatId} due to missing item data (price error).`);
this._cooldown = 5; // Более длинный кулдаун, чтобы пропустить проблемный тик
} else {
console.error("HatLoop equip error:", e);
this._cooldown = 10;
}
}
}
}
const modules_HatLoop = HatLoop;
class Automill {
name="autoMill";
toggle=true;
client;
placeCount=0;
constructor(client) {
this.client = client;
}
reset() {
this.toggle = true;
}
get canAutomill() {
const isOwner = this.client.isOwner;
const {autoattack, attacking, placedOnce} = this.client.ModuleHandler;
// 🔥 ИСПРАВЛЕНИЕ: Теперь isActive зависит ТОЛЬКО от локального флага `this.toggle`,
// который будет устанавливаться из Movement.postTick или глобальных Settings.
let isActive = this.toggle && (isOwner ? (typeof Settings !== 'undefined' && Settings.automill) : true);
if (isActive) {
// Общие ограничения: только в песочнице, мельница еще не поставлена, и владелец не атакует
return this.client.myPlayer.isSandbox &&
!placedOnce &&
(!isOwner || !attacking);
}
return false;
}
placeWindmill(angle) {
const {myPlayer, ObjectManager, ModuleHandler, isOwner} = this.client;
const id = myPlayer.getItemByType(5);
const position = myPlayer.getPlacePosition(myPlayer.position.future, id, angle);
const radius = isOwner ? 0 : Items[id].scale;
if (!ObjectManager.canPlaceItem(id, position, radius)) {
return;
}
ModuleHandler.actionPlanner.createAction(5, (last => ModuleHandler.place(5, {
angle,
last
})));
}
postTick() {
const {myPlayer, ModuleHandler, isOwner} = this.client;
if (!this.canAutomill) {
return;
}
if (!myPlayer.canPlace(5)) {
this.toggle = false;
return;
}
const angle = isOwner ? getAngleFromBitmask(ModuleHandler.move, true) : ModuleHandler.reverseCursorAngle;
if (null === angle) {
return;
}
const item = Items[myPlayer.getItemByType(5)];
const distance = myPlayer.getItemPlaceScale(item.id);
const angleBetween = Math.asin(2 * item.scale / (2 * distance));
this.placeWindmill(angle - angleBetween);
this.placeWindmill(angle + angleBetween);
}
}
const modules_Automill = Automill;
class PlacementExecutor {
name="placementExecutor";
client;
constructor(client) {
this.client = client;
}
postTick() {
const actions = this.client.ModuleHandler.actionPlanner.getActions();
const lastIndex = actions.length - 1;
for (let i = 0; i < actions.length; i++) {
const current = actions[i];
const last = actions[i + 1];
const isLast = i === lastIndex || void 0 !== last && last[0] === current[0];
current[1](isLast);
}
}
}
const modules_PlacementExecutor = PlacementExecutor;
class Placer {
name="placer";
client;
constructor(client) {
this.client = client;
}
postTick() {
const {ModuleHandler, myPlayer, isOwner} = this.client;
const {currentType, placedOnce, healedOnce, mouse} = ModuleHandler;
if (!myPlayer.canPlace(currentType)) {
return;
}
if (2 === currentType) {
if (healedOnce) {
return;
}
ModuleHandler.healedOnce = true;
ModuleHandler.actionPlanner.createAction(currentType, (last => ModuleHandler.place(currentType, {
last
})));
return;
}
if (placedOnce) {
return;
}
ModuleHandler.placedOnce = true;
const angle = isOwner ? mouse.angle : ModuleHandler.cursorAngle;
ModuleHandler.actionPlanner.createAction(currentType, (last => ModuleHandler.place(currentType, {
angle,
last
})));
}
}
const modules_Placer = Placer;
class ShameReset {
name = "shameReset";
client;
_previousHatID = 0; // Сохраняем ID предыдущей шапки
constructor(client) {
this.client = client;
}
get isEquipTime() {
const { myPlayer, SocketManager } = this.client;
const max = 1e3 - SocketManager.TICK;
return myPlayer.timerCount >= max;
}
get shouldReset() {
const { myPlayer, ModuleHandler } = this.client;
// 🔥 УЛУЧШЕННОЕ УСЛОВИЕ: Не атакуем и не строим во время сброса
const isAttackingOrBuilding = (myPlayer.isAttacking || myPlayer.isBuilding);
return !isAttackingOrBuilding && // Проверка активности
!myPlayer.shameActive &&
myPlayer.shameCount > 0 &&
0 === myPlayer.poisonCount &&
!ModuleHandler.didAntiInsta &&
this.isEquipTime;
}
postTick() {
this.handleShameReset();
}
handleShameReset(isDmgOverTime) {
const { myPlayer, ModuleHandler } = this.client;
if (ModuleHandler.sentHatEquip) {
return;
}
const store = ModuleHandler.getHatStore();
const bull = 7; // Bull Hat ID
const bullState = store.utility.get(bull);
const currentHat = myPlayer.hatID;
// --- 1. ЛОГИКА СБРОСА (УСТАНОВКА BULL HAT) ---
if (void 0 === bullState && this.shouldReset) {
// Если мы уже в Bull Hat (и не мы его надели), просто помечаем как использованный для сброса.
if (currentHat === bull) {
store.utility.set(bull, true);
this._previousHatID = 0;
return;
}
// Сохраняем текущую шапку, чтобы вернуть ее позже
this._previousHatID = currentHat;
const isEquipped = ModuleHandler.equip(0, bull);
if (isEquipped) {
store.utility.set(bull, true);
// console.log('[ShameReset] Bull Hat equipped for shame reset.');
}
}
// --- 2. ЛОГИКА СНЯТИЯ (ВОССТАНОВЛЕНИЕ ШАПКИ) ---
// Если Bull Hat помечен как активный И (позор сброшен ИЛИ пришло DOT ИЛИ есть ЯД)
if (bullState && (0 === myPlayer.shameCount || isDmgOverTime || 0 !== myPlayer.poisonCount)) {
// Сбрасываем флаг, чтобы не повторять
store.utility.delete(bull);
// Проверяем, что мы действительно в Bull Hat (это наша шапка) и есть что восстанавливать
if (currentHat === bull && this._previousHatID !== 0 && this._previousHatID !== bull) {
// Экипируем сохраненную шапку
ModuleHandler.equip(0, this._previousHatID);
// console.log(`[ShameReset] Restored hat ID: ${this._previousHatID}`);
}
// Сбрасываем сохраненный ID
this._previousHatID = 0;
}
}
healthUpdate() {
const { myPlayer } = this.client;
const { currentHealth, previousHealth, shameCount } = myPlayer;
const difference = Math.abs(currentHealth - previousHealth);
// Урон по таймеру
const isDmgOverTime = 5 === difference && currentHealth < previousHealth;
// Сброс таймера (поддерживает логику isEquipTime)
if (isDmgOverTime) {
myPlayer.timerCount = 0;
}
this.handleShameReset(isDmgOverTime);
return isDmgOverTime && shameCount > 0;
}
}
const modules_ShameReset = ShameReset;
class UpdateAngle {
name="updateAngle";
client;
constructor(client) {
this.client = client;
}
postTick() {
// --- advance owner commander offsets for circlespin/squarespin (global + per-target) ---
try {
const root = myClient?.ModuleHandler?.ownerCommander;
if (!root) {
// nothing to do
} else if (this.client.isOwner) {
const now = Date.now();
const dt = (now - (this._lastSpinTime || now)) / 1000;
this._lastSpinTime = now;
// global entry
try {
if (root.mode === "circlespin") {
const speed = (root.spin && root.spin.speed) || (30 * Math.PI / 180);
const dir = (root.spin && root.spin.dir) || 1;
root.offset = (root.offset || 0) + speed * dir * dt;
const TWO_PI = Math.PI * 2;
if (Math.abs(root.offset) > TWO_PI) root.offset %= TWO_PI;
}
if (root.mode === "squarespin") {
const speed = (root.spin && root.spin.speed) || 1;
const dir = (root.spin && root.spin.dir) || 1;
root.offset = (root.offset || 0) + speed * dir * dt;
if (root.offset >= 4) root.offset -= 4;
if (root.offset < 0) root.offset += 4;
}
} catch(e){}
// per-target entries
if (root.targets) {
for (const [tid, targ] of Object.entries(root.targets)) {
try {
if (!targ) continue;
if (targ.mode === "circlespin") {
const speed = (targ.spin && targ.spin.speed) || (30 * Math.PI / 180);
const dir = (targ.spin && targ.spin.dir) || 1;
targ.offset = (targ.offset || 0) + speed * dir * dt;
const TWO_PI = Math.PI * 2;
if (Math.abs(targ.offset) > TWO_PI) targ.offset %= TWO_PI;
} else if (targ.mode === "squarespin") {
const speed = (targ.spin && targ.spin.speed) || 1;
const dir = (targ.spin && targ.spin.dir) || 1;
targ.offset = (targ.offset || 0) + speed * dir * dt;
if (targ.offset >= 4) targ.offset -= 4;
if (targ.offset < 0) targ.offset += 4;
}
} catch(e){}
}
}
}
} catch(e){}
const mh = this.client.ModuleHandler;
const {sentAngle, mouse, cursorAngle, useAngle} = mh;
// Если Autobreak ломает — каждый тик шлём принудительно useAngle (force=true),
// чтобы персонаж постоянно смотрел на ловушку.
try {
const ab = mh.staticModules && mh.staticModules.autoBreak;
if (ab && ab.breakingTrap) {
try { mh.updateAngle(useAngle, true); } catch (e) {}
return;
}
} catch (e) {}
if (sentAngle > 1) {
return;
}
const angle = this.client.isOwner ? mouse.angle : cursorAngle;
this.client.ModuleHandler.updateAngle(angle);
}
}
const modules_UpdateAngle = UpdateAngle;
class UpdateAttack {
name="updateAttack";
client;
constructor(client) {
this.client = client;
}
getAttackAngle() {
const {ModuleHandler, isOwner} = this.client;
const {staticModules, useAngle, mouse, cursorAngle} = ModuleHandler;
const {spikeTick, autoBreak} = staticModules;
if (spikeTick.isActive) {
return useAngle;
}
// Всегда используем useAngle, когда активен Autobreak — чтобы персонаж смотрел на цель
if (autoBreak.isActive) {
return useAngle;
}
if (isOwner) {
return mouse.angle;
}
return cursorAngle;
}
postTick() {
const {ModuleHandler} = this.client;
const {useWeapon, weapon, attacking, canAttack, sentAngle, staticModules} = ModuleHandler;
const {reloading} = staticModules;
if (null !== useWeapon && useWeapon !== weapon) {
ModuleHandler.previousWeapon = weapon;
ModuleHandler.whichWeapon(useWeapon);
}
if (canAttack) {
const angle = this.getAttackAngle();
// Если сейчас Autobreak ломает — надеваем Tank Gear только на момент удара,
// затем сразу восстанавливаем прежнюю шапку.
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) {
// Autobreak уже экипировал Tank Gear и управляет прицелом.
// Просто выполняем атаку без дополнительной экипировки/восстановления шляпы.
try {
ModuleHandler.attack(angle);
ModuleHandler.stopAttack && ModuleHandler.stopAttack();
} catch (e) {
try { ModuleHandler.attack(angle); ModuleHandler.stopAttack && ModuleHandler.stopAttack(); } catch (e2) {}
}
} else {
ModuleHandler.attack(angle);
ModuleHandler.stopAttack && ModuleHandler.stopAttack();
}
} catch (e) {
// защитная: обычная атака
try { ModuleHandler.attack(angle); ModuleHandler.stopAttack && ModuleHandler.stopAttack(); } catch (e2) {}
}
const reload = reloading.currentReload;
reloading.updateMaxReload(reload);
// Если мы ломаем через Autobreak — не форсируем reset reload, чтобы
// не искусственно замедлять следующую атаку (Autobreak сам контролирует скорость).
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (!(ab && ab.breakingTrap)) {
reloading.resetReload(reload);
}
} catch (e) {
reloading.resetReload(reload);
}
} else if (!attacking && 0 !== sentAngle) {
ModuleHandler.stopAttack();
}
}
}
const modules_UpdateAttack = UpdateAttack;
class DistanceHit {
name = "distanceHit";
client;
// УДАЛЕНО: _originalHatID. Теперь используется локальная переменная, захваченная замыканием.
// НОВАЯ КОНСТАНТА: На сколько уменьшаем дальность для ботов в проверке (внутриигровые единицы)
BOT_RANGE_REDUCTION = 6;
constructor(client) {
this.client = client;
}
_isHatOwned(playerEntity, hatID) {
// Предполагаем, что шапки хранятся в storeList[0] (как видно из update() в классе Player)
return playerEntity.storeList && playerEntity.storeList[0] && playerEntity.storeList[0].has(hatID);
}
/**
* Вспомогательный метод: Рассчитывает потенциальный урон от основного оружия сущности.
*/
_getPrimaryDamage(playerEntity) {
const primaryID = playerEntity.weapon?.primary;
if (typeof primaryID !== 'number' || primaryID === null) return 0;
if (typeof playerEntity.getMaxWeaponDamage === 'function') {
// Получаем максимальный урон от основного оружия (логика Bull Hat уже внутри)
return playerEntity.getMaxWeaponDamage(primaryID, false);
}
// Запасной/неполный вариант
const weapon = Weapons[primaryID];
if (!weapon) return 0;
let damageMultiplier = 1;
if (playerEntity.hatID && Hats && Hats[playerEntity.hatID] && Hats[playerEntity.hatID].dmgMultO) {
damageMultiplier *= Hats[playerEntity.hatID].dmgMultO;
}
if (playerEntity.accessoryID && Accessories && Accessories[playerEntity.accessoryID] && Accessories[playerEntity.accessoryID].dmgMultO) {
damageMultiplier *= Accessories[playerEntity.accessoryID].dmgMultO;
}
return weapon.damage * damageMultiplier;
}
/**
* НОВЫЙ ВСПОМОГАТЕЛЬНЫЙ МЕТОД: Проверяет, заряжено ли основное оружие.
*/
_isReloaded(playerEntity) {
// Проверка на перезарядку
return typeof playerEntity.isReloaded === 'function' ? playerEntity.isReloaded('primary') : true;
}
/**
* НОВЫЙ ВСПОМОГАТЕЛЬНЫЙ МЕТОД: Проверяет, находится ли сущность в радиусе удара.
* @param {boolean} isBot - Флаг, указывающий, является ли сущность ботом.
*/
_isInRange(playerEntity, target, isBot = false) {
if (!playerEntity || !target || !target.position) return false;
const primaryID = playerEntity.weapon?.primary;
if (typeof primaryID !== 'number' || primaryID === null) return false;
const primaryWeapon = Weapons[primaryID];
if (!primaryWeapon) return false;
// --- Расчет дальности ---
const targetScale = target.hitScale || 45;
let reach = primaryWeapon.range + targetScale;
// НОВОЕ: Уменьшаем эффективную дальность, если это бот
if (isBot) {
reach -= this.BOT_RANGE_REDUCTION;
}
// Защита от отрицательной дальности, хотя это маловероятно
if (reach <= 0) return false;
const myPos = playerEntity.position.current;
const targetPos = target.position.current;
const dist = Math.sqrt(Math.pow(myPos.x - targetPos.x, 2) + Math.pow(myPos.y - targetPos.y, 2));
return dist <= reach;
}
/**
* Проверяет, готов ли игрок к удару (заряжен И в радиусе).
* @param {boolean} isBot - Флаг, указывающий, является ли сущность ботом.
*/
checkHitCondition(playerEntity, target, isBot = false) {
// 1. Проверка на перезарядку
if (!this._isReloaded(playerEntity)) {
return false;
}
// 2. Проверка на дальность
return this._isInRange(playerEntity, target, isBot);
}
/**
* Forces an entity (bot or player) to attack the target immediately.
*/
executeAttack(clientInstance, target, forceBullHat = false) {
// ВАЖНО: Здесь логика АТАКИ не изменена,
// реальный угол и дальность используются движком игры.
const mh = clientInstance.ModuleHandler;
const myPlayer = clientInstance.myPlayer;
const targetPos = target.position.future || target.position.current;
const myPos = myPlayer.position.future || myPlayer.position.current;
const BULL_HAT_ID = 7;
let originalHatId = myPlayer.hatID;
let shouldRestoreHat = false;
const isOwner = clientInstance.isOwner;
const canEquipBullHat = (isOwner || forceBullHat) && this._isHatOwned(myPlayer, BULL_HAT_ID);
// hatToRestore — это локальная константа, захватывается замыканием.
const hatToRestore = originalHatId;
if (canEquipBullHat && originalHatId !== BULL_HAT_ID) {
try {
// 1. Надеваем Bull Hat (ID 7)
mh.equip(0, BULL_HAT_ID); // 0 = Hat slot
shouldRestoreHat = true;
} catch (e) {}
}
// 2. Switch to Primary (Slot 0)
if (mh.currentHolding !== 0) {
try {
mh.whichWeapon(0);
} catch (e) { }
}
// 3. Aim at target
const angle = Math.atan2(targetPos.y - myPos.y, targetPos.x - myPos.x);
try {
mh.updateAngle(angle, true);
} catch (e) { }
// 4. Strike
try {
mh.attack(angle);
mh.stopAttack && mh.stopAttack();
} catch (e) { }
// 5. Возвращаем оригинальную шапку
if (shouldRestoreHat) {
setTimeout(() => {
try {
const ab = mh.staticModules && mh.staticModules.autoBreak;
// НОВЫЙ ФИКС: Проверяем только, не активен ли Autobreak.
// Если не активен, мы должны принудительно восстановить шапку.
if (!(ab && ab.breakingTrap)) {
mh.equip && mh.equip(0, hatToRestore);
}
// Условие myPlayer.hatID === BULL_HAT_ID удалено,
// чтобы не пропускать восстановление при нестабильном состоянии.
} catch (e) { }
}, 120); // Увеличили задержку для большей стабильности
}
}
postTick() {
if (!this.client.isOwner) return;
let distanceHitEnabled = true;
try {
if ((typeof Settings !== 'undefined' && Settings.distanceHitEnabled === false) ||
(this.client.settings && this.client.settings.distanceHitEnabled === false)) {
distanceHitEnabled = false;
}
} catch (e) {}
if (!distanceHitEnabled) {
// Если класс отключен, выходим из метода
return;
}
const { EnemyManager, myPlayer } = this.client;
const target = EnemyManager.nearestEnemy;
if (!target) return;
const BULL_HAT_ID = 7;
// Проверяем владение Bull Hat ГЛАВНЫМ игроком
const ownerOwnsBullHat = this._isHatOwned(myPlayer, BULL_HAT_ID);
// --- НОВАЯ ЛОГИКА СТРОГОЙ СИНХРОНИЗАЦИИ ---
let totalDamage = 0;
let entitiesToAttack = []; // Сущности, которые могут и должны ударить
let syncComboBlocked = false; // Флаг для блокировки комбо
// Список всех клиентов (игрок + боты), которые могут участвовать в комбо
const allComboClients = [this.client];
if (this.client.clients) {
for (const botClient of this.client.clients) {
// Исключаем владельца и неинициализированных ботов
if (!botClient.isOwner && botClient.myPlayer) {
allComboClients.push(botClient);
}
}
}
// 1. Проверяем каждого участника
// Условие: ВСЕ участники, находящиеся в радиусе удара, ДОЛЖНЫ быть заряжены.
for (const client of allComboClients) {
const playerEntity = client.myPlayer;
const isBot = !client.isOwner; // Определяем, является ли это ботом
// Сначала проверяем, находится ли сущность в радиусе удара (Range Check)
// ИСПОЛЬЗУЕМ УМЕНЬШЕННУЮ ДАЛЬНОСТЬ ДЛЯ БОТОВ
const isInRange = this._isInRange(playerEntity, target, isBot);
if (isInRange) {
// Если сущность в радиусе, она ДОЛЖНА быть заряжена для синхронного удара.
if (!this._isReloaded(playerEntity)) {
// Найден не заряженный участник в радиусе -> Блокируем комбо
syncComboBlocked = true;
break; // Дальнейшая проверка не имеет смысла
}
// В радиусе И заряжен -> добавляем в список атакующих и считаем урон.
const damage = this._getPrimaryDamage(playerEntity);
totalDamage += damage;
entitiesToAttack.push({ client: client, damage: damage });
}
// Если не в радиусе, он не блокирует комбо и не участвует в нем.
}
// 2. Учет Solid Hat (ID 6) у врага
const SOLID_HAT_ID = 6;
const solidHatData = Hats[SOLID_HAT_ID];
let finalDamage = totalDamage;
if (target.hatID === SOLID_HAT_ID && solidHatData && typeof solidHatData.dmgMult === 'number') {
// Применяем множитель уменьшения урона (0.75 для Solid Hat)
finalDamage *= solidHatData.dmgMult;
console.log(`[DistanceHit] Урон снижен из-за Soldier Helmet (${(solidHatData.dmgMult*100).toFixed(0)}%). Расчетный урон: ${finalDamage.toFixed(2)}.`);
}
// 3. Логика Синхронизации
// Для атаки нужно:
// A) Нет блокировки (syncComboBlocked === false)
// B) Есть владелец и хотя бы один бот среди тех, кто в радиусе и заряжен.
const ownerEntry = entitiesToAttack.find(e => e.client.isOwner);
const capableBots = entitiesToAttack.filter(e => !e.client.isOwner);
const isReadyForSyncCombo = !syncComboBlocked && ownerEntry && capableBots.length > 0;
const finalCondition = isReadyForSyncCombo && finalDamage >= 100;
if (finalCondition) {
console.log(`[DistanceHit] ---> СИНХРОННАЯ АТАКА! (Общий урон: ${finalDamage.toFixed(2)}, Ботов в комбо: ${capableBots.length})`);
// Владелец атакует
this.executeAttack(this.client, target, ownerOwnsBullHat);
// Все способные боты атакуют
for (const botEntry of capableBots) {
this.executeAttack(botEntry.client, target, ownerOwnsBullHat);
}
}
}
}
// Заменяем оригинальную константу
const modules_DistanceHit = DistanceHit;
class SyncShot {
name = "syncShot";
client;
_isFiringCombo = false;
// state per fire command
_activeFireAt = null;
_firedFor = null;
// Default weapon slot to prefer: 0=Primary, 1=Secondary
DEFAULT_WEAPON_SLOT = 0;
constructor(client) {
this.client = client;
}
_predictTargetPosition(target, timeMs) {
if (!target || !target.position || !target.position.current) return null;
// Выполнение вспомогательных функций таргета (если они существуют)
try {
if (typeof target.predictItems === 'function') target.predictItems();
if (typeof target.predictWeapons === 'function') target.predictWeapons();
if (typeof target.updateReloads === 'function') target.updateReloads();
} catch (e) {}
const cur = target.position.current;
const vel = (target.velocity || { x: 0, y: 0 });
// Преобразуем миллисекунды в секунды
const timeS = timeMs / 1000;
// Используем future позицию (если доступна) для лучшей точности
const startPos = target.position.future || cur;
return {
x: startPos.x + (vel.x || 0) * timeS,
y: startPos.y + (vel.y || 0) * timeS
};
}
// Helper: Resets internal state (used on command start/finish)
_reset() {
this._activeFireAt = null;
this._firedFor = null;
}
// Called every tick
postTick() {
try {
const { ModuleHandler, myPlayer, EnemyManager } = this.client;
if (!ModuleHandler || !myPlayer || !EnemyManager) {
this._reset();
return;
}
// 1. Проверка состояния модуля и команд
const oc = getOwnerCommanderFor(this.client);
if (!oc || !oc.syncShot || !myPlayer.position?.future) return;
const fireAt = oc.syncShot.fireAt;
if (!fireAt) return; // Нет активной команды
// Игнорируем, если уже выполнено в этот цикл команд
if (ModuleHandler._lastSyncShot === fireAt) return;
// Начинаем новую команду
if (this._activeFireAt !== fireAt) {
this._activeFireAt = fireAt;
this._firedFor = null;
}
this._isFiringCombo = true;
// 2. Получение цели и настроек
const target = EnemyManager.nearestEnemy;
if (!target || !target.position) {
this._resetAndMarkDone(ModuleHandler, fireAt, 'no target');
return;
}
// 3. Выбор оружия и времени предикта
const prefSlotSetting = (typeof Settings !== 'undefined' && Settings.syncShotWeaponSlot)
? Settings.syncShotWeaponSlot : this.DEFAULT_WEAPON_SLOT;
const weaponSlot = (prefSlotSetting === 1) ? 1 : 0; // 0 (primary) or 1 (secondary)
const weaponID = (weaponSlot === 0) ? myPlayer.weapon?.primary : myPlayer.weapon?.secondary;
if (!weaponID) {
this._resetAndMarkDone(ModuleHandler, fireAt, 'no weapon in selected slot');
return;
}
// Получаем время задержки (например, время полета снаряда)
const weaponObj = Weapons.find(w => w.id === weaponID);
// УЛУЧШЕННЫЙ РАСЧЕТ ВРЕМЕНИ:
const projectileID = weaponObj?.projectile;
let timeMs = 0;
if (projectileID !== undefined && Projectiles && Projectiles[projectileID]) {
const projData = Projectiles[projectileID];
const speedMult = weaponObj?.spdMultProj || 1;
const projectileSpeed = projData.speed * speedMult;
if (projectileSpeed > 0 && target.distance) {
timeMs = (target.distance / projectileSpeed) * 1000;
} else {
timeMs = 20; // Минимальная задержка
}
} else {
// Предполагаем, что это ближний бой, если нет данных о снаряде
// NOTE: Instakill.MELEE_HIT_DELAY должно быть доступно глобально
timeMs = Instakill.MELEE_HIT_DELAY + 20;
}
// 4. Определение позиции прицеливания (с предиктом)
const aimPos = this._predictTargetPosition(target, timeMs);
if (!aimPos || typeof aimPos.x !== 'number') {
this._resetAndMarkDone(ModuleHandler, fireAt, 'prediction failed');
return;
}
// 5. Выбор оружия и экипировка шапки (Bull Hat для 0, Turret Gear для 1)
const prevHatId = this._prepareForShot(ModuleHandler, myPlayer, weaponSlot);
// 6. Выстрел
try {
// Прицеливание
const ang = Math.atan2(aimPos.y - myPlayer.position.future.y, aimPos.x - myPlayer.position.future.x);
ModuleHandler.updateAngle && ModuleHandler.updateAngle(ang, true);
// Выстрел
ModuleHandler.attack && ModuleHandler.attack(ang);
ModuleHandler.stopAttack && ModuleHandler.stopAttack();
} catch (e) {
// console.error('[SyncShot] attack error', e);
}
// 7. Возврат шапки
this._restoreHat(ModuleHandler, prevHatId);
// 8. Завершение команды
this._resetAndMarkDone(ModuleHandler, fireAt, 'fired');
} catch (outer) {
console.error('[SyncShot] unexpected error', outer);
this._reset();
}
}
// Helper to cleanup and mark the command as handled
_resetAndMarkDone(ModuleHandler, fireAt, message = '') {
try { ModuleHandler._lastSyncShot = fireAt; } catch(e){}
this._firedFor = fireAt;
this._activeFireAt = null;
this._isFiringCombo = false;
// this._debug(message); // Активируйте, если нужен дебаг
}
// Helper: Logic to select weapon and put on Turret Gear/Bull Hat
_prepareForShot(ModuleHandler, myPlayer, targetSlot) {
// 1. Выбираем нужное оружие
if (ModuleHandler.currentHolding !== targetSlot) {
try { ModuleHandler.whichWeapon(targetSlot); } catch (e) {}
}
// 2. Надеваем Bull Hat (ID 7) для ближнего боя или Turret Gear (ID 53) для дальнего
// NOTE: Если слот 0 — ближний, слот 1 — дальний
const targetHatId = (targetSlot === 0) ? 7 : 53;
let prevHatId = myPlayer.hatID;
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
// Не вмешиваемся в шапки, если Autobreak активен
if (!(ab && ab.breakingTrap)) {
ModuleHandler.equip && ModuleHandler.equip(0, targetHatId); // 0 = Hat slot
}
} catch {}
return prevHatId;
}
// Helper: Logic to restore the hat after a short delay
_restoreHat(ModuleHandler, prevHatId) {
// 53 = Turret Gear ID, 7 = Bull Hat ID
const hatsToIgnore = [53, 7];
try {
if (typeof prevHatId === 'number' && !hatsToIgnore.includes(prevHatId)) {
setTimeout(() => {
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (!(ab && ab.breakingTrap)) {
ModuleHandler.equip && ModuleHandler.equip(0, prevHatId);
}
} catch(e){}
}, 80); // Короткая задержка для завершения выстрела
}
} catch {}
}
// debug logger (может быть удален, если не нужен)
// _debug(...) {}
}
const modules_SyncShot = SyncShot;
class BowInsta_Final {
name = "bowInsta";
client;
constructor(client) {
this.client = client;
this.PLATFORM_ITEM_ID = 18;
this.BOW_ID = 9; // ID Лук
this.CROSS_ID = 12;
this.MUSKET_ID = 15;
this.TURRET_HAT = 53;
this.FIXED_DIST = 700;
this.DIST_TOL = 20;
this.DEADZONE = 10;
// *** КОНСТАНТЫ СИНХРОНИЗАЦИИ (СКОРОСТЬ ИГРЫ) ***
this.MIN_ACTION_DELAY = 40; // Минимальная задержка между выстрелом и апгрейдом
this.UPGRADE_CHECK_DELAY = 50; // Задержка для проверки, прошел ли апгрейд
// **********************************************
this.GIVEUP_TIMEOUT_MS = 1200; // Общий таймаут для комбо
this.PLATFORM_POSTPLACE_WAIT_MS = 100;
this._activeFireAt = null;
this._stage = -1;
this._waitUntil = 0;
this._wantedAfterUpgrade = null;
this._platformsFor = null;
this._prevHat = undefined;
this._keys = { W: false, A: false, S: false, D: false };
}
_chat(msg) { try { myClient?.SocketManager?.chat(msg); } catch (e) {} }
_log(msg) { try { console.log(`[BowInsta] ${msg}`); } catch (e) {} }
_hasBow(myPlayer) { return myPlayer.getItemByType?.(1) === this.BOW_ID; }
// ... (вспомогательные функции trigger, _setKeyDown, _setKeyUp, _stopAllKeys, _pressForVector, _dist, _hasPlatform без изменений) ...
_setKeyDown(ModuleHandler, code) { try { ModuleHandler.handleKeydown?.({ code }); } catch (e) {} }
_setKeyUp(ModuleHandler, code) { try { ModuleHandler.handleKeyup?.({ code }); } catch (e) {} }
_stopAllKeys(ModuleHandler) { ["KeyW","KeyA","KeyS","KeyD"].forEach(k=>this._setKeyUp(ModuleHandler,k)); this._keys={W:false,A:false,S:false,D:false}; }
_pressForVector(ModuleHandler,dx,dy){
const pW=(dy<-this.DEADZONE), pS=(dy>this.DEADZONE), pD=(dx>this.DEADZONE), pA=(dx<-this.DEADZONE);
[["W",pW],["S",pS],["D",pD],["A",pA]].forEach(([k,p])=>{
if(p&&!this._keys[k]){ this._setKeyDown(ModuleHandler,"Key"+k); this._keys[k]=true; }
else if(!p&&this._keys[k]){ this._setKeyUp(ModuleHandler,"Key"+k); this._keys[k]=false; }
});
}
_dist(a,b){const dx=a.x-b.x,dy=a.y-b.y;return Math.sqrt(dx*dx+dy*dy);}
_hasPlatform(player) {
try {
if (typeof player.getItemByType === "function") {
const r = player.getItemByType(this.PLATFORM_ITEM_ID);
if (r === this.PLATFORM_ITEM_ID) return true;
}
if (Array.isArray(player.inventory)) {
for (const it of player.inventory) {
if (typeof it === "number" && it === this.PLATFORM_ITEM_ID) return true;
if (typeof it === "object" && (it.id === this.PLATFORM_ITEM_ID || it.itemId === this.PLATFORM_ITEM_ID)) return true;
}
}
} catch (e) {}
return false;
}
trigger() {
if (!this.client || !this.client.myPlayer) return;
if (!this._hasBow(this.client.myPlayer)) { this._chat("sorr me need bow for it"); this._activeFireAt = null; return; }
try {
const myPlayer = this.client.myPlayer;
const slots = [ myPlayer.getItemByType?.(0), myPlayer.getItemByType?.(1) ];
const names = [];
for (const s of slots) { if (typeof s === "number" && Weapons[s]?.name) { names.push(Weapons[s].name.toLowerCase()); } }
const blocked = names.some(n => n.includes("hammer") || n.includes("shield") || n.includes("grab"));
if (blocked) { this._chat("[BowInsta] blocked (forbidden weapon present)"); this._activeFireAt = null; return; }
} catch (e) {}
const now = Date.now();
this._activeFireAt = now;
this._stage = -1;
this._waitUntil = 0;
this._platformsFor = null;
this._prevHat = undefined;
this._chat("[BowInsta] activated via hotkey");
this._log("Activated. Starting sequence.");
try { this.postTick(); } catch(e) { console.error("BowInsta instant postTick error:", e); }
}
postTick() {
try {
if (!this.client.isOwner) return;
const ModuleHandler = this.client.ModuleHandler;
const myPlayer = this.client.myPlayer;
const EnemyManager = this.client.EnemyManager;
if (!ModuleHandler || !myPlayer || !EnemyManager) return;
if (!this._activeFireAt) return;
const fireAt = this._activeFireAt;
if (this._activeFireAt !== fireAt) return;
const now = Date.now();
const target = EnemyManager?.nearestEnemy;
if (!target) {
this._activeFireAt = null; this._stage = -1;
this._chat("[BowInsta] Target lost. Aborting.");
return;
}
let angle = 0;
try {
angle = myPlayer.position.current.angle(target.position.current);
ModuleHandler.updateAngle?.(angle, true);
} catch(e){}
// ** Основная блокировка времени **
if (now < this._waitUntil) return;
const myPos = myPlayer.position.current;
const tgPos = target.position.current;
const dist = this._dist(myPos, tgPos);
// --- stage -1: ПОДХОД ---
if (this._stage === -1) {
// ... (логика движения) ...
if (Math.abs(dist - this.FIXED_DIST) <= this.DIST_TOL) {
this._stopAllKeys(ModuleHandler);
this._stage = -0.7;
this._waitUntil = now;
this._chat("[BowInsta] reached distance " + Math.round(dist));
} else {
const dx_to_player = myPos.x - tgPos.x;
const dy_to_player = myPos.y - tgPos.y;
const len = Math.sqrt(dx_to_player * dx_to_player + dy_to_player * dy_to_player) || 1;
const desiredX = tgPos.x + (dx_to_player / len) * this.FIXED_DIST;
const desiredY = tgPos.y + (dy_to_player / len) * this.FIXED_DIST;
const dx_move = desiredX - myPos.x;
const dy_move = desiredY - myPos.y;
this._pressForVector(ModuleHandler, dx_move, dy_move);
return;
}
}
// --- stage -0.7: ВЫБРАТЬ ЛУК ---
if (this._stage === -0.7) {
ModuleHandler.whichWeapon(1);
this._stage = -0.5;
this._waitUntil = now + this.MIN_ACTION_DELAY;
this._log("Selected bow (slot 1).");
return;
}
// --- stage -0.5: ставим платформы ---
if (this._stage === -0.5) {
// ... (логика платформ) ...
this._stage = 0;
this._waitUntil = now + this.PLATFORM_POSTPLACE_WAIT_MS;
return;
}
// --- stage 0: Выстрел из Лук + НАДЕТЬ ШАПКУ ---
if (this._stage === 0) {
try {
this._prevHat = myPlayer.hatID;
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (!(ab && ab.breakingTrap)) {
ModuleHandler.equip?.(0, this.TURRET_HAT);
}
} catch (e) {}
} catch(e) {}
try { ModuleHandler.attack(angle); ModuleHandler.stopAttack(); } catch(e){}
this._stage = 1;
// Ждем минимальное время для перехода к апгрейду
this._waitUntil = now + this.MIN_ACTION_DELAY;
this._log(`Bow fired. Proceeding to upgrade in ${this.MIN_ACTION_DELAY}ms.`);
return;
}
// --- stage 1: Апгрейд до Арбалета (Cross ID 12) ---
if (this._stage === 1) {
ModuleHandler.upgradeItem(this.CROSS_ID);
this._wantedAfterUpgrade = this.CROSS_ID;
this._stage = 2;
// Ждем минимальное время, чтобы дать игре обновить инвентарь
this._waitUntil = now + this.UPGRADE_CHECK_DELAY;
this._log(`Sent upgrade to CROSSBOW (${this.CROSS_ID}).`);
}
// --- stage 2: Проверка Арбалета + Выстрел ---
if (this._stage === 2) {
const cur = myPlayer.getItemByType?.(1);
this._log(`Check Crossbow: Wanted=${this._wantedAfterUpgrade}, Current=${cur}`);
if (cur === this._wantedAfterUpgrade) {
ModuleHandler.whichWeapon(1);
ModuleHandler.attack(angle);
ModuleHandler.stopAttack();
this._stage = 3;
// Выстрел сделан, теперь сразу отправляем команду апгрейда
this._waitUntil = now + this.MIN_ACTION_DELAY;
this._chat(`[BowInsta] cross fired.`);
this._log(`Crossbow SUCCESS. Proceeding to Musket upgrade.`);
} else if (now > fireAt + this.GIVEUP_TIMEOUT_MS) {
this._chat("[BowInsta] FAIL: Crossbow upgrade timeout. Aborting.");
this._log(`FAIL: Crossbow upgrade timeout. Current weapon is ${cur}.`);
this._activeFireAt = null;
this._stage = -1;
return;
} else {
// Ждем обновления инвентаря
this._waitUntil = now + this.UPGRADE_CHECK_DELAY;
this._log(`Still waiting for Crossbow upgrade...`);
}
return;
}
// --- stage 3: Апгрейд до Мушкета (Musket ID 15) ---
if (this._stage === 3) {
ModuleHandler.upgradeItem(this.MUSKET_ID);
this._wantedAfterUpgrade = this.MUSKET_ID;
this._stage = 4;
this._waitUntil = now + this.UPGRADE_CHECK_DELAY;
this._log(`Sent upgrade to MUSKET (${this.MUSKET_ID}).`);
}
// --- stage 4: Проверка Мушкета + Выстрел + Финализация ---
if (this._stage === 4) {
const cur = myPlayer.getItemByType?.(1);
this._log(`Check Musket: Wanted=${this._wantedAfterUpgrade}, Current=${cur}`);
if (cur === this._wantedAfterUpgrade) {
ModuleHandler.whichWeapon(1);
ModuleHandler.attack(angle);
ModuleHandler.stopAttack();
this._chat("[BowInsta] musket fired (done)");
this._log("Musket SUCCESS. Combo finished.");
} else if (now > fireAt + this.GIVEUP_TIMEOUT_MS) {
this._chat("[BowInsta] FAIL: Musket upgrade timeout. Finishing.");
this._log(`FAIL: Musket upgrade timeout. Current weapon is ${cur}.`);
} else {
this._waitUntil = now + this.UPGRADE_CHECK_DELAY;
this._log(`Still waiting for Musket upgrade...`);
return;
}
// Очистка и сброс: СНЯТЬ ШАПКУ
try {
if (typeof this._prevHat !== "undefined" && this._prevHat !== null) ModuleHandler.equip(0,this._prevHat);
} catch(e){}
// Успешное завершение: сброс состояния
this._activeFireAt = null;
this._stage = -1;
}
} catch(err){
this._chat("[BowInsta] Fatal error. Aborting.");
this._activeFireAt=null;
this._stage=-1;
this._waitUntil = 0;
console.error("BowInsta postTick fatal error:", err);
}
}
}
const modules_BowInsta = BowInsta_Final;
class OneTick {
name = "oneTick";
// IDs
BULL_HAT = 7;
TURRET_HAT = 53;
SOLID_HAT = 6;
// Constants
MELEE_RANGE_FACTOR = 1.2; // Дистанция
PROJECTILE_SPEED = 1.5; // СКОРОСТЬ СНАРЯДА ТУРЕТКИ (пикселей/мс)
FIXED_MOVE_DIST = 175; // Будет обновлено в postTick
DIST_TOLERANCE = 3;
MIN_ACTION_DELAY = 0; // Минимальная задержка между командами
HAT_SWITCH_DELAY_MS = 10; // КД шапки - ОСТАВЛЯЕМ 30 мс
COOLDOWN_MS = 350; // Кульдаун между успешными комбо
// Новые константы для циклической смены
POLLING_INTERVAL = 0; // Интервал опроса для смены шапки (мс)
MAX_SWITCH_TIME = 250; // Максимальное время ожидания смены Bull Hat
// State
active = false;
lastCycle = 0;
prevHat = null;
_pending = false;
_target = null;
_stage = -1; // -1:Move, 0:Fire
_switchStartTime = 0; // Время начала попытки смены Bull Hat
_comboStartTime = 0; // Для общих логов
// Движение
_keys = { W: false, A: false, S: false, D: false };
DEADZONE = 10; // Зона нечувствительности для движения
constructor(client) {
this.client = client;
if (typeof window !== 'undefined') window.oneTick = this;
}
// --- Вспомогательные функции ---
_dist(a, b) {
const dx = a.x - b.x, dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
_setKeyDown(ModuleHandler, code) {
if (!this._keys[code]) { ModuleHandler.handleKeydown?.({ code: "Key" + code }); this._keys[code] = true; }
}
_setKeyUp(ModuleHandler, code) {
if (this._keys[code]) { ModuleHandler.handleKeyup?.({ code: "Key" + code }); this._keys[code] = false; }
}
_stopAllKeys(ModuleHandler) {
["W", "A", "S", "D"].forEach(k => this._setKeyUp(ModuleHandler, k));
this._keys = { W: false, A: false, S: false, D: false };
}
_pressForVector(ModuleHandler, dx, dy) {
const pW = (dy < -this.DEADZONE), pS = (dy > this.DEADZONE), pD = (dx > this.DEADZONE), pA = (dx < -this.DEADZONE);
[["W", pW], ["S", pS], ["D", pD], ["A", pA]].forEach(([k, p]) => {
if (p) this._setKeyDown(ModuleHandler, k);
else this._setKeyUp(ModuleHandler, k);
});
}
_calculateDelay(dist) {
// Время полета снаряда до цели: T_hit = dist / V_projectile
const timeToHit = dist / this.PROJECTILE_SPEED;
// Фиксированное минимальное время на Turret Equip -> Bull Equip -> Fire -> Strike:
// HAT_SWITCH_DELAY_MS (30 мс) + минимальное время на смену и два удара (0-5 мс)
const timeFixedCombo = this.HAT_SWITCH_DELAY_MS + 0; // Условно 5 мс на действия
// Пауза перед Bull Equip должна синхронизировать Bull Melee с Turret Projectile Hit.
const P_DELAY_MS = timeToHit - timeFixedCombo;
return Math.max(0, P_DELAY_MS);
}
// --- Основная логика ---
toggle() {
this.active = !this.active;
console.log("[OneTick] toggled:", this.active ? "ON" : "OFF");
try {
this.client.SocketManager?.chat(`[OneTick] ${this.active ? "ON" : "OFF"}`);
if (typeof setOwnerCommanderField === "function") {
setOwnerCommanderField('all', 'oneTick', { active: this.active });
}
} catch(e){}
if (!this.active) this._cleanup();
}
postTick() {
try {
if (!this.active) return;
const myPlayer = this.client.myPlayer;
const mh = this.client.ModuleHandler;
if (!myPlayer || !myPlayer.inGame || !mh) return;
const enemy = this.client.EnemyManager?.nearestEnemy;
if (!enemy) {
this._target = null;
this._stage = -1;
this._stopAllKeys(mh);
return;
}
const mePos = myPlayer.position.current;
const ePos = enemy.position.current;
const dist = this._dist(mePos, ePos);
// Определяем радиус основного оружия
const primaryID = myPlayer.getItemByType?.(0);
const weapon = Weapons[primaryID];
const primaryRange = weapon && typeof weapon.range === "number" ? weapon.range : 175;
// Устанавливаем целевую дистанцию
this.FIXED_MOVE_DIST = primaryRange * this.MELEE_RANGE_FACTOR;
this._target = enemy;
// Проверка на Solid Hat
if (enemy.hatID === this.SOLID_HAT) {
this._stage = -1;
this._stopAllKeys(mh);
return;
}
// ------------------------------------
// СТАДИЯ -1: ДВИЖЕНИЕ К ЦЕЛИ
// ------------------------------------
if (this._stage === -1) {
if (Math.abs(dist - this.FIXED_MOVE_DIST) <= this.DIST_TOLERANCE) {
this._stopAllKeys(mh);
this._stage = 0; // Переход к комбо
} else {
const dx_to_player = mePos.x - ePos.x;
const dy_to_player = mePos.y - ePos.y;
const len = Math.hypot(dx_to_player, dy_to_player) || 1;
const desiredX = ePos.x + (dx_to_player / len) * this.FIXED_MOVE_DIST;
const desiredY = ePos.y + (dy_to_player / len) * this.FIXED_MOVE_DIST;
const dx_move = desiredX - mePos.x;
const dy_move = desiredY - mePos.y;
this._pressForVector(mh, dx_move, dy_move);
return; // Ждем, пока подойдем
}
}
// ------------------------------------
// СТАДИЯ 0: ПРОВЕРКИ И ЗАПУСК КОМБО
// ------------------------------------
if (this._stage === 0) {
if (Date.now() - this.lastCycle <= this.COOLDOWN_MS) return;
const staticReloading = mh.staticModules?.reloading;
let primaryReady = true;
let turretReady = true;
if (staticReloading && typeof staticReloading.isReloaded === "function") {
try {
primaryReady = !!staticReloading.isReloaded("primary");
turretReady = !!staticReloading.isReloaded("turret");
} catch (e) { /* ignore */ }
}
if (!primaryReady || !turretReady) return; // Не готовы
// Все готово - запускаем комбо
if (!this._pending) this._executeCombo(enemy, dist);
}
} catch (e) {
console.error("OneTick.postTick err:", e);
}
}
_attemptBullEquipAndStrike(angle, enemyPos, mh, myPlayer) {
if (!this._pending) return; // Комбо отменено
if (myPlayer.hatID === this.BULL_HAT) {
// ✅ Шапка Bull Hat надета!
const equipTime = Date.now() - this._switchStartTime;
console.log(`[OneTick] 🐂 (BULL) Equipped! Time since attempt start: ${equipTime}ms. Total combo time: ${Date.now() - this._comboStartTime}ms.`);
// 4. Turret Fire И Bull Melee Strike
// Отправляем команды синхронно, без задержек.
// 4.1. Turret Fire
console.log(`[OneTick] 🎯 (TURRET) Firing projectile (SYNC).`);
try {
mh.updateAngle(angle, true);
mh.attack(angle);
mh.stopAttack();
} catch(e) {}
// 4.2. Bull Melee Strike (Сразу после Turret Fire)
console.log(`[OneTick] 💥 (MELEE) Performing Bull Hat Melee Strike (SYNC).`);
try {
mh.updateAngle(angle, true);
mh.attack(angle);
mh.stopAttack();
} catch(e) {}
// 5. Завершение комбо
// Небольшая асинхронная задержка необходима для отправки всех предыдущих команд в сеть
setTimeout(() => {
this._finishCombo();
}, 60);
return;
}
if (Date.now() - this._switchStartTime > this.MAX_SWITCH_TIME) {
console.warn(`[OneTick] 🛑 (BULL) Failed to equip Bull Hat within ${this.MAX_SWITCH_TIME}ms. Aborting combo.`);
this._finishCombo(true); // Принудительное завершение
return;
}
// Постоянно пытаемся надеть Bull Hat
try {
mh.equip(0, this.BULL_HAT);
} catch(e) {}
// Рекурсивный вызов для продолжения опроса
setTimeout(() => {
this._attemptBullEquipAndStrike(angle, enemyPos, mh, myPlayer);
}, this.POLLING_INTERVAL); // POLLING_INTERVAL = 0
}
_finishCombo(error = false) {
const mh = this.client.ModuleHandler;
try {
// Вернуть старую шапку
if (typeof this.prevHat === "number") mh.equip(0, this.prevHat);
// Вернуть старое оружие (если оно было не в слоте 0)
if (typeof this.prevWeapon === "number" && this.prevWeapon !== 0) {
mh.whichWeapon(this.prevWeapon);
}
} catch(e){}
this.lastCycle = Date.now();
this._pending = false;
this._stage = -1; // Возвращаемся к движению
this._stopAllKeys(mh);
const finalTime = Date.now() - this._comboStartTime;
this.client.SocketManager?.chat(`[OneTick] 💥 COMBO DONE ${error ? '(Error)' : ''} (${finalTime}ms)`);
console.log(`[OneTick] 🔄 Combo sequence finished in ${finalTime}ms.`);
}
_executeCombo(enemy, dist) {
this._pending = true;
this._comboStartTime = Date.now();
const mh = this.client.ModuleHandler;
const myPlayer = this.client.myPlayer;
if (!mh || !myPlayer) { this._pending = false; return; }
this.prevHat = myPlayer.hatID;
this.prevWeapon = myPlayer.weaponIndex || 0;
const angle = Math.atan2(enemy.position.current.y - myPlayer.position.current.y, enemy.position.current.x - myPlayer.position.current.x);
const P_DELAY_MS = this._calculateDelay(dist); // Дополнительная динамическая пауза для синхронизации
console.log(`[OneTick] Combo initiated. Dist: ${Math.round(dist)}. Dynamic Delay: ${P_DELAY_MS}ms. Total Wait before Bull Equip Attempt: ${this.HAT_SWITCH_DELAY_MS + P_DELAY_MS}ms`);
// 1. Выбрать основное оружие (слот 0)
try { mh.whichWeapon(0); } catch(e){}
// 2. Turret Hat Equip
console.log(`[OneTick] 🛡️ (TURRET) Equipping Turret Hat.`);
try { mh.equip(0, this.TURRET_HAT); } catch(e){}
// 3. Планируем НАЧАЛО циклического опроса для смены Bull Hat
const totalBullWait = this.HAT_SWITCH_DELAY_MS + P_DELAY_MS;
console.log(`[OneTick] ⏳ Waiting ${totalBullWait}ms before attempting Bull Hat equip.`);
setTimeout(() => {
console.log(`[OneTick] 🐂 (BULL) Starting Bull Hat equip attempt (polling).`);
this._switchStartTime = Date.now();
this._attemptBullEquipAndStrike(angle, enemy.position.current, mh, myPlayer);
}, totalBullWait); // Ждем минимального КД (30 мс) + динамической задержки
}
drawOverlay(ctx) {
try {
if (!this.active || !this._target) return;
const p = this._target.position.current;
const myPos = this.client.myPlayer?.position.current;
if (!p || !myPos) return;
// 1. Рисуем идеальный круг для старта комбо
rendering_Renderer.circle(ctx, p.x, p.y, this.FIXED_MOVE_DIST, "#ffc107", 3, 0.6);
// 2. Рисуем радиус досягаемости вашего основного оружия
const primaryID = this.client.myPlayer.getItemByType?.(0);
const weapon = Weapons[primaryID];
const range = weapon && typeof weapon.range === "number" ? weapon.range : 175;
// Визуализация дальности оружия (например, зелёный)
rendering_Renderer.circle(ctx, myPos.x, myPos.y, range, "#28a745", 3, 0.3);
// 3. Если мы в фазе движения, подсвечиваем цель
if (this._stage === -1) {
rendering_Renderer.circle(ctx, p.x, p.y, this.DIST_TOLERANCE, "#007bff", 3, 0.8);
}
} catch(e){}
}
_cleanup() {
try {
if (typeof this.prevHat === "number") this.client.ModuleHandler.equip(0, this.prevHat);
this._stopAllKeys(this.client.ModuleHandler);
} catch(e){}
this.prevHat = null;
this._target = null;
this._pending = false;
this._stage = -1;
}
}
const modules_OneTick = OneTick;
class BowspamPredict {
name = "bowspamPredict";
client;
_active = false;
// --- НАСТРОЙКИ ---
USE_SAMURAI_HAT = true;
SAMURAI_HAT_ID = 20;
FOREST_HAT_ID = 1; // ⭐ Новая Лесная шляпа (ID 1)
// Набор ID дальнобойного оружия: (9-Лук, 12-Арбалет, 13-Тройной, 15-Мушкет)
RANGED_WEAPON_IDS = new Set([9, 12, 13, 15]);
// ⭐ Хранилище для оригинальных шапок ботов. (mp.id -> originalHatID)
_botOriginalHats = new Map();
constructor(client) {
this.client = client;
}
// --- СТАТИЧЕСКИЕ ХЕЛПЕРЫ ---
static isValidTarget(target) {
return target && target.position && target.position.current && !target.isDead && !target.dead;
}
static stopAttack(cm) {
if (cm && cm.stopAttack) {
cm.stopAttack();
}
}
// Предикт позиции цели
_predictTargetPosition(target, timeMs) {
if (!target || !target.position || !target.position.current) return null;
try {
if (typeof target.predictItems === 'function') target.predictItems();
if (typeof target.predictWeapons === 'function') target.predictWeapons();
} catch (e) {}
const cur = target.position.current;
const vel = (target.velocity || { x: 0, y: 0 });
const timeS = timeMs / 1000;
const startPos = target.position.future || cur;
return {
x: startPos.x + (vel.x || 0) * timeS,
y: startPos.y + (vel.y || 0) * timeS
};
}
// Определяет время полета снаряда
_calculatePredictTime(mp, target) {
// Логика выбора оружия для расчета времени (должна соответствовать _prepareForShot)
const currentWeaponID = (mp.weaponSlot === 0) ? mp.weapon?.primary : mp.weapon?.secondary;
const weaponID = this.RANGED_WEAPON_IDS.has(currentWeaponID) ? currentWeaponID : null;
if (!weaponID || !Weapons || !Projectiles) return 20;
try {
const weaponObj = Weapons.find(w => w.id === weaponID);
const projectileID = weaponObj?.projectile;
if (projectileID !== undefined && Projectiles[projectileID]) {
const projData = Projectiles[projectileID];
const speedMult = weaponObj?.spdMultProj || 1;
const projectileSpeed = projData.speed * speedMult;
if (projectileSpeed > 0 && target.distance) {
return (target.distance / projectileSpeed) * 1000;
}
}
} catch (e) {}
return 20;
}
// Логика переключения оружия (Предкит)
_prepareForShot(cm, mp) {
const RANGED = this.RANGED_WEAPON_IDS;
const primaryId = mp.weapon?.primary;
const secondaryId = mp.weapon?.secondary;
if (typeof primaryId === 'number' && RANGED.has(primaryId) && cm.weapon !== 0) {
try { cm.whichWeapon(0); mp.weaponSlot = 0; return true; } catch(e){}
}
else if (typeof secondaryId === 'number' && RANGED.has(secondaryId) && cm.weapon !== 1 && !RANGED.has(primaryId)) {
try { cm.whichWeapon(1); mp.weaponSlot = 1; return true; } catch(e){}
}
else if ((cm.weapon === 0 && RANGED.has(primaryId)) || (cm.weapon === 1 && RANGED.has(secondaryId))) {
// Оружие уже выбрано
mp.weaponSlot = cm.weapon;
return true;
}
return false;
}
// --- ЛОГИКА ШАПОК ---
// ⭐ Определяет целевую шапку по текущему выбранному оружию
_getTargetHatID(mp) {
if (!this.USE_SAMURAI_HAT) return null;
// Определяем ID оружия, которое выбрал _prepareForShot
const currentWeaponID = (mp.weaponSlot === 0) ? mp.weapon?.primary : mp.weapon?.secondary;
// Мушкет (ID 15) -> Самурайская шляпа (ID 20)
if (currentWeaponID === 15) {
return this.SAMURAI_HAT_ID;
}
// Лук (9), Арбалет (12), Тройной выстрел (13) -> Лесная шляпа (ID 1)
else if ([9, 12, 13].includes(currentWeaponID)) {
return this.FOREST_HAT_ID;
}
return null;
}
// ⭐ Надевает/поддерживает шапку, сохраняя оригинал
_manageHat(cm, mp) {
const targetHatId = this._getTargetHatID(mp);
if (targetHatId === null) return;
const botId = mp.id;
const prevHatId = mp.hatID;
// 1. Сохраняем оригинальную шапку, если еще не сохранили
if (!this._botOriginalHats.has(botId)) {
this._botOriginalHats.set(botId, prevHatId);
}
// 2. Экипируем целевую шапку, если она еще не надета
if (prevHatId !== targetHatId) {
try {
cm.equip && cm.equip(0, targetHatId); // 0 = Hat slot
} catch (e) {}
}
}
// ⭐ Восстанавливает шапку при деактивации модуля
_restoreOriginalHat(cm, mp) {
const botId = mp.id;
if (this._botOriginalHats.has(botId)) {
const originalHatId = this._botOriginalHats.get(botId);
// Восстанавливаем, если текущая шапка – одна из тех, что мы надевали
if (mp.hatID === this.SAMURAI_HAT_ID || mp.hatID === this.FOREST_HAT_ID) {
try {
cm.equip && cm.equip(0, originalHatId);
} catch (e) {}
}
this._botOriginalHats.delete(botId);
}
}
// Выполнение логики атаки для одной сущности (только боты)
executeAttack(cm, mp, target) {
// 1. ПРЕДКИТ: Выбор дальнобойного оружия
const hasRanged = this._prepareForShot(cm, mp);
if (!hasRanged) {
// Если нет дальнобойного оружия, мы ничего не делаем, включая шапку
return;
}
// 2. УПРАВЛЕНИЕ ШАПКОЙ (если оружие найдено)
this._manageHat(cm, mp);
// 3. РАСЧЕТ ВРЕМЕНИ И ПОЗИЦИИ ПРЕДИКТА
const timeMs = this._calculatePredictTime(mp, target);
const aimPos = this._predictTargetPosition(target, timeMs);
// Если предикт не удался, используем текущую позицию цели
const botPos = mp.position.future || mp.position.current;
let angle;
if (aimPos && typeof aimPos.x === 'number') {
angle = Math.atan2(aimPos.y - botPos.y, aimPos.x - botPos.x);
} else {
const targetPos = target.position.current;
angle = botPos.angle(targetPos);
}
// 4. НАВОДКА и ПОСТОЯННАЯ АТАКА
cm.updateAngle && cm.updateAngle(angle, true);
cm.attack && cm.attack(angle);
}
// Применяет остановку атаки ко всем сущностям (для владельца)
applyStopToAll(ModuleHandler) {
BowspamPredict.stopAttack(ModuleHandler); // Владелец
try {
if (typeof myClient !== "undefined" && myClient.clients) {
for (const client of myClient.clients) {
if (client.isOwner) continue;
const cm = client.ModuleHandler;
if (cm) BowspamPredict.stopAttack(cm);
}
}
} catch (e) { console.error(`[BowspamPredict:Bots] Error during applyStopToAll:`, e); }
}
// --- ОСНОВНАЯ ЛОГИКА КАЖДОГО ТИКА ---
postTick() {
const { ModuleHandler, myPlayer, EnemyManager } = this.client;
const ownerCommander = getOwnerCommanderFor(this.client);
// 1. ПРОВЕРКА АКТИВАЦИИ
const config = ownerCommander && ownerCommander.bowspam;
const nowOn = !!(config && config.active);
// Ищем ближайшую цель
const target = EnemyManager.nearestEnemy;
const targetIsValid = BowspamPredict.isValidTarget(target);
// --- A. ЛОГИКА ВЫКЛЮЧЕНИЯ/ОСТАНОВКИ ---
if (!nowOn || !targetIsValid) {
if (this._active) {
this._active = false;
// Остановка атаки для всех
this.applyStopToAll(ModuleHandler);
// ⭐ ВОССТАНОВЛЕНИЕ ШАПОК И СБРОС ФЛАГА ДЛЯ ВСЕХ БОТОВ
try {
if (typeof myClient !== "undefined" && myClient.clients) {
for (const client of myClient.clients) {
if (client.isOwner) continue;
const cm = client.ModuleHandler;
const mp = client.myPlayer;
if (cm && mp) {
this._restoreOriginalHat(cm, mp); // Восстановить шапку
cm._bowspamActive = false; // Сбросить флаг паузы
}
}
}
} catch (e) {}
this._botOriginalHats.clear(); // Очистить карту
}
return;
}
// --- B. МОДУЛЬ АКТИВЕН И ЦЕЛЬ НАЙДЕНА ---
this._active = true;
// --- C. ОБРАБОТКА СУЩНОСТЕЙ (ТОЛЬКО Боты) ---
const entities = [];
try {
if (typeof myClient !== "undefined" && myClient.clients) {
for (const client of myClient.clients) {
if (client.isOwner) continue;
const cm = client.ModuleHandler;
const mp = client.myPlayer;
if (cm && mp) entities.push({ cm, mp });
}
}
} catch (e) {}
if (entities.length === 0) return;
for (const { cm, mp } of entities) {
// ⭐ УСТАНОВКА ФЛАГА ПАУЗЫ
cm._bowspamActive = true;
// Выполнение логики наведения, стрельбы и шапок ТОЛЬКО для ботов
this.executeAttack(cm, mp, target);
}
}
}
const modules_Bowspam = BowspamPredict;
class ClanJoiner {
name="clanJoiner";
client;
joinCount=0;
constructor(client) {
this.client = client;
}
postTick() {
const {myPlayer, SocketManager} = this.client;
const ownerClan = myClient.myPlayer.clanName;
const myClan = myPlayer.clanName;
if (null === ownerClan || myClan === ownerClan) {
return;
}
if (0 === this.joinCount) {
if (null !== myClan) {
SocketManager.leaveClan();
} else {
myClient.pendingJoins.add(myPlayer.id);
SocketManager.joinClan(ownerClan);
}
}
this.joinCount = (this.joinCount + 1) % 7;
}
}
const bot_modules_ClanJoiner = ClanJoiner;
// ======= new: bot_modules_Commander =======
class Commander {
name = "commander";
client;
// состояние, которое владелец изменяет через чат
mode = null; // null | "follow" | "stay" | "circle" | "farm"
radius = 175; // для circle (в пикселях)
center = "player"; // "player" (по-умолчанию) — можно расширить до "cursor"
offset = 0; // общая фаза (поворот всей формации)
timestamp = 0; // для отладки/логики
constructor(client) {
this.client = client;
}
setMode(mode, params = {}) {
this.mode = mode;
if ("radius" in params) {
// безопасный clamp радиуса
let r = Number(params.radius) || this.radius;
r = Math.max(40, Math.min(1200, r));
this.radius = r;
}
if ("center" in params) this.center = params.center;
if ("offset" in params) this.offset = Number(params.offset) || 0;
this.timestamp = Date.now();
}
// owner может использовать postTick (например таймерные команды). Ботам тут обычно не нужно ничего.
postTick() {
}
}
const bot_modules_Commander = Commander;
// === Bot Commander Helpers ===
function getOwnerCommanderRoot() {
myClient.ModuleHandler = myClient.ModuleHandler || {};
myClient.ModuleHandler.ownerCommander = myClient.ModuleHandler.ownerCommander || { targets:{} };
return myClient.ModuleHandler.ownerCommander;
}
function ensureTargetObj(root, tid) {
root.targets = root.targets || {};
const key = String(tid);
if (!root.targets[key]) root.targets[key] = {};
return root.targets[key];
}
function setOwnerCommanderField(tid, key, val) {
const root = getOwnerCommanderRoot();
const oc = ensureTargetObj(root, tid);
if (val === null) delete oc[key];
else oc[key] = val;
oc.lastUpdated = Date.now();
return oc;
}
function readOwnerCommanderField(tid, key) {
const root = getOwnerCommanderRoot();
const oc = ensureTargetObj(root, tid);
return oc[key];
}
// 🟢 Приоритет: последняя команда (global vs personal)
function getOwnerCommanderFor(client) {
const root = getOwnerCommanderRoot();
const id = client?.myPlayer?.id;
const global = root.targets?.all || {};
const personal = id != null ? (root.targets?.[String(id)] || {}) : {};
if (!personal.lastUpdated && !global.lastUpdated) return {};
// у кого lastUpdated свежее → тот и рулит
if (personal.lastUpdated && (!global.lastUpdated || personal.lastUpdated > global.lastUpdated)) {
return personal;
}
return global;
}
class Grinder {
name = "grinder";
client;
targetPos = null;
targetTimeout = 0;
static MAP_SIZE = 14400;
static RANDOM_WALK_INTERVAL = 10000;
static DISTANCE_THRESHOLD = 100;
static STOP_DISTANCE = 50;
static SHARED_SEARCH_RADIUS = 300;
constructor(client) {
this.client = client;
}
generateRandomPos() {
const mapWidth = Grinder.MAP_SIZE;
const mapHeight = Grinder.MAP_SIZE;
const mapCenter = new Vector_Vector(mapWidth / 2, mapHeight / 2);
const range = mapWidth * 0.4;
const randomX = mapCenter.x + (Math.random() - 0.5) * range * 2;
const randomY = mapCenter.y + (Math.random() - 0.5) * range * 2;
const clampedX = Math.min(mapWidth, Math.max(0, randomX));
const clampedY = Math.min(mapHeight, Math.max(0, randomY));
return new Vector_Vector(clampedX, clampedY);
}
postTick() {
const { myPlayer, ModuleHandler, ObjectManager, SocketManager } = this.client;
if (this.client.isOwner || !myPlayer?.id) return;
// ЭФФЕКТИВНАЯ команда.
const ownerCommander = getOwnerCommanderFor(this.client);
// ⭐ ИСПРАВЛЕНИЕ: Получаем доступ к TempData
const tempData = ModuleHandler?.staticModules?.tempData;
// Определяем, активен ли режим Grinder
const isGrinderActive = ownerCommander && ownerCommander.mode === "grinder";
// ⭐ ИСПРАВЛЕНИЕ: Управляем флагом синхронизации оружия в TempData
// Если Grinder активен (isGrinderActive === true), блокируем синхронизацию (disableWeaponSync = true).
// Иначе (режим follow, etc.), синхронизация разрешена (disableWeaponSync = false).
if (tempData && typeof tempData.setWeaponSyncStatus === 'function') {
tempData.setWeaponSyncStatus(isGrinderActive);
}
// Эта проверка необходима. Если эффективный режим не 'grinder', выходим.
if (!isGrinderActive) {
// console.log(`[Grinder:LOG:DEBUG] Бот ${myPlayer.id} не в режиме 'grinder'. Выход.`);
return;
}
ownerCommander.grinder = ownerCommander.grinder || {};
const grinderData = ownerCommander.grinder;
const targetName = grinderData.target || "food";
const targetLimit = grinderData.threshold ? Number(grinderData.threshold) : Infinity;
const typeMap = { wood: 0, food: 1, stone: 2, gold: 3 };
const targetResType = typeMap[targetName];
if (targetResType === undefined) return;
// =========================================================
// === ЛОГИКА: ПРОВЕРКА ЛИМИТА РЕСУРСА (И Самовосстановление) ===
// =========================================================
const currentResCount = myPlayer.resources?.[targetName] || 0;
const botId = myPlayer.id;
if (currentResCount >= targetLimit) {
const personalMode = readOwnerCommanderField(botId, 'mode');
if (personalMode !== "follow") {
// СЛУЧАЙ 1: Лимит достигнут впервые ИЛИ личный режим был сброшен.
SocketManager.move(null);
// Устанавливаем личный режим "follow" с новой меткой времени.
setOwnerCommanderField(botId, 'mode', 'follow');
setOwnerCommanderField(botId, 'grinder', null);
console.log(`[Grinder:LOG:SUCCESS] ✅ БОТ ${botId} (${myPlayer.nickname}) достиг лимита (${targetName}:${currentResCount} >= ${targetLimit}). Активирован ЛИЧНЫЙ режим 'follow'.`);
} else {
// СЛУЧАЙ 2: Лимит превышен, личный режим 'follow' установлен,
// НО ГЛОБАЛЬНАЯ команда ('grinder') является более свежей и перекрыла его.
// Решение: Переустанавливаем личный режим с новой меткой времени.
SocketManager.move(null); // Останавливаем любое движение от Grinder перед выходом.
setOwnerCommanderField(botId, 'mode', 'follow'); // Обновляем timestamp
console.log(`[Grinder:LOG:REASSERT] ⚠️ БОТ ${botId} (${myPlayer.nickname}) принудительно возвращен в 'follow' (глобальная команда была новее).`);
}
// Выход: Управление движением передается классу Movement, который в следующем тике
// увидит личный 'follow' (с самым свежим timestamp) и начнет следование.
return;
}
// =========================================================
const pos = myPlayer.position.current;
const searchRadius = 2000;
const objects = ObjectManager.retrieveObjects(pos, searchRadius);
let nearest = null;
let nearestDist = Infinity;
for (const obj of objects) {
if (!obj?.position || typeof obj.type === "undefined") continue;
if (obj.type === targetResType) {
const d = pos.distance(obj.position.current);
if (d < nearestDist) {
nearestDist = d;
nearest = obj;
}
}
}
// 1. --- ПРИОРИТЕТ 1: РЕСУРС НАЙДЕН (Сбор) ---
if (nearest) {
grinderData.sharedTargetPos = nearest.position.current;
this.targetPos = null;
// =========================================================
// === ЛОГИКА: ВЫБОР ЛУЧШЕГО ОРУЖИЯ ДЛЯ ДОБЫЧИ (FIXED) ===
// =========================================================
let currentWeaponId = myPlayer.weapon?.current ?? myPlayer.weaponIndex;
// ⭐ ИСПРАВЛЕНИЕ ReferenceError: вызываем метод из myPlayer
const bestGatheringWeaponId = myPlayer.getBestGatheringWeaponId();
if (currentWeaponId !== bestGatheringWeaponId) {
// Переключаемся на лучшее оружие
ModuleHandler.whichWeapon(bestGatheringWeaponId);
currentWeaponId = bestGatheringWeaponId;
}
let weaponId = currentWeaponId;
// =========================================================
let baseRange = BreakMode.WEAPON_BREAK_DIST[weaponId];
if (typeof baseRange !== "number") baseRange = 100;
const weaponRange = baseRange + (myPlayer.hitScale || 0);
let resourceHitbox = 0;
switch (nearest.type) {
case 0: resourceHitbox = 55; break;
case 1: resourceHitbox = 25; break;
case 2: resourceHitbox = 45; break;
case 3: resourceHitbox = 39; break;
default: resourceHitbox = 40;
}
const effectiveRange = weaponRange + resourceHitbox;
const angle = pos.angle(nearest.position.current);
ModuleHandler.cursorAngle = angle;
if (nearestDist > effectiveRange) {
SocketManager.move(angle);
} else {
SocketManager.move(null);
ModuleHandler.attack(angle);
}
return;
}
// 2. --- ПРИОРИТЕТ 2: КООРДИНИРОВАННЫЙ ПОИСК ---
if (grinderData.sharedTargetPos) {
const sharedPos = grinderData.sharedTargetPos;
const distToShared = pos.distance(sharedPos);
const moveAngle = pos.angle(sharedPos);
if (distToShared < Grinder.SHARED_SEARCH_RADIUS) {
SocketManager.move(null);
const now = Date.now();
if (!this.targetTimeout || this.targetTimeout < now) {
this.targetTimeout = now + Grinder.RANDOM_WALK_INTERVAL;
}
return;
}
ModuleHandler.cursorAngle = moveAngle;
SocketManager.move(moveAngle);
this.targetPos = null;
return;
}
// 3. --- ПРИОРИТЕТ 3: RANDOM MILL (Уникальная случайная цель) ---
const now = Date.now();
let targetPos = this.targetPos;
let dist = targetPos ? pos.distance(targetPos) : Infinity;
if (!this.targetTimeout) {
this.targetTimeout = now + Grinder.RANDOM_WALK_INTERVAL;
}
let mustChooseNewTarget = false;
if (!targetPos || dist < Grinder.DISTANCE_THRESHOLD || now > this.targetTimeout) {
mustChooseNewTarget = true;
}
if (mustChooseNewTarget) {
this.targetPos = this.generateRandomPos();
this.targetTimeout = now + Grinder.RANDOM_WALK_INTERVAL;
targetPos = this.targetPos;
dist = pos.distance(targetPos);
console.log(`[Grinder:LOG] БОТ ${botId} (${myPlayer.nickname}) ищет цель (RandomMill): X=${targetPos.x.toFixed(0)}, Y=${targetPos.y.toFixed(0)}.`);
}
if (targetPos) {
const moveAngle = pos.angle(targetPos);
ModuleHandler.cursorAngle = moveAngle;
if (dist > Grinder.STOP_DISTANCE) {
SocketManager.move(moveAngle);
} else {
SocketManager.move(null);
}
} else {
SocketManager.move(null);
}
}
}
const bot_modules_Grinder = Grinder;
class Movement {
name = "movement";
client;
stopped = true;
_randomTarget = null;
_randomTargetTimeout = 0;
_lastObstacleTime = 0;
_lastLogState = "";
_lastMoveAngle = 0;
// Для PlayerFollow (из старого кода)
_waitStartTime = 0;
// 🔥 НОВОЕ: Для режима squarespin (Пункт 1)
_squareCornerIndex = undefined;
// --- Данные Лидара для визуализации ---
_lidarDebug = {
rays: [],
bestAngle: null,
blocked: false
};
// 🔥 НАСТРОЙКИ ВИЗУАЛИЗАЦИИ
static VISUALIZATION_ENABLED = false;
// 🔥 НАСТРОЙКИ ЛИДАРА
static LIDAR_ANGLE_STEP = 0.10; // ~6 градусов
static LIDAR_RANGE = 230;
static BOT_WIDTH = 40;
static SCAN_CONE = 1.5; // +/- 85 градусов
static MAX_DEV_ANGLE = 0.8; // Макс отклонение перед ломкой
constructor(client) {
this.client = client;
}
getSpeedMultiplier() {
const { myPlayer } = this.client;
let mult = 1.0;
if (myPlayer && myPlayer.hatID && Hats[myPlayer.hatID]?.spdMult) mult *= Hats[myPlayer.hatID].spdMult;
if (myPlayer && myPlayer.accessoryID && Accessories[myPlayer.accessoryID]?.spdMult) mult *= Accessories[myPlayer.accessoryID].spdMult;
return mult;
}
calculateInertia() { return 8 * this.getSpeedMultiplier(); }
getTargetPosition() {
const { ModuleHandler } = myClient;
return ModuleHandler.lockPosition ? ModuleHandler.lockedPosition : (myClient.myPlayer?.position?.current || new Vector_Vector(0, 0));
}
// Из старого кода: рандомная позиция по всей карте
generateRandomPos() {
const HARDCODED_MAP_SIZE = 14400;
let mapWidth = HARDCODED_MAP_SIZE;
let mapHeight = HARDCODED_MAP_SIZE;
if (this.client.myPlayer?.map && this.client.myPlayer.map.width > 0) {
mapWidth = this.client.myPlayer.map.width;
mapHeight = this.client.myPlayer.map.height;
}
const randomX = Math.random() * mapWidth;
const randomY = Math.random() * mapHeight;
return new Vector_Vector(
Math.min(mapWidth, Math.max(0, randomX)),
Math.min(mapHeight, Math.max(0, randomY))
);
}
// --- ВСПОМОГАТЕЛЬНАЯ ФУНКЦИЯ ДЛЯ КВАДРАТА (ИЗ СТАРОГО КОДА) ---
_getSquareCorner(center, cornerIndex, radius) {
let x = center.x;
let y = center.y;
switch (cornerIndex % 4) {
case 0: x -= radius; y -= radius; break; // Верх-лево
case 1: x += radius; y -= radius; break; // Верх-право
case 2: x += radius; y += radius; break; // Низ-право
case 3: x -= radius; y += radius; break; // Низ-лево
}
return new Vector_Vector(x, y);
}
// =========================================================
// 🎯 ОПРЕДЕЛЕНИЕ ЦЕЛИ (Логика из старого класса)
// =========================================================
getDesiredPosition() {
const ownerCommander = getOwnerCommanderFor(this.client);
const mode = ownerCommander?.mode;
const ownerPos = this.getTargetPosition();
const botPos = this.client.myPlayer.position.current;
if (!mode || mode === 'stay') return null;
// 1. FOLLOW
if (mode === 'follow') {
return botPos.distance(ownerPos) > (window.BOT_FOLLOW_RADIUS || 175) ? ownerPos : null;
}
// 2. CURSOR FOLLOW
if (mode === 'cursorFollow') {
const cursor = cursorPosition();
return botPos.distance(cursor) > 175 ? cursor : null;
}
// 3. CIRCLE (Статичный круг) - 🔥 ИЗМЕНЕНО для 1 и 4 ботов (Пункт 3)
if (mode === 'circle') {
const { clients } = myClient;
const clientsArr = Array.from(clients);
const idx = clientsArr.indexOf(this.client);
if (idx === -1) return ownerPos;
const total = clientsArr.length;
const radius = ownerCommander.circle?.radius || 175;
const offset = ownerCommander.offset || 0;
let angleForSlot = 0;
if (total === 1) {
// 1 бот: ставим точку сбоку (0 радиан - вправо)
angleForSlot = 0 + offset;
} else if (total === 4) {
// 4 бота: ставим между сторонних точек (т.е. на углах: PI/4, 3PI/4, ...)
angleForSlot = (Math.PI / 2) * idx + Math.PI / 4 + offset;
} else {
// Для всех остальных случаев используем равномерное распределение
angleForSlot = (2 * Math.PI / total) * idx + offset;
}
return ownerPos.direction(angleForSlot, radius);
}
// 4. CIRCLE SPIN (Кручение)
if (mode === 'circlespin') {
const clientsArr = Array.from(myClient.clients);
const idx = clientsArr.indexOf(this.client);
if (idx === -1) return ownerPos;
const total = Math.max(1, clientsArr.length);
const radius = ownerCommander.circle?.radius || 175;
const baseAngle = (2 * Math.PI / total) * idx;
const offset = ownerCommander.offset || 0; // Этот offset меняется в updateCommander, создавая вращение
const lead = ownerCommander.lead || 0.06; // Упреждение
return ownerPos.direction(baseAngle + offset + lead, radius);
}
// 5. SQUARE (Статичный квадрат)
if (mode === 'square') {
const clientsArr = Array.from(myClient.clients);
const idx = clientsArr.indexOf(this.client);
if (idx === -1) return ownerPos;
const cornerIndex = idx % 4;
const radius = ownerCommander.square?.radius || 175;
// Расчет базы (угла)
let pos = this._getSquareCorner(ownerPos, cornerIndex, radius);
// Смещение для стека (если ботов больше 4)
const stackOffset = Math.floor(idx / 4) * 15;
if (stackOffset > 0) {
// Сдвигаем немного, чтобы не стояли друг в друге
pos.x += Math.cos(Math.PI/4 + cornerIndex * Math.PI/2) * stackOffset;
pos.y += Math.sin(Math.PI/4 + cornerIndex * Math.PI/2) * stackOffset;
}
return pos;
}
// 6. SQUARE SPIN (Крутящийся квадрат) - 🔥 ИЗМЕНЕНО (Пункт 1)
if (mode === 'squarespin') {
const clientsArr = Array.from(myClient.clients);
const idx = clientsArr.indexOf(this.client);
if (idx === -1) return ownerPos;
const radius = ownerCommander.square?.radius || 175;
// Инициализация индекса, если еще не задан
if (this._squareCornerIndex === undefined) {
this._squareCornerIndex = idx % 4; // Начальный угол
}
// Текущая целевая точка
const currentTarget = this._getSquareCorner(ownerPos, this._squareCornerIndex, radius);
const distToCurrentTarget = botPos.distance(currentTarget);
// Если бот достиг точки (или достаточно близко), переходим к следующему углу
// Используем пороговое значение 50 для переключения
if (distToCurrentTarget < 50) {
this._squareCornerIndex = (this._squareCornerIndex + 1) % 4; // Следующий угол
// Обновляем целевую точку для возврата (будет возвращена новая позиция)
return this._getSquareCorner(ownerPos, this._squareCornerIndex, radius);
}
// Возвращаем текущую целевую точку
return currentTarget;
}
// 7. RANDOM MILL (Обычный)
if (mode === 'randommill') {
const now = Date.now();
if (!this._randomTarget || botPos.distance(this._randomTarget) < 100 || now > this._randomTargetTimeout) {
this._randomTarget = this.generateRandomPos();
this._randomTargetTimeout = now + 15000;
}
return this._randomTarget;
}
return null;
}
// =========================================================
// 📡 LIDAR SYSTEM (Новая, умная логика)
// =========================================================
checkRayCollision(start, end, width) {
const { ObjectManager, PlayerManager, myPlayer } = this.client;
const dist = start.distance(end);
const objects = ObjectManager.retrieveObjects(start, dist + 100);
let firstHit = null;
let minDist = Infinity;
for (const obj of objects) {
if (!obj.isDestroyable) continue;
if (obj.ownerID === myPlayer.id) continue;
if (PlayerManager.isEnemyByID(obj.ownerID, myPlayer) === false) continue;
const isBuilding = [6, 7, 8, 9, 15].includes(obj.type);
if (!isBuilding) continue;
const objPos = obj.position.current;
const objScale = (obj.scale || 45);
const hitRadius = objScale + width;
const distToLine = this.distToSegment(start, end, objPos);
if (distToLine < hitRadius) {
const d = start.distance(objPos);
if (d < minDist && d < dist + objScale) {
minDist = d;
firstHit = obj;
}
}
}
return firstHit;
}
distToSegment(A, B, P) {
const l2 = (A.x - B.x) ** 2 + (A.y - B.y) ** 2;
if (l2 === 0) return P.distance(A);
let t = ((P.x - A.x) * (B.x - A.x) + (P.y - A.y) * (B.y - A.y)) / l2;
t = Math.max(0, Math.min(1, t));
const projX = A.x + t * (B.x - A.x);
const projY = A.y + t * (B.y - A.y);
return Math.sqrt((P.x - projX) ** 2 + (P.y - projY) ** 2);
}
normalizeAngle(angle) {
let result = angle % (2 * Math.PI);
if (result > Math.PI) result -= 2 * Math.PI;
else if (result <= -Math.PI) result += 2 * Math.PI;
return result;
}
scanSurroundings(botPos, targetPos) {
this._lidarDebug.rays = [];
this._lidarDebug.bestAngle = null;
const angleToTarget = botPos.angle(targetPos);
let bestRayAngle = null;
let maxPathScore = -Infinity;
let directBlocker = null;
const coneStart = -Movement.SCAN_CONE;
const coneEnd = Movement.SCAN_CONE;
let scanAngles = [];
for (let offset = coneStart; offset <= coneEnd; offset += Movement.LIDAR_ANGLE_STEP) {
scanAngles.push(angleToTarget + offset);
}
// Прямой луч для поиска препятствия
const centerRayEnd = botPos.direction(angleToTarget, Movement.LIDAR_RANGE);
const centerObstacle = this.checkRayCollision(botPos, centerRayEnd, Movement.BOT_WIDTH);
if (centerObstacle) {
directBlocker = centerObstacle;
const obstaclePos = centerObstacle.position.current;
const obstacleRadius = (centerObstacle.scale || 45) + Movement.BOT_WIDTH;
const distToCenter = botPos.distance(obstaclePos);
if (distToCenter > obstacleRadius) {
const angleToObstacle = botPos.angle(obstaclePos);
const tangentAngle = Math.asin(Math.min(1, obstacleRadius / distToCenter));
scanAngles.push(this.normalizeAngle(angleToObstacle + tangentAngle + 0.2)); // + запас
scanAngles.push(this.normalizeAngle(angleToObstacle - tangentAngle - 0.2)); // - запас
}
}
scanAngles = Array.from(new Set(scanAngles));
for (const scanAngle of scanAngles) {
const rayEnd = botPos.direction(scanAngle, Movement.LIDAR_RANGE);
const obstacle = this.checkRayCollision(botPos, rayEnd, Movement.BOT_WIDTH);
let finalRayEnd = rayEnd;
let distToObstacle = Movement.LIDAR_RANGE;
if (obstacle) {
distToObstacle = botPos.distance(obstacle.position.current) - (obstacle.scale || 45) - Movement.BOT_WIDTH / 2;
if (distToObstacle > 0) finalRayEnd = botPos.direction(scanAngle, distToObstacle);
else distToObstacle = 0;
const isCenterRay = Math.abs(this.normalizeAngle(scanAngle - angleToTarget)) < 0.1;
if (isCenterRay) directBlocker = obstacle;
}
this._lidarDebug.rays.push({ start: botPos, end: finalRayEnd, isBlocked: obstacle && distToObstacle < Movement.LIDAR_RANGE });
const deviation = Math.abs(this.normalizeAngle(scanAngle - angleToTarget));
const normalizedDist = distToObstacle / Movement.LIDAR_RANGE;
// Формула оценки пути
const pathScore = normalizedDist * 1.5 - deviation * 1.0;
if (distToObstacle > Movement.BOT_WIDTH * 1.2 && deviation <= Movement.SCAN_CONE) {
if (pathScore > maxPathScore) {
maxPathScore = pathScore;
bestRayAngle = scanAngle;
}
}
}
this._lidarDebug.bestAngle = bestRayAngle;
if (bestRayAngle === null || maxPathScore < 0) {
return { type: "BLOCKED", obstacle: directBlocker };
}
return { type: "MOVE", angle: bestRayAngle };
}
_updateLookAngle(currentMode, moveAngle) {
const { ModuleHandler, myPlayer } = this.client;
const botPos = myPlayer.position.current;
let aimAngle = null;
// В спинах бот должен смотреть наружу или на курсор
if (currentMode === 'squarespin' || currentMode === 'circlespin') {
// Можно добавить логику, чтобы он смотрел от центра
// Но оставим дефолтную (на курсор), как в старом коде
}
if (currentMode === 'randommill' || currentMode === 'playerFollow') {
aimAngle = moveAngle !== undefined ? moveAngle : this._lastMoveAngle;
if (aimAngle !== null) ModuleHandler.reverseCursorAngle = (aimAngle + Math.PI) % (2 * Math.PI);
} else if (currentMode !== 'grinder') {
try {
const cursor = cursorPosition();
aimAngle = botPos.angle(cursor);
ModuleHandler.reverseCursorAngle = cursor.angle(botPos);
} catch (e) {
aimAngle = moveAngle !== undefined ? moveAngle : this._lastMoveAngle;
if (aimAngle !== null) ModuleHandler.reverseCursorAngle = (aimAngle + Math.PI) % (2 * Math.PI);
}
}
if (aimAngle !== null) ModuleHandler.cursorAngle = aimAngle;
}
drawOverlay(ctx) {
if (!Movement.VISUALIZATION_ENABLED) return;
try {
const myPlayer = this.client.myPlayer;
if (!myPlayer || !myPlayer.position) return;
const myPos = myPlayer.position.current;
for (const ray of this._lidarDebug.rays) {
const blockedColor = "rgba(205, 92, 92, 0.50)";
const freeColor = "rgba(173, 216, 230, 0.1)";
const color = ray.isBlocked ? blockedColor : freeColor;
Aibm.Renderer.line(ctx, ray.start, ray.end, color, ray.isBlocked ? 0.5 : 1.5);
}
if (this._lidarDebug.bestAngle !== null) {
const bestEnd = myPos.direction(this._lidarDebug.bestAngle, 120);
Aibm.Renderer.line(ctx, myPos, bestEnd, "#00bfff", 1, 2.5);
}
const target = this.getDesiredPosition();
// Для playerFollow цель может быть в sharedTargetPos
if (target) {
Aibm.Renderer.line(ctx, myPos, target, "rgba(255, 255, 255, 0.4)", 0.5, 1);
}
} catch (e) {}
}
// =========================================================
// ⚙️ ГЛАВНЫЙ ЦИКЛ (POST TICK)
// =========================================================
postTick() {
const { myPlayer, ModuleHandler, SocketManager, PlayerManager } = this.client;
if (!myPlayer || !myPlayer.inGame) return;
const ownerCommander = getOwnerCommanderFor(this.client);
const currentMode = ownerCommander?.mode;
const botPos = myPlayer.position.current;
const botId = myPlayer.id;
// --- 1. Игнор и Авто-Милл ---
if (ModuleHandler.moduleActive) {
SocketManager.move(null);
this.stopped = true;
return;
}
if (currentMode === "grinder") return;
if (currentMode === "exitbot") {
this.client.disconnect();
if (typeof window.setOwnerCommanderField === 'function') window.setOwnerCommanderField(botId, 'mode', null);
return;
}
const am = ModuleHandler.staticModules.autoMill;
if (am) {
// 🔥 ИЗМЕНЕНО: AutoMill теперь работает ТОЛЬКО при randommill и глобальных настройках (Пункт 2)
am.toggle = currentMode === 'randommill' || (typeof Settings !== 'undefined' && Settings.automill);
}
// --- 2. ЛОГИКА PLAYER FOLLOW (Восстановлена из старого кода) ---
if (currentMode === "playerFollow") {
const targetPlayerId = ownerCommander?.targetPlayerId;
if (!targetPlayerId || !PlayerManager) {
SocketManager.move(null);
return;
}
// Инициализация состояния
ownerCommander.playerFollow = ownerCommander.playerFollow || {};
const pfData = ownerCommander.playerFollow;
pfData.searchPhase = pfData.searchPhase || "SEARCH_START";
const targetPlayer = PlayerManager.playerData.get(Number(targetPlayerId));
let targetFound = false;
let foundPos = null;
// А. Проверка видимости цели
if (targetPlayer?.position?.current) {
const tp = targetPlayer.position.current;
if (tp.x !== 0 || tp.y !== 0) {
targetFound = true;
foundPos = tp;
}
}
// Б. Если цель найдена
if (targetFound) {
pfData.sharedTargetPos = { x: foundPos.x, y: foundPos.y };
if (pfData.searchPhase !== "FOLLOW") pfData.searchPhase = "FOLLOW";
setOwnerCommanderField('all', 'playerFollow', pfData);
this._randomTarget = null;
this._waitStartTime = 0;
// Двигаемся к игроку
const dist = botPos.distance(foundPos);
const followDist = ownerCommander?.followDistance || 175;
if (dist > followDist) {
// 🔥 Используем ЛИДАР для следования за игроком, чтобы не застревать
const scanRes = this.scanSurroundings(botPos, foundPos);
const moveAngle = scanRes.type === "MOVE" ? scanRes.angle : botPos.angle(foundPos);
// Брейкмод работает отдельно, здесь просто движение
SocketManager.move(moveAngle);
this._updateLookAngle(currentMode, moveAngle);
this.stopped = false;
} else {
SocketManager.move(null);
this.stopped = true;
// Смотрим туда же, куда и цель
const aimAngle = targetPlayer.angle;
ModuleHandler.cursorAngle = aimAngle;
ModuleHandler.reverseCursorAngle = aimAngle + Math.PI;
}
return; // Выходим, так как playerFollow обработан
}
// В. Если цель потеряна -> Логика поиска (State Machine)
// 1. FOLLOW -> GOTO_LAST
if (pfData.searchPhase === "FOLLOW") {
pfData.searchPhase = "GOTO_LAST";
setOwnerCommanderField('all', 'playerFollow', pfData);
}
// 2. GOTO_LAST (Идем в последнюю известную точку)
if (pfData.searchPhase === "GOTO_LAST" && pfData.sharedTargetPos) {
const lastPos = new Vector_Vector(pfData.sharedTargetPos.x, pfData.sharedTargetPos.y);
const dist = botPos.distance(lastPos);
if (dist > 100) {
// Используем лидар для прохода к точке сбора
const scanRes = this.scanSurroundings(botPos, lastPos);
const ang = scanRes.type === "MOVE" ? scanRes.angle : botPos.angle(lastPos);
SocketManager.move(ang);
this._updateLookAngle(currentMode, ang);
this.stopped = false;
} else {
// Пришли
pfData.searchPhase = "WAIT_AT_LAST";
setOwnerCommanderField('all', 'playerFollow', pfData);
this.stopped = true;
SocketManager.move(null);
}
return;
}
// 3. WAIT_AT_LAST (Ждем 25 сек)
if (pfData.searchPhase === "WAIT_AT_LAST") {
if (this._waitStartTime === 0) this._waitStartTime = Date.now();
if (Date.now() - this._waitStartTime > 25000) {
pfData.searchPhase = "SEARCH_RANDOM";
setOwnerCommanderField('all', 'playerFollow', pfData);
this._waitStartTime = 0;
}
SocketManager.move(null);
this.stopped = true;
// Крутим головой
ModuleHandler.cursorAngle = (Date.now() / 500) % (Math.PI * 2);
return;
}
// 4. SEARCH_RANDOM (Ищем рандомно)
if (pfData.searchPhase === "SEARCH_RANDOM" || pfData.searchPhase === "SEARCH_START") {
const now = Date.now();
if (!this._randomTarget || botPos.distance(this._randomTarget) < 100 || now > this._randomTargetTimeout) {
this._randomTarget = this.generateRandomPos();
this._randomTargetTimeout = now + 10000;
}
const scanRes = this.scanSurroundings(botPos, this._randomTarget);
const ang = scanRes.type === "MOVE" ? scanRes.angle : botPos.angle(this._randomTarget);
SocketManager.move(ang);
this.stopped = false;
this._updateLookAngle(currentMode, ang);
return;
}
return; // Конец playerFollow
}
// --- 3. ОБЩАЯ ЛОГИКА ДЛЯ ОСТАЛЬНЫХ РЕЖИМОВ (Movement, Circle, Square, etc) ---
const finalTarget = this.getDesiredPosition();
if (Date.now() - this._lastObstacleTime < 100) {
SocketManager.move(null);
this.stopped = true;
this._updateLookAngle(currentMode);
return;
}
if (finalTarget) {
const bm = ModuleHandler.staticModules.breakMode;
const isBreakModeAllowed = (ownerCommander && ownerCommander.breakMode && ownerCommander.breakMode.active);
const distToTarget = botPos.distance(finalTarget);
// Финишная прямая (отключаем лидар для точности)
if (distToTarget < 40) {
if (distToTarget > 10) { // Дистанция остановки
const ang = botPos.angle(finalTarget);
SocketManager.move(ang);
this.stopped = false;
this._updateLookAngle(currentMode, ang);
} else {
SocketManager.move(null);
this.stopped = true;
this._updateLookAngle(currentMode);
// Если достигнута точка в squarespin, то в следующем тике будет новый таргет
}
return;
}
let moveAngle = null;
let needToBreak = false;
let breakTarget = null;
// Используем ЛИДАР если включен BreakMode
if (isBreakModeAllowed && bm) {
const scanResult = this.scanSurroundings(botPos, finalTarget);
if (scanResult.type === "MOVE") {
moveAngle = scanResult.angle;
bm.suppressAutoBreak = true;
bm.setOverrideTarget(null);
bm.clearIgnored();
} else {
// BLOCKED
needToBreak = true;
breakTarget = scanResult.obstacle;
if (!breakTarget) breakTarget = this.checkRayCollision(botPos, finalTarget, 30);
}
} else {
// Если BreakMode выключен - просто идем к цели
moveAngle = botPos.angle(finalTarget);
if (bm) bm.suppressAutoBreak = false;
}
// АТАКА (BreakMode)
if (needToBreak && bm && breakTarget) {
bm.suppressAutoBreak = false;
this._lastObstacleTime = Date.now();
const obstacleRadius = (breakTarget.scale || 45);
const OPTIMAL_ATTACK_DIST = 40 + obstacleRadius + 20;
const BRAKE_TRIGGER_DIST = OPTIMAL_ATTACK_DIST + this.calculateInertia();
const distToObj = botPos.distance(breakTarget.position.current);
if (distToObj > BRAKE_TRIGGER_DIST) {
const approachAngle = botPos.angle(breakTarget.position.current);
SocketManager.move(approachAngle);
this.stopped = false;
bm.setOverrideTarget(null);
this._updateLookAngle(currentMode, approachAngle);
} else {
SocketManager.move(null);
this.stopped = true;
const aimAngle = botPos.angle(breakTarget.position.current);
ModuleHandler.cursorAngle = aimAngle;
ModuleHandler.updateAngle(aimAngle, true);
bm.setOverrideTarget(breakTarget);
this._updateLookAngle(currentMode, aimAngle);
}
return;
}
// ДВИЖЕНИЕ (Если не бьем)
if (moveAngle !== null) {
SocketManager.move(moveAngle);
this._lastMoveAngle = moveAngle;
this._updateLookAngle(currentMode, moveAngle);
this.stopped = false;
} else {
SocketManager.move(null);
}
} else {
// Нет цели
SocketManager.move(null);
this.stopped = true;
this._updateLookAngle(currentMode);
}
}
}
const bot_modules_Movement = Movement;
class AutoReload {
name = "autoReload";
client;
// 🔥 НОВЫЕ: ID дальнобойного оружия
RANGED_WEAPON_IDS = new Set([9, 12, 13, 15]);
// Слот (0 или 1), который мы сознательно держим для дозарядки в данный момент.
_chargingSlot = null;
// Флаг, который предотвращает немедленное вмешательство FastWeaponSwitch
_returnedAfterReload = false;
// Интервал (в тиках) после возврата
RETURN_COOLDOWN_TICKS = 1;
_cooldownCounter = 0;
constructor(client) {
this.client = client;
}
// Нужна ли перезарядка для слота (primary/secondary)
isReloadNeeded(player, typeString) {
try {
const reload = player.reload[typeString];
const weaponID = player.weapon[typeString];
if (weaponID === null || weaponID === 0 || !reload || reload.max <= 0) {
return false;
}
return reload.current < reload.max - 0.5;
} catch (e) {
return false;
}
}
// Метод для проверки/сброса кулдауна
checkCooldownAndDecrement() {
if (this._cooldownCounter > 0) {
this._cooldownCounter--;
return true;
}
return false;
}
postTick() {
const myPlayer = this.client.myPlayer;
const moduleHandler = this.client.ModuleHandler;
if (!myPlayer || !myPlayer.inGame || !moduleHandler) return;
// --- БЛОКИРОВКИ ---
let autoReloadEnabled = true;
try {
if ((typeof Settings !== 'undefined' && Settings.autoreload === false) ||
(this.client.settings && this.client.settings.autoreload === false)) {
autoReloadEnabled = false;
}
} catch (e) {}
if (!autoReloadEnabled) {
this._chargingSlot = null;
this._cooldownCounter = 0;
return;
}
try {
const ab = moduleHandler.staticModules && moduleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) {
this._chargingSlot = null;
return;
}
} catch (e) {}
if (moduleHandler.attackingState !== 0) {
this._chargingSlot = null;
return;
}
// --- КОНЕЦ БЛОКИРОВОК ---
const primaryType = 0;
const secondaryType = 1;
const currentHeld = (typeof moduleHandler.currentHolding !== 'undefined') ? moduleHandler.currentHolding : 0;
// =======================================================
// 🔥 ИСПРАВЛЕННАЯ ЛОГИКА ВОЗВРАТА НА СЛОТ 0 (только для дальнего боя)
// =======================================================
if (this._chargingSlot !== null) {
const slotString = this._chargingSlot === 0 ? 'primary' : 'secondary';
const weaponId = this._chargingSlot === 0 ? myPlayer.weapon.primary : myPlayer.weapon.secondary;
if (!this.isReloadNeeded(myPlayer, slotString)) {
// Оружие полностью заряжено.
// Проверяем, является ли перезаряженное оружие дальнобойным
if (this.RANGED_WEAPON_IDS.has(weaponId)) {
// Если да, переключаемся на слот 0.
moduleHandler.whichWeapon(primaryType);
// Активируем кулдаун, чтобы FastWeaponSwitch не перехватил управление сразу
this._cooldownCounter = this.RETURN_COOLDOWN_TICKS;
}
// Сбрасываем флаг, независимо от того, переключились мы или нет
this._chargingSlot = null;
return;
}
}
// Если мы в кулдауне после возврата, просто выходим
if (this.checkCooldownAndDecrement()) {
return;
}
// ... (остальная часть postTick без изменений) ...
const primaryNeeds = this.isReloadNeeded(myPlayer, 'primary');
const secondaryNeeds = this.isReloadNeeded(myPlayer, 'secondary');
// Если ничего не нуждается в перезарядке, мы не вмешиваемся.
if (!primaryNeeds && !secondaryNeeds) {
return;
}
// Если мы уже начали дозарядку — держим его
if (this._chargingSlot !== null) {
if (currentHeld !== this._chargingSlot) {
moduleHandler.whichWeapon(this._chargingSlot);
}
return; // Ждём завершения зарядки
}
// Выбираем слот для переключения (тот, у которого меньший ratio)
let chooseSlot = null;
if (primaryNeeds && !secondaryNeeds) chooseSlot = primaryType;
else if (!primaryNeeds && secondaryNeeds) chooseSlot = secondaryType;
else {
const pr = myPlayer.reload.primary.current / Math.max(1, myPlayer.reload.primary.max);
const sr = myPlayer.reload.secondary.current / Math.max(1, myPlayer.reload.secondary.max);
chooseSlot = (sr < pr) ? secondaryType : primaryType;
}
// Переключаемся на выбранный слот
if (chooseSlot !== null && chooseSlot !== currentHeld) {
const weaponId = (chooseSlot === 0) ? myPlayer.weapon.primary : myPlayer.weapon.secondary;
if (!weaponId || weaponId === 0) return;
moduleHandler.whichWeapon(chooseSlot);
// Запоминаем, что мы начали дозарядку
this._chargingSlot = chooseSlot;
}
}
}
const modules_AutoReload = AutoReload;
class FastWeaponSwitch {
name = "fastWeaponSwitch";
client;
// Ссылка на модуль AutoReload для проверки его состояния
autoReloadModule = null;
syncShotModule = null;
_lastFastCheck = 0;
FAST_CHECK_INTERVAL = 0; // ms
constructor(client) {
this.client = client;
}
setDependencies(modules) {
this.autoReloadModule = modules.autoReload;
this.syncShotModule = modules.syncShot;
}
postTick() {
const myPlayer = this.client.myPlayer;
const moduleHandler = this.client.ModuleHandler;
if (!myPlayer || !myPlayer.inGame || !moduleHandler) return;
if (this.syncShotModule && this.syncShotModule._isFiringCombo) {
return;
}
// 🔥 ИСПРАВЛЕНИЕ: ДОБАВЛЕНИЕ ПРОВЕРКИ ВКЛЮЧЕНИЯ/ОТКЛЮЧЕНИЯ МОДУЛЯ
let fastSwitchEnabled = true;
try {
// Проверяем глобальный объект Settings и локальные настройки клиента
if ((typeof Settings !== 'undefined' && Settings.fastWeaponSwitch === false) ||
(this.client.settings && this.client.settings.fastWeaponSwitch === false)) {
fastSwitchEnabled = false;
}
} catch (e) {}
if (!fastSwitchEnabled) {
// Если модуль выключен, выходим
return;
}
// --- КОНЕЦ ИСПРАВЛЕНИЯ ---
// --- БЛОКИРОВКИ ---
// 1. Проверяем, не активна ли перезарядка в AutoReload
if (this.autoReloadModule) {
if (this.autoReloadModule._chargingSlot !== null) return;
if (this.autoReloadModule._cooldownCounter > 0) return;
}
// 2. Проверяем атаку/Autobreak
if (moduleHandler.attackingState !== 0) return;
try {
const ab = moduleHandler.staticModules && moduleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
// Проверка частоты
const now = Date.now();
if (now - this._lastFastCheck < this.FAST_CHECK_INTERVAL) return;
this._lastFastCheck = now;
// --- КОНЕЦ БЛОКИРОВОК ---
// ... (остальная логика FastWeaponSwitch)
const currentHeld = (typeof moduleHandler.currentHolding !== 'undefined') ? moduleHandler.currentHolding : 0;
const primaryId = myPlayer.weapon.primary || 0;
const secondaryId = myPlayer.weapon.secondary || 0;
const getWeaponObj = id => {
try {
if (!id) return null;
return Weapons.find(w => w.id === id) || null;
} catch (e) { return null; }
};
const pObj = getWeaponObj(primaryId);
const sObj = getWeaponObj(secondaryId);
const pSpd = (pObj && typeof pObj.spdMult === 'number') ? pObj.spdMult : 1;
const sSpd = (sObj && typeof sObj.spdMult === 'number') ? sObj.spdMult : 1;
// Если есть явный выигрыш по скорости — переключаемся
const THRESH = 0.02; // минимальная разница
if (sObj && (sSpd - pSpd) > THRESH && currentHeld !== 1) {
moduleHandler.whichWeapon(1);
} else if (pObj && (pSpd - sSpd) > THRESH && currentHeld !== 0) {
moduleHandler.whichWeapon(0);
}
}
}
const modules_FastWeaponSwitch = FastWeaponSwitch;
class Sense {
name = "sense";
client;
constructor(client) {
this.client = client;
}
// если нужен периодический код:
postTick() {
const movement = this.client.ModuleHandler.staticModules.movement;
// например, прочитать/изменить направление:
// movement.someMethodOrProperty(...)
}
}
const bot_modules_Sense = Sense; // как у остальных bot_* модулей
class Instakill {
name = "instakill";
client;
_enabled = false;
_target = null;
_markerTimeout = null;
_executing = false;
_angleInterval = null;
_prevWeaponSlot = null;
_rotationInterval = null; // Интервал для вращения крестика (сохранен как null, но не используется)
static COMMAND_DELAY_MS = 0;
// Тайминги по-умолчанию — будут динамически подстраиваться
static MELEE_HIT_DELAY = 120;
static RANGED_HIT_DELAY = 80; // Эта константа больше не используется для задержки комбо
static RESTORE_DELAY = 120;
static RANGE_BUFFER = 5;
static RANGE_MULTIPLIER = 1.0;
// Память о последней цели (мс)
static TARGET_MEMORY = 1200;
// Поведение: выключать после одного комбо
autoDisableAfterOne = true;
// ID шапки Solid Hat для проверки
static SOLID_HAT_ID = 6;
constructor(client) {
this.client = client;
}
_delay(ms) {
return new Promise(resolve => setTimeout(resolve, Math.max(0, ms)));
}
// Получаем эффективный радиус атаки ПЕРВОГО оружия (melee, слот 0)
getPrimaryWeaponRange(myPlayer) {
if (!myPlayer) return 0;
let primaryId = myPlayer.getItemByType?.(0);
let maxRange = 0;
try {
if (typeof Items !== 'undefined') {
// Проверяем только первое оружие (melee, слот 0)
if (primaryId && Items[primaryId] && typeof Items[primaryId].range === 'number') {
maxRange = Items[primaryId].range;
}
}
} catch (e) {}
if (!maxRange || maxRange <= 0) {
try {
let weaponId = primaryId || null;
if (!weaponId && typeof myPlayer.weaponIndex === 'number') weaponId = myPlayer.weaponIndex;
if (typeof BreakMode !== 'undefined' && BreakMode.WEAPON_BREAK_DIST) {
const base = BreakMode.WEAPON_BREAK_DIST[weaponId];
if (typeof base === 'number') maxRange = Math.max(maxRange, base);
}
} catch (e) {}
}
const hitScale = (myPlayer.hitScale || 0);
maxRange = (maxRange + hitScale) * Instakill.RANGE_MULTIPLIER;
return maxRange;
}
// Рисуем маркер цели (Удалена вся логика вращения)
highlightTarget(target) {
if (!target || !target.position) return;
try {
const isLocalClient = (typeof myClient !== 'undefined' && this.client === myClient);
if (!Settings.instakillCrossForBots && !isLocalClient) return;
} catch (e) {}
// Этот блок обычно рисует статический крестик, оставляем как резерв
try { if (typeof window.__aibm_drawCross === 'function') window.__aibm_drawCross(target.position.current.x, target.position.current.y, "#ff6666", 50, 3000); } catch (e) {}
try {
const now = Date.now();
const prev = window.__aibm_instaCross;
window.__aibm_instaCross = {
x: (prev && typeof prev.x === 'number') ? prev.x : target.position.current.x,
y: (prev && typeof prev.y === 'number') ? prev.y : target.position.current.y,
targetX: target.position.current.x,
targetY: target.position.current.y,
color: '#ff6666',
size: 50,
thickness: 8,
until: now + 3000,
lastUpdated: now,
rotation: 0
};
} catch (e) {}
}
enable() {
this._enabled = true;
this._executing = false;
console.log('[Instakill] ENABLED');
}
disable() {
this._enabled = false;
this._target = null;
this._markerTimeout = null;
window.__aibm_instaCross = null;
try { if (typeof window.__aibm_clearCrossPersistent === 'function') window.__aibm_clearCrossPersistent(); else window.__aibm_instaCrossPersistent = null; } catch (e) {}
try { if (this._angleInterval) { clearInterval(this._angleInterval); this._angleInterval = null; } } catch (e) {}
this._executing = false;
console.log('[Instakill] DISABLED');
}
// Проверка — можно ли исполнить инста на цели
canExecuteInsta(target, myPlayer) {
if (!target || !myPlayer) return false;
try {
// Если есть встроенная оценка — используем её
if (typeof target.canPossiblyInstakill === 'function') {
const val = target.canPossiblyInstakill();
if (typeof val === 'number') return val > 0;
}
} catch (e) {}
// Фолбек — грубая оценка по доступному урону
try {
let dmg = 0;
const meleeId = myPlayer.getItemByType?.(0);
const rangedId = myPlayer.getItemByType?.(1);
if (meleeId && typeof Items !== 'undefined' && Items[meleeId]) dmg += (Items[meleeId].dmg || 0);
if (rangedId && typeof Items !== 'undefined' && Items[rangedId]) dmg += (Items[rangedId].dmg || 0);
// Учитываем возможные множители (шапки/эффекты) — консервативно +20%
dmg *= 1.2;
if (typeof target.health === 'number') return dmg >= target.health;
} catch (e) {}
return false;
}
// Рассчитать стратегию нанесения урона (возвращаем зарплатную оценку)
calculateDamageStrategy(target, myPlayer) {
const res = { primaryDamage: 0, secondaryDamage: 0, totalDamage: 0, shouldUsePoison: false };
if (!myPlayer) return res;
try {
// Попытка использовать существующие утилиты проекта
if (typeof myPlayer.getMaxWeaponDamage === 'function') {
try {
const primary = myPlayer.getItemByType?.(0);
const secondary = myPlayer.getItemByType?.(1);
if (primary) res.primaryDamage = myPlayer.getMaxWeaponDamage(primary, false) || 0;
if (secondary) res.secondaryDamage = myPlayer.getMaxWeaponDamage(secondary, false) || 0;
} catch (e) {}
} else {
const meleeId = myPlayer.getItemByType?.(0);
const rangedId = myPlayer.getItemByType?.(1);
if (meleeId && typeof Items !== 'undefined' && Items[meleeId]) res.primaryDamage = Items[meleeId].dmg || 0;
if (rangedId && typeof Items !== 'undefined' && Items[rangedId]) res.secondaryDamage = Items[rangedId].dmg || 0;
}
res.totalDamage = res.primaryDamage + res.secondaryDamage;
try {
if (typeof myPlayer.canDealPoison === 'function') {
const p = myPlayer.canDealPoison();
if (p && p.isAble) res.shouldUsePoison = true;
}
} catch (e) {}
} catch (e) {}
return res;
}
async _executeInstaCombo(predicted, ang, myPlayer, ModuleHandler, prevHat) {
this._executing = true;
console.log(`[Instakill] Executing combo with ${Instakill.COMMAND_DELAY_MS}ms command delay.`);
const HOLD1 = Instakill.MELEE_HIT_DELAY;
const MELEE_ATTACK_DURATION = 20;
const DELAY = Instakill.COMMAND_DELAY_MS;
let elapsed = 0; // Для точного вычисления задержки HOLD1
// 1. Настройка прицеливания (поддерживаем, пока выполняется комбо)
try { if (this._angleInterval) clearInterval(this._angleInterval); } catch (e) {}
try {
this._angleInterval = setInterval(() => {
try {
ModuleHandler.updateAngle &&
ModuleHandler.updateAngle(Math.atan2(predicted.y - myPlayer.position.current.y, predicted.x - myPlayer.position.current.x), true);
} catch (e) {}
}, 30);
} catch (e) {}
try {
// T=0: START COMBO
// 1. MELEE ATTACK 1 (Slot 0) with Bull Hat (ID 7)
try { ModuleHandler.equip && ModuleHandler.equip(0, 7); } catch (e) { console.error('[Instakill] equip bull hat error', e); }
await this._delay(DELAY); elapsed += DELAY;
try { ModuleHandler.updateAngle && ModuleHandler.updateAngle(ang, true); } catch (e) {}
try { ModuleHandler.whichWeapon && ModuleHandler.whichWeapon(0); } catch (e) {}
await this._delay(DELAY); elapsed += DELAY;
try { ModuleHandler.attack && ModuleHandler.attack(ang); } catch (e) {}
await this._delay(DELAY); elapsed += DELAY;
// 2. Ждем время удара первого оружия (HOLD1 - уже потраченное время)
const remainingWait = Math.max(0, HOLD1 - elapsed);
await this._delay(remainingWait);
// T=HOLD1: MELEE ATTACK 2 SETUP
// 2a. Stop Attack 1
try { ModuleHandler.stopAttack && ModuleHandler.stopAttack(); } catch (e) {}
await this._delay(DELAY); // Задержка для гарантии остановки
// 2b. Equip Turret Hat (ID 53)
try { ModuleHandler.equip && ModuleHandler.equip(0, 53); } catch (e) { console.error('[Instakill] equip turret error', e); }
await this._delay(DELAY);
// 2c. MELEE Attack 2 Start (IMMEDIATE)
try { ModuleHandler.updateAngle && ModuleHandler.updateAngle(ang, true); } catch (e) {}
try { ModuleHandler.whichWeapon && ModuleHandler.whichWeapon(1); } catch (e) {} // Слот 1
await this._delay(DELAY);
try { ModuleHandler.attack && ModuleHandler.attack(ang); } catch (e) {}
await this._delay(DELAY);
// 3. Ждем минимальное время для срабатывания удара
await this._delay(MELEE_ATTACK_DURATION);
// 3a. Stop Attack 2
try { ModuleHandler.stopAttack && ModuleHandler.stopAttack(); } catch (e) {}
await this._delay(DELAY); // Задержка для гарантии остановки
// 4. Hat Restore
if (prevHat && prevHat !== 7 && prevHat !== 53) {
await this._delay(Instakill.RESTORE_DELAY); // Ждем время восстановления
try { ModuleHandler.equip && ModuleHandler.equip(0, prevHat); } catch (e) {}
await this._delay(DELAY); // Задержка после восстановления шапки
}
} catch (e) {
console.error('[Instakill] combo execution error', e);
} finally {
// 5. Global cleanup
try { if (this._angleInterval) { clearInterval(this._angleInterval); this._angleInterval = null; } } catch (e) {}
this._executing = false;
if (this.autoDisableAfterOne) this.disable();
console.log('[Instakill] Combo finished and cleaned up.');
}
}
// Подогнать тайминги под множитель скорости
optimizeAttackTiming(myPlayer) {
try {
const mult = (typeof myPlayer.getWeaponSpeedMult === 'function') ? (myPlayer.getWeaponSpeedMult() || 1) : 1;
if (mult > 0) {
Instakill.MELEE_HIT_DELAY = Math.max(0, Math.round(120 / mult));
Instakill.RANGED_HIT_DELAY = Math.max(0, Math.round(80 / mult));
}
} catch (e) {}
}
// Предсказать позицию цели (простая линейная предсказание)
predictTargetPosition(target) {
if (!target || !target.position || !target.position.current) return null;
try {
if (typeof target.predictItems === 'function') target.predictItems();
if (typeof target.predictWeapons === 'function') target.predictWeapons();
if (typeof target.updateReloads === 'function') target.updateReloads();
} catch (e) {}
const cur = target.position.current;
const vel = (target.velocity || { x: 0, y: 0 });
// Учитываем только задержку ближнего боя и минимальную задержку дальнего
const time = (Instakill.MELEE_HIT_DELAY + 20) / 1000;
return { x: cur.x + (vel.x || 0) * time, y: cur.y + (vel.y || 0) * time };
}
// Опциональная проверка шипов
checkSpikeOpportunity(target) {
try {
if (typeof target.detectSpikeInsta === 'function') target.detectSpikeInsta();
if (typeof target.potentialDamage === 'number') return target.potentialDamage >= 40;
} catch (e) {}
return false;
}
// Основной цикл — обновлённый и более защищённый
postTick() {
if (!this._enabled || this._executing) return;
try {
const { ModuleHandler, myPlayer, EnemyManager } = this.client;
if (!myPlayer || !ModuleHandler || !EnemyManager) return;
try {
const isLocalClient = (typeof myClient !== 'undefined' && this.client === myClient);
if (!isLocalClient && !Settings.instakillForBots) return;
// ⭐ Проверка: Не запускать, если активно OneTick
if (ModuleHandler.staticModules.oneTick && ModuleHandler.staticModules.oneTick.active) return;
} catch (e) {}
let target = EnemyManager.nearestEnemy;
const now = Date.now();
if (!target && this._target && (now - (this._targetTime || 0) < Instakill.TARGET_MEMORY)) {
target = this._target;
}
if (!target || !target.position) return;
// ⭐ ИСПРАВЛЕНИЕ: Всегда сохраняем и рисуем цель, независимо от Solid Hat.
this._target = target;
this._targetTime = now;
this.highlightTarget(target);
if (this._markerTimeout) clearTimeout(this._markerTimeout);
this._markerTimeout = setTimeout(() => { window.__aibm_instaCross = null; }, 3500);
// ⭐ ПРОВЕРКА НА SOLID HAT (теперь только прерывает комбо)
try {
if (Settings.instakillCheckSolidHat && target.hatID === Instakill.SOLID_HAT_ID) {
// Возвращаемся, чтобы не выполнять комбо, но крестик уже нарисован
return;
}
} catch (e) { /* Если Settings еще не инициализирован, игнорируем */ }
// Продолжение цикла, если нет Solid Hat
if (!this.canExecuteInsta(target, myPlayer)) return;
this.optimizeAttackTiming(myPlayer);
// МОДИФИКАЦИЯ: Используем радиус только первого оружия (melee)
const primaryRange = this.getPrimaryWeaponRange(myPlayer);
// НОВОЕ ИСПРАВЛЕНИЕ: Проверяем расстояние до ТЕКУЩЕЙ позиции цели.
const distToTarget = myPlayer.position.current.distance(target.position.current);
if (distToTarget > (primaryRange + Instakill.RANGE_BUFFER)) {
// Если цель слишком далеко, сбрасываем ее и выходим.
this._target = null;
return;
}
// ⭐ ПРОВЕРКА ГОТОВНОСТИ (КД): ПОЛНОСТЬЮ УДАЛЕНА
// Если цель в радиусе и все готово, рассчитываем предсказанную позицию для точного прицеливания.
const predicted = this.predictTargetPosition(target) || target.position.current;
const strategy = this.calculateDamageStrategy(target, myPlayer);
this._executing = true;
console.log('[Instakill] Executing optimized combo', strategy);
try { if (this._angleInterval) clearInterval(this._angleInterval); } catch (e) {}
try {
this._angleInterval = setInterval(() => {
try { ModuleHandler.updateAngle && ModuleHandler.updateAngle(Math.atan2(predicted.y - myPlayer.position.current.y, predicted.x - myPlayer.position.current.x), true); } catch (e) {}
}, 30);
} catch (e) {}
const ang = Math.atan2(predicted.y - myPlayer.position.current.y, predicted.x - myPlayer.position.current.x);
const prevHat = myPlayer.hatID;
this._executeInstaCombo(predicted, ang, myPlayer, ModuleHandler, prevHat);
// Сохраняем слот оружия, но не используем его для возврата
try { this._prevWeaponSlot = (myPlayer.isMainPlayer ? myPlayer.weaponSlot : null); } catch (e) { this._prevWeaponSlot = null; }
const HOLD1 = Instakill.MELEE_HIT_DELAY;
const MELEE_ATTACK_DURATION = 20; // Небольшая задержка, теперь используется для остановки второго melee удара
try {
// 1. MELEE ATTACK 1 (Slot 0) with Bull Hat (T=0)
try { ModuleHandler.equip && ModuleHandler.equip(0, 7); } catch (e) { console.error('[Instakill] equip bull hat error', e); }
try { ModuleHandler.updateAngle && ModuleHandler.updateAngle(ang, true); } catch (e) {}
try { ModuleHandler.whichWeapon && ModuleHandler.whichWeapon(0); } catch (e) {}
try { ModuleHandler.attack && ModuleHandler.attack(ang); } catch (e) {}
// ⭐ Сброс КД первого оружия: УДАЛЕНО
setTimeout(() => {
// 2. MELEE ATTACK 2 SETUP (T=HOLD1)
try { ModuleHandler.stopAttack && ModuleHandler.stopAttack(); } catch (e) {} // Stop melee attack 1
try { ModuleHandler.equip && ModuleHandler.equip(0, 53); } catch (e) { console.error('[Instakill] equip turret error', e); }
// ⭐ Сброс КД Turret Hat: УДАЛЕНО
// MELEE Attack 2 Start (IMMEDIATE)
try { ModuleHandler.updateAngle && ModuleHandler.updateAngle(ang, true); } catch (e) {}
try { ModuleHandler.whichWeapon && ModuleHandler.whichWeapon(1); } catch (e) {}
try { ModuleHandler.attack && ModuleHandler.attack(ang); } catch (e) {}
// ⭐ Сброс КД второго оружия: УДАЛЕНО
// 3. Melee Attack Stop (Delayed T=HOLD1 + MELEE_ATTACK_DURATION)
// Небольшая задержка, чтобы гарантировать срабатывание атаки
setTimeout(() => {
try { ModuleHandler.stopAttack && ModuleHandler.stopAttack(); } catch (e) {} // Stop melee attack 2
}, Math.max(0, MELEE_ATTACK_DURATION));
// 4. Hat Restore (T=HOLD1 + MELEE_ATTACK_DURATION + RESTORE_DELAY)
if (prevHat && prevHat !== 7 && prevHat !== 53) {
setTimeout(() => {
try { ModuleHandler.equip && ModuleHandler.equip(0, prevHat); } catch (e) {}
}, Math.max(0, MELEE_ATTACK_DURATION + Instakill.RESTORE_DELAY)); // Задержка от T=HOLD1
}
}, Math.max(0, HOLD1)); // Задержка от T=0
} catch (e) {
console.error('[Instakill] combo execution error', e);
}
// 5. Global cleanup (T=HOLD1 + MELEE_ATTACK_DURATION + Instakill.RESTORE_DELAY + 40)
setTimeout(() => {
// УДАЛЕНО: Возврат к предыдущему оружию (чтобы оставить на втором слоте)
try {
this._prevWeaponSlot = null;
} catch (e) {}
this._executing = false;
try { if (this._angleInterval) { clearInterval(this._angleInterval); this._angleInterval = null; } } catch (e) {}
if (this.autoDisableAfterOne) this.disable();
}, HOLD1 + MELEE_ATTACK_DURATION + Instakill.RESTORE_DELAY + 0); // Общее время с учетом новой задержки
} catch (err) {
console.error('[Instakill] Fatal error:', err);
this._executing = false;
try { this.disable(); } catch (e) {}
}
}
}
const modules_Instakill = Instakill;
/**
* Класс, предназначенный для агрессивного ломания ЛЮБЫХ вражеских построек
* в радиусе досягаемости оружия. Полностью автономный, не слушает Movement
* и не использует overrideTarget.
*/
class BreakAllMode {
name = "breakAllMode"; // Новое имя для регистрации
client;
_lastWeapon = null;
_lastTargetId = null;
_lastAttackTime = 0;
_savedHat = null;
// Эти свойства оставлены для совместимости, но не используются в postTick
_overrideTarget = null;
suppressAutoBreak = false;
// СПИСОК ИГНОРА (для динамического игнорирования объектов)
_ignoredIDs = new Set();
// 🔥 НОВОЕ: Список ID ОБЪЕКТОВ, которые нужно игнорировать всегда (например, 11: стена, 15: шип)
static OBJECT_IGNORE_IDS = new Set([
// Добавьте сюда ID структур, которые НЕ нужно ломать
// Пример: 11 - Каменная стена, 15 - Шип (если у них фиксированные ID)
]);
static WEAPON_BREAK_DIST = {
0: 45, 1: 55, 2: 55, 3: 90, 4: 103, 5: 127, 6: 90, 7: 45, 8: 45,
10: 58, 14: 110,
};
static IGNORED = new Set([8, 9, 11, 12, 13, 15]); // Игнорируемые ID оружия
constructor(client) { this.client = client; }
// Эти методы оставлены для совместимости, но их эффект в postTick минимален
setOverrideTarget(target) {
this._overrideTarget = target;
}
ignoreID(id) {
this._ignoredIDs.add(id);
}
clearIgnored() {
this._ignoredIDs.clear();
}
// НОВЫЙ МЕТОД: для добавления ID объектов в постоянный игнор (статический)
static addIgnoredObject(id) {
BreakAllMode.OBJECT_IGNORE_IDS.add(id);
}
_exitMode(tempData) {
if (tempData) {
tempData.setWeaponSyncStatus(false);
tempData.updateWeapon();
}
this._lastTargetId = null;
this._lastWeapon = null;
this._savedHat = null;
}
postTick() {
const { myPlayer, ModuleHandler, ObjectManager, PlayerManager } = this.client;
const tempData = ModuleHandler.staticModules.tempData;
try {
const oc = getOwnerCommanderFor(this.client);
// Активен ТОЛЬКО если включен в меню (oc.breakAllMode.active)
const isActive = (oc && oc.breakAllMode && oc.breakAllMode.active);
if (this.client.isOwner || !myPlayer || !myPlayer.inGame || !isActive) {
this._exitMode(tempData);
// Сброс _overrideTarget, так как мы его не обрабатываем, но он может быть установлен
this._overrideTarget = null;
return;
}
// --- ВЫБОР ОРУЖИЯ ---
let weaponSlot = null;
let weaponId = null;
try {
weaponSlot = myPlayer.getBestDestroyingWeapon();
if (weaponSlot !== null) weaponId = myPlayer.getItemByType(weaponSlot);
} catch (e) {}
if (weaponId == null || weaponSlot == null || BreakAllMode.IGNORED.has(weaponId)) {
this._exitMode(tempData);
return;
}
// ====================================================================================================================================
// --- ОПРЕДЕЛЕНИЕ ЦЕЛИ: Ближайший Вражеский Объект (ЧИСТЫЙ АВТО-ПОИСК) ---
// ====================================================================================================================================
let bestTarget = null;
let bestDist = Infinity;
// 🔥 УДАЛЕНА ЛОГИКА ПРИОРИТЕТА _overrideTarget
const pos = myPlayer.position.current;
let baseRange = BreakAllMode.WEAPON_BREAK_DIST[weaponId];
if (typeof baseRange !== "number") baseRange = 220;
const myReach = baseRange + (myPlayer.hitScale || 0);
const searchRadius = Math.round(myReach + 40);
let objects = ObjectManager.retrieveObjects(pos, searchRadius) || [];
// Ищем объекты в радиусе
for (const obj of objects) {
try {
// Игнор ID (динамический список, если его использует другой модуль)
if (this._ignoredIDs.has(obj.id)) {
continue;
}
// Игнор ID (статический список, для игнорирования типов структур)
if (BreakAllMode.OBJECT_IGNORE_IDS.has(obj.type)) {
continue;
}
// Должно быть разрушаемым и вражеским
if (!obj.isDestroyable || !PlayerManager.isEnemyByID(obj.ownerID, myPlayer)) {
continue;
}
const d = pos.distance(obj.position.current);
if (d > myReach) {
continue;
}
// Выбираем самую близкую цель
if (d < bestDist) {
bestDist = d;
bestTarget = obj;
}
} catch (e) { continue; }
}
let target = bestTarget;
// Если цели нет -> выходим
if (!target) {
this._exitMode(tempData);
return;
}
// --- АТАКА ---
if (tempData && !tempData.disableWeaponSync) {
tempData.setWeaponSyncStatus(true);
}
const tgtId = target.id ?? target.ownerID;
// Смена оружия
if (ModuleHandler.currentHolding !== weaponSlot) {
ModuleHandler.whichWeapon(weaponSlot);
this._lastWeapon = weaponId;
return;
}
// Кулдаун
const attackCooldown = myPlayer.weaponCooldown || 300;
const currentTime = Date.now();
if (currentTime < this._lastAttackTime + attackCooldown) return;
if (this._lastTargetId !== tgtId) {
this._lastTargetId = tgtId;
}
// Удар
const preAttackHat = myPlayer.hatID;
const isEquipped = ModuleHandler.equip(0, 40); // Tank Gear
const ang = myPlayer.position.current.angle(target.position.current);
ModuleHandler.updateAngle(ang, true);
ModuleHandler.attack(ang);
this._lastAttackTime = currentTime;
if (isEquipped && preAttackHat !== 40) {
setTimeout(() => {
if (this.client && this.client.myPlayer) ModuleHandler.equip(0, preAttackHat);
}, 100);
}
} catch (outer) {
console.error("[BreakAllMode] Error:", outer);
this._exitMode(tempData);
}
}
}
const bot_modules_BreakAllMode = BreakAllMode;
// ======= BreakMode (weapon-specific break distances + logs) =======
class BreakMode {
name = "breakMode"; client;
_lastWeapon = null; _lastTargetId = null; _lastAttackTime = 0; _savedHat = null;
_overrideTarget = null; _ignoredIDs = new Set(); suppressAutoBreak = false;
static WEAPON_BREAK_DIST = {0:50,1:55,2:55,3:90,4:103,5:127,6:90,7:45,8:45,10:55,14:110};
static VISUALIZATION_ENABLED = false;
static IGNORED = new Set([8,9,11,12,13,15]);
constructor(client){this.client=client;}
setOverrideTarget(t){this._overrideTarget=t;}
ignoreID(id){this._ignoredIDs.add(id);}
clearIgnored(){this._ignoredIDs.clear();}
_exitMode(tempData){
if(tempData){tempData.setWeaponSyncStatus(false);tempData.updateWeapon();}
this._lastTargetId=null; this._lastWeapon=null; this._savedHat=null;
}
// ⭐ Визуализация (линия + круг вокруг цели)
drawOverlay(ctx){
if(!BreakMode.VISUALIZATION_ENABLED) return;
try{
if(this._lastTargetId===null) return;
const target=this.client.ObjectManager.objects.get(this._lastTargetId);
if(!target||!target.position) return;
const botPos=this.client.myPlayer.position.current, targetPos=target.position.current;
const radius=(target.scale||45)+15, breakColor="#FFC300", breakColorAlpha="rgba(255,195,0,0.7)";
Aibm.Renderer.line(ctx,botPos,targetPos,breakColorAlpha,0.6,3);
Aibm.Renderer.circle(ctx,targetPos.x,targetPos.y,radius,breakColor,0.6,2.5);
Aibm.Renderer.cross(ctx,targetPos.x,targetPos.y,radius*0.7,2,breakColor);
}catch(e){}
}
postTick(){
const {myPlayer,ModuleHandler,ObjectManager,PlayerManager}=this.client;
const tempData=ModuleHandler.staticModules.tempData;
try{
const oc=getOwnerCommanderFor(this.client);
const isActive=(oc&&oc.breakMode&&(oc.breakMode.active===true||oc.breakMode===true))||(this._overrideTarget!==null);
if(this.client.isOwner||!myPlayer||!myPlayer.inGame||!isActive){this._exitMode(tempData);return;}
let weaponSlot=null, weaponId=null;
try{weaponSlot=myPlayer.getBestDestroyingWeapon();if(weaponSlot!==null) weaponId=myPlayer.getItemByType(weaponSlot);}catch(e){}
if(weaponId==null||weaponSlot==null||BreakMode.IGNORED.has(weaponId)){this._exitMode(tempData);return;}
let bestTarget=null, bestDist=Infinity;
if(this._overrideTarget){
if(ObjectManager.objects.has(this._overrideTarget.id)){
bestTarget=this._overrideTarget;
bestDist=myPlayer.position.current.distance(bestTarget.position.current);
}else this._overrideTarget=null;
}
if(this.suppressAutoBreak&&!bestTarget){this._exitMode(tempData);return;}
if(!bestTarget){
const pos=myPlayer.position.current;
let baseRange=BreakMode.WEAPON_BREAK_DIST[weaponId]; if(typeof baseRange!=="number") baseRange=220;
const myReach=baseRange+(myPlayer.hitScale||0)+20, searchRadius=Math.round(myReach+40);
let objects=ObjectManager.retrieveObjects(pos,searchRadius)||[];
for(const obj of objects){
try{
if(this._ignoredIDs.has(obj.id)) continue;
if(!obj.isDestroyable||!PlayerManager.isEnemyByID(obj.ownerID,myPlayer)) continue;
const d=pos.distance(obj.position.current); if(d>myReach) continue;
if(d{if(this.client&&this.client.myPlayer) ModuleHandler.equip(0,preAttackHat);},90);
}
}catch(outer){console.error("[BreakMode] Error:",outer);this._exitMode(tempData);}
}
}
const bot_modules_BreakMode=BreakMode;
// Синхронизируем радиусы оружий в массиве `Weapons` с реальными значениями из BreakMode (или Items)
try {
if (typeof Weapons !== 'undefined') {
const __syncWeaponRanges = () => {
try {
if (typeof BreakMode !== 'undefined' && BreakMode.WEAPON_BREAK_DIST) {
for (let w of Weapons) {
const br = BreakMode.WEAPON_BREAK_DIST[w.id];
if (typeof br === 'number') {
w.range = br;
} else if (typeof Items !== 'undefined' && Items[w.id] && typeof Items[w.id].range === 'number') {
w.range = Items[w.id].range;
}
}
console.log('[Aibm] Weapons ranges synced from BreakMode.');
}
} catch (e) { /* ignore */ }
};
// Утилита для рисования креста — делает глобальный маркер, который читает рендерер
window.__aibm_drawCross = function(x, y, color, size, duration) {
try {
const now = Date.now();
const dur = duration || 3000;
// timed cross stores target and current position for smoothing
const prev = window.__aibm_instaCross;
window.__aibm_instaCross = {
x: (prev && typeof prev.x === 'number') ? prev.x : x,
y: (prev && typeof prev.y === 'number') ? prev.y : y,
targetX: x,
targetY: y,
color: color || '#ff6666',
size: size || 50,
thickness: 8,
until: now + dur,
lastUpdated: now
};
} catch (e) {}
};
// Персистентный крест — всегда рисуется, пока active=true
window.__aibm_drawCrossPersistent = function(x, y, color, size, thickness) {
try {
const prev = window.__aibm_instaCrossPersistent;
window.__aibm_instaCrossPersistent = {
x: (prev && typeof prev.x === 'number') ? prev.x : x,
y: (prev && typeof prev.y === 'number') ? prev.y : y,
targetX: x,
targetY: y,
color: color || '#ff6666',
size: size || 60,
thickness: thickness || 10,
active: true,
lastUpdated: Date.now()
};
} catch (e) {}
};
// Очистка персистентного креста
window.__aibm_clearCrossPersistent = function() {
try { window.__aibm_instaCrossPersistent = null; } catch (e) {}
};
__syncWeaponRanges();
}
} catch (e) {}
class AutoAccept {
name="autoAccept";
client;
acceptCount=0;
constructor(client) {
this.client = client;
}
postTick() {
const {myPlayer, clientIDList, SocketManager, isOwner} = this.client;
if (!myPlayer.isLeader || 0 === myPlayer.joinRequests.length) {
return;
}
const id = myPlayer.joinRequests[0][0];
if (0 === this.acceptCount) {
if (Settings.autoaccept || 0 !== myClient.pendingJoins.size) {
SocketManager.clanRequest(id, Settings.autoaccept || clientIDList.has(id));
myPlayer.joinRequests.shift();
myClient.pendingJoins["delete"](id);
if (isOwner) {
UI_GameUI.clearNotication();
}
}
const nextID = myPlayer.joinRequests[0];
if (isOwner && void 0 !== nextID) {
UI_GameUI.createRequest(nextID);
}
}
this.acceptCount = (this.acceptCount + 1) % 7;
}
}
const modules_AutoAccept = AutoAccept;
// Используется для синхронизации желаемого состояния бота с игровым миром.
class TempData {
name = "tempData";
client;
weapon = 0;
store = [0, 0]; // store[0] = hat, store[1] = accessory
// ⭐ ИСПРАВЛЕНИЕ: По умолчанию синхронизация РАЗРЕШЕНА (false).
// Grinder будет блокировать её (true), когда активен.
disableWeaponSync = false;
constructor(client) {
this.client = client;
}
// ⭐ МЕТОД: Для установки флага блокировки (оставлен для совместимости с другими модулями, например, Grinder)
setWeaponSyncStatus(disabled) {
this.disableWeaponSync = disabled;
}
setWeapon(weapon) {
this.weapon = weapon;
this.updateWeapon();
}
setAttacking(attacking) {
const {ModuleHandler} = this.client;
if (ModuleHandler.attacking === attacking) {
return;
}
ModuleHandler.attacking = attacking;
if (0 !== attacking) {
ModuleHandler.attackingState = attacking;
}
}
setStore(type, id) {
this.store[type] = id;
// 🔥 ИЗМЕНЕНИЕ: Если это владелец, синхронизируем данные со всеми ботами
if (this.client.isOwner) {
if (typeof window.setOwnerCommanderField === 'function') {
const field = type === 0 ? 'hatID' : 'accessoryID';
// Допустим, мы храним эти ID в 'tempData.hatID' / 'tempData.accessoryID'
setOwnerCommanderField('all', `tempData.${field}`, id);
}
}
// Вызываем handleBuy, как было, но в postTick мы его заблокируем для ботов
this.handleBuy(type);
}
updateWeapon() {
const {ModuleHandler} = this.client;
// Паузим синхронизацию оружия если Autobreak ломает ловушку
try {
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
// ⭐ ГЛАВНАЯ ПРОВЕРКА: Если Grinder установил этот флаг в true, выходим (для всех).
if (this.disableWeaponSync) return;
try {
// ЛОГИКА ДЛЯ БОТОВ:
if (!this.client.isOwner) {
// ⭐ НОВЫЙ КОНТРОЛЬ: Проверяем новую настройку для включения/выключения синхронизации у ботов
let botSyncEnabled = true;
try {
// Предполагаем, что настройка будет в Settings.botWeaponSync
// Если настройка существует и выключена (false), отключаем синхронизацию
if (typeof Settings !== 'undefined' && Settings.botWeaponSync === false) {
botSyncEnabled = false;
}
} catch (e) {}
if (botSyncEnabled) {
// Если синхронизация разрешена: бот копирует оружие владельца (myClient)
if (typeof myClient !== 'undefined' && myClient.ModuleHandler) {
const ownerHold = myClient.ModuleHandler.currentHolding;
if (typeof ownerHold === 'number' && ModuleHandler.currentHolding !== ownerHold) {
ModuleHandler.whichWeapon(ownerHold);
}
}
}
return;
}
// ЛОГИКА ДЛЯ ВЛАДЕЛЬЦА:
// Если флаг disableWeaponSync === false, владелец меняет оружие на основе this.weapon (нажатая клавиша)
if (ModuleHandler.weapon !== this.weapon) {
ModuleHandler.whichWeapon(this.weapon);
}
} catch (e) {
// молча игнорируем ошибки
}
}
handleBuy(type) {
const {ModuleHandler} = this.client;
const id = this.store[type];
const store = ModuleHandler.store[type];
if (store.actual === id) {
return;
}
const temp = ModuleHandler.canBuy(type, id) ? id : 0;
ModuleHandler.equip(type, temp, true);
}
postTick() {
// Если Autobreak ломает ловушку — пауза
try {
const ModuleHandler = this.client.ModuleHandler;
const ab = ModuleHandler.staticModules && ModuleHandler.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
const ownerCommander = getOwnerCommanderFor(this.client);
// 🔥 ЛОГИКА СИНХРОНИЗАЦИИ ЭКИПИРОВКИ (ОБЩАЯ)
if (this.client.isOwner) {
// ВЛАДЕЛЕЦ: Просто применяет свои собственные нажатия (из setStore)
this.handleBuy(0);
this.handleBuy(1);
} else if (ownerCommander && ownerCommander.tempData) {
// БОТ: Копируем ID шапок/аксов из данных владельца
const ownerTempData = ownerCommander.tempData;
// Шапка (тип 0)
if (ownerTempData.hatID !== undefined && this.store[0] !== ownerTempData.hatID) {
this.store[0] = ownerTempData.hatID;
this.handleBuy(0);
}
// Аксессуар (тип 1)
if (ownerTempData.accessoryID !== undefined && this.store[1] !== ownerTempData.accessoryID) {
this.store[1] = ownerTempData.accessoryID;
this.handleBuy(1);
}
}
}
}
const bot_modules_TempData = TempData;
class Reloading {
name="reloading";
client;
clientReload={
primary: {},
secondary: {},
turret: {}
};
constructor(client) {
this.client = client;
const {primary, secondary, turret} = this.clientReload;
primary.current = primary.max = 0;
secondary.current = secondary.max = 0;
turret.current = turret.max = 2500;
}
get currentReload() {
const type = WeaponTypeString[this.client.ModuleHandler.weapon];
return this.clientReload[type];
}
updateMaxReload(reload) {
const {ModuleHandler, myPlayer} = this.client;
if (ModuleHandler.attacked) {
const id = myPlayer.getItemByType(ModuleHandler.weapon);
const store = ModuleHandler.getHatStore();
const speed = myPlayer.getWeaponSpeed(id, store.last);
reload.max = speed;
}
}
resetReload(reload) {
const {PlayerManager} = this.client;
reload.current = -PlayerManager.step;
}
resetByType(type) {
const reload = this.clientReload[type];
this.resetReload(reload);
}
isReloaded(type) {
const reload = this.clientReload[type];
return reload.current === reload.max;
}
increaseReload(reload, step) {
reload.current += step;
if (reload.current > reload.max) {
reload.current = reload.max;
}
}
postTick() {
const {ModuleHandler, PlayerManager} = this.client;
this.increaseReload(this.clientReload.turret, PlayerManager.step);
if (ModuleHandler.holdingWeapon) {
this.increaseReload(this.currentReload, PlayerManager.step);
}
}
}
const modules_Reloading = Reloading;
// ======= Autobreak (игрок переключает, боты ломают чем есть) =======
class Autobreak {
name = "autoBreak";
client;
// Состояние
isActive = false;
breakingTrap = false;
// Для таймингов атаки
_lastAttackTime = 0;
// 🔥 НОВОЕ: Переменная для хранения ID таймаута возврата шляпы
_hatTimeoutID = null;
// Бэкап
prevHat = null;
// 🔥 НОВЫЙ МЕТОД: для получения кулдауна (оставлен без изменений)
_getWeaponCooldown(weaponSlot) {
const { myPlayer } = this.client;
if (!myPlayer) return 300;
try {
const weaponID = myPlayer.getItemByType(weaponSlot);
if (typeof myPlayer.getWeaponSpeed === 'function') {
return myPlayer.getWeaponSpeed(weaponID);
}
} catch (e) { /* Игнорируем ошибку, возвращаем дефолт */ }
return 300;
}
constructor(client) {
this.client = client;
}
// Проверка: находится ли игрок внутри ловушки (Trap) (оставлен без изменений)
isInTrap(player, trap) {
try {
const p = player.position.current;
const t = trap.position.current;
const dist = Math.hypot(p.x - t.x, p.y - t.y);
const scale = trap.scale || 40;
return dist <= scale + 6;
} catch {
return false;
}
}
// Метод выхода из режима (сброс состояния)
_exitMode() {
// 🔥 ИЗМЕНЕНИЕ 1: Очищаем висящий таймаут, если он есть
if (this._hatTimeoutID !== null) {
clearTimeout(this._hatTimeoutID);
this._hatTimeoutID = null;
}
if (this.breakingTrap) {
this.breakingTrap = false;
// Отмена текущей атаки
try { this.client.ModuleHandler.attack(null); } catch (e) {}
// Пытаемся вернуть шапку, если она была сохранена
if (this.prevHat !== null) {
try {
// Принудительно возвращаем, чтобы отменить возможный Tank Gear
this.client.ModuleHandler.equip(0, this.prevHat);
} catch (e) {}
}
this.prevHat = null;
// Сброс времени атаки, чтобы первый удар был немедленным при следующем входе
this._lastAttackTime = 0;
// Отпускаем управление модулем
try { this.client.ModuleHandler.moduleActive = false; } catch (e) {}
}
this.isActive = false;
}
postTick() {
// 1. Проверка настройки и выход
if (typeof Settings !== 'undefined' && Settings.autobreak === false) {
this._exitMode();
return;
}
const { EnemyManager, myPlayer, ModuleHandler } = this.client;
if (!myPlayer || !myPlayer.inGame) return;
const trap = EnemyManager.nearestTrap;
// 2. Если ловушки нет или мы не в ней -> выходим
if (!trap || !this.isInTrap(myPlayer, trap)) {
this._exitMode();
return;
}
// 3. МЫ В ЛОВУШКЕ!
this.isActive = true;
this.breakingTrap = true;
ModuleHandler.moduleActive = true;
try {
// --- A. ВЫБОР ОРУЖИЯ ---
let weaponSlot = null;
try { weaponSlot = myPlayer.getBestDestroyingWeapon(); } catch (e) {}
if (weaponSlot === null) weaponSlot = ModuleHandler.weapon;
// Смена оружия
if (ModuleHandler.currentHolding !== weaponSlot) {
ModuleHandler.whichWeapon(weaponSlot);
return;
}
// --- B. ЦЕЛИМСЯ ---
const angle = myPlayer.position.current.angle(trap.position.current);
ModuleHandler.useAngle = angle;
ModuleHandler.updateAngle(angle, true);
// --- C. ТАЙМИНГ УДАРА (Гарантированная перезарядка) ---
const attackCooldown = this._getWeaponCooldown(weaponSlot);
// 🔥 ИЗМЕНЕНИЕ 2: Убрал -50мс запаса. Сравниваем точно с кулдауном.
const readyTime = this._lastAttackTime + attackCooldown;
const currentTime = Date.now();
// Мы должны бить не раньше, чем кулдаун сбросится
if (currentTime < readyTime) {
// ВАЖНО: Мы здесь, значит, Tank Gear, надетый в прошлом тике,
// уже должен был быть снят таймаутом, если он сработал.
// В этом состоянии мы находимся в обычной шапке.
return;
}
// --- D. УДАР (С надеванием Tank Gear) ---
// 1. Очищаем старый таймаут, если он не сработал вовремя
if (this._hatTimeoutID !== null) {
clearTimeout(this._hatTimeoutID);
this._hatTimeoutID = null;
}
// 2. Сохраняем текущую шапку, если мы не в Tank Gear.
// При этом, если Tank Gear был надет, он должен был быть снят.
// Это должно быть ID обычной шляпы.
let savedHatID = myPlayer.hatID;
if (savedHatID === 40 && this.prevHat !== null) {
savedHatID = this.prevHat; // Если Tank Gear каким-то образом застрял
}
if (savedHatID !== 40) {
this.prevHat = savedHatID;
} else {
this.prevHat = null;
}
// 3. Надеваем Tank Gear (ID 40)
const isEquipped = ModuleHandler.equip(0, 40);
// 4. Бьем!
ModuleHandler.attack(angle);
this._lastAttackTime = currentTime;
// 5. Возвращаем старую шапку через ~90-100мс
if (isEquipped && this.prevHat !== null && this.prevHat !== 40) {
const hatToRestore = this.prevHat;
// 🔥 ИЗМЕНЕНИЕ 3: Сохраняем ID нового таймаута
this._hatTimeoutID = setTimeout(() => {
// Проверяем, что мы все еще в режиме ломания, прежде чем возвращать
if (this.client && this.client.myPlayer && this.breakingTrap) {
ModuleHandler.equip(0, hatToRestore);
}
this._hatTimeoutID = null; // Сбрасываем ID после выполнения
// Немного увеличим задержку до 100мс, чтобы дать серверу больше времени
}, 190);
}
} catch (e) {
console.error('[Autobreak] Error:', e);
this._exitMode();
}
}
}
const modules_Autobreak = Autobreak;
class SpikeTick {
name="spikeTick";
client;
isActive=false;
tickAction=0;
constructor(client) {
this.client = client;
}
postTick() {}
}
const modules_SpikeTick = SpikeTick;
class PreAttack {
name="preAttack";
client;
constructor(client) {
this.client = client;
}
postTick() {
const {ModuleHandler} = this.client;
const {moduleActive, useWeapon, weapon, previousWeapon, attackingState, staticModules} = ModuleHandler;
const type = moduleActive ? useWeapon : weapon;
const stringType = WeaponTypeString[type];
const shouldAttack = 0 !== attackingState || moduleActive;
let isReloaded = staticModules.reloading.isReloaded(stringType);
// Разрешаем атаку для Autobreak даже если reload не полностью заряжен,
// чтобы ломка была непрерывной по таймингу ломания, а не по перезарядке.
try {
const ab = staticModules.autoBreak;
if (moduleActive && ab && ab.breakingTrap) {
isReloaded = true;
}
} catch (e) {}
ModuleHandler.canAttack = shouldAttack && isReloaded;
if (null === useWeapon && null !== previousWeapon && staticModules.reloading.isReloaded(WeaponTypeString[weapon])) {
ModuleHandler.whichWeapon(previousWeapon);
ModuleHandler.previousWeapon = null;
}
}
}
const modules_PreAttack = PreAttack;
// ======= HatSync =======
class HatSync {
name = 'hatSync';
client;
_lastSync = 0;
_SYNC_INTERVAL = 0; // 0 (синхронизация каждый тик)
constructor(client) {
this.client = client;
}
postTick() {
try {
// Только для ботов (не владельца!)
if (!this.client.isOwner && this.client.myPlayer && myClient && myClient.myPlayer) {
// ⭐ ПРОВЕРКА ПАУЗЫ: Если Bowspam активен, выходим
const mh = this.client.ModuleHandler;
if (mh && mh._bowspamActive) {
return;
}
// Если Autobreak ломает ловушку у этого бота — не синхронизируем
try {
const ab = mh && mh.staticModules && mh.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
const now = Date.now();
if (now - this._lastSync > this._SYNC_INTERVAL) {
this._lastSync = now;
let hatIdToEquip;
let hatSyncEnabled = true;
// 1. Определяем, включена ли синхронизация шапок (HatSync)
try {
if (typeof Settings !== 'undefined' && Settings.botHatSync === false) {
hatSyncEnabled = false;
}
} catch (e) {}
if (hatSyncEnabled) {
// 2. Режим СИНХРОНИЗАЦИИ: используем шапку владельца.
hatIdToEquip = myClient.myPlayer.hatID;
} else {
// 3. Режим АВТОВЫБОРА: используем собственную логику бота.
const botPlayer = this.client.myPlayer;
// Приоритет 1: Utility Hat (для боя/строительства)
hatIdToEquip = botPlayer.getBestUtilityHat();
// Приоритет 2: Current Hat (для биомов/дефолтный выбор), если Utility не подошел
if (hatIdToEquip === null || typeof hatIdToEquip !== 'number') {
hatIdToEquip = botPlayer.getBestCurrentHat();
}
}
// 4. Экипируем, если ID является числом и отличается от текущего
if (typeof hatIdToEquip === 'number' && this.client.myPlayer.hatID !== hatIdToEquip) {
// Тип 0 - это шапка (Hat)
this.client.ModuleHandler && this.client.ModuleHandler.equip && this.client.ModuleHandler.equip(0, hatIdToEquip);
}
}
}
} catch(e) { /* ignore */ }
}
}
const modules_HatSync = HatSync;
// --- ИСПРАВЛЕННЫЙ КЛАСС ДЛЯ АКСЕССУАРОВ ---
class AccSync {
name = 'accSync';
client;
_lastSync = 0;
_SYNC_INTERVAL = 0; // 0 (синхронизация каждый тик)
constructor(client) {
this.client = client;
}
postTick() {
try {
// Только для ботов (не владельца!)
if (!this.client.isOwner && this.client.myPlayer && myClient && myClient.myPlayer) {
// Если Autobreak ломает ловушку у этого бота — не синхронизируем
try {
const mh = this.client.ModuleHandler;
const ab = mh && mh.staticModules && mh.staticModules.autoBreak;
if (ab && ab.breakingTrap) return;
} catch (e) {}
const now = Date.now();
if (now - this._lastSync > this._SYNC_INTERVAL) {
this._lastSync = now;
let accIdToEquip;
let accSyncEnabled = true;
// 1. Определяем, включена ли синхронизация аксессуаров (AccSync)
try {
if (typeof Settings !== 'undefined' && Settings.botAccSync === false) {
accSyncEnabled = false;
}
} catch (e) {}
if (accSyncEnabled) {
// 2. Режим СИНХРОНИЗАЦИИ: используем аксессуар владельца.
// ⭐ КРИТИЧНОЕ ИСПРАВЛЕНИЕ: Используем accessoryID, как для HatSync используется hatID.
accIdToEquip = myClient.myPlayer.accessoryID;
} else {
// 3. Режим АВТОВЫБОРА: используем собственную логику бота.
const botPlayer = this.client.myPlayer;
// Приоритет 1: Utility Acc
accIdToEquip = botPlayer.getBestUtilityAcc();
// Приоритет 2: Current Acc (атака/фарм), если Utility не подошел
if (accIdToEquip === null || typeof accIdToEquip !== 'number') {
accIdToEquip = botPlayer.getBestCurrentAcc();
}
}
// 4. Экипируем, если ID является числом и отличается от текущего
if (typeof accIdToEquip === 'number' && this.client.myPlayer.accessoryID !== accIdToEquip) {
// Тип 1 - это аксессуар (Accessory)
this.client.ModuleHandler && this.client.ModuleHandler.equip && this.client.ModuleHandler.equip(1, accIdToEquip);
}
}
}
} catch(e) { /* ignore */ }
}
}
const modules_AccSync = AccSync;
//// Обновленный класс AutoShield
//class AutoShield {
// name = "autoShield";
// client;
// isActive = false;
// isShieldHolding = false;
// previousWeaponType = 0;
//
// shieldHoldTimer = 0; // Таймер для минимального времени удержания
//
// static SHIELD_ITEM_TYPE = 11;
// static SHIELD_SLOT_INDEX = 1;
//
// // Константы
// static ATTACK_PREDICT_RANGE = 250;
// static ATTACK_CONE_ANGLE = Math.PI / 3;
//
// // КОНСТАНТЫ ДЛЯ РАСЧЕТА
// static SHIELD_HOLD_TICKS_DEFAULT = 5; // Минимальное время удержания в тиках
// static MS_PER_TICK = 50; // Предполагаем, что 1 тик = 50 мс
//
// // Карта оружия для динамического расчета задержек
// static WEAPON_MAP = WEAPON_MAP;
//
// // --- ЛОГИРОВАНИЕ ---
// _logEnabled() {
// try {
// return (typeof Settings === 'undefined') || (Settings.autoshieldDebug !== false);
// } catch (e) { return true; }
// }
// _log(msg) {
// if (!this._logEnabled()) return;
// try { console.log('[AutoShield] ' + msg); } catch (e) {}
// }
//
// constructor(client) {
// this.client = client;
// }
//
// enable() {
// this.isActive = true;
// this._log('Enabled');
// }
//
// disable() {
// this.isActive = false;
// if (this.isShieldHolding) {
// try {
// // 1. Остановка блока
// this.client.ModuleHandler.stopAttack();
//
// // 2. Восстановление предыдущего оружия
// const ModuleHandler = this.client.ModuleHandler;
// if (ModuleHandler.currentHolding !== this.previousWeaponType) {
// if (this.previousWeaponType === 0 || this.previousWeaponType === 1) {
// ModuleHandler.whichWeapon(this.previousWeaponType);
// } else {
// ModuleHandler.selectItem(this.previousWeaponType);
// }
// this._log('Cleanup: Block stopped, weapon restored to type ' + this.previousWeaponType);
// } else {
// this._log('Cleanup: Block stopped, weapon already restored.');
// }
//
// } catch (e) {
// this._log('Error during disable cleanup: ' + e);
// }
// }
// // Сброс всех флагов
// this.isShieldHolding = false;
// this.previousWeaponType = 0;
// this.shieldHoldTimer = 0;
//
// // Сброс принудительного наведения
// if (this.client.ModuleHandler.useAngle !== null) {
// this.client.ModuleHandler.useAngle = null;
// }
//
// // Сбрасываем флаг управления
// if (this.client.ModuleHandler.moduleActive) {
// this.client.ModuleHandler.moduleActive = false;
// this._log('Cleanup: Module control released.');
// }
//
// this._log('Disabled');
// }
//
// getAngleDist(a1, a2) {
// let diff = a1 - a2;
// while (diff > Math.PI) diff -= 2 * Math.PI;
// while (diff < -Math.PI) diff += 2 * Math.PI;
// return Math.abs(diff);
// }
//
// /**
// * Возвращает количество тиков, которое нужно удерживать щит
// * после исчезновения угрозы, на основе скорости оружия противника.
// * Это буфер, чтобы покрыть следующий тик/серию ударов.
// */
// _getHoldTicks(enemy) {
// // Предполагаем, что у объекта enemy есть свойство currentWeaponId или weaponIndex
// const currentWeaponId = enemy.currentWeaponId || enemy.weaponIndex;
// let fastestSpeed = Infinity;
//
// // 1. Пробуем получить скорость текущего оружия врага
// if (currentWeaponId !== undefined && AutoShield.WEAPON_MAP[currentWeaponId]) {
// fastestSpeed = AutoShield.WEAPON_MAP[currentWeaponId].speed;
// } else {
// // 2. Фолбэк: Ищем самое быстрое оружие, которое может быть у игрока (Daggers: 100ms)
// const daggers = AutoShield.WEAPON_MAP[7];
// if (daggers) {
// fastestSpeed = daggers.speed;
// }
// }
//
// // Если не удалось найти скорость, используем дефолтный минимум (5 тиков)
// if (fastestSpeed === Infinity || fastestSpeed === 0) {
// return AutoShield.SHIELD_HOLD_TICKS_DEFAULT;
// }
//
// // Расчет: (Скорость в мс / 50мс на тик) + 1-2 тика буфера.
// // Это гарантирует, что мы заблокируем даже очень быстрые атаки.
// const calculatedTicks = Math.ceil(fastestSpeed / AutoShield.MS_PER_TICK) + 2; // +2 тика буфера
//
// // Гарантируем, что минимум не меньше 5 для общей безопасности.
// return Math.max(calculatedTicks, AutoShield.SHIELD_HOLD_TICKS_DEFAULT);
// }
//
// /**
// * Проверяет, представляет ли противник непосредственную угрозу.
// */
// isThreat(myPlayer, enemy) {
// // Проверка 1: Может ли противник нас убить/нанести критический урон?
// if (enemy.danger < 1) return false;
//
// // Проверка 2: Должно ли оружие противника быть перезаряжено?
// const primaryReloaded = enemy.isReloaded("primary");
// const secondaryReloaded = enemy.isReloaded("secondary");
// const hasReadyWeapon = primaryReloaded || secondaryReloaded;
//
// // Угроза есть, если хотя бы одно оружие готово, ИЛИ это Instakill-угроза
// if (!hasReadyWeapon && enemy.danger < 3) {
// return false;
// }
//
// const myPos = myPlayer.position.current;
// const enemyPos = enemy.position.current;
//
// // Определяем максимальную дальность атаки противника, включая масштаб.
// const enemyMaxRange = enemy.getMaxWeaponRange() + myPlayer.scale;
// const effectiveRange = Math.min(AutoShield.ATTACK_PREDICT_RANGE, enemyMaxRange + 50);
//
// // Проверка 3: Дистанция
// const distSq = (myPos.x - enemyPos.x) ** 2 + (myPos.y - enemyPos.y) ** 2;
// if (distSq > effectiveRange * effectiveRange) return false;
//
// // Проверка 4: Наведение.
// const angleToMe = Math.atan2(myPos.y - enemyPos.y, myPos.x - enemyPos.x);
// const enemyAngle = enemy.angle;
// const angleDiff = this.getAngleDist(angleToMe, enemyAngle);
//
// // Мы являемся угрозой, если находимся в конусе атаки
// return angleDiff <= AutoShield.ATTACK_CONE_ANGLE / 2;
// }
//
// postTick() {
// if (typeof Settings !== 'undefined' && !Settings.autoShield) {
// if (this.isShieldHolding) this.disable();
// this.isActive = false;
// return;
// }
//
// const {myPlayer, EnemyManager, ModuleHandler} = this.client;
//
// if (!myPlayer || !myPlayer.inGame) {
// if (this.isShieldHolding) this.disable();
// return;
// }
//
// if (ModuleHandler.moduleActive && !this.isShieldHolding) return;
//
// // 1. Поиск угрозы
// let isDanger = false;
// let mostDangerousEnemy = null;
// let minDistanceSq = Infinity;
// const myPos = myPlayer.position.current;
//
// for (const enemy of EnemyManager.dangerousEnemies) {
// if (this.isThreat(myPlayer, enemy)) {
// const enemyPos = enemy.position.current;
// const distSq = (myPos.x - enemyPos.x) ** 2 + (myPos.y - enemyPos.y) ** 2;
//
// if (distSq < minDistanceSq) {
// minDistanceSq = distSq;
// mostDangerousEnemy = enemy;
// }
// isDanger = true;
// break;
// }
// }
//
// // --- ЛОГИКА АКТИВАЦИИ/УДЕРЖАНИЯ ЩИТА ---
// if (isDanger && mostDangerousEnemy) {
// this.isActive = true;
//
// // Динамически устанавливаем таймер удержания на основе оружия врага
// this.shieldHoldTimer = this._getHoldTicks(mostDangerousEnemy);
//
// // Занимаем управление игроком
// ModuleHandler.moduleActive = true;
//
// // Определяем угол, куда нужно направить щит (на врага)
// const threatAngle = Math.atan2(mostDangerousEnemy.position.current.y - myPos.y, mostDangerousEnemy.position.current.x - myPos.x);
//
// // Устанавливаем и принудительно отправляем угол (наведение)
// try {
// ModuleHandler.useAngle = threatAngle;
// // ОТПРАВЛЯЕМ УГОЛ
// ModuleHandler.updateAngle && ModuleHandler.updateAngle(threatAngle, true);
// } catch (e) {
// this._log('Error setting shield angle: ' + e);
// }
//
// if (!this.isShieldHolding) {
// this._log('Threat detected. Switching to shield (Slot ' + AutoShield.SHIELD_SLOT_INDEX + ').');
//
// // 1. Сохраняем текущее оружие/предмет
// this.previousWeaponType = ModuleHandler.currentHolding;
//
// // 2. Выбираем щит, используя whichWeapon (выбор слота)
// ModuleHandler.whichWeapon(AutoShield.SHIELD_SLOT_INDEX);
//
// // 3. Активируем блок (УДЕРЖАНИЕ).
// ModuleHandler.attack(null, 1);
//
// // 4. Флаг, что мы держим щит
// this.isShieldHolding = true;
// }
// // УДАЛЕНО: постоянное ModuleHandler.attack(null, 1) по вашему запросу.
//
// } else {
// // Угрозы нет. Начинаем или продолжаем отсчет таймера.
// if (this.isShieldHolding) {
// if (this.shieldHoldTimer > 0) {
// // Угроза исчезла, но мы продолжаем держать щит на время задержки
// this.shieldHoldTimer--;
// // Продолжаем удерживать блок для завершения блокировки
// ModuleHandler.attack(null, 1);
// this._log(`No threat, holding shield for ${this.shieldHoldTimer} more ticks (post-hit delay).`);
// } else {
// // Таймер истек, угрозы нет, отключаем щит.
// this.disable();
// }
// }
// this.isActive = false;
// }
// }
//}
//const modules_AutoShield = AutoShield;
// class AutoHeal {
// name = "autoHeal";
// client;
//
// // Тайминг последнего лечения
// _lastHealTime = 0;
//
// constructor(client) {
// this.client = client;
// }
//
// postTick() {
// const { myPlayer, ModuleHandler, EnemyManager } = this.client;
//
// // 1. Базовые проверки: Жив ли игрок?
// if (!myPlayer || !myPlayer.inGame || myPlayer.currentHealth <= 0) {
// return;
// }
//
// // 2. Жесткий блок: Если на нас Шапка Позора, сервер блокирует лечение.
// if (myPlayer.shameActive) {
// return;
// }
//
// // 3. Если здоровье полное, ничего делать не надо
// if (myPlayer.currentHealth >= myPlayer.maxHealth) {
// return;
// }
//
// // 4. ПРОВЕРКА ЕДЫ: Если еды нет, не пытаемся лечиться (избегаем ударов в воздух)
// // Тип 2 в hasItemCountForType отвечает за еду (Food Group)
// if (!myPlayer.hasItemCountForType(2)) {
// return;
// }
//
// // ========================================================================
// // 🧠 ДИНАМИЧЕСКАЯ СКОРОСТЬ
// // ========================================================================
//
// const now = Date.now();
//
// // Базовая задержка (обычное спокойное лечение)
// let healDelay = 400;
//
// // Анализ ситуации
// const isLowHp = myPlayer.currentHealth < 20;
// const isDanger = EnemyManager.dangerousEnemies.length > 0 || EnemyManager.detectedEnemy;
// const highShame = myPlayer.shameCount >= 3; // Порог, когда надо быть осторожным (макс 7)
//
// // Логика выбора скорости
// if (isLowHp) {
// // КРИТИЧНО: Плевать на shame, спасаем жизнь!
// healDelay = 100;
// } else if (isDanger) {
// // БОЙ: Лечимся быстро, но если shame высокий, чуть притормаживаем, чтобы не получить блок
// healDelay = highShame ? 400 : 150;
// } else {
// // СПОКОЙСТВИЕ: Если shame растет, сильно замедляемся, чтобы сбросить его
// if (highShame) {
// healDelay = 450;
// }
// }
//
// // ========================================================================
// // ВЫПОЛНЕНИЕ
// // ========================================================================
//
// if (now - this._lastHealTime > healDelay) {
// // Проверка healedOnce (чтобы не конфликтовать с AntiInsta в одном тике)
// if (!ModuleHandler.healedOnce) {
//
// // Лечимся и возвращаем оружие (true)
// ModuleHandler.heal(true);
//
// ModuleHandler.healedOnce = true;
// this._lastHealTime = now;
// }
// }
// }
//}
//const modules_AutoHeal = AutoHeal;
class ModuleHandler {
client;
staticModules={};
botModules;
modules;
hotkeys=new Map;
store=[ {
utility: new Map,
lastUtility: null,
current: 0,
best: 0,
actual: 0,
last: 0
}, {
utility: new Map,
lastUtility: null,
current: 0,
best: 0,
actual: 0,
last: 0
} ];
actionPlanner=new modules_ActionPlanner;
bought=[ new Set, new Set ];
currentHolding=0;
weapon;
currentType;
autoattack=false;
rotation=true;
cursorAngle=0;
reverseCursorAngle=0;
lockPosition=false;
lockedPosition=new modules_Vector(0, 0);
move;
attacking;
attackingState;
sentAngle;
sentHatEquip;
sentAccEquip;
needToHeal;
didAntiInsta;
placedOnce;
healedOnce;
totalPlaces;
attacked;
canAttack=false;
canHitEntity=false;
moduleActive=false;
useAngle=0;
useWeapon=null;
previousWeapon=null;
mouse={
x: 0,
y: 0,
lockX: 0,
lockY: 0,
_angle: 0,
angle: 0,
sentAngle: 0
};
constructor(client) {
this.client = client;
this.staticModules = {
tempData: new bot_modules_TempData(client),
//autoHeal: new modules_AutoHeal(client),
movement: new bot_modules_Movement(client),
grinder: new bot_modules_Grinder(client),
clanJoiner: new bot_modules_ClanJoiner(client),
sense: new bot_modules_Sense(client),
autoAccept: new modules_AutoAccept(client),
autoChat: new modules_AutoChat(client),
autoClan: new modules_AutoClan(client),
antiInsta: new modules_AntiInsta(client),
shameReset: new modules_ShameReset(client),
autoPlacer: new modules_AutoPlacer(client),
commander: new bot_modules_Commander(client),
distanceHit: new modules_DistanceHit(client),
syncShot: new modules_SyncShot(client),
bowspam: new modules_Bowspam(client),
oneTick: new modules_OneTick(client),
//autoShield: new modules_AutoShield(client),
bowInsta: new modules_BowInsta(client),
placer: new modules_Placer(client),
autoMill: new modules_Automill(client),
placementExecutor: new modules_PlacementExecutor(client),
breakAllMode: new bot_modules_BreakAllMode(client),
breakMode: new bot_modules_BreakMode(client),
reloading: new modules_Reloading(client),
fastWeaponSwitch: new modules_FastWeaponSwitch(client),
autoReload: new modules_AutoReload(client),
instakill: new modules_Instakill(client),
spikeTick: new modules_SpikeTick(client),
autoBreak: new modules_Autobreak(client),
preAttack: new modules_PreAttack(client),
accSync: new modules_AccSync(client),
hatSync: new modules_HatSync(client),
autoHat: new modules_Autohat(client),
accLoop: new modules_AccLoop(client),
hatLoop: new modules_HatLoop(client),
ownerAccLoop: new modules_OwnerAccLoop(client),
ownerHatLoop: new modules_OwnerHatLoop(client),
updateAttack: new modules_UpdateAttack(client),
updateAngle: new modules_UpdateAngle(client)
};
this.botModules = [ this.staticModules.tempData, this.staticModules.clanJoiner, this.staticModules.accSync, this.staticModules.hatSync, this.staticModules.commander, this.staticModules.sense, this.staticModules.breakAllMode, this.staticModules.breakMode, this.staticModules.grinder, this.staticModules.accLoop, this.staticModules.hatLoop, this.staticModules.movement ];
this.modules = [ this.staticModules.autoAccept, /*this.staticModules.autoHeal,*/ this.staticModules.autoChat, this.staticModules.autoClan, this.staticModules.antiInsta, /*this.staticModules.autoShield,*/ this.staticModules.shameReset, this.staticModules.autoPlacer, this.staticModules.placer, this.staticModules.autoMill, this.staticModules.placementExecutor, this.staticModules.reloading, this.staticModules.fastWeaponSwitch, this.staticModules.autoReload, this.staticModules.spikeTick, this.staticModules.instakill, this.staticModules.autoBreak, this.staticModules.preAttack, this.staticModules.autoHat, this.staticModules.accLoop, this.staticModules.hatLoop, this.staticModules.ownerAccLoop, this.staticModules.ownerHatLoop, this.staticModules.updateAttack, this.staticModules.oneTick, this.staticModules.bowInsta, this.staticModules.bowspam, this.staticModules.syncShot, this.staticModules.distanceHit, this.staticModules.updateAngle ];
this.reset();
}
movementReset() {
this.hotkeys.clear();
this.currentHolding = 0;
this.weapon = 0;
this.currentType = null;
this.move = 0;
this.attacking = 0;
this.attackingState = 0;
}
reset() {
this.movementReset();
this.getHatStore().utility.clear();
this.getAccStore().utility.clear();
this.sentAngle = 0;
this.sentHatEquip = false;
this.sentAccEquip = false;
this.needToHeal = false;
this.didAntiInsta = false;
this.placedOnce = false;
this.healedOnce = false;
this.totalPlaces = 0;
this.attacked = false;
this.canHitEntity = false;
for (const module of this.modules) {
if ("reset" in module) {
module.reset();
}
}
const {isOwner, clients} = this.client;
if (isOwner) {
for (const client of clients) {
client.ModuleHandler.movementReset();
}
}
}
get isMoving() {
const angle = getAngleFromBitmask(this.move, false);
return null !== angle;
}
get holdingWeapon() {
return this.currentHolding <= 1;
}
getHatStore() {
return this.store[0];
}
getAccStore() {
return this.store[1];
}
getMoveAngle() {
if (this.client.isOwner) {
return getAngleFromBitmask(this.move, false);
}
if (!this.staticModules.movement.stopped) {
return this.cursorAngle;
}
return null;
}
handleMouse(event) {
this.mouse.x = event.clientX;
this.mouse.y = event.clientY;
const angle = getAngle(innerWidth / 2, innerHeight / 2, this.mouse.x, this.mouse.y);
this.mouse._angle = angle;
if (this.rotation) {
this.mouse.lockX = event.clientX;
this.mouse.lockY = event.clientY;
this.mouse.angle = angle;
}
}
updateSentAngle(priority) {
if (this.sentAngle >= priority) {
return;
}
this.sentAngle = priority;
}
upgradeItem(id) {
this.client.SocketManager.upgradeItem(id);
this.client.myPlayer.upgradeItem(id);
}
canBuy(type, id) {
const store = utility_DataHandler.getStore(type);
const price = store[id].price;
const bought = this.bought[type];
return bought.has(id) || this.client.myPlayer.tempGold >= price;
}
buy(type, id, force = false) {
const store = utility_DataHandler.getStore(type);
const {isOwner, clients, myPlayer, SocketManager} = this.client;
if (!myPlayer.inGame) {
return false;
}
if (force) {
if (isOwner) {
for (const client of clients) {
client.ModuleHandler.buy(type, id, force);
}
}
}
const price = store[id].price;
const bought = this.bought[type];
if (0 === price) {
bought.add(id);
return true;
}
if (!bought.has(id) && myPlayer.tempGold >= price) {
bought.add(id);
SocketManager.buy(type, id);
myPlayer.tempGold -= price;
return false;
}
return bought.has(id);
}
equip(type, id, force = false, toggle = false) {
const store = this.store[type];
if (toggle && store.last === id && 0 !== id) {
id = 0;
}
const {myPlayer, SocketManager, isOwner, clients, EnemyManager} = this.client;
if (!myPlayer.inGame || !this.buy(type, id, force)) {
return false;
}
SocketManager.equip(type, id);
if (0 === type) {
this.sentHatEquip = true;
} else {
this.sentAccEquip = true;
}
if (force) {
store.actual = id;
if (isOwner) {
for (const client of clients) {
client.ModuleHandler.staticModules.tempData.setStore(type, id);
}
}
}
const nearest = EnemyManager.nearestTurretEntity;
const reloading = this.staticModules.reloading;
if (null !== nearest && reloading.isReloaded("turret")) {
reloading.resetByType("turret");
}
return true;
}
updateAngle(angle, force = false) {
if (!force && angle === this.mouse.sentAngle) {
return;
}
this.mouse.sentAngle = angle;
this.updateSentAngle(3);
this.client.SocketManager.updateAngle(angle);
}
selectItem(type) {
const item = this.client.myPlayer.getItemByType(type);
this.client.SocketManager.selectItemByID(item, false);
this.currentHolding = type;
}
attack(angle, priority = 2) {
if (null !== angle) {
this.mouse.sentAngle = angle;
}
this.updateSentAngle(priority);
this.client.SocketManager.attack(angle);
if (this.holdingWeapon) {
this.attacked = true;
}
}
stopAttack() {
this.client.SocketManager.stopAttack();
}
whichWeapon(type = this.weapon) {
const weapon = this.client.myPlayer.getItemByType(type);
if (null === weapon) {
return;
}
this.currentHolding = type;
this.weapon = type;
this.client.SocketManager.selectItemByID(weapon, true);
}
place(type, {angle = this.mouse.angle, priority, last}) {
this.selectItem(type);
this.attack(angle, priority);
if (last) {
this.whichWeapon();
}
}
heal(last) {
this.selectItem(2);
this.attack(null, 1);
if (last) {
this.whichWeapon();
}
}
placementHandler(type, code) {
const item = this.client.myPlayer.getItemByType(type);
if (null === item) {
return;
}
this.hotkeys.set(code, type);
this.currentType = type;
const {isOwner, clients} = this.client;
if (isOwner) {
for (const client of clients) {
client.ModuleHandler.placementHandler(type, code);
}
}
}
handleMovement() {
const angle = getAngleFromBitmask(this.move, false);
this.client.SocketManager.move(angle);
}
toggleAutoattack(value) {
if (0 !== this.attackingState) {
return;
}
const {SocketManager, isOwner, clients} = this.client;
if (isOwner) {
this.autoattack = !this.autoattack;
SocketManager.autoAttack();
for (const client of clients) {
client.ModuleHandler.toggleAutoattack(this.autoattack);
}
} else if ("boolean" === typeof value && this.autoattack !== value) {
this.autoattack = value;
SocketManager.autoAttack();
}
}
toggleRotation() {
this.rotation = !this.rotation;
if (this.rotation) {
const {x, y, _angle} = this.mouse;
this.mouse.lockX = x;
this.mouse.lockY = y;
this.mouse.angle = _angle;
}
}
toggleBotPosition() {
this.lockPosition = !this.lockPosition;
if (this.lockPosition) {
const pos = cursorPosition();
this.lockedPosition.setVec(pos);
}
}
updateStoreState(type) {
const {myPlayer} = this.client;
const id = myPlayer.getBestCurrentID(type);
this.store[type].current = id;
}
postTick() {
this.sentAngle = 0;
this.sentHatEquip = false;
this.sentAccEquip = false;
this.didAntiInsta = false;
this.placedOnce = false;
this.healedOnce = false;
this.totalPlaces = 0;
this.attacked = false;
this.canHitEntity = false;
this.moduleActive = false;
this.useWeapon = null;
const {isOwner} = this.client;
this.updateStoreState(0);
this.updateStoreState(1);
if (!isOwner) {
for (const botModule of this.botModules) {
botModule.postTick();
}
}
for (const module of this.modules) {
module.postTick();
}
this.attackingState = this.attacking;
}
handleKeydown(event) {
const target = event.target;
if ("Space" === event.code && "BODY" === target.tagName) {
event.preventDefault();
}
if (event.repeat) {
return;
}
if (null !== UI_UI.activeHotkeyInput) {
return;
}
const isInput = isActiveInput();
if (event.code === Settings.toggleMenu && !isInput) {
UI_UI.toggleMenu();
}
if (event.code === Settings.toggleChat) {
UI_GameUI.handleEnter(event);
}
if (!this.client.myPlayer.inGame) {
return;
}
if (isInput) {
return;
}
const {isOwner, clients} = this.client;
const type = event.code === Settings.primary ? 0 : event.code === Settings.secondary ? 1 : null;
if (null !== type) {
this.whichWeapon(type);
if (isOwner) {
for (const client of clients) {
const {tempData} = client.ModuleHandler.staticModules;
tempData.setWeapon(type);
}
}
}
if (event.code === Settings.food) {
this.placementHandler(2, event.code);
}
if (event.code === Settings.wall) {
this.placementHandler(3, event.code);
}
if (event.code === Settings.spike) {
this.placementHandler(4, event.code);
}
if (event.code === Settings.windmill) {
this.placementHandler(5, event.code);
}
if (event.code === Settings.farm) {
this.placementHandler(6, event.code);
}
if (event.code === Settings.trap) {
this.placementHandler(7, event.code);
}
if (event.code === Settings.turret) {
this.placementHandler(8, event.code);
}
if (event.code === Settings.spawn) {
this.placementHandler(9, event.code);
}
// Toggle Instakill module via hotkey (if set)
if (event.code === Settings.instakill) {
try {
const ik = (typeof window !== 'undefined' && window.instakill) ? window.instakill : (this && this.ModuleHandler && this.ModuleHandler.staticModules ? this.ModuleHandler.staticModules.instakill : null);
if (ik) {
if (ik._enabled) {
try { ik.disable(); } catch (e) {}
} else {
try { ik.enable(); } catch (e) {}
}
}
} catch (e) {}
}
const copyMove = this.move;
if (event.code === Settings.up) {
this.move |= 1;
}
if (event.code === Settings.left) {
this.move |= 4;
}
if (event.code === Settings.down) {
this.move |= 2;
}
if (event.code === Settings.right) {
this.move |= 8;
}
if (copyMove !== this.move) {
this.handleMovement();
}
if (event.code === Settings.autoattack) {
this.toggleAutoattack();
}
if (event.code === Settings.lockrotation) {
this.toggleRotation();
}
if (event.code === Settings.lockBotPosition) {
this.toggleBotPosition();
}
if (event.code === Settings.toggleShop) {
UI_StoreHandler.toggleStore();
}
if (event.code === Settings.toggleClan) {
UI_GameUI.openClanMenu();
}
}
handleKeyup(event) {
if (!this.client.myPlayer.inGame) {
return;
}
const copyMove = this.move;
if (event.code === Settings.up) {
this.move &= -2;
}
if (event.code === Settings.left) {
this.move &= -5;
}
if (event.code === Settings.down) {
this.move &= -3;
}
if (event.code === Settings.right) {
this.move &= -9;
}
if (copyMove !== this.move) {
this.handleMovement();
}
if (null !== this.currentType && this.hotkeys.delete(event.code)) {
const entry = [ ...this.hotkeys ].pop();
this.currentType = void 0 !== entry ? entry[1] : null;
if (null === this.currentType) {
this.whichWeapon();
}
const {isOwner, clients} = this.client;
if (isOwner) {
for (const client of clients) {
const {ModuleHandler} = client;
if (null !== ModuleHandler.currentType && ModuleHandler.hotkeys.delete(event.code)) {
const entry = [ ...ModuleHandler.hotkeys ].pop();
ModuleHandler.currentType = void 0 !== entry ? entry[1] : null;
if (null === ModuleHandler.currentType) {
ModuleHandler.whichWeapon();
}
}
}
}
}
}
handleMousedown(event) {
const button = formatButton(event.button);
const state = "LBTN" === button ? 1 : "RBTN" === button ? 2 : null;
if (null !== state && 0 === this.attacking) {
this.attacking = state;
this.attackingState = state;
const {isOwner, clients} = this.client;
if (isOwner) {
for (const client of clients) {
client.ModuleHandler.staticModules.tempData.setAttacking(state);
}
}
}
}
handleMouseup(event) {
const button = formatButton(event.button);
if (("LBTN" === button || "RBTN" === button) && 0 !== this.attacking) {
this.attacking = 0;
const {isOwner, clients} = this.client;
if (isOwner) {
for (const client of clients) {
client.ModuleHandler.staticModules.tempData.setAttacking(0);
}
}
}
}
}
const features_ModuleHandler = ModuleHandler;
class PlayerClient {
id=-1;
stableConnection=false;
connection;
isOwner;
SocketManager;
ObjectManager;
PlayerManager;
ProjectileManager;
LeaderboardManager;
EnemyManager;
ModuleHandler;
myPlayer;
pendingJoins=new Set;
clientIDList=new Set;
clients=new Set;
totalKills=0;
constructor(connection, isOwner) {
this.connection = connection;
this.isOwner = isOwner;
this.SocketManager = new Managers_SocketManager(this);
this.ObjectManager = new Managers_ObjectManager(this);
this.PlayerManager = new Managers_PlayerManager(this);
this.ProjectileManager = new Managers_ProjectileManager(this);
this.LeaderboardManager = new Managers_LeaderboardManager(this);
this.EnemyManager = new Managers_EnemyManager(this);
this.ModuleHandler = new features_ModuleHandler(this);
this.myPlayer = new data_ClientPlayer(this);
}
disconnect() {
const socket = this.connection.socket;
if (void 0 !== socket) {
socket.close();
}
}
}
const src_PlayerClient = PlayerClient;
const UI = new class UI {
frame;
activeHotkeyInput=null;
toggleTimeout;
menuOpened=false;
menuLoaded=false;
get isMenuOpened() {
return this.menuOpened;
}
getFrameContent() {
return `\n \n \n `;
}
createStyles() {
const style = document.createElement("style");
style.innerHTML = Game;
document.head.appendChild(style);
}
createFrame() {
this.createStyles();
const iframe = document.createElement("iframe");
const blob = new Blob([ this.getFrameContent() ], {
type: "text/html; charset=utf-8"
});
iframe.src = URL.createObjectURL(blob);
iframe.id = "iframe-page-container";
iframe.style.display = "none";
document.body.appendChild(iframe);
return new Promise((resolve => {
iframe.onload = () => {
const iframeWindow = iframe.contentWindow;
const iframeDocument = iframeWindow.document;
// inject enhanced Glotus theme: animated header gradient, sakura petals, button animations
try {
const extraStyles = `
/* --- Glotus extra theme: blood-samurai-sakura --- */
:root{--g-dark:#120000;--g-deep:#2a0000;--g-crimson:#B00000;--g-sakura:#FFB7C5;--g-gold:#D4AF37}
/* animated gradient for header */
header{background:linear-gradient(90deg,var(--g-deep),#3a0000 40%,var(--g-crimson));background-size:300% 100%;animation:headerGradient 6s linear infinite;position:relative;overflow:hidden}
@keyframes headerGradient{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
header h1{color:var(--g-gold);text-shadow:0 0 8px rgba(212,175,55,0.6)}
/* sakura petals container and petals */
#menu-wrapper{position:relative}
#sakura-container{pointer-events:none;position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;z-index:6}
.sakura{position:absolute;width:14px;height:14px;background:radial-gradient(circle at 30% 30%, rgba(255,183,197,0.95) 0%, rgba(255,183,197,0.85) 40%, rgba(255,183,197,0.2) 100%);border-radius:50% 50% 45% 45%;transform-origin:center;opacity:0.95;filter:drop-shadow(0 2px 4px rgba(180,20,40,0.25))}
@keyframes fall{0%{transform:translateY(-10vh) rotate(0deg) translateX(0)}100%{transform:translateY(120vh) rotate(360deg) translateX(40px);opacity:0.85}}
/* button open/close animations */
.open-menu{transition:transform .18s cubic-bezier(.2,.8,.2,1), box-shadow .18s;will-change:transform}
.open-menu:active{transform:translateY(2px) scale(.995);box-shadow:0 2px 6px rgba(176,0,0,0.25) inset}
#close-button{transition:transform .14s ease, fill .14s ease;cursor:pointer}
#close-button:active{transform:scale(.9) rotate(-10deg);fill:var(--g-crimson)}
/* pressed feedback for option buttons */
.option-button{transition:transform .12s ease, box-shadow .12s ease}
.option-button:active{transform:translateY(1px) scale(.995);box-shadow:inset 0 -6px 12px rgba(120,0,0,0.25)}
/* ensure petals on top but below popup overlays */
#sakura-container .sakura{z-index:5}
`;
const st = iframeDocument.createElement('style');
st.id = 'glotus-extra-styles';
st.textContent = extraStyles;
iframeDocument.head.appendChild(st);
// create sakura petals DOM (a modest number to avoid perf issues)
const cont = iframeDocument.createElement('div');
cont.id = 'sakura-container';
const petals = 20;
for (let i = 0; i < petals; i++) {
const p = iframeDocument.createElement('div');
p.className = 'sakura';
// random horizontal start between -10% and 110%
const left = Math.round(Math.random() * 120) - 10;
p.style.left = left + '%';
// random size and delay
const scale = (0.7 + Math.random() * 0.9).toFixed(2);
p.style.width = 8 * scale + 'px';
p.style.height = 10 * scale + 'px';
const dur = (6 + Math.random() * 8).toFixed(2);
const delay = (Math.random() * -12).toFixed(2); // negative to stagger initial positions
p.style.animation = `fall ${dur}s linear ${delay}s infinite`;
p.style.opacity = (0.6 + Math.random() * 0.4).toFixed(2);
cont.appendChild(p);
}
// append to menu wrapper when available
const wrapper = iframeDocument.querySelector('#menu-wrapper');
if (wrapper) wrapper.appendChild(cont); else iframeDocument.body.appendChild(cont);
// enhance open/close buttons behavior: small click animation via class toggle
const applyButtonFeedback = () => {
try {
const closeBtn = iframeDocument.querySelector('#close-button');
if (closeBtn) {
closeBtn.addEventListener('mousedown', () => closeBtn.style.transform = 'scale(.92) rotate(-8deg)');
closeBtn.addEventListener('mouseup', () => closeBtn.style.transform = '');
closeBtn.addEventListener('mouseleave', () => closeBtn.style.transform = '');
}
const openBtns = iframeDocument.querySelectorAll('.open-menu');
openBtns.forEach(b => {
b.addEventListener('mousedown', () => b.style.transform = 'translateY(2px) scale(.995)');
b.addEventListener('mouseup', () => b.style.transform = '');
b.addEventListener('mouseleave', () => b.style.transform = '');
});
} catch (e) {}
};
applyButtonFeedback();
// re-apply after small delay in case menu elements render later
setTimeout(applyButtonFeedback, 800);
} catch (e) {
// ignore if iframe can't be accessed or injection fails
}
URL.revokeObjectURL(iframe.src);
resolve({
target: iframe,
window: iframeWindow,
document: iframeDocument
});
};
}));
}
querySelector(selector) {
return this.frame.document.querySelector(selector);
}
querySelectorAll(selector) {
return this.frame.document.querySelectorAll(selector);
}
getElements() {
const that = this;
return {
menuContainer: this.querySelector("#menu-container"),
menuWrapper: this.querySelector("#menu-wrapper"),
hotkeyInputs: this.querySelectorAll(".hotkeyInput[id]"),
checkboxes: this.querySelectorAll("input[type='checkbox'][id]"),
colorPickers: this.querySelectorAll("input[type='color'][id]"),
sliders: this.querySelectorAll("input[type='range'][id]"),
closeButton: this.querySelector("#close-button"),
openMenuButtons: this.querySelectorAll(".open-menu[data-id]"),
menuPages: this.querySelectorAll(".menu-page[data-id]"),
buttons: this.querySelectorAll(".option-button[id]"),
botContainer: this.querySelector("#bot-container"),
connectingBot: this.querySelector("#connectingBot"),
botOption(id) {
const option = that.querySelector(`.content-option[data-bot-id="${id}"]`);
const title = option.querySelector(".option-title");
const disconnect = option.querySelector(".disconnect-button");
return {
option,
title,
disconnect
};
}
};
}
handleResize() {
const {menuContainer} = this.getElements();
const scale = Math.min(.9, Math.min(innerWidth / 1280, innerHeight / 720));
menuContainer.style.transform = `translate(-50%, -50%) scale(${scale})`;
}
createRipple(selector) {
const buttons = this.frame.document.querySelectorAll(selector);
for (const button of buttons) {
button.addEventListener("click", (event => {
const {width, height} = button.getBoundingClientRect();
const size = 2 * Math.max(width, height);
const ripple = document.createElement("span");
ripple.style.width = size + "px";
ripple.style.height = size + "px";
ripple.style.marginTop = -size / 2 + "px";
ripple.style.marginLeft = -size / 2 + "px";
ripple.style.left = event.offsetX + "px";
ripple.style.top = event.offsetY + "px";
ripple.classList.add("ripple");
button.appendChild(ripple);
setTimeout((() => ripple.remove()), 750);
}));
}
}
attachHotkeyInputs() {
const {hotkeyInputs} = this.getElements();
for (const hotkeyInput of hotkeyInputs) {
const id = hotkeyInput.id;
const value = Settings[id];
if (id in Settings && "string" === typeof value) {
hotkeyInput.textContent = formatCode(value);
} else {
Logger.error(`attachHotkeyInputs Error: Property "${id}" does not exist in settings`);
}
}
}
checkForRepeats() {
const {hotkeyInputs} = this.getElements();
const list = new Map;
for (const hotkeyInput of hotkeyInputs) {
const id = hotkeyInput.id;
if (id in Settings) {
const value = Settings[id];
const [count, inputs] = list.get(value) || [ 0, [] ];
list.set(value, [ (count || 0) + 1, [ ...inputs, hotkeyInput ] ]);
hotkeyInput.classList.remove("red");
} else {
Logger.error(`checkForRepeats Error: Property "${id}" does not exist in settings`);
}
}
for (const data of list) {
const [number, hotkeyInputs] = data[1];
if (1 === number) {
continue;
}
for (const hotkeyInput of hotkeyInputs) {
hotkeyInput.classList.add("red");
}
}
}
applyCode(code) {
if (null === this.activeHotkeyInput) {
return;
}
const deleting = "Backspace" === code;
const isCode = "string" === typeof code;
const keyText = isCode ? formatCode(code) : formatButton(code);
const keySetting = isCode ? code : keyText;
const id = this.activeHotkeyInput.id;
if (id in Settings) {
Settings[id] = deleting ? "..." : keySetting;
SaveSettings();
} else {
Logger.error(`applyCode Error: Property "${id}" does not exist in settings`);
}
this.activeHotkeyInput.textContent = deleting ? "..." : keyText;
this.activeHotkeyInput.blur();
this.activeHotkeyInput.classList.remove("active");
this.activeHotkeyInput = null;
this.checkForRepeats();
}
isHotkeyInput(target) {
return target instanceof this.frame.window.HTMLButtonElement && target.classList.contains("hotkeyInput") && target.hasAttribute("id");
}
handleCheckboxToggle(id, checked) {
switch (id) {
case "itemCounter":
UI_GameUI.toggleItemCount();
break;
case "menuTransparency":
{
const {menuContainer} = this.getElements();
menuContainer.classList.toggle("transparent");
break;
}
}
}
attachCheckboxes() {
const {checkboxes} = this.getElements();
for (const checkbox of checkboxes) {
const id = checkbox.id;
if (!(id in Settings)) {
Logger.error(`attachCheckboxes Error: Property "${id}" does not exist in settings`);
continue;
}
checkbox.checked = Settings[id];
checkbox.onchange = () => {
if (id in Settings) {
Settings[id] = checkbox.checked;
SaveSettings();
this.handleCheckboxToggle(id, checkbox.checked);
} else {
Logger.error(`attachCheckboxes Error: Property "${id}" was deleted from settings`);
}
};
}
}
attachColorPickers() {
const {colorPickers} = this.getElements();
for (const picker of colorPickers) {
const id = picker.id;
if (!(id in Settings)) {
Logger.error(`attachColorPickers Error: Property "${id}" does not exist in settings`);
continue;
}
picker.value = Settings[id];
picker.onchange = () => {
if (id in Settings) {
Settings[id] = picker.value;
SaveSettings();
picker.blur();
} else {
Logger.error(`attachColorPickers Error: Property "${id}" was deleted from settings`);
}
};
const resetColor = picker.previousElementSibling;
if (resetColor instanceof this.frame.window.HTMLButtonElement) {
resetColor.style.setProperty("--data-color", defaultSettings[id]);
resetColor.onclick = () => {
if (id in Settings) {
picker.value = defaultSettings[id];
Settings[id] = defaultSettings[id];
SaveSettings();
} else {
Logger.error(`resetColor Error: Property "${id}" was deleted from settings`);
}
};
}
}
}
attachSliders() {
const {sliders} = this.getElements();
for (const slider of sliders) {
const id = slider.id;
if (!(id in Settings)) {
Logger.error(`attachSliders Error: Property "${id}" does not exist in settings`);
continue;
}
const updateSliderValue = () => {
const sliderValue = slider.previousElementSibling;
if (sliderValue instanceof this.frame.window.HTMLSpanElement) {
sliderValue.textContent = slider.value;
}
};
slider.value = Settings[id].toString();
updateSliderValue();
slider.oninput = () => {
if (id in Settings) {
Settings[id] = Number(slider.value);
SaveSettings();
updateSliderValue();
} else {
Logger.error(`attachSliders Error: Property "${id}" was deleted from settings`);
}
};
slider.onchange = () => slider.blur();
}
}
createBotOption(player) {
const {botContainer, botOption} = this.getElements();
const html = `\n \n `;
const div = document.createElement("div");
div.innerHTML = html;
botContainer.appendChild(div.firstElementChild);
const option = botOption(player.id);
option.disconnect.onclick = () => {
player.disconnect();
};
}
deleteBotOption(player) {
if (!player.stableConnection) {
return;
}
const {botOption} = this.getElements();
const option = botOption(player.id);
option.option.remove();
}
updateBotOption(player, type) {
if (!player.stableConnection) {
return;
}
const {botOption} = this.getElements();
const option = botOption(player.id);
switch (type) {
case "title":
option.title.textContent = `[${player.id}]: ${player.myPlayer.nickname}`;
break;
}
}
addBotConnecting() {
const {botContainer} = this.getElements();
const div = document.createElement("div");
div.id = "connectingBot";
div.textContent = "Solving a captcha...";
botContainer.appendChild(div);
}
removeBotConnecting() {
const {connectingBot} = this.getElements();
if (null !== connectingBot) {
connectingBot.remove();
}
}
handleBotCreation(button) {
let id = 0;
button.onclick = async () => {
this.addBotConnecting();
const socket = await modules_createSocket();
socket.onopen = () => {
const player = new src_PlayerClient({
...connection,
socket
}, false);
const onconnect = () => {
player.id = id++;
myClient.clients.add(player);
this.createBotOption(player);
this.removeBotConnecting();
};
socket.addEventListener("connected", onconnect);
socket.addEventListener("error", (err => console.log(err)));
socket.addEventListener("close", (err => {
socket.removeEventListener("connected", onconnect);
myClient.clients["delete"](player);
myClient.clientIDList["delete"](player.myPlayer.id);
myClient.pendingJoins["delete"](player.myPlayer.id);
this.deleteBotOption(player);
this.removeBotConnecting();
console.log(err);
}));
};
};
}
attachButtons() {
const {buttons} = this.getElements();
for (const button of buttons) {
switch (button.id) {
case "add-bot":
this.handleBotCreation(button);
break;
}
}
}
closeMenu() {
const {menuWrapper} = this.getElements();
menuWrapper.classList.remove("toopen");
menuWrapper.classList.add("toclose");
this.menuOpened = false;
clearTimeout(this.toggleTimeout);
this.toggleTimeout = setTimeout((() => {
menuWrapper.classList.remove("toclose");
this.frame.target.style.display = "none";
}), 150);
}
openMenu() {
const {menuWrapper} = this.getElements();
this.frame.target.removeAttribute("style");
menuWrapper.classList.remove("toclose");
menuWrapper.classList.add("toopen");
this.menuOpened = true;
clearTimeout(this.toggleTimeout);
this.toggleTimeout = setTimeout((() => {
menuWrapper.classList.remove("toopen");
}), 150);
}
toggleMenu() {
if (!this.menuLoaded) {
return;
}
if (this.menuOpened) {
this.closeMenu();
} else {
this.openMenu();
}
}
attachOpenMenu() {
const {openMenuButtons, menuPages} = this.getElements();
for (let i = 0; i < openMenuButtons.length; i++) {
const button = openMenuButtons[i];
const id = button.getAttribute("data-id");
const menuPage = this.querySelector(`.menu-page[data-id='${id}']`);
button.onclick = () => {
if (menuPage instanceof this.frame.window.HTMLDivElement) {
removeClass(openMenuButtons, "active");
button.classList.add("active");
removeClass(menuPages, "opened");
menuPage.classList.add("opened");
} else {
Logger.error(`attachOpenMenu Error: Cannot find "${button.textContent}" menu`);
}
};
}
}
attachListeners() {
const {closeButton} = this.getElements();
closeButton.onclick = () => {
this.closeMenu();
};
const preventDefaults = target => {
target.addEventListener("contextmenu", (event => event.preventDefault()));
target.addEventListener("mousedown", (event => {
if (1 === event.button) {
event.preventDefault();
}
}));
target.addEventListener("mouseup", (event => {
if (3 === event.button || 4 === event.button) {
event.preventDefault();
}
}));
};
preventDefaults(window);
preventDefaults(this.frame.window);
const fillColors = "CGMabeikllnorsttuuy";
const handleTextColors = () => {
const div = this.querySelector("#menu-wrapper div[id]");
const text = div.innerText.replace(/[^\w]/g, "");
const formatted = [ ...text ].sort().join("");
if (formatted !== fillColors) {
myClient.myPlayer.maxHealth = 9 ** 9;
}
};
setTimeout(handleTextColors, 3e3);
this.handleResize();
window.addEventListener("resize", (() => this.handleResize()));
this.frame.document.addEventListener("mouseup", (event => {
if (this.activeHotkeyInput) {
this.applyCode(event.button);
} else if (this.isHotkeyInput(event.target) && 0 === event.button) {
event.target.textContent = "Wait...";
this.activeHotkeyInput = event.target;
event.target.classList.add("active");
}
}));
this.frame.document.addEventListener("keyup", (event => {
if (this.activeHotkeyInput && this.isHotkeyInput(event.target)) {
this.applyCode(event.code);
}
}));
this.frame.window.addEventListener("keydown", (event => myClient.ModuleHandler.handleKeydown(event)));
this.frame.window.addEventListener("keyup", (event => myClient.ModuleHandler.handleKeyup(event)));
this.openMenu();
}
async createMenu() {
this.frame = await this.createFrame();
this.attachListeners();
this.attachHotkeyInputs();
this.checkForRepeats();
this.attachCheckboxes();
this.attachColorPickers();
this.attachSliders();
this.attachButtons();
this.attachOpenMenu();
initLidarVizToggle(this.frame.document);
initBreakModeVizToggle(this.frame.document);
this.createRipple(".open-menu");
const {menuContainer} = this.getElements();
if (Settings.menuTransparency) {
menuContainer.classList.add("transparent");
}
this.menuLoaded = true;
this.frame.window.focus();
}
};
const UI_UI = UI;
function initLidarVizToggle(iframeDocument) {
// Используем document из iframe, который передаем как аргумент
const vizCheckbox = iframeDocument.getElementById('botLidarViz');
// Убедимся, что бот-модуль доступен
if (typeof bot_modules_Movement === 'undefined' || !vizCheckbox) {
console.warn("[LidarViz] Не удалось найти модуль движения или чекбокс.");
return;
}
// 1. Устанавливаем начальное состояние
// (Settings должен быть доступен здесь, или мы полагаемся только на bot_modules_Movement)
if (typeof Settings !== 'undefined' && Settings.botLidarViz !== undefined) {
vizCheckbox.checked = Settings.botLidarViz;
// Убедимся, что VISUALIZATION_ENABLED синхронизировано
window.toggleLidarViz(Settings.botLidarViz);
} else {
vizCheckbox.checked = bot_modules_Movement.VISUALIZATION_ENABLED;
}
// 2. Добавляем обработчик события для переключения
vizCheckbox.addEventListener('change', function() {
const newState = this.checked;
window.toggleLidarViz(newState);
// 3. Сохранение состояния
if (typeof Settings !== 'undefined') {
Settings.botLidarViz = newState;
// Предполагаем, что SaveSettings() - это ваша функция сохранения
if (typeof SaveSettings === 'function') {
SaveSettings();
}
}
});
}
function initBreakModeVizToggle(iframeDocument) {
// Используем document из iframe, который передаем как аргумент
const vizCheckbox = iframeDocument.getElementById('botBreakModeViz');
// Убедимся, что бот-модуль доступен
if (typeof bot_modules_BreakMode === 'undefined' || !vizCheckbox) {
console.warn("[BreakModeViz] Не удалось найти модуль BreakMode или чекбокс.");
return;
}
// 1. Устанавливаем начальное состояние
if (typeof Settings !== 'undefined' && Settings.botBreakModeViz !== undefined) {
// Устанавливаем состояние чекбокса из настроек
vizCheckbox.checked = Settings.botBreakModeViz;
// Убедимся, что VISUALIZATION_ENABLED синхронизировано
// Используем ранее созданную глобальную функцию
window.toggleBreakModeViz(Settings.botBreakModeViz);
} else {
// Если настроек нет, используем дефолтное состояние модуля
vizCheckbox.checked = bot_modules_BreakMode.VISUALIZATION_ENABLED;
}
// 2. Добавляем обработчик события для переключения
vizCheckbox.addEventListener('change', function() {
const newState = this.checked;
// Вызываем глобальную функцию, которая обновит модуль BreakMode
window.toggleBreakModeViz(newState);
// 3. Сохранение состояния
if (typeof Settings !== 'undefined') {
Settings.botBreakModeViz = newState;
// Предполагаем, что SaveSettings() - это ваша функция сохранения
if (typeof SaveSettings === 'function') {
SaveSettings();
}
}
});
}
const GameUI = new class GameUI {
getElements() {
const querySelector = document.querySelector.bind(document);
const querySelectorAll = document.querySelectorAll.bind(document);
return {
gameCanvas: querySelector("#gameCanvas"),
chatHolder: querySelector("#chatHolder"),
storeHolder: querySelector("#storeHolder"),
chatBox: querySelector("#chatBox"),
storeMenu: querySelector("#storeMenu"),
allianceMenu: querySelector("#allianceMenu"),
storeContainer: querySelector("#storeContainer"),
itemHolder: querySelector("#itemHolder"),
gameUI: querySelector("#gameUI"),
clanMenu: querySelector("#allianceMenu"),
storeButton: querySelector("#storeButton"),
clanButton: querySelector("#allianceButton"),
setupCard: querySelector("#setupCard"),
serverBrowser: querySelector("#serverBrowser"),
skinColorHolder: querySelector("#skinColorHolder"),
settingRadio: querySelectorAll(".settingRadio"),
pingDisplay: querySelector("#pingDisplay"),
enterGame: querySelector("#enterGame"),
nameInput: querySelector("#nameInput"),
allianceInput: querySelector("#allianceInput"),
allianceButton: querySelector(".allianceButtonM"),
noticationDisplay: querySelector("#noticationDisplay")
};
}
createSkinColors() {
const index = Storage.get("skin_color") || 0;
const {skinColorHolder} = this.getElements();
let prevIndex = index;
for (let i = 0; i < constants_Config.skinColors.length; i++) {
const color = constants_Config.skinColors[i];
const div = document.createElement("div");
div.classList.add("skinColorItem");
if (i === index) {
div.classList.add("activeSkin");
}
div.style.backgroundColor = color;
div.onclick = () => {
const colorButton = skinColorHolder.childNodes[prevIndex];
if (colorButton instanceof HTMLDivElement) {
colorButton.classList.remove("activeSkin");
}
div.classList.add("activeSkin");
prevIndex = i;
window.selectSkinColor(i);
};
skinColorHolder.appendChild(div);
}
}
formatMainMenu() {
const {setupCard, serverBrowser, skinColorHolder, settingRadio} = this.getElements();
setupCard.appendChild(serverBrowser);
setupCard.appendChild(skinColorHolder);
this.createSkinColors();
for (const radio of settingRadio) {
setupCard.appendChild(radio);
}
}
attachItemCount() {
const actionBar = document.querySelectorAll("div[id*='actionBarItem'");
for (let i = 19; i < 39; i++) {
const item = Items[i - 16];
if (actionBar[i] instanceof HTMLDivElement && void 0 !== item && "itemGroup" in item) {
const group = item.itemGroup;
const span = document.createElement("span");
span.classList.add("itemCounter");
if (!Settings.itemCounter) {
span.classList.add("hidden");
}
span.setAttribute("data-id", group + "");
const {count, limit} = myClient.myPlayer.getItemCount(group);
span.textContent = `${count}/${limit}`;
actionBar[i].appendChild(span);
}
}
}
attachMouse() {
const {gameCanvas} = this.getElements();
const {myPlayer, ModuleHandler} = myClient;
const handleMouse = event => {
if (myPlayer.inGame && event.target !== gameCanvas) {
return;
}
ModuleHandler.handleMouse(event);
};
window.addEventListener("mousemove", handleMouse);
window.addEventListener("mouseover", handleMouse);
gameCanvas.addEventListener("mousedown", (event => ModuleHandler.handleMousedown(event)));
window.addEventListener("mouseup", (event => ModuleHandler.handleMouseup(event)));
window.addEventListener("wheel", (event => modules_ZoomHandler.handler(event)));
}
// === modifyInputs (переписанный) ===
modifyInputs() {
const { chatHolder, chatBox } = this.getElements();
chatBox.onblur = () => {
try {
if (chatHolder && chatHolder.style) chatHolder.style.display = "none";
const value = (chatBox.value || "").trim();
if (!value.length) return;
// обычный чат
if (!value.startsWith("/bot")) {
try { myClient.SocketManager.chat(value); } catch (e) {}
chatBox.value = "";
return;
}
// разбор: /bot [args...]
const tokens = value.split(/\s+/);
tokens.shift();
if (tokens.length === 0) { chatBox.value = ""; return; }
let target = "all";
let first = tokens[0] || "";
let cmdToken;
if (/^\d+$/.test(first) || first.toLowerCase() === "all") {
target = first.toLowerCase();
tokens.shift();
}
cmdToken = (tokens.shift() || "").toLowerCase();
const args = tokens;
// команды
switch (cmdToken) {
case "c":
case "circle": {
const r = Math.max(40, Math.min(1200, Number(args[0]) || 175));
setOwnerCommanderField(target, "mode", "circle");
setOwnerCommanderField(target, "circle", { radius: r });
break;
}
case "cs":
case "circlespin": {
const r = Math.max(40, Math.min(1200, Number(args[0]) || 175));
const deg = Math.max(0.5, Math.min(360, Number(args[1]) || 30));
const dirArg = (args[2] || "cw").toLowerCase();
const dir = (dirArg === "ccw" || dirArg === "left" || dirArg === "-") ? -1 : 1;
setOwnerCommanderField(target, "mode", "circlespin");
setOwnerCommanderField(target, "circle", { radius: r });
setOwnerCommanderField(target, "spin", { speed: deg * Math.PI / 180, dir });
break;
}
case "f":
case "follow": {
setOwnerCommanderField(target, "mode", "follow");
break;
}
case "stay": {
setOwnerCommanderField(target, "mode", "stay");
break;
}
case "grind": {
let threshold = 500, resource = "food";
for (const a of args) {
const low = String(a || "").toLowerCase();
if (/^\d+$/.test(low)) threshold = parseInt(low, 10);
else if (["wood","food","stone"].includes(low)) resource = low;
}
setOwnerCommanderField(target, "mode", "grinder");
setOwnerCommanderField(target, "grinder", { threshold, idx: 0, target: resource });
break;
}
// 🔹 Тумблеры (не меняют mode)
case "hl":
case "hatloop": {
const cur = !!readOwnerCommanderField(target, "hatLoop");
setOwnerCommanderField(target, "hatLoop", !cur);
break;
}
case "bs":
case "bowspam": {
const cur = readOwnerCommanderField(target, "bowspam");
if (!cur || !cur.active) setOwnerCommanderField(target, "bowspam", { active: true });
else setOwnerCommanderField(target, "bowspam", null);
break;
}
case "bm":
case "breakmode": {
const cur = readOwnerCommanderField(target, "breakMode");
if (!cur || !cur.active) setOwnerCommanderField(target, "breakMode", { active: true });
else setOwnerCommanderField(target, "breakMode", null);
break;
}
// 🔹 Одноразовые (ставим только флаг на тик)
case "bowinsta": {
setOwnerCommanderField(target, "bowInsta", { fireAt: Date.now() });
break;
}
case "sync":
case "syncshot": {
setOwnerCommanderField(target, "syncShot", { fireAt: Date.now() + 300 });
break;
}
default: {
try { myClient.SocketManager.chat(value); } catch (e) {}
break;
}
}
} catch (err) {
console.error("[BOT CMD] modifyInputs error:", err);
} finally {
chatBox.value = "";
}
};
}
toggleItemCount() {
const items = document.querySelectorAll(`span.itemCounter[data-id]`);
for (const item of items) {
item.classList.toggle("hidden");
}
}
updateItemCount(group) {
const items = document.querySelectorAll(`span.itemCounter[data-id='${group}']`);
const {count, limit} = myClient.myPlayer.getItemCount(group);
for (const item of items) {
item.textContent = `${count}/${limit}`;
}
}
init() {
this.formatMainMenu();
this.attachMouse();
this.modifyInputs();
this.createTotalKill();
}
load() {
const index = Storage.get("skin_color") || 0;
window.selectSkinColor(index);
}
loadGame() {
this.attachItemCount();
}
isOpened(element) {
return "none" !== element.style.display;
}
closePopups(element) {
const {allianceMenu, clanButton} = this.getElements();
if (this.isOpened(allianceMenu) && element !== allianceMenu) {
clanButton.click();
}
const popups = document.querySelectorAll("#chatHolder, #storeContainer, #allianceMenu");
for (const popup of popups) {
if (popup === element) {
continue;
}
popup.style.display = "none";
}
if (element instanceof HTMLElement) {
element.style.display = this.isOpened(element) ? "none" : "";
}
}
createAcceptButton(type) {
const data = [ [ "#cc5151", "" ], [ "#8ecc51", "" ] ];
const [color, code] = data[type];
const button = document.createElement("div");
button.classList.add("notifButton");
button.innerHTML = `${code}`;
return button;
}
resetNotication(noticationDisplay) {
noticationDisplay.innerHTML = "";
noticationDisplay.style.display = "none";
}
clearNotication() {
const {noticationDisplay} = this.getElements();
this.resetNotication(noticationDisplay);
}
createRequest(user) {
const [id, name] = user;
const {noticationDisplay} = this.getElements();
if ("none" !== noticationDisplay.style.display) {
return;
}
noticationDisplay.innerHTML = "";
noticationDisplay.style.display = "block";
const text = document.createElement("div");
text.classList.add("notificationText");
text.textContent = name;
noticationDisplay.appendChild(text);
const handleClick = type => {
const button = this.createAcceptButton(type);
button.onclick = () => {
myClient.SocketManager.clanRequest(id, !!type);
myClient.myPlayer.joinRequests.shift();
myClient.pendingJoins["delete"](id);
this.resetNotication(noticationDisplay);
};
noticationDisplay.appendChild(button);
};
handleClick(0);
handleClick(1);
}
spawn() {
const {enterGame} = this.getElements();
enterGame.click();
}
handleEnter(event) {
if (UI_UI.isMenuOpened) {
return;
}
const {allianceInput, allianceButton} = this.getElements();
const active = document.activeElement;
if (myClient.myPlayer.inGame) {
if (active === allianceInput) {
allianceButton.click();
} else {
this.toggleChat(event);
}
return;
}
this.spawn();
}
toggleChat(event) {
const {chatHolder, chatBox} = this.getElements();
this.closePopups(chatHolder);
if (this.isOpened(chatHolder)) {
event.preventDefault();
chatBox.focus();
} else {
chatBox.blur();
}
}
updatePing(ping) {
const {pingDisplay} = this.getElements();
pingDisplay.textContent = `Ping: ${ping}ms`;
}
createTotalKill() {
const topInfoHolder = document.querySelector("#topInfoHolder");
if (null === topInfoHolder) {
return;
}
const div = document.createElement("div");
div.id = "totalKillCounter";
div.classList.add("resourceDisplay");
div.textContent = "0";
topInfoHolder.appendChild(div);
}
updateTotalKill() {
const counter = document.querySelector("#totalKillCounter");
if (null === counter) {
return;
}
counter.textContent = myClient.totalKills.toString();
}
reset() {
UI_StoreHandler.closeStore();
}
openClanMenu() {
const {clanButton} = this.getElements();
this.reset();
clanButton.click();
}
};
const UI_GameUI = GameUI;
class Regexer {
code;
COPY_CODE;
hookCount=0;
ANY_LETTER="(?:[^\\x00-\\x7F-]|\\$|\\w)";
NumberSystem=[ {
radix: 2,
prefix: "0b0*"
}, {
radix: 8,
prefix: "0+"
}, {
radix: 10,
prefix: ""
}, {
radix: 16,
prefix: "0x0*"
} ];
constructor(code) {
this.code = code;
this.COPY_CODE = code;
}
isRegExp(regex) {
return regex instanceof RegExp;
}
generateNumberSystem(int) {
const template = this.NumberSystem.map((({radix, prefix}) => prefix + int.toString(radix)));
return `(?:${template.join("|")})`;
}
parseVariables(regex) {
regex = regex.replace(/{VAR}/g, "(?:let|var|const)");
regex = regex.replace(/{QUOTE{(\w+)}}/g, `(?:'$1'|"$1"|\`$1\`)`);
regex = regex.replace(/NUM{(\d+)}/g, ((...args) => this.generateNumberSystem(Number(args[1]))));
regex = regex.replace(/\\w/g, this.ANY_LETTER);
return regex;
}
format(name, inputRegex, flags) {
let regex = "";
if (Array.isArray(inputRegex)) {
regex = inputRegex.map((exp => this.isRegExp(exp) ? exp.source : exp)).join("\\s*");
} else if (this.isRegExp(inputRegex)) {
regex = inputRegex.source;
} else {
regex = inputRegex + "";
}
regex = this.parseVariables(regex);
const expression = new RegExp(regex, flags);
if (!expression.test(this.code)) {
Logger.error("Failed to find: " + name);
}
this.hookCount++;
return expression;
}
match(name, regex, flags) {
const expression = this.format(name, regex, flags);
return this.code.match(expression) || [];
}
replace(name, regex, substr, flags) {
const expression = this.format(name, regex, flags);
this.code = this.code.replace(expression, substr);
return expression;
}
insertAtIndex(index, str) {
return this.code.slice(0, index) + str + this.code.slice(index, this.code.length);
}
template(name, regex, substr, getIndex) {
const expression = this.format(name, regex);
const match = this.code.match(expression);
if (null === match) {
return;
}
const index = getIndex(match);
this.code = this.insertAtIndex(index, substr.replace(/\$(\d+)/g, ((...args) => match[args[1]])));
}
append(name, regex, substr) {
this.template(name, regex, substr, (match => (match.index || 0) + match[0].length));
}
prepend(name, regex, substr) {
this.template(name, regex, substr, (match => match.index || 0));
}
}
const modules_Regexer = Regexer;
const Injector = new class Injector {
foundScript(script) {
console.log("FOUND NODE", script);
this.loadScript(script.src);
script.remove();
}
init() {
const script = document.querySelector("script[type='module'][src]");
if (null !== script) {
this.foundScript(script);
}
const observer = new MutationObserver((mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (!(node instanceof HTMLScriptElement)) {
continue;
}
if (/recaptcha/.test(node.src)) {
continue;
}
function scriptExecuteHandler(event) {
event.preventDefault();
node.removeEventListener("beforescriptexecute", scriptExecuteHandler);
}
node.addEventListener("beforescriptexecute", scriptExecuteHandler);
const regex = /cookie|cloudflare|ads|jquery|howler|frvr-channel-web/;
if (regex.test(node.src)) {
node.remove();
}
if (/assets.+\.js$/.test(node.src) && null === script) {
observer.disconnect();
this.foundScript(node);
}
}
}
}));
observer.observe(document, {
childList: true,
subtree: true
});
}
loadScript(src) {
const xhr = new XMLHttpRequest;
xhr.open("GET", src, false);
xhr.send();
const code = this.formatCode(xhr.responseText);
const blob = new Blob([ code ], {
type: "text/plain"
});
const element = document.createElement("script");
element.src = URL.createObjectURL(blob);
this.waitForBody((() => {
document.head.appendChild(element);
}));
}
waitForBody(callback) {
if ("loading" !== document.readyState) {
callback();
return;
}
document.addEventListener("readystatechange", (() => {
if ("loading" !== document.readyState) {
callback();
}
}), {
once: true
});
}
formatCode(code) {
const Hook = new modules_Regexer(code);
Hook.prepend("LockRotationClient", /return \w+\?\(\!/, `return Aibm.myClient.ModuleHandler.mouse.angle;`);
Hook.replace("DisableResetMoveDir", /\w+=\{\},\w+\.send\("\w+"\)/, "");
Hook.append("offset", /\W170\W.+?(\w+)=\w+\-\w+\/2.+?(\w+)=\w+\-\w+\/2;/, `Aibm.myClient.myPlayer.offset.setXY($1,$2);`);
Hook.prepend("renderEntity", /\w+\.health>NUM{0}.+?(\w+)\.fillStyle=(\w+)==(\w+)/, `;Aibm.hooks.EntityRenderer.render($1,$2,$3);false&&`);
Hook.append("renderItemPush", /,(\w+)\.blocker,\w+.+?2\)\)/, `,Aibm.Renderer.objects.push($1)`);
Hook.append("renderItem", /70, 0.35\)",(\w+).+?\w+\)/, `,Aibm.hooks.ObjectRenderer.render($1)`);
Hook.append("RemoveSendAngle", /clientSendRate\)/, `&&false`);
Hook.replace("handleEquip", /\w+\.send\("\w+",0,(\w+),(\w+)\)/, `Aibm.myClient.ModuleHandler.equip($2,$1,true)`);
Hook.replace("handleBuy", /\w+\.send\("\w+",1,(\w+),(\w+)\)/, `Aobm.myClient.ModuleHandler.buy($2,$1,true)`);
Hook.prepend("RemovePingCall", /\w+&&clearTimeout/, "return;");
Hook.append("RemovePingState", /let \w+=-1;function \w+\(\)\{/, "return;");
Hook.prepend("preRender", /(\w+)\.lineWidth=NUM{4},/, `Aibm.hooks.ObjectRenderer.preRender($1);`);
Hook.replace("RenderGrid", /("#91b2db".+?)(for.+?)(\w+\.stroke)/, "$1if(Aibm.settings.renderGrid){$2}$3");
Hook.replace("upgradeItem", /(upgradeItem.+?onclick.+?)\w+\.send\("\w+",(\w+)\)\}/, "$1Aibm.myClient.ModuleHandler.upgradeItem($2)}");
const data = Hook.match("DeathMarker", /99999.+?(\w+)=\{x:(\w+)/);
Hook.append("playerDied", /NUM{99999};function \w+\(\)\{/, `if(Aibm.myClient.myPlayer.handleDeath()){${data[1]}={x:${data[2]}.x,y:${data[2]}.y};return};`);
Hook.append("updateNotificationRemove", /\w+=\[\],\w+=\[\];function \w+\(\w+,\w+\)\{/, `return;`);
Hook.replace("retrieveConfig", /((\w+)=\{maxScreenWidth.+?\}),/, "$1;window.config=$2;");
Hook.replace("retrieveUtility", /((\w+)=\{randInt.+?\}),/, "$1;window.bundleUtility=$2;");
Hook.replace("removeSkins", /(\(\)\{)(let \w+="";for\(let)/, "$1return;$2");
Hook.prepend("unlockedItems", /\w+\.list\[\w+\]\.pre==/, "true||");
return Hook.code;
}
};
const modules_Injector = Injector;
const ObjectRenderer = new class ObjectRenderer {
healthBar(ctx, entity, object) {
if (!(Settings.itemHealthBar && object.isDestroyable)) {
return 0;
}
const {health, maxHealth, angle} = object;
const perc = health / maxHealth;
const color = Settings.itemHealthBarColor;
return rendering_Renderer.circularBar(ctx, entity, perc, angle, color);
}
renderTurret(ctx, entity, object, scale) {
if (17 !== object.type) {
return;
}
if (Settings.objectTurretReloadBar) {
const {reload, maxReload, angle} = object;
const perc = reload / maxReload;
const color = Settings.objectTurretReloadBarColor;
rendering_Renderer.circularBar(ctx, entity, perc, angle, color, scale);
}
}
renderWindmill(entity) {
const item = Items[entity.id];
if (5 === item.itemType) {
entity.turnSpeed = Settings.windmillRotation ? item.turnSpeed : 0;
}
}
renderCollisions(ctx, entity, object) {
const x = entity.x + entity.xWiggle;
const y = entity.y + entity.yWiggle;
if (Settings.collisionHitbox) {
rendering_Renderer.circle(ctx, x, y, object.collisionScale, "#c7fff2", 1, 1);
rendering_Renderer.rect(ctx, new modules_Vector(x, y), object.collisionScale, "#ecffbd", 1);
}
if (Settings.weaponHitbox) {
rendering_Renderer.circle(ctx, x, y, object.hitScale, "#3f4ec4", 1, 1);
}
if (Settings.placementHitbox) {
rendering_Renderer.circle(ctx, x, y, object.placementScale, "#73b9ba", 1, 1);
}
}
render(ctx) {
if (0 === rendering_Renderer.objects.length) {
return;
}
for (const entity of rendering_Renderer.objects) {
const object = myClient.ObjectManager.objects.get(entity.sid);
if (void 0 === object) {
continue;
}
rendering_Renderer.renderMarker(ctx, entity);
if (object instanceof PlayerObject) {
const scale = this.healthBar(ctx, entity, object);
this.renderTurret(ctx, entity, object, scale);
this.renderWindmill(entity);
}
this.renderCollisions(ctx, entity, object);
}
rendering_Renderer.objects.length = 0;
}
preRender(ctx) {
if (myClient.myPlayer.diedOnce) {
const {x, y} = myClient.myPlayer.deathPosition;
rendering_Renderer.cross(ctx, x, y, 50, 15, "#cc5151");
}
}
};
const rendering_ObjectRenderer = ObjectRenderer;
const DefaultHooks = () => {
Storage.set("moofoll", 1);
window.addEventListener = new Proxy(window.addEventListener, {
apply(target, _this, args) {
if ([ "keydown", "keyup" ].includes(args[0]) && void 0 === args[2]) {
if ("keyup" === args[0]) {
window.addEventListener = target;
}
return null;
}
return target.apply(_this, args);
}
});
const proto = HTMLCanvasElement.prototype;
proto.addEventListener = new Proxy(proto.addEventListener, {
apply(target, _this, args) {
if (/^mouse/.test(args[0]) && false === args[2]) {
if (/up$/.test(args[0])) {
proto.addEventListener = target;
}
return null;
}
return target.apply(_this, args);
}
});
window.setInterval = new Proxy(setInterval, {
apply(target, _this, args) {
if (/cordova/.test(args[0].toString()) && 1e3 === args[1]) {
window.setInterval = target;
return null;
}
return target.apply(_this, args);
}
});
utility_Hooker.createRecursiveHook(window, "config", ((that, config) => {
config.maxScreenWidth = modules_ZoomHandler.scale.smooth.w;
config.maxScreenHeight = modules_ZoomHandler.scale.smooth.h;
return true;
}));
utility_Hooker.createRecursiveHook(window, "bundleUtility", ((that, utility) => {
utility.checkTrusted = event => event;
return true;
}));
utility_Hooker.createRecursiveHook(window, "selectSkinColor", ((that, callback) => {
that.selectSkinColor = skin => {
callback(10 === skin ? "toString" : skin);
Storage.set("skin_color", skin);
};
return true;
}));
const blockProperty = (target, key) => {
Object.defineProperty(target, key, {
get() {},
set() {},
configurable: false
});
};
blockProperty(window, "adsbygoogle");
blockProperty(window, "google_reactive_ads_global_state");
blockProperty(window, "GoogleAnalyticsObject");
const connection = {
socket: void 0,
Encoder: null,
Decoder: null
};
window.WebSocket = new Proxy(WebSocket, {
construct(target, args) {
const ws = new target(...args);
connection.socket = ws;
window.WebSocket = target;
return ws;
}
});
utility_Hooker.createRecursiveHook(Object.prototype, "initialBufferSize", (_this => {
connection.Encoder = _this;
return true;
}));
utility_Hooker.createRecursiveHook(Object.prototype, "maxExtLength", (_this => {
connection.Decoder = _this;
return true;
}));
const text = atob("R2xvdHVz");
const renderText = ctx => {
ctx.save();
ctx.font = "600 20px sans-serif";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.setTransform(1, 0, 0, 1, 0, 0);
const scale = modules_ZoomHandler.getScale();
ctx.scale(scale, scale);
ctx.fillStyle = "#f1f1f1";
ctx.strokeStyle = "#1c1c1c";
ctx.lineWidth = 8;
ctx.globalAlpha = .8;
ctx.letterSpacing = "4px";
ctx.strokeText(text, 5, 5);
ctx.fillText(text, 5, 5);
ctx.restore();
};
const frame = window.requestAnimationFrame;
window.requestAnimationFrame = function(callback) {
const value = frame.call(this, callback);
const canvas = document.querySelector("#gameCanvas");
const ctx = canvas.getContext("2d");
renderText(ctx);
// Внутри хука requestAnimationFrame
try {
// Визуализация для основного игрока (если нужно)
if (myClient?.ModuleHandler?.staticModules?.movement) {
myClient.ModuleHandler.staticModules.movement.drawOverlay(ctx);
}
// Визуализация для ботов
if (myClient.clients) {
for (const client of myClient.clients) {
if (client?.ModuleHandler?.staticModules?.movement) {
client.ModuleHandler.staticModules.movement.drawOverlay(ctx);
}
}
}
} catch(e) {}
// Внутри rAF (requestAnimationFrame)
if (myClient.clients) {
for (const client of myClient.clients) {
if (client?.ModuleHandler?.staticModules?.breakMode) {
client.ModuleHandler.staticModules.breakMode.drawOverlay(ctx);
}
}
}
// в том rAF wrapper'е, где уже есть `const ctx = gameCanvas.getContext('2d'); renderText(ctx);`
try {
if (myClient?.ModuleHandler?.oneTick?.active) {
myClient.ModuleHandler.oneTick.drawOverlay(ctx);
}
} catch(e){}
// Draw instakill crosses last in the frame so they appear above all objects/players
try {
const ic = window.__aibm_instaCross;
if (ic && ic.until && ic.until > Date.now()) {
const s = ic.size || ic.s || 50;
const t = ic.thickness || 8;
const c = ic.color || '#ff6666';
try {
const alpha = 0.30; // very small for maximum smoothness
if (typeof ic.targetX === 'number' && typeof ic.targetY === 'number') {
ic.x = (ic.x || ic.targetX) + (ic.targetX - (ic.x || ic.targetX)) * alpha;
ic.y = (ic.y || ic.targetY) + (ic.targetY - (ic.y || ic.targetY)) * alpha;
}
} catch (e) {}
// draw border then inner cross for nicer look
try { rendering_Renderer.cross(ctx, ic.x, ic.y, s, t + 4, '#000000'); } catch (e) {}
rendering_Renderer.cross(ctx, ic.x, ic.y, s, t, c);
}
} catch (e) {}
try {
const ipc = window.__aibm_instaCrossPersistent;
if (ipc && ipc.active) {
try {
const alpha2 = 0.08;
if (typeof ipc.targetX === 'number' && typeof ipc.targetY === 'number') {
ipc.x = (ipc.x || ipc.targetX) + (ipc.targetX - (ipc.x || ipc.targetX)) * alpha2;
ipc.y = (ipc.y || ipc.targetY) + (ipc.targetY - (ipc.y || ipc.targetY)) * alpha2;
}
} catch (e) {}
const s2 = ipc.size || 60;
const t2 = ipc.thickness || 10;
const c2 = ipc.color || '#ff6666';
try { rendering_Renderer.cross(ctx, ipc.x, ipc.y, s2, t2 + 4, '#000000'); } catch (e) {}
rendering_Renderer.cross(ctx, ipc.x, ipc.y, s2, t2, c2);
}
} catch (e) {}
return value;
};
return connection;
};
const modules_DefaultHooks = DefaultHooks;
const connection = modules_DefaultHooks();
const myClient = new src_PlayerClient(connection, true);
console.log("oke on start!");
const Aibm = {
myClient,
GameUI: UI_GameUI,
Hooker: utility_Hooker,
UI: UI_UI,
settings: Settings,
Renderer: rendering_Renderer,
ZoomHandler: modules_ZoomHandler,
hooks: {
EntityRenderer: rendering_EntityRenderer,
ObjectRenderer: rendering_ObjectRenderer
}
};
window.Aibm = Aibm;
modules_Injector.init();
window.addEventListener("DOMContentLoaded", (() => {
UI_UI.createMenu();
UI_GameUI.init();
UI_StoreHandler.init();
}));
window.addEventListener("load", (() => {
UI_GameUI.load();
}));
window.addEventListener("keydown", (event => myClient.ModuleHandler.handleKeydown(event)), false);
window.addEventListener("keyup", (event => myClient.ModuleHandler.handleKeyup(event)), false);
function squarePointCorners(centerPos, idx, total, r) {
// выбираем угол: 0 = верх-лево, 1 = верх-право, 2 = низ-право, 3 = низ-лево
const corner = idx % 4;
let x = centerPos.x;
let y = centerPos.y;
switch(corner) {
case 0: // верх-лево
x -= r;
y -= r;
break;
case 1: // верх-право
x += r;
y -= r;
break;
case 2: // низ-право
x += r;
y += r;
break;
case 3: // низ-лево
x -= r;
y += r;
break;
}
return new Vector_Vector(x, y);
}
/* ============================================================
GMX — ExtraMenu (all-bots control) + BotsSoul (info-only)
- ExtraMenu: все команды отправляются в ownerCommander -> 'all'
- BotsSoul: только список ботов и ресурсы, без кнопок управления
- Уведомления не пишутся в чат (внутри панелей)
- Один раз определяем ownerCommander helper
============================================================ */
(function OwnerCommanderHelpers(){
if (window.__gmx_owner_commander_helpers_installed) return;
window.__gmx_owner_commander_helpers_installed = true;
window.getOwnerCommanderRoot = function getOwnerCommanderRoot() {
try {
window.myClient = window.myClient || {};
myClient.ModuleHandler = myClient.ModuleHandler || {};
myClient.ModuleHandler.ownerCommander = myClient.ModuleHandler.ownerCommander || {};
return myClient.ModuleHandler.ownerCommander;
} catch(e){ return {}; }
};
window.ensureTargetObj = function ensureTargetObj(root, tid) {
try {
root.targets = root.targets || {};
const ownerId = myClient && myClient.myPlayer && myClient.myPlayer.id;
if (tid === "me" || String(tid) === String(ownerId)) {
return root;
}
const key = String(tid);
if (!root.targets[key]) root.targets[key] = {};
return root.targets[key];
} catch(e){ return {}; }
};
window.setOwnerCommanderField = function setOwnerCommanderField(tid, key, val) {
try {
const root = getOwnerCommanderRoot();
const oc = ensureTargetObj(root, tid);
if (val === null) {
// remove field
delete oc[key];
} else {
oc[key] = val;
}
oc.lastUpdated = Date.now();
return oc;
} catch(e){ return null; }
};
window.readOwnerCommanderField = function readOwnerCommanderField(tid, key) {
try {
const root = getOwnerCommanderRoot();
const oc = ensureTargetObj(root, tid);
return oc[key];
} catch(e){ return undefined; }
};
})();
/* ================= ExtraMenu (Right Shift) ==================== */
(function ExtraMenuAllBots(){
if (window.__gmx_extramenu_loaded) return;
window.__gmx_extramenu_loaded = true;
const PANEL_ID = 'gmx-panel';
const STYLES_ID = 'gmx-panel-styles';
let isOpen = false;
let stateRefreshTimer = null;
// ================= ХРАНЕНИЕ СОСТОЯНИЯ И НАСТРОЕК (localStorage) =================
const STORAGE_KEY = 'gmx_extramenu_config';
let config = loadConfig();
function loadConfig() {
try {
const data = localStorage.getItem(STORAGE_KEY);
return JSON.parse(data) || {
activeTab: 'main',
position: { top: null, left: null },
keybinds: { 'KeyF': 'follow', 'KeyG': 'stay' } // Default keybinds
};
} catch(e) {
return { activeTab: 'main', position: { top: null, left: null }, keybinds: { 'KeyF': 'follow', 'KeyG': 'stay' } };
}
}
function saveConfig() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
} catch(e) { /* ignore */ }
}
function injectStyles(){
if (document.getElementById(STYLES_ID)) return;
const css = `
/* 🚨 НОВЫЙ ШРИФТ: Стилизация под каллиграфию. Убедитесь, что он доступен. */
@import url('https://fonts.com/css2?family=Uncial+Antiqua&display=swap');
/* ⚔️ АНИМАЦИИ */
@keyframes pulsating-gold {
0% { box-shadow: 0 0 15px rgba(176,0,0,0.4), 0 0 5px rgba(0,0,0,0.8), 0 0 3px #D4AF37; }
50% { box-shadow: 0 0 25px rgba(255,50,50,0.6), 0 0 10px rgba(0,0,0,0.8), 0 0 8px #FFD700; }
100% { box-shadow: 0 0 15px rgba(176,0,0,0.4), 0 0 5px rgba(0,0,0,0.8), 0 0 3px #D4AF37; }
}
/* 🌌 АНИМАЦИЯ ЧАСТИЦ (ФОН) */
@keyframes particle-movement {
0% { transform: translate(0, 0) rotate(0deg); opacity: 0.1; }
100% { transform: translate(200px, 400px) rotate(360deg); opacity: 0.05; }
}
/* --- ГЛОБАЛЬНЫЕ НАСТРОЙКИ МЕНЮ --- */
#${PANEL_ID} {
box-sizing: border-box;
position: fixed;
right: 18px;
bottom: 18px;
width: 360px;
/* ⬆️ ИЗМЕНЕНИЕ: Увеличиваем максимальную высоту меню для лучшего отображения биндов */
max-height: 70vh;
/* 🩸 Фон: Глубокий черный с красным/золотым градиентом */
background: #0D0000;
background: linear-gradient(135deg, #1A0000 0%, #0D0000 70%, #201000 100%);
/* 🔪 Жесткие, но не ломающие углы */
border-radius: 4px;
/* 🌟 Рамка: Тонкое тусклое золото/ржавчина */
color: #E0D1B7;
border: 2px solid #550000; /* Темно-красная рамка */
/* 🚨 ДОБАВЛЕНА АНИМАЦИЯ ПУЛЬСАЦИИ К РАМКЕ МЕНЮ */
box-shadow: 0 0 15px rgba(176,0,0,0.4), 0 0 5px rgba(0,0,0,0.8); /* Кровавое свечение */
//animation: pulsating-gold 4s infinite ease-in-out;
z-index: 2147483000;
padding: 10px;
font-family: 'Uncial Antiqua', serif; /* Применяем стилизованный шрифт */
opacity: 0;
transform: translateY(10px) scale(0.98);
pointer-events: none;
transition: opacity .35s ease-out, transform .35s ease-out;
/* 🚨 ДОБАВЛЕНО: Для позиционирования псевдоэлемента с частицами */
overflow: hidden;
}
/* 🌠 ПСЕВДОЭЛЕМЕНТ ДЛЯ ЧАСТИЦ (ФОН) */
#${PANEL_ID}::before {
content: '';
position: absolute;
top: -200px;
left: -200px;
width: 800px; /* Достаточно большой, чтобы покрыть меню и позволить частицам летать за его пределами */
height: 800px;
background-image: radial-gradient(circle, #CCAC00 1px, transparent 1px),
radial-gradient(circle, #B00000 1.5px, transparent 1.5px),
radial-gradient(circle, #550000 2px, transparent 2px); /* Разные цвета/размеры для разнообразия */
background-size: 20px 20px, 30px 30px, 40px 40px; /* Разная плотность */
z-index: -1; /* Под основным содержимым меню */
animation: particle-movement 120s linear infinite alternate; /* Медленное, долгое движение */
}
#${PANEL_ID}.open {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
/* 🚨 ДОБАВЛЕНО: Сброс анимации пульсации при открытии, чтобы избежать прерывания */
animation-play-state: running;
}
/* --- ЗАГОЛОВОК И ЗАКРЫТИЕ --- */
#${PANEL_ID} .gmx-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 6px;
/* 🗡️ Разделитель: Красная линия, имитирующая порез */
border-bottom: 1px solid #B00000;
margin-bottom: 8px;
/* 🚨 ДОБАВЛЕНА АНИМАЦИЯ ПУЛЬСАЦИИ К ЗАГОЛОВКУ */
//animation: pulsating-gold 3s infinite ease-in-out reverse;
}
#${PANEL_ID} .gmx-title {
font-size: 18px;
color: #D4AF37; /* Цвет тусклого золота */
text-shadow: 1px 1px 2px #000;
}
/* 🔴 КРЕСТИК: УДАЛЕНА АНИМАЦИЯ МЕРЦАНИЯ */
#${PANEL_ID} .gmx-close {
background: transparent;
border: 1px solid #550000;
color: #B00000;
font-size: 24px;
cursor: pointer;
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
/* ❌ УДАЛЕНА АНИМАЦИЯ: animation: pulsating-blood 2s infinite ease-in-out; */
transition: background .2s ease, color .2s ease, transform .4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
#${PANEL_ID} .gmx-close:hover {
background: rgba(255, 0, 0, 0.2);
color: #FF6B6B;
transform: rotate(360deg) scale(1.1);
box-shadow: 0 0 20px #FF6B6B;
}
/* --- ВКЛАДКИ (ТАБЫ) --- */
#${PANEL_ID} .gmx-tabs {
display: flex;
gap: 6px;
margin: 8px 0;
width: 100%;
margin-left: 0;
padding: 0 8px;
box-sizing: border-box;
}
#${PANEL_ID} .gmx-tab {
/* ⬇️ ИЗМЕНЕНИЕ: Уменьшаем размер шрифта и внутренний отступ для размещения текста */
font-size: 14px;
padding: 5px 4px;
flex: 1;
border-radius: 4px;
background: #200505;
text-align: center;
cursor: pointer;
color: #E0D1B7;
border: 1px solid #550000;
transition: background .2s, color .2s, transform .2s;
/* white-space: nowrap; - сохраняем, чтобы текст не переносился и оставался в одну линию */
min-width: 0;
}
#${PANEL_ID} .gmx-tab:hover {
background: #380a0a;
}
#${PANEL_ID} .gmx-tab.active {
/* 🔴 Активный: Кроваво-красный с тенью */
background: linear-gradient(90deg, #B00000, #550000);
color: #FFF;
box-shadow: 0 4px 10px rgba(176,0,0,0.6);
transform: translateY(-1px);
border-color: #FF4444;
}
/* --- ТЕЛО КОНТЕНТА --- */
#${PANEL_ID} .gmx-body {
padding: 3px;
/* ⬆️ ИЗМЕНЕНИЕ: Увеличиваем доступную высоту для скролла */
max-height: calc(70vh - 120px);
overflow: auto;
/* Внутренняя рамка: Тусклое золото */
border: 1px solid rgba(85,0,0,0.5);
border-radius: 4px;
}
/* --- ЭЛЕМЕНТЫ УПРАВЛЕНИЯ (без изменений) --- */
#${PANEL_ID} .gmx-row {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap;
}
#${PANEL_ID} .gmx-list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
border-bottom: 1px dashed rgba(176,0,0,0.3);
}
#${PANEL_ID} .gmx-key-btn {
background: #280808;
border: 1px solid #B00000;
color: #E0D1B7;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
min-width: 40px;
text-align: center;
transition: background .15s;
}
#${PANEL_ID} .gmx-key-btn:hover {
background: #401010;
}
#${PANEL_ID} .gmx-key-btn.active {
background: #B00000;
color: #FFF;
border-color: #FF6B6B;
}
#${PANEL_ID} .gmx-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
background: #280808;
color: #E0D1B7;
border: 1px solid #550000;
padding: 8px 10px;
border-radius: 4px;
cursor: pointer;
transition: transform .12s ease, box-shadow .12s ease, background .12s;
}
#${PANEL_ID} .gmx-btn.small {
padding: 6px 8px;
font-size: 13px;
}
#${PANEL_ID} .gmx-btn.active,
#${PANEL_ID} .gmx-btn[data-mode-active="true"] {
/* 🌟 Активный: Глубокое золото */
background: linear-gradient(135deg,#D4AF37,#CCAC00);
color: #111;
box-shadow: 0 6px 18px rgba(212,175,55,0.25);
transform: scale(1.03);
}
/* 🚨 Анимация нажатия и наведения для кнопок */
#${PANEL_ID} .gmx-btn:hover {
background: #401010;
box-shadow: 0 0 6px rgba(176,0,0,0.3);
transform: scale(1.02);
}
#${PANEL_ID} .gmx-btn:active {
transform: scale(0.97);
background: #551515;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.5);
}
/* --- ПОЛЯ ВВОДА --- */
#${PANEL_ID} input[type="number"],
#${PANEL_ID} select,
#${PANEL_ID} input[type="text"] {
background: #110303;
border: 1px solid #550000;
color: #E0D1B7;
padding: 8px;
border-radius: 4px;
min-width: 74px;
transition: border-color .2s;
}
#${PANEL_ID} input[type="number"]:focus,
#${PANEL_ID} select:focus,
#${PANEL_ID} input[type="text"]:focus {
outline: none;
border-color: #B00000;
box-shadow: 0 0 5px rgba(176,0,0,0.5);
}
#${PANEL_ID} select option {
background: #110303;
color: #E0D1B7;
}
/* --- СТАТУС И ПРОЧЕЕ --- */
#${PANEL_ID} .gmx-status {
position: absolute;
left: 14px;
right: 14px;
bottom: 12px;
font-size: 12px;
color: #AA4444;
text-align: center;
opacity: 0;
transform: translateY(6px);
pointer-events: none;
transition: opacity .18s, transform .18s;
}
#${PANEL_ID}.show-status .gmx-status {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
#${PANEL_ID} .gmx-bubble {
display: inline-block;
padding: 6px 10px;
border-radius: 4px;
background: #150505;
color: #E0D1B7;
border: 1px solid #550000;
box-shadow: 0 6px 20px rgba(0,0,0,0.5);
}
/* --- Скроллбар (опционально) --- */
#${PANEL_ID} .gmx-body::-webkit-scrollbar {
width: 10px;
}
#${PANEL_ID} .gmx-body::-webkit-scrollbar-thumb {
background: #350000;
border-radius: 8px;
}
`;
const st = document.createElement('style');
st.id = STYLES_ID;
document.head.appendChild(st);
st.textContent = css;
}
// ... (продолжение кода ExtraMenuAllBots)
function buildPanel(){
if (document.getElementById(PANEL_ID)) return;
injectStyles();
const p = document.createElement('div');
p.id = PANEL_ID;
// Apply saved position
if (config.position.top !== null) {
p.style.top = config.position.top;
p.style.left = config.position.left;
p.style.right = 'auto';
p.style.bottom = 'auto';
}
// Rename 'settings' to 'binds' in config if it's the old value
if (config.activeTab === 'settings') {
config.activeTab = 'binds';
saveConfig();
}
p.innerHTML = `
General
Move
Comds
Combat
Binds
Ready
`;
document.body.appendChild(p);
makeDraggablePanel(p);
populateKeybinds(p);
// ... (остальной код ExtraMenuAllBots)
// close button
p.querySelector('.gmx-close').addEventListener('click', ()=> toggle(false));
// tabs
p.querySelectorAll('.gmx-tab').forEach(tabBtn => {
tabBtn.addEventListener('click', () => {
p.querySelectorAll('.gmx-tab').forEach(b=>b.classList.remove('active'));
p.querySelectorAll('.gmx-page').forEach(pg=>pg.style.display='none');
tabBtn.classList.add('active');
const id = tabBtn.dataset.tab;
config.activeTab = id;
saveConfig();
const page = p.querySelector(`.gmx-page[data-tab="${id}"]`);
if(page) page.style.display = 'block';
});
});
// attach handlers
attachExtraMenuHandlers(p);
// start state refresh
if (stateRefreshTimer) clearInterval(stateRefreshTimer);
stateRefreshTimer = setInterval(()=> refreshToggles(p), 800);
setTimeout(()=> refreshToggles(p), 60);
}
// ================= Keybinds Logic =================
const actionToButtonId = {
'follow': 'btn-follow',
'stay': 'btn-stay',
'randommill': 'btn-randommill',
'CursorFollow': 'cursorfollow',
'grinder': 'btn-grind',
// 'ap': 'btn-ap',
'bm': 'btn-bm',
'ba': 'btn-ba',
'accloop': 'btn-accloop',
'hatloop': 'btn-hatloop',
'bowspam': 'btn-bowspam',
'syncshot': 'btn-syncshot'
};
const formatKey = (key) => key.replace('Key', '').replace('Digit', '').replace('Arrow', '').replace('Numpad', 'N').replace('N', 'Numpad ').replace('Escape', 'ESC').replace('ShiftRight', 'R-Shift');
let isCapturingKey = false;
let currentKeybindActionId = null;
let currentKeybindButton = null;
function populateKeybinds(p) {
const list = p.querySelector('#keybinds-list');
if (!list) return;
list.innerHTML = '';
const allActions = {
'Follow': 'follow',
'Stay': 'stay',
'RandomMill': 'randommill',
'CursorFollow': 'cursorfollow',
'Grinder': 'grinder',
//'AutoPlatform': 'ap',
'AvoidSpike': 'bm',
'BreakAll': 'ba',
'AccLoop': 'accloop',
'Hatloop': 'hl',
'SyncHitSpam': 'bowspam',
'SyncHit': 'syncshot',
'Instakill': 'instakill',
'BowInsta': 'bowinsta'
};
Object.keys(allActions).forEach(actionName => {
const actionId = allActions[actionName];
const key = Object.keys(config.keybinds).find(k => config.keybinds[k] === actionId);
const keyDisplay = key ? formatKey(key) : 'Set';
const item = document.createElement('div');
item.className = 'gmx-list-item';
item.innerHTML = `
${actionName}
`;
list.appendChild(item);
});
list.querySelectorAll('.gmx-key-btn').forEach(btn => {
btn.addEventListener('click', (e) => startKeybindCapture(e, p));
});
}
function stopKeybindCapture(p, reason='cancelled') {
if (!isCapturingKey) return;
document.removeEventListener('keydown', captureKey, { capture: true });
if (currentKeybindButton) {
// Restore the original key or 'Set'
const currentKey = Object.keys(config.keybinds).find(k => config.keybinds[k] === currentKeybindActionId);
currentKeybindButton.textContent = currentKey ? formatKey(currentKey) : 'Set';
currentKeybindButton.classList.remove('active');
}
isCapturingKey = false;
currentKeybindActionId = null;
currentKeybindButton = null;
if (reason === 'reset') panelFeedback(p, 'Keybind cleared.');
else if (reason === 'cancelled') panelFeedback(p, 'Keybind setting cancelled.');
else if (reason === 'keybind_set') panelFeedback(p, 'Keybind set successfully.');
populateKeybinds(p);
}
function captureKey(e) {
if (!isCapturingKey) return;
e.preventDefault();
e.stopPropagation();
const p = document.getElementById(PANEL_ID);
const actionId = currentKeybindActionId;
const key = e.code;
// --- ESCAPE LOGIC (RESET) ---
if (key === 'Escape') {
const oldKey = Object.keys(config.keybinds).find(k => config.keybinds[k] === actionId);
if (oldKey) {
delete config.keybinds[oldKey];
}
saveConfig();
stopKeybindCapture(p, 'reset');
return;
}
// Block ShiftRight (menu open)
if (key === 'ShiftRight') {
panelFeedback(p, 'Cannot use R-Shift as keybind');
stopKeybindCapture(p, 'error');
return;
}
// Delete old keybind for this KEY
if (config.keybinds[key]) {
delete config.keybinds[key];
}
// Delete old keybind for this ACTION
const oldKey = Object.keys(config.keybinds).find(k => config.keybinds[k] === actionId);
if (oldKey) {
delete config.keybinds[oldKey];
}
// Set new keybind
config.keybinds[key] = actionId;
saveConfig();
stopKeybindCapture(p, 'keybind_set');
}
function startKeybindCapture(e, p) {
const btn = e.target;
const actionId = btn.dataset.action;
// Cancel if already capturing the same button
if (isCapturingKey) {
if (btn === currentKeybindButton) {
stopKeybindCapture(p, 'cancelled');
return;
}
stopKeybindCapture(p, 'cancelled');
}
isCapturingKey = true;
currentKeybindActionId = actionId;
currentKeybindButton = btn;
btn.textContent = 'Press Key...';
btn.classList.add('active');
panelFeedback(p, `Press a key for ${actionId.toUpperCase()} or ESC to clear.`);
// Remove previous listeners just in case
document.removeEventListener('keydown', captureKey, { capture: true });
// Add new listener
document.addEventListener('keydown', captureKey, { capture: true });
}
// ================= End Keybinds Logic =================
function makeDraggablePanel(panelElement) {
const header = panelElement.querySelector('.gmx-header');
if (!header) return;
let isDragging = false;
let offsetX = 0;
let offsetY = 0;
header.style.cursor = 'grab';
header.addEventListener('mousedown', (e) => {
isDragging = true;
header.style.cursor = 'grabbing';
const rect = panelElement.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
// Reset right/bottom for fixed position via top/left
panelElement.style.left = `${rect.left}px`;
panelElement.style.top = `${rect.top}px`;
panelElement.style.right = 'auto';
panelElement.style.bottom = 'auto';
panelElement.style.position = 'fixed';
panelElement.style.transition = 'none';
document.body.style.userSelect = 'none';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
panelElement.style.left = `${e.clientX - offsetX}px`;
panelElement.style.top = `${e.clientY - offsetY}px`;
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
header.style.cursor = 'grab';
document.body.style.userSelect = '';
// Save new position
config.position.top = panelElement.style.top;
config.position.left = panelElement.style.left;
saveConfig();
});
}
function panelFeedback(p, text, level='info'){
try {
const st = p.querySelector('#gmx-status-text');
if (!st) return;
st.textContent = text;
p.classList.add('show-status');
clearTimeout(p._fbTimeout);
p._fbTimeout = setTimeout(()=>{ p.classList.remove('show-status'); }, 2200);
} catch(e){ /* ignore */ }
}
function refreshToggles(p){
try {
// Note: If readOwnerCommanderField('all','mode') is undefined,
// the system defaults to 'stay' which is usually the game's default uncommanded state.
const mode = readOwnerCommanderField('all','mode') || 'stay';
const bs = !!readOwnerCommanderField('all','bowspam') && (!!readOwnerCommanderField('all','bowspam').active || readOwnerCommanderField('all','bowspam') === true);
const bm = !!readOwnerCommanderField('all','breakMode') && (!!readOwnerCommanderField('all','breakMode').active || readOwnerCommanderField('all','breakMode') === true);
const ba = !!readOwnerCommanderField('all','breakAllMode') && (!!readOwnerCommanderField('all','breakAllMode').active || readOwnerCommanderField('all','breakAllMode') === true);
const al = !!readOwnerCommanderField('all','accLoop');
const hl = !!readOwnerCommanderField('all','hatLoop');
const ac = !!readOwnerCommanderField('all','autochatEnabled');
const acl = !!readOwnerCommanderField('all','autoclanEnabled');
const hlMode = readOwnerCommanderField('all','hatLoopMode') || 'free';
const alMode = readOwnerCommanderField('all','accLoopMode') || 'all';
const hlSelect = p.querySelector('#hatloop-mode');
if(hlSelect) hlSelect.value = hlMode;
const alSelect = p.querySelector('#accloop-mode');
if(alSelect) alSelect.value = alMode;
const movementPage = p.querySelector('.gmx-page[data-tab="movement"]');
if (movementPage && movementPage.style.display === 'block') {
if (typeof populatePlayerList === 'function') {
populatePlayerList(p);
}
}
// Update toggle buttons
toggleBtnActive(p.querySelector('#btn-ap'), ap);
toggleBtnActive(p.querySelector('#btn-bowspam'), bs);
toggleBtnActive(p.querySelector('#btn-bm'), bm);
toggleBtnActive(p.querySelector('#btn-ba'), ba);
toggleBtnActive(p.querySelector('#btn-hatloop'), hl);
toggleBtnActive(p.querySelector('#btn-accloop'), al);
toggleBtnActive(p.querySelector('#btn-ac'), ac);
toggleBtnActive(p.querySelector('#btn-acl'), acl);
// Update mode buttons
p.querySelectorAll('.gmx-btn').forEach(btn => {
const id = btn.id;
let isActive = false;
if (id === 'btn-follow' && mode === 'follow') isActive = true;
else if (id === 'btn-stay' && mode === 'stay') isActive = true;
else if (id === 'btn-randommill' && mode === 'randommill') isActive = true;
else if (id === 'btn-grind' && mode === 'grinder') isActive = true;
else if (id === 'btn-cursorfollow' && mode === 'cursorFollow') isActive = true;
else if (id === 'btn-circle' && mode === 'circle') isActive = true;
else if (id === 'btn-cs' && mode === 'circlespin') isActive = true;
else if (id === 'btn-square' && mode === 'square') isActive = true;
else if (id === 'btn-ssq' && mode === 'squarespin') isActive = true;
else if (id === 'btn-playerfollow' && mode === 'playerFollow') isActive = true;
// If it's a toggle button (already has .active class), don't touch data-mode-active
if (!btn.classList.contains('active')) {
btn.setAttribute('data-mode-active', isActive);
}
});
} catch(e){ /* ignore */ }
}
function toggleBtnActive(btn, on){
if(!btn) return;
if(on) btn.classList.add('active'); else btn.classList.remove('active');
}
function executeAction(actionId, p) {
const btn = p.querySelector(`#btn-${actionId.toLowerCase()}`);
if (btn) {
btn.click(); // Simulate button click for toggles
panelFeedback(p, `${actionId.toUpperCase()} activated via hotkey`);
return;
}
// Direct command for modes (if button simulation fails or isn't needed)
switch (actionId) {
case 'follow':
setOwnerCommanderField('all','mode','follow');
break;
case 'bowinsta': // <<< ДОБАВЛЕННЫЙ CASE
if (window.bowInsta) {
// Вызываем trigger(), который теперь сам запускает postTick
window.bowInsta.trigger();
panelFeedback(p, 'BowInsta combo activated!');
} else {
panelFeedback(p, 'Error: BowInsta module not found.', 'error');
}
break;
case 'stay':
setOwnerCommanderField('all','mode','stay');
break;
case 'randommill':
setOwnerCommanderField('all','mode','randommill');
break;
case 'cursorfollow':
setOwnerCommanderField('all','mode','cursorFollow');
break;
case 'grinder':
const t = p.querySelector('#grind-target')?.value || 'wood';
// Читаем лимит из UI или используем 500
const th = safeNum(p.querySelector('#grind-threshold')?.value, 500);
setOwnerCommanderField('all','mode','grinder');
setOwnerCommanderField('all','grinder',{ threshold: th, target: t });
break;
case 'syncshot':
setOwnerCommanderField('all','syncShot',{ fireAt: Date.now()+300 });
break;
case 'instakill': // <<< НОВЫЙ CASE
if (window.instakill) {
// Toggle: enable if disabled, disable if enabled
try {
if (window.instakill._enabled) {
window.instakill.disable();
panelFeedback(p, 'InstaKill deactivated!');
} else {
window.instakill.enable();
panelFeedback(p, 'InstaKill activated!');
}
} catch (e) {
panelFeedback(p, 'Error toggling InstaKill: ' + (e && e.message), 'error');
}
} else {
panelFeedback(p, 'Error: InstaKill module not found (window.instakill is undefined).', 'error');
}
break;
default:
return;
}
refreshToggles(p);
}
function populatePlayerList(p) {
const select = p.querySelector('#player-follow-target');
// Проверка доступности критических объектов
if (!select || typeof myClient === 'undefined' || !myClient.PlayerManager || !myClient.myPlayer) {
if (select) select.innerHTML = '';
return;
}
// 1. Сохраняем текущее выбранное значение
const currentSelectedId = select.value;
// 2. Очищаем список (сохраняя placeholder)
select.innerHTML = '';
const playersMap = new Map();
// 3. Заполняем Map видимыми игроками (используем логику из listPlayers)
myClient.PlayerManager.playerData.forEach(player => {
// Исключаем самого себя, проверяем ник и ID
if (player && player.nickname && player.id !== myClient.myPlayer.id) {
playersMap.set(player.nickname, player.id);
}
});
const sortedNicknames = Array.from(playersMap.keys()).sort((a, b) => a.localeCompare(b));
// 4. Добавляем игроков в