Initial commit

This commit is contained in:
2026-08-24 22:20:36 +02:00
commit 62f7b1278a
251 changed files with 16670 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
const Game = require("../Game/Game");
const { players } = Game;
const extractBehaviourFeatures = require("../Utility/extractBehaviourFeatures");
// const NORMAL_CLOSE_CODES = [0, 1, 2];
module.exports = function (ws, code, message) {
if (code === 1337) return;
const player = players[ws.accountID];
if (!player) {
ws.isInterrupted = true;
delete socket.sockets[ws.id];
return;
};
const playerActions = global.actions[player.id];
const features = extractBehaviourFeatures(playerActions);
delete global.actions[player.id];
if (features) {
features.playerID = player.id;
features.timestampStart = new Date(ws.connectedAt);
global.StatisticsWorker.postMessage({
id: 2,
data: features
});
};
player.inDistance = {};
player.isAvailable = false;
player.hasUnexpectedlyLeft = true;
const individualPackets = player.map.networkPacketsInvidiual[player.id];
if (individualPackets) individualPackets.length = 0;
console.info("close event", performance.now());
delete socket.sockets[ws.id];
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = function (ws) {
console.info("Drain?");
};
+94
View File
@@ -0,0 +1,94 @@
const packetTable = require("../Data/packets");
const { players } = require("../Game/Game");
const actions = [];
global.actions = actions;
const { MongoClient } = require("mongodb");
let db = null;
async function connect() {
const client = new MongoClient("mongodb://localhost:27017");
await client.connect({
maxPoolSize: 100,
minPoolSize: 20,
maxConnecting: 20,
waitQueueTimeoutMS: 0,
socketTimeoutMS: 30000,
connectTimeoutMS: 10000,
retryWrites: false,
directConnection: true
});
return client.db('test');
};
(async function() {
db = await connect();
global.mongodb = db;
return;
setInterval(async () => {
const t = [];
for (let i = 0; i < 32; i++) {
t.push({
playerID: 1,
timestamp: new Date(),
opcode: Math.floor(Math.random() * 120)
});
};
const bulkOps = t.map(doc => ({
insertOne: doc
}));
const n = performance.now();
await db.collection("Actions").insertMany(t, {
ordered: false,
writeConcern: { w: 0 },
bypassDocumentValidation: true
});
/* await db.collection('Actions').bulkWrite(bulkOps, {
ordered: false,
writeConcern: { w: 0 },
bypassDocumentValidation: true
});*/
}, 500);
})();
module.exports = async function (ws, message, isBinary) {
const player = players[ws.accountID];
if (!player) return;
const isLimitReached = ws.limiter.update();
if (isLimitReached) return ws.close();
if (player.isInactive) return; // this shouldn't happen, though
const view = new DataView(message);
const packetID = view.getUint8(0);
const packet = packetTable[packetID];
if (packetID !== 1 && !player.isAvailable) return;
if (!packet) return;
if (packet.expectedSize && packet.expectedSize !== view.byteLength) return console.info("expectedSize", view.byteLength, packet.expectedSize);
if (!packet.isAvailable) return;
player.lastInteractionTimestamp = performance.now();
const metadata = packet.handler(ws, view, player) ?? [];
if (packetID > 1) {
const action = {
ts: new Date(),
pid: player.id,
opcode: packetID,
metadata
};
const relevantContext = global.actionContext.getElement(player.id).save(2000);
relevantContext.push(action);
console.info("asd", relevantContext.length)
};
};
+239
View File
@@ -0,0 +1,239 @@
const Limiter = require("../Game/PacketLimiter");
const EquipmentLimiter = require("../Game/EquipmentLimiter");
const Inventory = require("../Game/Inventory");
const Player = require("../Game/Player");
const Game = require("../Game/Game");
const playerHandlers = require("../Handlers/Player");
const EnvironmentSetupPacket = require("../Packets/EnvironmentSetup");
const rewardCallback = require("../Callbacks/reward");
const attackCallback = require("../Callbacks/attack");
const baseStarterRewards = require("../Data/startRewards");
const { maps, players, guilds } = Game;
const queries = require("../Data/queries");
const { QUERY_FETCH_PLAYER_DATA, QUERY_FETCH_PLAYER_INFORMATION, QUERY_FETCH_GUILD_INFORMATION, QUERY_INSERT_DEFAULT_PLAYER, QUERY_FETCH_ACTIVE_QUESTS, QUERY_FETCH_STATUS, QUERY_FETCH_RESOURCES, QUERY_FETCH_EQUIPMENTS } = queries;
const playerEvents = Object.keys(playerHandlers);
const PlayerBehaviour = require("../Game/PlayerBehaviour");
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
global.actionContext = new DoubleLinkedList();
module.exports = async function (ws) {
console.info("Before open for", ws.username, Game.state, ws.isAdmin);
if (Game.state === 0 && !ws.isAdmin) return ws.end(1337);
ws.connectedAt = Date.now();
let abruptDisconnectTest = performance.now();
const ID = ws.accountID;
const sessionID = Date.now().toString(36) + Math.random().toString(36).substring(2) + "!" + ID;
socket.sockets[sessionID] = ws;
ws.id = sessionID;
ws.limiter = new Limiter({
countThreshold: 10,
timeThreshold: 1000
});
const alreadyConnectedPlayer = players[ID];
if (alreadyConnectedPlayer) {
const player = alreadyConnectedPlayer;
const alreadyConnectedSoket = socket.sockets[player.sessionID];
if (alreadyConnectedSoket) alreadyConnectedSoket.end(1);
player.sessionID = sessionID;
ws.subscribe(player.map.id.toString());
player.map.removePlayer(player.id);
player.map.addPlayer(player);
player.isUnderDisconnect = false;
player.cancelDisconnect();
const packet = EnvironmentSetupPacket(player.map.id);
return ws.send(packet, true, true);
};
const playerBehaviour = new PlayerBehaviour({
size: 16,
buffer: global.playerBehaviourContext,
id: ID
});
global.actionContext.addItem(ID, playerBehaviour);
const generalQuery = execute(QUERY_FETCH_PLAYER_DATA, [ID]);
const informationQuery = execute(QUERY_FETCH_PLAYER_INFORMATION, [ID]);
const guildQuery = execute(QUERY_FETCH_GUILD_INFORMATION, [ID]);
const questQuery = execute(QUERY_FETCH_ACTIVE_QUESTS, [ID]);
const statusQuery = execute(QUERY_FETCH_STATUS, [ID, true]);
const resourcesQuery = execute(QUERY_FETCH_RESOURCES, [ID]);
const equipmentsQuery = execute(QUERY_FETCH_EQUIPMENTS, [ID]);
const res = await Promise.all([generalQuery, informationQuery, guildQuery, questQuery, statusQuery, resourcesQuery, equipmentsQuery]);
const hasError = res.some(q => q.error !== undefined);
if (hasError) return;
if (ws.isInterrupted) return;
let isAlive = false;
let map = null;
let hitpoints = {
current: 0,
maximum: 0
};
let position = {};
let attemptToDoubleLog = false;
let maximumCannonRange = 0;
let maximumHarpoonerRange = 0;
let textureID = 0;
let sight = 0;
let speed = 0;
let globalRank = 0;
let levelRank = 0;
const playerFromDB = res[0].data[0];
const guildFromDB = res[2].data[0];
const questFromDB = res[3].data;
const statusFromDB = res[4].data;
const resourcesFromDB = res[5].data;
const equipmentsFromDB = res[6].data;
const statusPoints = [];
const guildID = guildFromDB ? guildFromDB.id : 0;
const guild = guilds[guildID];
const selectedAmmunition = playerFromDB ? playerFromDB.selectedAmmunitionID : 0;
const selectedHarpoon = playerFromDB ? playerFromDB.selectedHarpoonID : 0;
const resourceSharedLimiter = {
cannon: new EquipmentLimiter({
maximum: 50
}),
harpooner: new EquipmentLimiter({
maximum: 25
}),
sail: new EquipmentLimiter({
maximum: 5
}),
pirate: new EquipmentLimiter({
maximum: 250
})
};
const inventory = new Inventory({
resourceSharedLimiter
});
resourcesFromDB.forEach(r => {
switch (r.categoryID) {
case 6:
statusPoints.push(r);
break;
case 7:
case 8:
const equipment = equipmentsFromDB.find(e => e.id === r.id && e.categoryID === r.categoryID);
r.amountEquipped = equipment ? equipment.amountEquipped : 0;
break;
};
inventory.refreshSingle(r);
});
inventory.setSelectedAmmunition(selectedAmmunition);
inventory.setSelectedHarpoon(selectedHarpoon);
if (!attemptToDoubleLog) {
const playerHasData = playerFromDB ? true : false;
if (!playerHasData) {
const startingMap = maps.find(m => m.id === 1);
const { x, y } = startingMap.grid.randomNode(10);
textureID = 1;
sight = 450;
speed = 250;
globalRank = 1;
levelRank = 1;
isAlive = true;
map = startingMap;
position = {
x,
y
};
hitpoints = {
current: 10000,
maximum: 50000
};
baseStarterRewards.forEach(r => inventory.refreshSingle(r));
await execute(QUERY_INSERT_DEFAULT_PLAYER, [ID, x, y, 1]);
} else {
textureID = playerFromDB.designID;
sight = playerFromDB.sight;
speed = playerFromDB.speed;
globalRank = playerFromDB.globalRank;
levelRank = playerFromDB.levelRank;
isAlive = !!hitpoints;
map = maps.find(m => m.id === playerFromDB.mapID);
position = {
x: playerFromDB.x,
y: playerFromDB.y
};
hitpoints = {
current: playerFromDB.currentHitpoints,
maximum: playerFromDB.maximumHitpoints
};
};
};
const player = new Player({
id: ID,
sessionID: ws.id,
name: ws.username,
textureID,
map,
guild,
positionX: position.x,
positionY: position.y,
isAlive,
hitpoints,
maximumCannonRange,
maximumHarpoonerRange,
sight,
speed,
globalRank,
levelRank,
inventory,
statusPoints,
activeStatusEffects: statusFromDB,
quests: questFromDB,
rewardCallback,
attackCallback
});
player.setDesign(textureID);
ws.subscribe(player.map.id.toString());
playerEvents.forEach(e => player.on(e, playerHandlers[e]));
players[ID] = player;
const packet = EnvironmentSetupPacket(player.map.id);
ws.send(packet, true, true);
};
+64
View File
@@ -0,0 +1,64 @@
const crypto = require("crypto");
const redis = require("../Miscellaneous/redis");
const Game = require("../Game/Game");
const HTTP_BAD_REQUEST = "403";
function badRequest(res) {
res.cork(() => res.isAborted ? null : res
.writeStatus(HTTP_BAD_REQUEST)
.end());
};
module.exports = async function (res, req, context) {
const cookieHeader = req.getHeader("cookie");
const cookie = cookieHeader.split("ps=")[1];
res.onAborted(() => {
res.isAborted = true;
console.info("ABORTED!!!");
});
if (!cookie) {
console.info("upgrade 1")
return badRequest(res);
};
const secWebSocketKey = req.getHeader("sec-websocket-key");
const secWebSocketProtocol = req.getHeader("sec-websocket-protocol");
const secWebSocketExtensions = req.getHeader("sec-websocket-extensions");
const userAgent = req.getHeader("user-agent");
const ip = req.getHeader("x-forwarded-for");
const formattedCookie = cookie.split(".")[0].slice(4);
const cookieInDB = await redis.get(`sess:${formattedCookie}`);
if (!cookieInDB) {
console.info("upgrade 2")
return badRequest(res);
};
const parsedCookie = JSON.parse(cookieInDB);
if (!parsedCookie.accountID) {
console.info("upgrade 3")
return badRequest(res);
};
const hash = crypto.createHash("md5")
.update(userAgent)
.update(ip)
.digest("hex");
// if (hash !== parsedCookie.fingerprint) return badRequest(res);
const { accountID, username, isAdmin } = parsedCookie;
console.info("Before upgrading connection for", username);
res.cork(() => res.upgrade({ accountID, username, cookie, isAdmin },
secWebSocketKey,
secWebSocketProtocol,
secWebSocketExtensions,
context));
};