483 lines
16 KiB
JavaScript
483 lines
16 KiB
JavaScript
const mysql = require('mysql2/promise');
|
|
const WebSocket = require('ws');
|
|
const axios = require('axios');
|
|
const events = require('events');
|
|
|
|
// Database configuration
|
|
const dbConfig = {
|
|
host: "95.217.113.161",
|
|
user: "Likima",
|
|
password: "27As$r0q1",
|
|
database: "jedaiito",
|
|
charset: "utf8"
|
|
};
|
|
|
|
// Create a MySQL connection pool
|
|
const pool = mysql.createPool({
|
|
...dbConfig,
|
|
waitForConnections: true,
|
|
connectionLimit: 10,
|
|
queueLimit: 0
|
|
});
|
|
|
|
// Elo Rating Calculation
|
|
function calculateElo(currentRating, opponentRating, actualScore, kFactor = 32) {
|
|
const expectedScore = 1 / (1 + Math.pow(10, (opponentRating - currentRating) / 400));
|
|
const newRating = currentRating + kFactor * (actualScore - expectedScore);
|
|
return newRating;
|
|
}
|
|
|
|
// Simplified Player class
|
|
class Player {
|
|
constructor(id, ip, name, guid, elo = 1000, score = 0, team = '') {
|
|
this.id = id;
|
|
this.ip = ip;
|
|
this.name = name;
|
|
this.guid = guid;
|
|
this.elo = elo;
|
|
this.score = score;
|
|
this.team = team;
|
|
this.is_alive = true;
|
|
}
|
|
}
|
|
|
|
// Utility function to remove color codes
|
|
function removeColorCodes(name) {
|
|
return name.replace(/\^\d/g, '');
|
|
}
|
|
|
|
// Update Elo ratings in the database
|
|
async function updatePlayerElo(player) {
|
|
const connection = await pool.getConnection();
|
|
try {
|
|
const updateQuery = `
|
|
UPDATE players
|
|
SET elo = ?
|
|
WHERE guid = ?
|
|
`;
|
|
await connection.query(updateQuery, [player.elo, player.guid]);
|
|
} finally {
|
|
connection.release();
|
|
}
|
|
}
|
|
|
|
// Event handling
|
|
class GameEventEmitter extends events.EventEmitter {}
|
|
|
|
const gameEventEmitter = new GameEventEmitter();
|
|
|
|
// Player lists
|
|
const currentPlayers = [];
|
|
const playerQueue = [];
|
|
|
|
// Handle round end event
|
|
gameEventEmitter.on('RoundEnd', async (redScore, blueScore, playerScores) => {
|
|
console.log(`Round ended. Red: ${redScore}, Blue: ${blueScore}`);
|
|
|
|
let winningTeam, losingTeam;
|
|
|
|
if (redScore > blueScore) {
|
|
winningTeam = 'red';
|
|
losingTeam = 'blue';
|
|
} else if (blueScore > redScore) {
|
|
winningTeam = 'blue';
|
|
losingTeam = 'red';
|
|
} else {
|
|
console.log("It's a draw. No Elo change.");
|
|
return;
|
|
}
|
|
|
|
const kFactor = 32; // You can adjust this as needed
|
|
|
|
const winningPlayers = currentPlayers.filter(player => player.team === winningTeam);
|
|
const losingPlayers = currentPlayers.filter(player => player.team === losingTeam);
|
|
|
|
const averageWinningElo = winningPlayers.reduce((acc, player) => acc + player.elo, 0) / winningPlayers.length;
|
|
const averageLosingElo = losingPlayers.reduce((acc, player) => acc + player.elo, 0) / losingPlayers.length;
|
|
|
|
for (const player of winningPlayers) {
|
|
const individualPerformanceFactor = playerScores[player.id] / redScore; // Adjust based on individual contribution
|
|
player.elo = calculateElo(player.elo, averageLosingElo, 1 * individualPerformanceFactor, kFactor);
|
|
await updatePlayerElo(player);
|
|
}
|
|
|
|
for (const player of losingPlayers) {
|
|
const individualPerformanceFactor = playerScores[player.id] / blueScore; // Adjust based on individual contribution
|
|
player.elo = calculateElo(player.elo, averageWinningElo, 0 * individualPerformanceFactor, kFactor);
|
|
await updatePlayerElo(player);
|
|
}
|
|
|
|
console.log("Elo ratings updated.");
|
|
});
|
|
|
|
// Function to request a new token
|
|
async function requestNewToken() {
|
|
const API_URL = 'https://panel.pineriver.net/api/client/servers/35e1d7ab/websocket'; // Corrected API endpoint
|
|
const API_KEY = 'Bearer ptlc_uohTuXbtbTmVaSAhwtD16R2oDynhQgTWBZKN2m5lWdh'; // Your actual API key
|
|
try {
|
|
const response = await axios.get(API_URL, {
|
|
headers: {
|
|
'Authorization': API_KEY,
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const { data } = response;
|
|
return data.data.token;
|
|
} catch (error) {
|
|
console.error('Error requesting new token:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Main function to start WebSocket connection and handle console output
|
|
async function startWebSocketConnection() {
|
|
try {
|
|
const API_URL = 'https://panel.pineriver.net/api/client/servers/35e1d7ab/websocket';
|
|
const API_KEY = 'Bearer ptlc_uohTuXbtbTmVaSAhwtD16R2oDynhQgTWBZKN2m5lWdh';
|
|
|
|
// Request the token and WebSocket URL
|
|
const response = await axios.get(API_URL, {
|
|
headers: {
|
|
'Authorization': API_KEY,
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
const { token, socket } = response.data.data;
|
|
|
|
// Create WebSocket connection
|
|
const ws = new WebSocket(socket);
|
|
|
|
ws.on('open', () => {
|
|
ws.send(JSON.stringify({
|
|
event: 'auth',
|
|
args: [token]
|
|
}));
|
|
console.log('WebSocket connection established and authenticated');
|
|
});
|
|
|
|
ws.on('message', async (message) => {
|
|
const parsedMessage = JSON.parse(message);
|
|
|
|
if (parsedMessage.event === 'console output') {
|
|
const consoleOutput = parsedMessage.args[0];
|
|
handleConsoleOutput(consoleOutput);
|
|
}
|
|
|
|
if (parsedMessage.event === 'token expiring') {
|
|
console.log('Token is expiring soon, requesting a new token...');
|
|
const newToken = await requestNewToken();
|
|
ws.send(JSON.stringify({
|
|
event: 'auth',
|
|
args: [newToken]
|
|
}));
|
|
console.log('WebSocket re-authenticated with new token');
|
|
}
|
|
|
|
if (parsedMessage.event === 'token expired') {
|
|
console.log('Token has expired, reconnecting with a new token...');
|
|
ws.close(); // Close the current WebSocket connection
|
|
startWebSocketConnection(); // Start a new WebSocket connection
|
|
}
|
|
});
|
|
|
|
ws.on('close', () => {
|
|
console.log('WebSocket connection closed');
|
|
});
|
|
|
|
ws.on('error', (error) => {
|
|
console.error('WebSocket error:', error);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error starting WebSocket connection:', error);
|
|
}
|
|
}
|
|
|
|
// Handle console output received via WebSocket
|
|
function handleConsoleOutput(line) {
|
|
const playerScores = {};
|
|
|
|
if (eventClientConnect(line, line, playerQueue)) return;
|
|
if (eventPlayer(line, line, currentPlayers, playerQueue)) return;
|
|
if (eventDisconnect(line, line, currentPlayers, playerQueue)) return;
|
|
if (eventKill(line, line, currentPlayers)) return;
|
|
if (eventSay(line, line, currentPlayers)) return;
|
|
if (eventBroadcast(line, line, currentPlayers)) return;
|
|
if (eventShutdown(line, currentPlayers, playerQueue)) return;
|
|
|
|
if (line.startsWith('>>> red:') && line.includes(' blue:')) {
|
|
const redScore = parseInt(line.split('red:')[1].split(' ')[0]);
|
|
const blueScore = parseInt(line.split('blue:')[1]);
|
|
gameEventEmitter.emit('RoundEnd', redScore, blueScore, playerScores);
|
|
} else if (line.startsWith('>>> score:')) {
|
|
const parts = line.split(' ');
|
|
const score = parseInt(parts[2]);
|
|
const clientId = parseInt(parts[6]);
|
|
const playerName = parts.slice(7).join(' ');
|
|
|
|
const player = currentPlayers.find(p => p.id === clientId);
|
|
if (player) {
|
|
player.score = score;
|
|
playerScores[clientId] = score;
|
|
console.log(`Player ${playerName} (ID: ${clientId}) has a score of ${score}`);
|
|
}
|
|
}
|
|
|
|
if (checkLine(line)) {
|
|
// Additional event handling can go here
|
|
gameEventEmitter.emit('SomeOtherEvent', line);
|
|
}
|
|
}
|
|
|
|
// Event Handlers
|
|
async function eventClientConnect(event, eventContent, playerQueue) {
|
|
if (event.includes("ClientConnect")) {
|
|
console.log("Client connecting...");
|
|
|
|
const pid = parseInt(eventContent.split("ID: ")[1].split(" ")[0], 10);
|
|
const guid = eventContent.includes("\\ja_guid\\") ? eventContent.split("\\ja_guid\\")[1].split("\\")[0] : null;
|
|
|
|
if (!guid) {
|
|
console.log("GUID not found in event content, skipping player initialization.");
|
|
return false;
|
|
}
|
|
|
|
if (currentPlayers.some(player => player.guid === guid)) {
|
|
return false;
|
|
}
|
|
|
|
console.log(`Adding player to queue: [PID: ${pid}] [GUID: ${guid}]`);
|
|
playerQueue.push({ guid: guid, id: pid, ip: null, name: null });
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function eventPlayer(event, eventContent, currentPlayers, playerQueue) {
|
|
if (event.startsWith("Player")) {
|
|
try {
|
|
// Extract GUID, name, and IP from eventContent
|
|
const guid = eventContent.split("\\ja_guid\\", 1)[1].split("\\", 1)[0];
|
|
const name = eventContent.split("\\name\\", 1)[1].split("\\", 1)[0];
|
|
const ip = eventContent.split("\\ip\\", 1)[1].split("\\", 1)[0].split(":", 1)[0];
|
|
|
|
if (guid === "0") {
|
|
console.log(`Skipping player with invalid GUID: ${guid}`);
|
|
return false;
|
|
}
|
|
|
|
// Use GUID to load or create the player
|
|
const playerInfo = await loadPlayerByGuid(guid, name, ip, currentPlayers);
|
|
playerInfo.id = parseInt(event.split(" ")[1], 10); // Set session-specific ID
|
|
console.log(`Player event detected. Player ID: ${playerInfo.id}`);
|
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.log("Error processing player event:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async function loadPlayerByGuid(guid, name, ip, currentPlayers) {
|
|
let player = currentPlayers.find(p => p.guid === guid);
|
|
|
|
if (!player) {
|
|
// Check if the player already exists in the database
|
|
const playerExists = await checkIfPlayerExists(guid);
|
|
|
|
if (playerExists) {
|
|
player = await getPlayerFromDatabase(guid);
|
|
console.log(`Loaded player from database: ${player.name} (GUID: ${guid})`);
|
|
} else {
|
|
// Create a new player in the database
|
|
player = new Player(null, ip, name, guid);
|
|
await insertNewPlayer(player);
|
|
console.log(`New player added to the database: ${name} (GUID: ${guid})`);
|
|
}
|
|
|
|
// Add to current players
|
|
currentPlayers.push(player);
|
|
} else {
|
|
console.log(`Player already in current session: ${name} (GUID: ${guid})`);
|
|
}
|
|
|
|
return player;
|
|
}
|
|
|
|
// Check if a player with the given GUID already exists in the database
|
|
async function checkIfPlayerExists(guid) {
|
|
const connection = await pool.getConnection();
|
|
try {
|
|
const query = `SELECT COUNT(*) AS count FROM players WHERE guid = ?`;
|
|
const [rows] = await connection.execute(query, [guid]);
|
|
return rows[0].count > 0;
|
|
} finally {
|
|
if (connection) connection.release();
|
|
}
|
|
}
|
|
|
|
// Get player data from the database
|
|
async function getPlayerFromDatabase(guid) {
|
|
const connection = await pool.getConnection();
|
|
try {
|
|
const query = `SELECT * FROM players WHERE guid = ?`;
|
|
const [rows] = await connection.execute(query, [guid]);
|
|
const playerData = rows[0];
|
|
return new Player(null, playerData.ip, playerData.name, playerData.guid, playerData.elo);
|
|
} finally {
|
|
if (connection) connection.release();
|
|
}
|
|
}
|
|
|
|
// Insert a new player into the database
|
|
async function insertNewPlayer(player) {
|
|
const connection = await pool.getConnection();
|
|
try {
|
|
const aliasesStr = JSON.stringify(player.aliases || []);
|
|
const insertQuery = `
|
|
INSERT INTO players (guid, name, ip, elo, aliases)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`;
|
|
await connection.execute(insertQuery, [player.guid, player.name, player.ip, player.elo, aliasesStr]);
|
|
console.log(`Player with GUID: ${player.guid} inserted into the database.`);
|
|
} finally {
|
|
if (connection) connection.release();
|
|
}
|
|
}
|
|
|
|
function searchByNameOrGuid(identifier, currentPlayers) {
|
|
const cleanIdentifier = removeColorCodes(identifier);
|
|
for (let i = 0; i < currentPlayers.length; i++) {
|
|
const player = currentPlayers[i];
|
|
if (player.guid === cleanIdentifier || removeColorCodes(player.name).toLowerCase().includes(cleanIdentifier.toLowerCase())) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
async function eventSay(event, eventContent, currentPlayers) {
|
|
if (!event.includes('say')) {
|
|
return false;
|
|
}
|
|
|
|
let name = eventContent.split(": ", 1)[0];
|
|
const chat = eventContent.split(": ", 2)[1];
|
|
const pIndex = searchByNameOrGuid(name, currentPlayers);
|
|
|
|
if (pIndex === -1) return false;
|
|
|
|
const player = currentPlayers[pIndex];
|
|
const parts = chat.split(" ");
|
|
|
|
// Check if the player typed "!rank"
|
|
if (parts[0] === "!rank") {
|
|
const playerElo = player.elo; // Get the player's Elo rating
|
|
const message = `say ${player.name}, your current Elo rating is: ${playerElo}`;
|
|
console.log(message); // Debugging: log the message
|
|
network.sendCmd(message); // Send the message back to the game server
|
|
return true;
|
|
}
|
|
|
|
// Additional chat commands handling
|
|
// ...
|
|
|
|
return true;
|
|
}
|
|
|
|
async function eventBroadcast(event, eventContent, currentPlayers) {
|
|
if (event.includes("broadcast")) {
|
|
if (eventContent.includes("print ")) {
|
|
const templine = eventContent.split("print ")[1].trim();
|
|
console.log(`Debug: Broadcast event detected: ${templine}`);
|
|
|
|
// Additional broadcast event handling
|
|
// ...
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function eventShutdown(event, currentPlayers, playerQueue) {
|
|
if (event.toLowerCase().includes("shutdown")) {
|
|
console.log(">>>>>ROUND END<<<<<<<<<");
|
|
|
|
playerQueue.length = 0; // Clear the player queue
|
|
currentPlayers.length = 0; // Clear the current players list
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function eventDisconnect(event, eventContent, currentPlayers, playerQueue) {
|
|
if (!event.includes("ClientD")) {
|
|
return false;
|
|
}
|
|
|
|
const pid = parseInt(eventContent.split(" ")[0], 10);
|
|
console.log("Disconnected id:", pid);
|
|
|
|
const pindex = searchById(pid, currentPlayers);
|
|
if (pindex === -1) {
|
|
console.log("Uninitialized client disconnected ID:", pid);
|
|
return false;
|
|
}
|
|
|
|
const player = currentPlayers[pindex];
|
|
|
|
if (player.name) {
|
|
network.sendCmd(`say ${player.name} ^1has left the server to pick ^6flowers.`);
|
|
}
|
|
|
|
console.log("REMOVING AND SAVING PLAYER...\n");
|
|
currentPlayers.splice(pindex, 1); // Remove the player from the currentPlayers array
|
|
const queueIndex = searchById(pid, playerQueue);
|
|
if (queueIndex !== -1) {
|
|
playerQueue.splice(queueIndex, 1); // Remove the player from the playerQueue array
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async function eventKill(event, eventContent, currentPlayers) {
|
|
if (event.includes("Kill")) {
|
|
const pid = parseInt(eventContent.split(" ")[1], 10);
|
|
console.log("Death ID:", pid);
|
|
|
|
const deadPlayerIndex = searchById(pid, currentPlayers);
|
|
if (deadPlayerIndex === -1) {
|
|
console.log("Client not initialized, please reconnect.", pid);
|
|
return false;
|
|
}
|
|
|
|
// Additional kill event handling
|
|
// ...
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function checkLine(line) {
|
|
if (line.startsWith("Pl")) return true;
|
|
if (line.startsWith("Shut")) return true;
|
|
if (line.startsWith("ClientConne")) return true;
|
|
if (line.startsWith("ClientDisconn")) return true;
|
|
if (line.startsWith("Kill")) return true;
|
|
if (line.includes("say:")) return true;
|
|
if (line.startsWith("ClientUser")) return true;
|
|
if (line.startsWith("broadcast")) return true;
|
|
return false;
|
|
}
|
|
|
|
// Start the WebSocket connection
|
|
startWebSocketConnection();
|