// ==UserScript==
// @name Sploop.io Socket API
// @author Murka
// @description Provides a basic understanding of how sploop handles websockets, lets connect a bot via console
// @icon https://sploop.io/img/ui/favicon.png
// @version 1.0
// @match *://sploop.io/
// @run-at document-start
// @grant none
// @license MIT
// @namespace https://greasyfork.org/users/919633
// ==/UserScript==
/* jshint esversion:6 */
/*
Author: Murka
Github: https://github.com/Murka007
Greasyfork: https://greasyfork.org/users/919633
Discord: https://discord.gg/cPRFdcZkeD
Connect a bot via console using `SocketAPI.createClient();`
*/
(function() {
"use strict";
const log = console.log;
localStorage.removeItem("_adIds");
const getAngle = (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1);
const hashing = {
9303: function(t, n) {
t = [t[0] >>> 16, 65535 & t[0], t[1] >>> 16, 65535 & t[1]];
n = [n[0] >>> 16, 65535 & n[0], n[1] >>> 16, 65535 & n[1]];
const i = [0, 0, 0, 0];
i[3] += t[3] + n[3];
i[2] += i[3] >>> 16;
i[3] &= 65535;
i[2] += t[2] + n[2];
i[1] += i[2] >>> 16;
i[2] &= 65535;
i[1] += t[1] + n[1];
i[0] += i[1] >>> 16;
i[1] &= 65535;
i[0] += t[0] + n[0];
i[0] &= 65535;
return [i[0] << 16 | i[1], i[2] << 16 | i[3]];
},
3235: function(t, n) {
t = [t[0] >>> 16, 65535 & t[0], t[1] >>> 16, 65535 & t[1]];
n = [n[0] >>> 16, 65535 & n[0], n[1] >>> 16, 65535 & n[1]];
const i = [0, 0, 0, 0];
i[3] += t[3] * n[3];
i[2] += i[3] >>> 16;
i[3] &= 65535;
i[2] += t[2] * n[3];
i[1] += i[2] >>> 16;
i[2] &= 65535;
i[2] += t[3] * n[2];
i[1] += i[2] >>> 16;
i[2] &= 65535;
i[1] += t[1] * n[3];
i[0] += i[1] >>> 16;
i[1] &= 65535;
i[1] += t[2] * n[2];
i[0] += i[1] >>> 16;
i[1] &= 65535;
i[1] += t[3] * n[1];
i[0] += i[1] >>> 16;
i[1] &= 65535;
i[0] += t[0] * n[3] + t[1] * n[2] + t[2] * n[1] + t[3] * n[0];
i[0] &= 65535
return [i[0] << 16 | i[1], i[2] << 16 | i[3]];
},
9869: function(t, n) {
n %= 64;
if (32 == n) return [t[1], t[0]];
if (n < 32) return [t[0] << n | t[1] >>> 32 - n, t[1] << n | t[0] >>> 32 - n];
n -= 32;
return [t[1] << n | t[0] >>> 32 - n, t[0] << n | t[1] >>> 32 - n];
},
1318: function(t, n) {
n %= 64;
if (0 == n) return t;
if (n < 32) return [t[0] << n | t[1] >>> 32 - n, t[1] << n];
return [t[1] << n - 32, 0];
},
6217: function(t, n) {
return [t[0] ^ n[0], t[1] ^ n[1]];
},
1552: function(t) {
const o = hashing[3235];
const e = hashing[6217];
t = e(t, [0, t[0] >>> 1]);
t = o(t, [4283543511, 3981806797]);
t = e(t, [0, t[0] >>> 1]);
t = o(t, [3301882366, 444984403]);
return e(t, [0, t[0] >>> 1]);
},
6112: function(t, i) {
const e = hashing[9303];
const r = hashing[3235];
const c = hashing[9869];
const a = hashing[1318];
const s = hashing[6217];
const h = hashing[1552];
i = i || 0;
const u = (t = t || "").length % 16;
const f = t.length - u;
let l = [0, i];
let d = [0, i];
let g = [0, 0];
let w = [0, 0];
const b = [2277735313, 289559509];
const M = [1291169091, 658871167];
let v;
for (v = 0; v < f; v += 16) {
g = [255 & t.charCodeAt(v + 4) | (255 & t.charCodeAt(v + 5)) << 8 | (255 & t.charCodeAt(v + 6)) << 16 | (255 & t.charCodeAt(v + 7)) << 24, 255 & t.charCodeAt(v) | (255 & t.charCodeAt(v + 1)) << 8 | (255 & t.charCodeAt(v + 2)) << 16 | (255 & t.charCodeAt(v + 3)) << 24];
w = [255 & t.charCodeAt(v + 12) | (255 & t.charCodeAt(v + 13)) << 8 | (255 & t.charCodeAt(v + 14)) << 16 | (255 & t.charCodeAt(v + 15)) << 24, 255 & t.charCodeAt(v + 8) | (255 & t.charCodeAt(v + 9)) << 8 | (255 & t.charCodeAt(v + 10)) << 16 | (255 & t.charCodeAt(v + 11)) << 24];
g = r(g, b); g = c(g, 31); g = r(g, M);
l = s(l, g); l = c(l, 27); l = e(l, d);
l = e(r(l, [0, 5]), [0, 1390208809]); w = r(w, M); w = c(w, 33); w = r(w, b);
d = s(d, w); d = c(d, 31); d = e(d, l); d = e(r(d, [0, 5]), [0, 944331445]);
}
g = [0, 0];
w = [0, 0];
switch (u) {
case 15:
w = s(w, a([0, t.charCodeAt(v + 14)], 48));
case 14:
w = s(w, a([0, t.charCodeAt(v + 13)], 40));
case 13:
w = s(w, a([0, t.charCodeAt(v + 12)], 32));
case 12:
w = s(w, a([0, t.charCodeAt(v + 11)], 24));
case 11:
w = s(w, a([0, t.charCodeAt(v + 10)], 16));
case 10:
w = s(w, a([0, t.charCodeAt(v + 9)], 8));
case 9:
w = s(w, [0, t.charCodeAt(v + 8)]), w = r(w, M), w = c(w, 33), w = r(w, b), d = s(d, w);
case 8:
g = s(g, a([0, t.charCodeAt(v + 7)], 56));
case 7:
g = s(g, a([0, t.charCodeAt(v + 6)], 48));
case 6:
g = s(g, a([0, t.charCodeAt(v + 5)], 40));
case 5:
g = s(g, a([0, t.charCodeAt(v + 4)], 32));
case 4:
g = s(g, a([0, t.charCodeAt(v + 3)], 24));
case 3:
g = s(g, a([0, t.charCodeAt(v + 2)], 16));
case 2:
g = s(g, a([0, t.charCodeAt(v + 1)], 8));
case 1:
g = s(g, [0, t.charCodeAt(v)]), g = r(g, b), g = c(g, 31), g = r(g, M), l = s(l, g);
}
l = s(l, [0, t.length]);
d = s(d, [0, t.length]);
l = e(l, d); d = e(d, l);
l = h(l); d = h(d);
l = e(l, d); d = e(d, l);
return ("00000000" + (l[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (l[1] >>> 0).toString(16)).slice(-8) + ("00000000" + (d[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (d[1] >>> 0).toString(16)).slice(-8);
},
3028: function(t, n, i, o) {
return function() {
let e = (t >>>= 0) + (n >>>= 0) | 0;
t = n ^ n >>> 9;
n = (i >>>= 0) + (i << 3) | 0;
i = (i = i << 21 | i >>> 11) + (e = e + (o = 1 + (o >>>= 0) | 0) | 0) | 0;
return (e >>> 0) / 4294967296;
}
},
9623: function(t) {
for (var o = 0, e = 1779033703 ^ t.length; o < t.length; o++) {
e = (e = Math.imul(e ^ t.charCodeAt(o), 3432918353)) << 13 | e >>> 19;
}
return function() {
e = Math.imul(e ^ e >>> 16, 2246822507);
e = Math.imul(e ^ e >>> 13, 3266489909);
return (e ^= e >>> 16) >>> 0;
}
},
1122: function(t) {
const o = hashing[3028];
const e = hashing[9623];
t = e(t || "");
return o(t(), t(), t(), t());
},
9629: function(t, n) {
const o = hashing[6112];
const e = hashing[1122];
const i = o("" + t, "" + n);
const r = e(i);
return [~~(246 * r()), ~~(255 * r()), ~~(255 * r()), ~~(255 * r())];
}
}
class Vector {
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
static fromAngle(angle, length = 1) {
return new Vector(Math.cos(angle) * length, Math.sin(angle) * length);
}
add(vec) {
if (vec instanceof Vector) {
this.x += vec.x;
this.y += vec.y;
} else {
this.x += vec;
this.y += vec;
}
return this;
}
sub(vec) {
if (vec instanceof Vector) {
this.x -= vec.x;
this.y -= vec.y;
} else {
this.x -= vec;
this.y -= vec;
}
return this;
}
mult(vec) {
if (vec instanceof Vector) {
this.x *= vec.x;
this.y *= vec.y;
} else {
this.x *= vec;
this.y *= vec;
}
return this;
}
div(vec) {
if (vec instanceof Vector) {
this.x /= vec.x;
this.y /= vec.y;
} else {
this.x /= vec;
this.y /= vec;
}
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(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.fromAngle(angle, length));
}
isEqual(vec) {
return this.x === vec.x && this.y === vec.y;
}
stringify() {
return this.x + ":" + this.y;
}
min(vec) {
this.x = Math.min(this.x, vec.x);
this.y = Math.min(this.y, vec.y);
return this;
}
max(vec) {
this.x = Math.max(this.x, vec.x);
this.y = Math.max(this.y, vec.y);
return this;
}
trunc() {
this.x = Math.trunc(this.x);
this.y = Math.trunc(this.y);
return this;
}
}
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 InputHandler = new class InputHandler {
mouse = new Vector;
innerSize = new Vector;
angle = 0;
canvas = {};
startMouse = false;
attach() {
const canvas = document.querySelector("#game-canvas");
this.canvas.mousedown = canvas.onmousedown;
this.canvas.mouseup = canvas.onmouseup;
canvas.onmousedown = null
canvas.onmouseup = null;
window.addEventListener("mousemove", event => {
this.mouse.setXY(event.clientX, event.clientY);
this.angle = getAngle(innerWidth / 2, innerHeight / 2, this.mouse.x, this.mouse.y);
});
canvas.addEventListener("mousedown", event => this.mousedown(event));
window.addEventListener("mouseup", event => this.mouseup(event));
const resize = () => {
const w = window.innerWidth;
const h = window.innerHeight;
this.mouse.x *= w / this.innerSize.x;
this.mouse.y *= h / this.innerSize.y;
this.innerSize.setXY(w, h);
}
window.addEventListener("resize", resize);
resize();
}
cursorPosition(target) {
const w = window.innerWidth;
const h = window.innerHeight;
const scale = Math.max(w / 1824, h / 1026);
const cursorX = (this.mouse.x - w / 2) / scale;
const cursorY = (this.mouse.y - h / 2) / scale;
return new Vector(target.x + cursorX, target.y + cursorY);
}
mousedown(event) {
const { ModuleHandler, myPlayer, clients } = playerClient;
const button = formatButton(event.button);
if (button === "LBTN" && !this.startMouse) {
this.startMouse = true;
this.canvas.mousedown(event);
/*ModuleHandler.attacking = 1;
const pos = this.cursorPosition(myPlayer.position);
for (const client of clients) {
const angle = client.myPlayer.position.angle(pos);
client.PacketHandler.attack(angle);
}*/
}
}
mouseup(event) {
const { ModuleHandler, clients } = playerClient;
const button = formatButton(event.button);
if (button === "LBTN" && this.startMouse) {
this.startMouse = false;
this.canvas.mouseup(event);
/*if (ModuleHandler.attacking !== 0) {
for (const client of clients) {
client.PacketHandler.stopAttack();
}
}*/
}
}
}
window.addEventListener("load", () => {
InputHandler.attach();
});
const HookedArrays = {
joins: null,
}
class TempData {
constructor(client) {
this.client = client;
}
setAttacking(attacking) {
/*const { ModuleHandler } = this.client;
if (ModuleHandler.attacking === attacking) {
return;
}
ModuleHandler.attacking = attacking;
if (0 !== attacking) {
ModuleHandler.attackingState = attacking;
}*/
}
}
class ClanJoiner {
constructor(client) {
this.client = client;
this.joinCount = 0;
}
postTick() {
const { myPlayer, owner, PacketHandler } = this.client;
const ownerClan = owner.myPlayer.clan;
if (ownerClan === myPlayer.clan) return;
if (this.joinCount === 0) {
if (myPlayer.clan === 0) {
owner.pendingJoins.add(myPlayer.globalID);
PacketHandler.joinClan(ownerClan);
} else {
PacketHandler.leaveClan();
}
}
this.joinCount = (this.joinCount + 1) % 10;
}
}
class Movement {
constructor(client) {
this.client = client;
}
postTick() {
const { myPlayer, owner, ModuleHandler } = this.client;
const pos1 = this.client.myPlayer.position;
const pos2 = InputHandler.cursorPosition(this.client.owner.myPlayer.position);
const distance = pos1.distance(pos2);
const angle = pos1.angle(pos2);
const currentAngle = distance > 0 ? angle: null;
ModuleHandler.targetAngle = angle;
ModuleHandler.moveByAngle(currentAngle);
}
}
class AutoAccept {
constructor(client) {
this.client = client;
}
postTick() {
const { myPlayer, PlayerManager, owner, pendingJoins, PacketHandler } = this.client;
if (!PlayerManager.isClanOwner(myPlayer) || myPlayer.joinRequests.length === 0) return;
const id = myPlayer.joinRequests[0];
if (pendingJoins.size !== 0) {
PacketHandler.acceptDecline(pendingJoins.has(id));
this.client.removeJoin(id);
}
}
}
class AutoHeal {
constructor(client) {
this.client = client;
}
postTick() {
if (this.client.myPlayer.health === 100) return;
setTimeout(() => {
this.client.ModuleHandler.heal();
}, 120);
}
}
class UpdateAngle {
constructor(client) {
this.client = client;
}
/*getAngle() {
if (this.client.isOwner) return InputHandler._angle;
return null;
}*/
postTick() {
const { ModuleHandler } = this.client;
const angle = ModuleHandler.targetAngle;
if (angle !== null) {
ModuleHandler.updateAngle(angle);
}
}
}
class ModuleHandler {
constructor(client) {
this.client = client;
this.reset();
this.statisModules = {
tempData: new TempData(client),
};
this.botModules = [
new ClanJoiner(client),
new Movement(client),
];
this.modules = [
new AutoAccept(client),
new AutoHeal(client),
new UpdateAngle(client),
];
this.rotation = true;
this.mouse = {
x: 0,
y: 0,
lockX: 0,
lockY: 0,
angle: 0,
_angle: 0,
}
}
reset() {
this.weapon = 0;
this.moveAngle = null;
this.targetAngle = null;
this.currentAngle = 0;
this.attacking = 0;
this.attackingState = 0;
}
attachMouse() {
window.addEventListener("mousemove", 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;
}
});
}
moveByAngle(angle) {
if (angle === this.moveAngle) return;
this.moveAngle = angle;
const { PacketHandler } = this.client;
if (angle === null) {
PacketHandler.stopMovement();
} else {
PacketHandler.moveByAngle(angle);
}
}
updateAngle(angle) {
if (angle === this.currentAngle) return;
this.currentAngle = angle;
const { PacketHandler } = this.client;
PacketHandler.updateAngle(angle);
}
whichWeapon(type = this.weapon) {
if (this.weapon === type) return;
this.weapon = type;
this.client.PacketHandler.selectItemByType(type);
}
placeItemByType(type) {
this.client.PacketHandler.selectItemByType(type);
this.client.PacketHandler.attack(this.mouse.angle);
this.client.PacketHandler.stopAttack();
this.whichWeapon();
}
heal() {
this.placeItemByType(2);
}
postTick() {
if (!this.client.isOwner) {
for (const botModule of this.botModules) {
botModule.postTick();
}
}
for (const module of this.modules) {
module.postTick();
}
this.attackingState = this.attacking;
}
}
class Player {
type = 0;
globalID = 0;
id = 0;
position = new Vector;
angle = 0;
health = 100;
clan = 0;
constructor(client) {
this.client = client;
}
update(type, globalID, id, x, y, angle, health, clanID) {
this.type = type;
this.globalID = globalID;
this.id = id;
this.position.setXY(x, y);
this.angle = angle;
this.health = health;
this.clan = clanID;
}
}
class ClientPlayer extends Player {
constructor(client) {
super(client);
this.joinRequests = [];
this.upgradeList = [];
this.reset();
}
reset() {
this.inGame = false;
this.upgradeList.length = 0;
this.upgradeIndex = 0;
this.upgradeAge = 0;
this.prevAge = 0;
this.age = 0;
this.food = 0;
this.wood = 0;
this.stone = 0;
this.gold = 0;
}
handleUpgrade(items) {
if (this.client.isOwner) return;
const { upgradeList } = this.client.owner.myPlayer;
const currentItem = upgradeList[this.upgradeIndex];
if (upgradeList.includes(currentItem)) {
this.upgradeItem(currentItem);
}
}
upgradeItem(id) {
this.upgradeAge += 1;
if (this.client.isOwner) {
this.upgradeList.push(id);
for (const client of this.client.clients) {
const { age, upgradeAge, upgradeIndex } = client.myPlayer;
if (age > upgradeAge) {
const item = this.upgradeList[upgradeIndex];
client.myPlayer.upgradeItem(item);
}
}
return;
}
this.client.PacketHandler.upgradeItem(id);
this.upgradeIndex += 1;
}
updateAge() {
//log(this.age, this.upgradeAge);
}
handleStats(age, food, wood, stone, gold) {
this.food = food;
this.wood = wood;
this.stone = stone;
this.gold = gold;
if (age === 0) return;
this.prevAge = this.age;
this.age = age;
if (this.age > this.prevAge) {
this.updateAge();
}
}
postTick() {
if (this.inGame) {
this.client.ModuleHandler.postTick();
}
}
}
class PlayerManager {
playerData = new Map();
clanData = new Map();
constructor(client) {
this.client = client;
}
isClanOwner(player) {
const clan = this.clanData.get(player.clan);
return clan && clan.owner === player.globalID;
}
getPlayer(id) {
if (this.playerData.has(id)) {
return this.playerData.get(id);
}
const player = new Player();
this.playerData.set(id, player);
return player;
}
createClan(id, owner, name) {
const clanData = this.clanData;
if (!clanData.has(id)) {
clanData.set(id, {});
}
const clan = clanData.get(id);
clan.id = id;
clan.owner = owner;
clan.name = name;
}
}
const random = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const PI = Math.PI;
const PI2 = PI * 2;
const formatAge = (age) => Math.floor(Math.log(1 + Math.max(0, age)) ** 2.4 / 13);
const formatAngle = (byte) => byte / 255 * PI2 - PI;
const encodeAngle = (angle) => {
angle = 65535 * (angle + PI) / PI2;
return [255 & angle, angle >> 8 & 255];
}
const getRandomString = (len, numbers) => {
let output = "";
const nums = "0123456789";
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const text = numbers ? nums : alphabet;
for (let i=0;i<len;i++) {
const index = Math.floor(Math.random() * (text.length - 1));
const char = text[index];
output += char;
}
return output;
}
const StoreState = {
BUY: 0,
EQUIP: 1,
UNEQUIP: 2,
}
const EntityState = {
UPDATE: 0,
Nr: 1,
DELETE: 2,
IN_WATER: 4,
UNDER_ROOF: 8,
Vr: 16,
TRAPPED: 32,
ON_PLATFORM: 64,
Yr: 128
}
const SocketServer = {
UPDATE_MOVEMENT: 20,
MY_PLAYER_INIT: 35,
CONNECT: 25,
Xn: 26, // not used in the code
$n: 9, // most likely old player init, with toggle mechanism or death marker, [byte, byte, byte, byte]
UPDATE_INVENTORY: 2,
PLAYER_SPAWNED: 32,
ATTACK_ANIMATION: 29,
LEADERBOARD: 3,
DEFAULT_DATA: 33,
ri: 31, // most likely unvalid shit, used in rendering, some boss or icon shit
DAMAGE: 6,
KILL_UPDATE: 22,
DIED: 19,
PLAYER_RESOURCES: 8,
KILL_MESSAGE: 28,
ch: 13, // somehow related to clans
UPDATE_STORE: 5,
TEXT_MESSAGE: 30,
UPGRADE: 14,
CLEAR_UPGRADE: 34, // some init, reset upgrade bar
wi: 11, // not used in the code
PING_REQUEST: 0,
PING_RESPONSE: 15,
CLAN_JOIN: 24,
CLAN_LEAVE: 27,
CLAN_CREATED: 4,
CLAN_DELETED: 1,
JOIN_REQUEST: 17,
CLAN_UPDATED: 16,
ITEM_COUNTER: 36,
MINIMAP_DATA: 10,
BOSS_SPAWNED: 37,
BOSS_KILLED: 21,
cy: 23, // some shit with text rendering
HANDSHAKE: 12, // some hash shit
Ui: 7, // some server event or boss shit
CLAN_MESSAGE: 18
};
const SocketClient = {
VERIFICATION: 11,
MOVE_BITMASK: 6,
UPDATE_ANGLE: 13,
SELECT_BY_ID: 2,
START_ATTACK: 19,
STOP_ATTACK: 18,
LOGIN: 10,
UPGRADE_SCYTHE: 20,
SELECT_BY_TYPE: 0,
SWITCH_HAT: 5,
CHAT: 7,
UPGRADE_ITEM: 14,
Ri: 12, // not used in the code
PING_CALLBACK: 3,
TOGGLE_AUTOATTACK: 23,
MOVE_ANGLE: 1,
STOP_MOVEMENT: 15,
touchAttackStart: 9,
Wi: 4, // not used in the code
touchAttackStop: 8, // some shit with angle
LEAVE_CLAN: 24,
JOIN_CLAN: 21,
ACCEPT_DECLINE: 17,
KICK: 25,
CREATE_CLAN: 22,
io: 16, // not used in the code
}
class PacketHandler {
constructor(client) {
this.client = client;
}
encoder = new TextEncoder();
sendData(data) {
const socket = this.client.SocketManager.socket;
if (socket !== null && socket.readyState === 1) {
socket.send(data, true);
}
}
sendString(data) {
this.sendData(JSON.stringify(data));
}
send(...bytes) {
this.sendData(new Uint8Array(bytes));
}
moveByBitmask(bitmask) {
this.send(SocketClient.MOVE_BITMASK, bitmask);
}
updateAngle(angle) {
this.send(SocketClient.UPDATE_ANGLE, ...encodeAngle(angle));
}
selectByID(itemID) {
this.send(SocketClient.SELECT_BY_ID, itemID);
}
attack(angle) {
this.send(SocketClient.START_ATTACK, ...encodeAngle(angle));
}
stopAttack() {
this.send(SocketClient.STOP_ATTACK);
}
login(nickname = "", skin = "0", accessory = "0", back = "0", email = "", token = "") {
this.sendString([SocketClient.LOGIN, nickname, skin + "", "FFFFFEEEEGGBBBAAA", accessory + "", email, token, back + ""]);
}
upgradeScythe(goldenCowID) {
this.send(SocketClient.UPGRADE_SCYTHE, 255 & goldenCowID, goldenCowID >> 8);
}
selectItemByType(type) {
this.send(SocketClient.SELECT_BY_TYPE, type);
}
switchHat(hat) {
this.send(SocketClient.SWITCH_HAT, hat);
}
chat(message) {
const bytes = this.encoder.encode(message);
this.send(SocketClient.CHAT, ...bytes);
}
upgradeItem(id) {
this.send(SocketClient.UPGRADE_ITEM, id);
}
pingCallback(value) {
this.send(SocketClient.PING_CALLBACK, value);
}
autoAttack(state) {
this.send(SocketClient.TOGGLE_AUTOATTACK, Number(state));
}
moveByAngle(angle) {
this.send(SocketClient.MOVE_ANGLE, ...encodeAngle(angle));
}
stopMovement() {
this.send(SocketClient.STOP_MOVEMENT);
}
touchAttackStart(angle) {
this.send(SocketClient.touchAttackStart, ...encodeAngle(angle));
}
touchAttackStop(angle) {
this.send(SocketClient.touchAttackStop, ...encodeAngle(angle));
}
leaveClan() {
this.send(SocketClient.LEAVE_CLAN);
}
joinClan(id) {
this.send(SocketClient.JOIN_CLAN, id);
}
acceptDecline(state) {
this.send(SocketClient.ACCEPT_DECLINE, Number(state));
}
kick(id) {
this.send(SocketClient.KICK, id);
}
createClan(name) {
const bytes = this.encoder.encode(name);
this.send(SocketClient.CREATE_CLAN, ...bytes);
}
}
class SocketManager {
constructor(client) {
this.client = client;
this.socket = null;
this.decoder = new TextDecoder();
}
init(socket) {
this.socket = socket;
socket.addEventListener("message", event => {
const data = event.data;
if (typeof data === "string") {
this.handleStringMessage(JSON.parse(data));
} else {
this.handleByteMessage(new Uint8Array(data));
}
});
const that = this;
const _send = socket.send;
socket.send = function(data, ignore) {
if (!ignore && typeof data !== "string" && that.client.isOwner) {
const { myPlayer, clients } = that.client;
switch (data[0]) {
case SocketClient.START_ATTACK: {
const pos = InputHandler.cursorPosition(myPlayer.position);
for (const client of clients) {
const angle = client.myPlayer.position.angle(pos);
client.PacketHandler.attack(angle);
}
break;
}
case SocketClient.STOP_ATTACK: {
for (const client of clients) {
client.PacketHandler.stopAttack();
}
break;
}
case SocketClient.UPGRADE_ITEM: {
const item = data[1];
that.client.myPlayer.upgradeItem(item);
break;
}
}
}
return _send.call(this, data);
}
}
selfMessage(data) {
this.socket.onmessage({ data });
}
handleStringMessage(data) {
const { myPlayer, PlayerManager, PacketHandler } = this.client;
switch (data[0]) {
case SocketServer.MY_PLAYER_INIT: {
const id = data[1];
const nickname = data[2];
// const ageXP = data[3];
const inventory = data[4];
const resources = data[5];
// const hatData = data[6];
// const someEvent = data[7];
//myPlayer.id = id;
//myPlayer.nickname = nickname;
//myPlayer.inGame = true;
const { myPlayer } = this.client;
myPlayer.id = id;
myPlayer.inGame = true;
//log("MY_PLAYER_INIT", id, nickname);
break;
}
case SocketServer.PLAYER_SPAWNED: {
const globalID = data[1];
const nickname = data[2];
//log("PLAYER_SPAWNED", globalID, nickname);
break;
}
case SocketServer.LEADERBOARD: {
for (const [id, score] of data[1]) {
}
break;
}
case SocketServer.DEFAULT_DATA: {
const globalID = data[1];
// const serverSize = data[2];
const players = data[3]; // [globalID, nickname, id]
const clanData = data[4]; // [clanID, ownerID, clanName]
for (const [id, owner, name] of clanData) {
PlayerManager.createClan(id, owner, name);
}
// const someState = data[5];
myPlayer.globalID = globalID;
PlayerManager.playerData.set(globalID, myPlayer);
log("DEFAULT_DATA", globalID, data);
if (!this.client.isOwner) {
this.client.connection();
}
break;
}
case SocketServer.DAMAGE: {
const damage = data[1];
const id = data[2];
const isHeal = data[3];
//log("DAMAGE", damage, id, isHeal);
break;
}
case SocketServer.KILL_UPDATE: {
const [ kills, nuggets ] = data[1];
//log("KILL_UPDATE", kills, nuggets);
break;
}
case SocketServer.DIED: {
myPlayer.reset();
if (!this.client.isOwner) {
PacketHandler.login("Murka");
}
//log("DIED");
break;
}
case SocketServer.KILL_MESSAGE: {
const text = data[1]; // Killed (nickname)
//log(text);
break;
}
case SocketServer.UPGRADE: {
const items = data[1];
/*for (const item of items) {
}*/
myPlayer.handleUpgrade(items);
break;
}
case SocketServer.HANDSHAKE: {
const someHash = data[1];
if (!this.client.isOwner) {
PacketHandler.sendData("");//window.solve(0));
}
break;
}
default: {
log("default data", data);
break;
}
}
}
handleByteMessage(buffer) {
const { myPlayer, PlayerManager, PacketHandler } = this.client;
switch (buffer[0]) {
case SocketServer.UPDATE_MOVEMENT: {
for (let i=1;i<buffer.length;i+=19) {
const type = buffer[i];
const globalID = buffer[i + 1];
const id = buffer[i + 2] | buffer[i + 3] << 8;
const x = buffer[i + 4] | buffer[i + 5] << 8;
const y = buffer[i + 6] | buffer[i + 7] << 8;
const state = buffer[i + 8];
const angle = formatAngle(buffer[i + 9]);
const item = buffer[i + 10];
const hat = buffer[i + 11];
const clanID = buffer[i + 12];
const health = buffer[i + 13];
const skin = buffer[i + 14];
const accessory = buffer[i + 15];
const rank = buffer[i + 16];
const back = buffer[i + 17];
const a9 = buffer[i + 18];
if (type === 0) {
const player = PlayerManager.getPlayer(globalID);
player.update(type, globalID, id, x, y, angle, health / 255 * 100, clanID);
}
}
myPlayer.postTick();
break;
}
case SocketServer.UPDATE_INVENTORY: {
for (let i=1;i<buffer.length;i++) {
const id = buffer[i];
}
break;
}
case SocketServer.CONNECT: { // executed only once
const num = buffer[1];
// Necessary handshakre, otherwise the game wont let to connect
if (!this.client.isOwner) {
const bytes = hashing[9629](num, getRandomString(16));
const tokenBytes = new Array(24).fill(0);
PacketHandler.send(SocketClient.VERIFICATION, num, ...bytes, ...tokenBytes);
}
break;
}
case SocketServer.ATTACK_ANIMATION: {
for (let i=1;i<buffer.length;i+=5) {
const type = buffer[i];
const id = buffer[i + 1] | buffer[i + 2] << 8;
const isObject = buffer[3];
const weapon = buffer[4];
//log(type, id, isObject, weapon);
}
break;
}
case SocketServer.PLAYER_RESOURCES: {
const b = buffer;
const age = formatAge(b[1] | b[2] << 8 | b[3] << 16 | b[4] << 24);
const food = b[5] | b[6] << 8 | b[7] << 16 | b[8] << 24;
const wood = b[9] | b[10] << 8 | b[11] << 16 | b[12] << 24;
const stone = b[13] | b[14] << 8 | b[15] << 16 | b[16] << 24;
const gold = b[17] | b[18] << 8 | b[19] << 16 | b[20] << 24;
myPlayer.handleStats(age, food, wood, stone, gold);
break;
}
case SocketServer.UPDATE_STORE: {
for (let i=1;i<buffer.length;i+=2) {
const hatID = buffer[i];
const state = buffer[i + 1];
//log(hatID, state);
}
break;
}
case SocketServer.TEXT_MESSAGE: {
const id = buffer[1] | buffer[2] << 8;
const text = this.decoder.decode(buffer.subarray(3));
//log(id, text);
break;
}
case SocketServer.CLEAR_UPGRADE: {
//log("clear upgrade bar");
break;
}
case SocketServer.PING_REQUEST: {
const value = buffer[1];
//log("PING_REQUEST", value);
break;
}
case SocketServer.PING_RESPONSE: {
const ping = buffer[1] | buffer[2] << 8;
//myPlayer.ping = ping;
//log("PING_RESPONSE", ping);
break;
}
case SocketServer.CLAN_JOIN: {
const clanID = buffer[1];
const isOwner = buffer[2];
/*myPlayer.clan.id = clanID;
myPlayer.clan.isOwner = isOwner;
myPlayer.clan.name = clanData.get(clanID).name;
for (let i=3;i<buffer.length;i++) {
const id = buffer[i];
if (id !== myPlayer.id) {
myPlayer.clan.members.push(id);
}
}*/
//log("CLAN_JOIN", clanID, isOwner, buffer.slice(3));
break;
}
case SocketServer.CLAN_LEAVE: {
/*myPlayer.clan.id = 0;
myPlayer.clan.isOwner = 0;
myPlayer.clan.members.length = 0;
myPlayer.clan.name = "";*/
break;
}
case SocketServer.CLAN_CREATED: {
const clanID = buffer[1];
const ownerID = buffer[2];
const clanName = this.decoder.decode(buffer.subarray(3));
PlayerManager.createClan(clanID, ownerID, clanName);
//log("CLAN_CREATED", { clanID, ownerID, clanName });
break;
}
case SocketServer.CLAN_DELETED: {
const clanID = buffer[1];
PlayerManager.clanData.delete(clanID);
//log("CLAN_DELETED", { clanID });
break;
}
case SocketServer.JOIN_REQUEST: {
const globalID = buffer[1];
myPlayer.joinRequests.push(globalID);
break;
}
case SocketServer.CLAN_UPDATED: {
/*const myPlayer = this.client.myPlayer;
myPlayer.clan.members.length = 0;
const clanID = buffer[1];
for (let i=2;i<buffer.length;i++) {
const id = buffer[i];
if (id !== myPlayer.id) {
myPlayer.clan.members.push(id);
}
}
log("clan updated", myPlayer.clan);*/
break;
}
case SocketServer.ITEM_COUNTER: {
break;
}
case SocketServer.MINIMAP_DATA: {
for (let i=1;i<buffer.length;i+=3) {
const id = buffer[i + 0];
const x = buffer[i + 1] / 255;
const y = buffer[i + 2] / 255;
//log("MINIMAP_DATA", id, x, y);
}
break;
}
case SocketServer.BOSS_SPAWNED: {
const type = buffer[1];
const x = buffer[2] / 255;
const y = buffer[3] / 255;
//log("BOSS_SPAWNED", type, x, y);
break;
}
case SocketServer.BOSS_KILLED: {
//log("BOSS_KILLED", buffer);
break;
}
case SocketServer.CLAN_MESSAGE: {
const id = buffer[1] | buffer[2] << 8;
const text = this.decoder.decode(buffer.subarray(3));
break;
}
default: {
log("default byte", buffer);
break;
}
}
}
}
class PlayerClient {
constructor(owner = null) {
this.owner = owner;
this.clients = new Set();
this.pendingJoins = new Set();
this.PacketHandler = new PacketHandler(this);
this.SocketManager = new SocketManager(this);
this.ModuleHandler = new ModuleHandler(this);
this.myPlayer = new ClientPlayer(this);
this.PlayerManager = new PlayerManager(this);
}
get isOwner() {
return this.owner === null;
}
addClient(client) {
this.clients.add(client);
}
removeClient(client) {
this.clients.delete(client);
}
init(socket) {
this.SocketManager.init(socket);
}
disconnect() {
const socket = this.SocketManager.socket;
if (socket !== null) {
socket.close();
}
}
connection() {
this.SocketManager.socket.dispatchEvent(new Event("connected"));
}
removeJoin(globalID) {
this.pendingJoins.delete(globalID);
this.myPlayer.joinRequests.shift();
if (this.isOwner) HookedArrays.joins.shift();
}
}
const _WebSocket = WebSocket;
const playerClient = new PlayerClient();
const createClient = () => {
const socket = new _WebSocket(playerClient.SocketManager.socket.url);
socket.binaryType = "arraybuffer";
socket.onopen = () => {
//log("Opened");
const client = new PlayerClient(playerClient);
client.init(socket);
const onconnect = () => {
//log("Connected");
playerClient.addClient(client);
client.PacketHandler.login("Murka");
}
socket.addEventListener("connected", onconnect, { once: true });
socket.onclose = () => {
log("Closed");
playerClient.removeClient(client);
}
}
}
window.SocketAPI = {
myClient: playerClient,
createClient,
HookedArrays,
}
let hooked = false;
const defaultHooks = () => {
if (hooked) return;
hooked = true;
Array.prototype.push = new Proxy(Array.prototype.push, {
apply(target, _this, args) {
if (args[0] === 255) {
HookedArrays.joins = _this;
Array.prototype.push = target;
return;
}
return target.apply(_this, args);
}
});
playerClient.SocketManager.selfMessage([SocketServer.JOIN_REQUEST, 255]);
}
window.WebSocket = new Proxy(WebSocket, {
construct(target, args) {
const socket = new target(...args);
playerClient.init(socket);
Object.defineProperty(socket, "onmessage", {
set(callback) {
delete socket.onmessage;
socket.onmessage = callback;
defaultHooks();
},
configurable: true
});
return socket;
}
});
})();
/*
Author: Мurkа
Github: github.com/Murka007
Greasyfork: greasyfork.org/users/919633
Discord: discord.gg/cPRFdcZkeD
*/