Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 39

/* VARIABLES */

/* ROOM */

const roomName = '[BOT] Howitzer Private #';


const maxPlayers = 40;
const roomPublic = false;
const token = ""; // Insert token here

var roomWebhook =
'https://discord.com/api/webhooks/1100134155259490374/9YJjLd1t9ydgQ1-x_c42I-
YeQVrxxXfKhocLI55j67u3qJMy4Dh9TEe22a0gtt9UWCZX';
var gameWebhook =
'https://discord.com/api/webhooks/1100419409278943253/LfEID1_TWL1kz1DY4FL5C5ETpEXXI
-C1aD8qXNGkIRld4XW2VFsAwBfIz_FGnsjMa60g';
var timeLimit = 3;
var scoreLimit = 3;

var gameConfig = {
roomName: roomName,
maxPlayers: maxPlayers,
public: roomPublic,
noPlayer: true,
}

if (typeof token == 'string' && token.length == 39) {


gameConfig.token = token;
}

var room = HBInit(gameConfig);

const trainingMap = '{"name":"Classic


Training","width":420,"height":200,"spawnDistance":170,"bg":
{"type":"grass","width":370,"height":170,"kickOffRadius":75,"cornerRadius":0},"vert
exes":[{"x":-370,"y":170,"trait":"ballArea"},{"x":-370,"y":64,"trait":"ballArea"},
{"x":-370,"y":-64,"trait":"ballArea"},{"x":-370,"y":-170,"trait":"ballArea"},
{"x":370,"y":170,"trait":"ballArea"},{"x":370,"y":64,"trait":"ballArea"},
{"x":370,"y":-64,"trait":"ballArea"},{"x":370,"y":-170,"trait":"ballArea"},
{"x":0,"y":200,"trait":"kickOffBarrier"},{"x":0,"y":75,"trait":"kickOffBarrier"},
{"x":0,"y":-75,"trait":"kickOffBarrier"},{"x":0,"y":-200,"trait":"kickOffBarrier"},
{"x":-380,"y":-64,"trait":"goalNet"},{"x":-400,"y":-44,"trait":"goalNet"},{"x":-
400,"y":44,"trait":"goalNet"},{"x":-380,"y":64,"trait":"goalNet"},{"x":380,"y":-
64,"trait":"goalNet"},{"x":400,"y":-44,"trait":"goalNet"},
{"x":400,"y":44,"trait":"goalNet"},{"x":380,"y":64,"trait":"goalNet"}],"segments":
[{"v0":0,"v1":1,"trait":"ballArea"},{"v0":2,"v1":3,"trait":"ballArea"},
{"v0":4,"v1":5,"trait":"ballArea"},{"v0":6,"v1":7,"trait":"ballArea"},
{"v0":12,"v1":13,"trait":"goalNet","curve":-90},
{"v0":13,"v1":14,"trait":"goalNet"},{"v0":14,"v1":15,"trait":"goalNet","curve":-
90},{"v0":16,"v1":17,"trait":"goalNet","curve":90},
{"v0":17,"v1":18,"trait":"goalNet"},{"v0":18,"v1":19,"trait":"goalNet","curve":90},
{"v0":8,"v1":9,"trait":"kickOffBarrier"},
{"v0":9,"v1":10,"trait":"kickOffBarrier","curve":180,"cGroup":["blueKO"]},
{"v0":9,"v1":10,"trait":"kickOffBarrier","curve":-180,"cGroup":["redKO"]},
{"v0":10,"v1":11,"trait":"kickOffBarrier"}],"goals":[],"discs":[{"pos":[-
370,64],"trait":"goalPost","color":"FFCCCC"},{"pos":[-370,-
64],"trait":"goalPost","color":"FFCCCC"},{"pos":
[370,64],"trait":"goalPost","color":"CCCCFF"},{"pos":[370,-
64],"trait":"goalPost","color":"CCCCFF"}],"planes":[{"normal":[0,1],"dist":-
170,"trait":"ballArea"},{"normal":[0,-1],"dist":-170,"trait":"ballArea"},{"normal":
[0,1],"dist":-200,"bCoef":0.1},{"normal":[0,-1],"dist":-200,"bCoef":0.1},{"normal":
[1,0],"dist":-420,"bCoef":0.1},{"normal":[-1,0],"dist":-420,"bCoef":0.1}],"traits":
{"ballArea":{"vis":false,"bCoef":1,"cMask":["ball"]},"goalPost":
{"radius":8,"invMass":0,"bCoef":0.5},"goalNet":{"vis":true,"bCoef":0.1,"cMask":
["ball"]},"kickOffBarrier":{"vis":false,"bCoef":0.1,"cGroup":
["redKO","blueKO"],"cMask":["red","blue"]}}}';
const classicMap =
'{"name":"Classic","width":420,"height":200,"spawnDistance":170,"bg":
{"type":"grass","width":370,"height":170,"kickOffRadius":75,"cornerRadius":0},"vert
exes":[{"x":-370,"y":170,"trait":"ballArea"},{"x":-370,"y":64,"trait":"ballArea"},
{"x":-370,"y":-64,"trait":"ballArea"},{"x":-370,"y":-170,"trait":"ballArea"},
{"x":370,"y":170,"trait":"ballArea"},{"x":370,"y":64,"trait":"ballArea"},
{"x":370,"y":-64,"trait":"ballArea"},{"x":370,"y":-170,"trait":"ballArea"},
{"x":0,"y":200,"trait":"kickOffBarrier"},{"x":0,"y":75,"trait":"kickOffBarrier"},
{"x":0,"y":-75,"trait":"kickOffBarrier"},{"x":0,"y":-200,"trait":"kickOffBarrier"},
{"x":-380,"y":-64,"trait":"goalNet"},{"x":-400,"y":-44,"trait":"goalNet"},{"x":-
400,"y":44,"trait":"goalNet"},{"x":-380,"y":64,"trait":"goalNet"},{"x":380,"y":-
64,"trait":"goalNet"},{"x":400,"y":-44,"trait":"goalNet"},
{"x":400,"y":44,"trait":"goalNet"},{"x":380,"y":64,"trait":"goalNet"}],"segments":
[{"v0":0,"v1":1,"trait":"ballArea"},{"v0":2,"v1":3,"trait":"ballArea"},
{"v0":4,"v1":5,"trait":"ballArea"},{"v0":6,"v1":7,"trait":"ballArea"},
{"v0":12,"v1":13,"trait":"goalNet","curve":-90},
{"v0":13,"v1":14,"trait":"goalNet"},{"v0":14,"v1":15,"trait":"goalNet","curve":-
90},{"v0":16,"v1":17,"trait":"goalNet","curve":90},
{"v0":17,"v1":18,"trait":"goalNet"},{"v0":18,"v1":19,"trait":"goalNet","curve":90},
{"v0":8,"v1":9,"trait":"kickOffBarrier"},
{"v0":9,"v1":10,"trait":"kickOffBarrier","curve":180,"cGroup":["blueKO"]},
{"v0":9,"v1":10,"trait":"kickOffBarrier","curve":-180,"cGroup":["redKO"]},
{"v0":10,"v1":11,"trait":"kickOffBarrier"}],"goals":[{"p0":[-370,64],"p1":[-370,-
64],"team":"red"},{"p0":[370,64],"p1":[370,-64],"team":"blue"}],"discs":[{"pos":[-
370,64],"trait":"goalPost","color":"FFCCCC"},{"pos":[-370,-
64],"trait":"goalPost","color":"FFCCCC"},{"pos":
[370,64],"trait":"goalPost","color":"CCCCFF"},{"pos":[370,-
64],"trait":"goalPost","color":"CCCCFF"}],"planes":[{"normal":[0,1],"dist":-
170,"trait":"ballArea"},{"normal":[0,-1],"dist":-170,"trait":"ballArea"},{"normal":
[0,1],"dist":-200,"bCoef":0.1},{"normal":[0,-1],"dist":-200,"bCoef":0.1},{"normal":
[1,0],"dist":-420,"bCoef":0.1},{"normal":[-1,0],"dist":-420,"bCoef":0.1}],"traits":
{"ballArea":{"vis":false,"bCoef":1,"cMask":["ball"]},"goalPost":
{"radius":8,"invMass":0,"bCoef":0.5},"goalNet":{"vis":true,"bCoef":0.1,"cMask":
["ball"]},"kickOffBarrier":{"vis":false,"bCoef":0.1,"cGroup":
["redKO","blueKO"],"cMask":["red","blue"]}}}';
const bigMap = '{"name":"Big","width":600,"height":270,"spawnDistance":350,"bg":
{"type":"grass","width":550,"height":240,"kickOffRadius":80,"cornerRadius":0},"vert
exes":[{"x":-550,"y":240,"trait":"ballArea"},{"x":-550,"y":80,"trait":"ballArea"},
{"x":-550,"y":-80,"trait":"ballArea"},{"x":-550,"y":-240,"trait":"ballArea"},
{"x":550,"y":240,"trait":"ballArea"},{"x":550,"y":80,"trait":"ballArea"},
{"x":550,"y":-80,"trait":"ballArea"},{"x":550,"y":-240,"trait":"ballArea"},
{"x":0,"y":270,"trait":"kickOffBarrier"},{"x":0,"y":80,"trait":"kickOffBarrier"},
{"x":0,"y":-80,"trait":"kickOffBarrier"},{"x":0,"y":-270,"trait":"kickOffBarrier"},
{"x":-560,"y":-80,"trait":"goalNet"},{"x":-580,"y":-60,"trait":"goalNet"},{"x":-
580,"y":60,"trait":"goalNet"},{"x":-560,"y":80,"trait":"goalNet"},{"x":560,"y":-
80,"trait":"goalNet"},{"x":580,"y":-60,"trait":"goalNet"},
{"x":580,"y":60,"trait":"goalNet"},{"x":560,"y":80,"trait":"goalNet"}],"segments":
[{"v0":0,"v1":1,"trait":"ballArea"},{"v0":2,"v1":3,"trait":"ballArea"},
{"v0":4,"v1":5,"trait":"ballArea"},{"v0":6,"v1":7,"trait":"ballArea"},
{"v0":12,"v1":13,"trait":"goalNet","curve":-90},
{"v0":13,"v1":14,"trait":"goalNet"},{"v0":14,"v1":15,"trait":"goalNet","curve":-
90},{"v0":16,"v1":17,"trait":"goalNet","curve":90},
{"v0":17,"v1":18,"trait":"goalNet"},{"v0":18,"v1":19,"trait":"goalNet","curve":90},
{"v0":8,"v1":9,"trait":"kickOffBarrier"},
{"v0":9,"v1":10,"trait":"kickOffBarrier","curve":180,"cGroup":["blueKO"]},
{"v0":9,"v1":10,"trait":"kickOffBarrier","curve":-180,"cGroup":["redKO"]},
{"v0":10,"v1":11,"trait":"kickOffBarrier"}],"goals":[{"p0":[-550,80],"p1":[-550,-
80],"team":"red"},{"p0":[550,80],"p1":[550,-80],"team":"blue"}],"discs":[{"pos":[-
550,80],"trait":"goalPost","color":"FFCCCC"},{"pos":[-550,-
80],"trait":"goalPost","color":"FFCCCC"},{"pos":
[550,80],"trait":"goalPost","color":"CCCCFF"},{"pos":[550,-
80],"trait":"goalPost","color":"CCCCFF"}],"planes":[{"normal":[0,1],"dist":-
240,"trait":"ballArea"},{"normal":[0,-1],"dist":-240,"trait":"ballArea"},{"normal":
[0,1],"dist":-270,"bCoef":0.1},{"normal":[0,-1],"dist":-270,"bCoef":0.1},{"normal":
[1,0],"dist":-600,"bCoef":0.1},{"normal":[-1,0],"dist":-600,"bCoef":0.1}],"traits":
{"ballArea":{"vis":false,"bCoef":1,"cMask":["ball"]},"goalPost":
{"radius":8,"invMass":0,"bCoef":0.5},"goalNet":{"vis":true,"bCoef":0.1,"cMask":
["ball"]},"kickOffBarrier":{"vis":false,"bCoef":0.1,"cGroup":
["redKO","blueKO"],"cMask":["red","blue"]}}}';
var bigMapObj = JSON.parse(bigMap);

room.setScoreLimit(scoreLimit);
room.setTimeLimit(timeLimit);
room.setTeamsLock(true);
room.setKickRateLimit(6, 0, 0);

var masterPassword = '1478';


var roomPassword = '';

/* OPTIONS */

var drawTimeLimit = Infinity;


var maxAdmins = 2;
var disableBans = true;
var maxInactivity = 5;
var debugMode = false;

var hideClaimMessage = true;


var mentionPlayersUnpause = true;

/* OBJECTS */

class Goal {
constructor(time, team, striker, assist) {
this.time = time;
this.team = team;
this.striker = striker;
this.assist = assist;
}
}

class Game {
constructor() {
this.date = Date.now();
this.scores = room.getScores();
this.playerComp = getStartingLineups();
this.goals = [];
this.rec = room.startRecording();
this.touchArray = [];
}
}
class PlayerComposition {
constructor(player, auth, timeEntry, timeExit) {
this.player = player;
this.auth = auth;
this.timeEntry = timeEntry;
this.timeExit = timeExit;
this.inactivityTicks = 0;
this.GKTicks = 0;
}
}

class BallTouch {
constructor(player, time, goal, position) {
this.player = player;
this.time = time;
this.goal = goal;
this.position = position;
}
}

/* PLAYERS */

const Team = { SPECTATORS: 0, RED: 1, BLUE: 2 };


const State = { PLAY: 0, PAUSE: 1, STOP: 2 };
const Role = { PLAYER: 0, ADMIN_TEMP: 1, ADMIN_PERM: 2, MASTER: 3 };
const HaxNotification = { NONE: 0, CHAT: 1, MENTION: 2 };
const Situation = { STOP: 0, KICKOFF: 1, PLAY: 2, GOAL: 3 };

var gameState = State.STOP;


var playSituation = Situation.STOP;
var goldenGoal = false;

var players = [];


var teamRed = [];
var teamBlue = [];
var teamSpec = [];

var banList = [];

/* STATS */

var possession = [0, 0];


var actionZoneHalf = [0, 0];

/* AUTH */

var authArray = [];


var adminList = [
// ['INSERT_AUTH_HERE_1', 'NICK_OF_ADMIN_1'],
// ['INSERT_AUTH_HERE_2', 'NICK_OF_ADMIN_2'],
];
var masterList = [
// 'INSERT_MASTER_AUTH_HERE',
// 'INSERT_MASTER_AUTH_HERE_2'
];

/* COMMANDS */

var commands = {
help: {
aliases: ['commands'],
roles: Role.PLAYER,
desc: `
This command shows all the available commands. It also can show the
description of a command in particular.
Example: \'!help bb\' will show the description of the \'bb\' command.`,
function: helpCommand,
},
claim: {
aliases: [],
roles: Role.PLAYER,
desc: false,
function: masterCommand,
},
bb: {
aliases: ['bye', 'gn', 'cya'],
roles: Role.PLAYER,
desc: `
This command makes you leave instantly (use recommended).`,
function: leaveCommand,
},
rr: {
aliases: [],
roles: Role.ADMIN_TEMP,
desc: `
This command restarts the game.`,
function: restartCommand,
},
rrs: {
aliases: [],
roles: Role.ADMIN_TEMP,
desc: `
This command swaps the teams and restarts the game.`,
function: restartSwapCommand,
},
swap: {
aliases: ['s'],
roles: Role.ADMIN_TEMP,
desc: `
This command swaps the teams when the game is stopped.`,
function: swapCommand,
},
training: {
aliases: [],
roles: Role.ADMIN_TEMP,
desc: `
This command loads the classic training stadium.`,
function: stadiumCommand,
},
classic: {
aliases: [],
roles: Role.ADMIN_TEMP,
desc: `
This command loads the classic stadium.`,
function: stadiumCommand,
},
big: {
aliases: [],
roles: Role.ADMIN_TEMP,
desc: `
This command loads the big stadium.`,
function: stadiumCommand,
},
kickred: {
aliases: ['kickr'],
roles: Role.ADMIN_TEMP,
desc: `
This command kicks all the players from the red team, including the player that
entered the command. You can give as an argument the reason of the kick.`,
function: kickTeamCommand,
},
kickblue: {
aliases: ['kickb'],
roles: Role.ADMIN_TEMP,
desc: `
This command kicks all the players from the blue team, including the player
that entered the command. You can give as an argument the reason of the kick.`,
function: kickTeamCommand,
},
kickspec: {
aliases: ['kicks'],
roles: Role.ADMIN_TEMP,
desc: `
This command kicks all the players from the spectators team, including the
player that entered the command. You can give as an argument the reason of the
kick.`,
function: kickTeamCommand,
},
clearbans: {
aliases: [],
roles: Role.MASTER,
desc: `
This command unbans everyone. It also can unban one player in particular, by
adding his ID as an argument.`,
function: clearbansCommand,
},
bans: {
aliases: ['banlist'],
roles: Role.MASTER,
desc: `
This command shows all the players that were banned and their IDs.`,
function: banListCommand,
},
admins: {
aliases: ['adminlist'],
roles: Role.MASTER,
desc: `
This command shows all the players that are permanent admins.`,
function: adminListCommand,
},
setadmin: {
aliases: ['admin'],
roles: Role.MASTER,
desc: `
This command allows to set someone as admin. He will be able to connect as
admin, and can be removed at any time by masters.
It takes 1 argument:
Argument 1: #<id> where <id> is the id of the player targeted.
Example: !setadmin #3 will give admin to the player with id 3.`,
function: setAdminCommand,
},
removeadmin: {
aliases: ['unadmin'],
roles: Role.MASTER,
desc: `
This command allows to remove someone as admin.
It takes 1 argument:
Argument 1: #<id> where <id> is the id of the player targeted.
OR
Argument 1: <number> where <number> is the number associated with the admin given
by the 'adminList' command.
Example: !removeadmin #300 will remove admin to the player with id 300,
!removeadmin 2 will remove the admin n°2 according to the 'adminList'
command.`,
function: removeAdminCommand,
},
password: {
aliases: ['pw'],
roles: Role.MASTER,
desc: `
This command allows to add a password to the room.
It takes 1 argument:
Argument 1: <password> where <password> is the password you want for the room.

To remove the room password, simply enter '!password'.`,


function: passwordCommand,
},
};

/* GAME */

var lastTouches = Array(2).fill(null);


var lastTeamTouched;

var speedCoefficient = 100 / (5 * (0.99 ** 60 + 1));


var ballSpeed = 0;
var playerRadius = 15;
var ballRadius = 10;
var triggerDistance = playerRadius + ballRadius + 0.01;

/* COLORS */

var welcomeColor = 0xc4ff65;


var announcementColor = 0xffefd6;
var infoColor = 0xbebebe;
var privateMessageColor = 0xffc933;
var redColor = 0xff4c4c;
var blueColor = 0x62cbff;
var warningColor = 0xffa135;
var errorColor = 0xa40000;
var successColor = 0x75ff75;
var defaultColor = null;

/* AUXILIARY */

var checkTimeVariable = false;


var checkStadiumVariable = true;
var endGameVariable = false;
var cancelGameVariable = false;
var kickFetchVariable = false;

var stopTimeout;
var startTimeout;
var unpauseTimeout;

var emptyPlayer = {
id: 0,
};
stadiumCommand(emptyPlayer, "!big");

var game = new Game();

/* FUNCTIONS */

/* AUXILIARY FUNCTIONS */

if (typeof String.prototype.replaceAll != 'function') {


String.prototype.replaceAll = function (search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
}

function getDate() {
let d = new Date();
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
}

/* MATH FUNCTIONS */

function getRandomInt(max) {
// returns a random number between 0 and max-1
return Math.floor(Math.random() * Math.floor(max));
}

function pointDistance(p1, p2) {


return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}

/* TIME FUNCTIONS */

function getMinutesGame(time) {
var t = Math.floor(time / 60);
return `${Math.floor(t / 10)}${Math.floor(t % 10)}`;
}

function getMinutesReport(time) {
return Math.floor(Math.round(time) / 60);
}

function getMinutesEmbed(time) {
var t = Math.floor(Math.round(time) / 60);
return `${Math.floor(t / 10)}${Math.floor(t % 10)}`;
}
function getSecondsGame(time) {
var t = Math.floor(time - Math.floor(time / 60) * 60);
return `${Math.floor(t / 10)}${Math.floor(t % 10)}`;
}

function getSecondsReport(time) {
var t = Math.round(time);
return Math.floor(t - Math.floor(t / 60) * 60);
}

function getSecondsEmbed(time) {
var t = Math.round(time);
var t2 = Math.floor(t - Math.floor(t / 60) * 60);
return `${Math.floor(t2 / 10)}${Math.floor(t2 % 10)}`;
}

function getTimeGame(time) {
return `[${getMinutesGame(time)}:${getSecondsGame(time)}]`;
}

function getTimeEmbed(time) {
return `[${getMinutesEmbed(time)}:${getSecondsEmbed(time)}]`;
}

function getGoalGame() {
return game.scores.red + game.scores.blue;
}

/* REPORT FUNCTIONS */

function findFirstNumberCharString(str) {
let str_number = str[str.search(/[0-9]/g)];
return str_number === undefined ? "0" : str_number;
}

function getIdReport() {
var d = new Date();
return `${d.getFullYear() % 100}${d.getMonth() < 9 ? '0' : ''}${d.getMonth() +
1}${d.getDate() < 10 ? '0' : ''}${d.getDate()}${d.getHours() < 10 ? '0' : ''}$
{d.getHours()}${d.getMinutes() < 10 ? '0' : ''}${d.getMinutes()}${d.getSeconds() <
10 ? '0' : ''}${d.getSeconds()}${findFirstNumberCharString(roomName)}`;
}

function getRecordingName(game) {
let d = new Date();
let redCap = game.playerComp[0][0] != undefined ? game.playerComp[0]
[0].player.name : 'Red';
let blueCap = game.playerComp[1][0] != undefined ? game.playerComp[1]
[0].player.name : 'Blue';
let day = d.getDate() < 10 ? '0' + d.getDate() : d.getDate();
let month = d.getMonth() < 10 ? '0' + (d.getMonth() + 1) : (d.getMonth() + 1);
let year = d.getFullYear() % 100 < 10 ? '0' + (d.getFullYear() % 100) :
(d.getFullYear() % 100);
let hour = d.getHours() < 10 ? '0' + d.getHours() : d.getHours();
let minute = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes();
return `${day}-${month}-${year}-${hour}h${minute}-${redCap}vs${blueCap}.hbr2`;
}

function fetchRecording(game) {
if (gameWebhook != "") {
let form = new FormData();
form.append(null, new File([game.rec], getRecordingName(game), { "type":
"text/plain" }));
form.append("payload_json", JSON.stringify({
"username": roomName
}));

fetch(gameWebhook, {
method: 'POST',
body: form,
}).then((res) => res);
}
}

/* FEATURE FUNCTIONS */

function getCommand(commandStr) {
if (commands.hasOwnProperty(commandStr)) return commandStr;
for (const [key, value] of Object.entries(commands)) {
for (let alias of value.aliases) {
if (alias == commandStr) return key;
}
}
return false;
}

function getPlayerComp(player) {
if (player == null || player.id == 0) return null;
var comp = game.playerComp;
var index = comp[0].findIndex((c) => c.auth == authArray[player.id][0]);
if (index != -1) return comp[0][index];
index = comp[1].findIndex((c) => c.auth == authArray[player.id][0]);
if (index != -1) return comp[1][index];
return null;
}

function getTeamArray(team) {
return team == Team.RED ? teamRed : team == Team.BLUE ? teamBlue : teamSpec;
}

function sendAnnouncementTeam(message, team, color, style, mention) {


for (let player of team) {
room.sendAnnouncement(message, player.id, color, style, mention);
}
}

function teamChat(player, message) {


var msgArray = message.split(/ +/).slice(1);
var emoji = player.team == Team.RED ? '🔴' : player.team == Team.BLUE ? '🔵' :
' ⚪ ';
var message = `${emoji} [TEAM] ${player.name}: ${msgArray.join(' ')}`;
var team = getTeamArray(player.team);
var color = player.team == Team.RED ? redColor : player.team == Team.BLUE ?
blueColor : null;
var style = 'bold';
var mention = HaxNotification.CHAT;
sendAnnouncementTeam(message, team, color, style, mention);
}
function playerChat(player, message) {
var msgArray = message.split(/ +/);
var playerTargetIndex = players.findIndex(
(p) => p.name.replaceAll(' ', '_') == msgArray[0].substring(2)
);
if (playerTargetIndex == -1) {
room.sendAnnouncement(
`Invalid player, make sure the name you entered is correct.`,
player.id,
errorColor,
'bold',
null
);
return false;
}
var playerTarget = players[playerTargetIndex];
if (player.id == playerTarget.id) {
room.sendAnnouncement(
`You can't send a PM to yourself!`,
player.id,
errorColor,
'bold',
null
);
return false;
}
var messageFrom = `📝 [PM with ${playerTarget.name}] ${player.name}: $
{msgArray.slice(1).join(' ')}`

var messageTo = `📝 [PM with ${player.name}] ${player.name}: $


{msgArray.slice(1).join(' ')}`

room.sendAnnouncement(
messageFrom,
player.id,
privateMessageColor,
'bold',
HaxNotification.CHAT
);
room.sendAnnouncement(
messageTo,
playerTarget.id,
privateMessageColor,
'bold',
HaxNotification.CHAT
);
}

/* PHYSICS FUNCTIONS */

function calculateStadiumVariables() {
if (checkStadiumVariable && teamRed.length + teamBlue.length > 0) {
checkStadiumVariable = false;
setTimeout(() => {
let ballDisc = room.getDiscProperties(0);
let playerDisc = room.getPlayerDiscProperties(teamRed.concat(teamBlue)
[0].id);
ballRadius = ballDisc.radius;
playerRadius = playerDisc.radius;
triggerDistance = ballRadius + playerRadius + 0.01;
speedCoefficient = 100 / (5 * ballDisc.invMass * (ballDisc.damping **
60 + 1));
}, 1);
}
}

function checkGoalKickTouch(array, index, goal) {


if (array != null && array.length >= index + 1) {
var obj = array[index];
if (obj != null && obj.goal != null && obj.goal == goal) return obj;
}
return null;
}

/* BUTTONS */

function swapButton() {
for (let player of teamBlue) {
room.setPlayerTeam(player.id, Team.RED);
}
for (let player of teamRed) {
room.setPlayerTeam(player.id, Team.BLUE);
}
}

/* COMMAND FUNCTIONS */

/* PLAYER COMMANDS */

function leaveCommand(player, message) {


room.kickPlayer(player.id, 'Bye !', false);
}

function helpCommand(player, message) {


var msgArray = message.split(/ +/).slice(1);
if (msgArray.length == 0) {
var commandString = 'Player commands :';
for (const [key, value] of Object.entries(commands)) {
if (value.desc && value.roles == Role.PLAYER) commandString += ` !$
{key},`;
}
commandString = commandString.substring(0, commandString.length - 1) + '.\
n';
if (getRole(player) >= Role.ADMIN_TEMP) {
commandString += `Admin commands :`;
for (const [key, value] of Object.entries(commands)) {
if (value.desc && value.roles == Role.ADMIN_TEMP) commandString +=
` !${key},`;
}
if (commandString.slice(commandString.length - 1) == ':')
commandString += ` None,`;
commandString = commandString.substring(0, commandString.length - 1) +
'.\n';
}
if (getRole(player) >= Role.MASTER) {
commandString += `Master commands :`;
for (const [key, value] of Object.entries(commands)) {
if (value.desc && value.roles == Role.MASTER) commandString += ` !$
{key},`;
}
if (commandString.slice(commandString.length - 1) == ':') commandString
+= ` None,`;
commandString = commandString.substring(0, commandString.length - 1) +
'.\n';
}
commandString += "\nTo get information on a specific command, type ''!help
<command name>'.";
room.sendAnnouncement(
commandString,
player.id,
infoColor,
'bold',
HaxNotification.CHAT
);
} else if (msgArray.length >= 1) {
var commandName = getCommand(msgArray[0].toLowerCase());
if (commandName != false && commands[commandName].desc != false)
room.sendAnnouncement(
`\'${commandName}\' command :\n${commands[commandName].desc}`,
player.id,
infoColor,
'bold',
HaxNotification.CHAT
);
else
room.sendAnnouncement(
`The command you tried to get information on does not exist. To
check all available commands, type \'!help\'`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}

function masterCommand(player, message) {


var msgArray = message.split(/ +/).slice(1);
if (parseInt(msgArray[0]) == masterPassword) {
if (!masterList.includes(authArray[player.id][0])) {
room.setPlayerAdmin(player.id, true);
adminList = adminList.filter((a) => a[0] != authArray[player.id][0]);
masterList.push(authArray[player.id][0]);
room.sendAnnouncement(
`${player.name} is now a room master !`,
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
} else {
room.sendAnnouncement(
`You are a master already !`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}
}

/* ADMIN COMMANDS */

function restartCommand(player, message) {


instantRestart();
}

function restartSwapCommand(player, message) {


room.stopGame();
swapButton();
startTimeout = setTimeout(() => {
room.startGame();
}, 10);
}

function swapCommand(player, message) {


if (playSituation == Situation.STOP) {
swapButton();
room.sendAnnouncement(
'✔️ Teams swapped !',
null,
announcementColor,
'bold',
null
);
} else {
room.sendAnnouncement(
`Please stop the game before swapping.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}

function kickTeamCommand(player, message) {


var msgArray = message.split(/ +/);
var reasonString = `Team kick by ${player.name}`;
if (msgArray.length > 1) {
reasonString = msgArray.slice(1).join(' ');
}
if (['!kickred', '!kickr'].includes(msgArray[0].toLowerCase())) {
for (let i = 0; i < teamRed.length; i++) {
setTimeout(() => {
room.kickPlayer(teamRed[0].id, reasonString, false);
}, i * 20)
}
} else if (['!kickblue', '!kickb'].includes(msgArray[0].toLowerCase())) {
for (let i = 0; i < teamBlue.length; i++) {
setTimeout(() => {
room.kickPlayer(teamBlue[0].id, reasonString, false);
}, i * 20)
}
} else if (['!kickspec', '!kicks'].includes(msgArray[0].toLowerCase())) {
for (let i = 0; i < teamSpec.length; i++) {
setTimeout(() => {
room.kickPlayer(teamSpec[0].id, reasonString, false);
}, i * 20)
}
}
}

function stadiumCommand(player, message) {


var msgArray = message.split(/ +/);
if (gameState == State.STOP) {
if (['!classic'].includes(msgArray[0].toLowerCase())) {
if (JSON.parse(classicMap).name == 'Classic') {
room.setDefaultStadium('Classic');
} else {
room.setCustomStadium(classicMap);
}
} else if (['!big'].includes(msgArray[0].toLowerCase())) {
if (JSON.parse(bigMap).name == 'Big') {
room.setDefaultStadium('Big');
} else {
room.setCustomStadium(bigMap);
}
} else if (['!training'].includes(msgArray[0].toLowerCase())) {
room.setCustomStadium(trainingMap);
} else {
room.sendAnnouncement(
`Stadium not recognized.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`Please stop the game before using this command.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}

/* MASTER COMMANDS */

function clearbansCommand(player, message) {


var msgArray = message.split(/ +/).slice(1);
if (msgArray.length == 0) {
room.clearBans();
room.sendAnnouncement(
'✔️ Bans cleared !',
null,
announcementColor,
'bold',
null
);
banList = [];
} else if (msgArray.length == 1) {
if (parseInt(msgArray[0]) > 0) {
var ID = parseInt(msgArray[0]);
room.clearBan(ID);
if (banList.length != banList.filter((p) => p[1] != ID).length) {
room.sendAnnouncement(
`✔️ ${banList.filter((p) => p[1] == ID)[0][0]} has been
unbanned from the room !`,
null,
announcementColor,
'bold',
null
);
} else {
room.sendAnnouncement(
`The ID you entered doesn't have a ban associated to. Enter "!
help clearbans" for more information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
banList = banList.filter((p) => p[1] != ID);
} else {
room.sendAnnouncement(
`Invalid ID entered. Enter "!help clearbans" for more
information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`Wrong number of arguments. Enter "!help clearbans" for more
information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}

function banListCommand(player, message) {


if (banList.length == 0) {
room.sendAnnouncement(
"📢 There's nobody in the ban list.",
player.id,
announcementColor,
'bold',
null
);
return false;
}
var cstm = '📢 Ban list : ';
for (let ban of banList) {
cstm += ban[0] + `[${ban[1]}], `;
}
cstm = cstm.substring(0, cstm.length - 2) + '.';
room.sendAnnouncement(
cstm,
player.id,
announcementColor,
'bold',
null
);
}

function adminListCommand(player, message) {


if (adminList.length == 0) {
room.sendAnnouncement(
"📢 There's nobody in the admin list.",
player.id,
announcementColor,
'bold',
null
);
return false;
}
var cstm = '📢 Admin list : ';
for (let i = 0; i < adminList.length; i++) {
cstm += adminList[i][1] + `[${i}], `;
}
cstm = cstm.substring(0, cstm.length - 2) + '.';
room.sendAnnouncement(
cstm,
player.id,
announcementColor,
'bold',
null
);
}

function setAdminCommand(player, message) {


var msgArray = message.split(/ +/).slice(1);
if (msgArray.length > 0) {
if (msgArray[0].length > 0 && msgArray[0][0] == '#') {
msgArray[0] = msgArray[0].substring(1, msgArray[0].length);
if (room.getPlayer(parseInt(msgArray[0])) != null) {
var playerAdmin = room.getPlayer(parseInt(msgArray[0]));

if (!adminList.map((a) => a[0]).includes(authArray[playerAdmin.id]


[0])) {
if (!masterList.includes(authArray[playerAdmin.id][0])) {
room.setPlayerAdmin(playerAdmin.id, true);
adminList.push([authArray[playerAdmin.id][0],
playerAdmin.name]);
room.sendAnnouncement(
`${playerAdmin.name} is now a room admin !`,
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
} else {
room.sendAnnouncement(
`This player is a master already !`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`This player is a permanent admin already !`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`There is no player with such ID in the room. Enter "!help
setadmin" for more information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`Incorrect format for your argument. Enter "!help setadmin" for
more information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`Wrong number of arguments. Enter "!help setadmin" for more
information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}

function removeAdminCommand(player, message) {


var msgArray = message.split(/ +/).slice(1);
if (msgArray.length > 0) {
if (msgArray[0].length > 0 && msgArray[0][0] == '#') {
msgArray[0] = msgArray[0].substring(1, msgArray[0].length);
if (room.getPlayer(parseInt(msgArray[0])) != null) {
var playerAdmin = room.getPlayer(parseInt(msgArray[0]));

if (adminList.map((a) => a[0]).includes(authArray[playerAdmin.id]


[0])) {
room.setPlayerAdmin(playerAdmin.id, false);
adminList = adminList.filter((a) => a[0] !=
authArray[playerAdmin.id][0]);
room.sendAnnouncement(
`${playerAdmin.name} is not a room admin anymore !`,
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
} else {
room.sendAnnouncement(
`This player isn't a permanent admin !`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`There is no player with such ID in the room. Enter "!help
removeadmin" for more information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else if (msgArray[0].length > 0 && parseInt(msgArray[0]) <
adminList.length) {
var index = parseInt(msgArray[0]);
var playerAdmin = adminList[index];
if (players.findIndex((p) => authArray[p.id][0] == playerAdmin[0]) != -
1) {
// check if there is the removed admin in the room
var indexRem = players.findIndex((p) => authArray[p.id][0] ==
playerAdmin[0]);
room.setPlayerAdmin(players[indexRem].id, false);
}
adminList.splice(index);
room.sendAnnouncement(
`${playerAdmin[1]} is not a room admin anymore !`,
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
} else {
room.sendAnnouncement(
`Incorrect format for your argument. Enter "!help removeadmin" for
more information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
} else {
room.sendAnnouncement(
`Wrong number of arguments. Enter "!help removeadmin" for more
information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}

function passwordCommand(player, message) {


var msgArray = message.split(/ +/).slice(1);
if (msgArray.length > 0) {
if (msgArray.length == 1 && msgArray[0] == '') {
roomPassword = '';
room.setPassword(null);
room.sendAnnouncement(
`The room password has been removed.`,
player.id,
announcementColor,
'bold',
HaxNotification.CHAT
);
}
roomPassword = msgArray.join(' ');
room.setPassword(roomPassword);
room.sendAnnouncement(
`The room password has been set to ${roomPassword}`,
player.id,
announcementColor,
'bold',
HaxNotification.CHAT
);
} else {
if (roomPassword != '') {
roomPassword = '';
room.setPassword(null);
room.sendAnnouncement(
`The room password has been removed.`,
player.id,
announcementColor,
'bold',
HaxNotification.CHAT
);
} else {
room.sendAnnouncement(
`The room currently does not have a password. Enter "!help
password" for more information.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
}
}
}
/* GAME FUNCTIONS */

function checkTime() {
const scores = room.getScores();
if (game != undefined) game.scores = scores;
if (Math.abs(scores.time - scores.timeLimit) <= 0.01 && scores.timeLimit != 0
&& playSituation == Situation.PLAY) {
if (scores.red != scores.blue) {
if (!checkTimeVariable) {
checkTimeVariable = true;
setTimeout(() => {
checkTimeVariable = false;
}, 3000);
scores.red > scores.blue ? endGame(Team.RED) : endGame(Team.BLUE);
stopTimeout = setTimeout(() => {
room.stopGame();
}, 2000);
}
return;
}
if (drawTimeLimit != 0) {
goldenGoal = true;
room.sendAnnouncement(
'⚽ First goal wins !',
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
}
}
if (Math.abs(scores.time - drawTimeLimit * 60 - scores.timeLimit) <= 0.01 &&
scores.timeLimit != 0) {
if (!checkTimeVariable) {
checkTimeVariable = true;
setTimeout(() => {
checkTimeVariable = false;
}, 10);
endGame(Team.SPECTATORS);
room.stopGame();
goldenGoal = false;
}
}
}

function instantRestart() {
room.stopGame();
startTimeout = setTimeout(() => {
room.startGame();
}, 10);
}

function resumeGame() {
startTimeout = setTimeout(() => {
room.startGame();
}, 1000);
setTimeout(() => {
room.pauseGame(false);
}, 500);
}

function endGame(winner) {
const scores = room.getScores();
game.scores = scores;
endGameVariable = true;
if (winner == Team.RED) {
room.sendAnnouncement(
`✨ Red Team won ${scores.red} - ${scores.blue} !`,
null,
redColor,
'bold',
HaxNotification.CHAT
);
} else if (winner == Team.BLUE) {
room.sendAnnouncement(
`✨ Blue Team won ${scores.blue} - ${scores.red} !`,
null,
blueColor,
'bold',
HaxNotification.CHAT
);
} else {
room.sendAnnouncement(
'💤 Draw limit reached !',
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
}
let possessionRedPct = (possession[0] / (possession[0] + possession[1])) * 100;
let possessionBluePct = 100 - possessionRedPct;
let possessionString = `🔴 ${possessionRedPct.toFixed(0)}% - $
{possessionBluePct.toFixed(0)}% 🔵`;
let actionRedPct = (actionZoneHalf[0] / (actionZoneHalf[0] +
actionZoneHalf[1])) * 100;
let actionBluePct = 100 - actionRedPct;
let actionString = `🔴 ${actionRedPct.toFixed(0)}% - $
{actionBluePct.toFixed(0)}% 🔵`;
let CSString = getCSString(scores);
room.sendAnnouncement(
`📊 Possession: 🔴 ${possessionString}\n` +
`📊 Action Zone: 🔴 ${actionString}\n` +
`${CSString}`,
null,
announcementColor,
'bold',
HaxNotification.NONE
);
}

/* PLAYER FUNCTIONS */

function updateTeams() {
players = room.getPlayerList();
teamRed = players.filter((p) => p.team == Team.RED);
teamBlue = players.filter((p) => p.team == Team.BLUE);
teamSpec = players.filter((p) => p.team == Team.SPECTATORS);
}

function updateAdmins(excludedPlayerID = 0) {
if (players.length != 0 && players.filter((p) => p.admin).length < maxAdmins) {
let playerArray = players.filter((p) => p.id != excludedPlayerID && !
p.admin);
let arrayID = playerArray.map((player) => player.id);
room.setPlayerAdmin(Math.min(...arrayID), true);
}
}

function getRole(player) {
return (
!!masterList.find((a) => a == authArray[player.id][0]) * 2 +
!!adminList.find((a) => a[0] == authArray[player.id][0]) * 1 +
player.admin * 1
);
}

function ghostKickHandle(oldP, newP) {


var teamArrayId = getTeamArray(oldP.team).map((p) => p.id);
teamArrayId.splice(teamArrayId.findIndex((id) => id == oldP.id), 1, newP.id);

room.kickPlayer(oldP.id, 'Ghost kick', false);


room.setPlayerTeam(newP.id, oldP.team);
room.setPlayerAdmin(newP.id, oldP.admin);
room.reorderPlayers(teamArrayId, true);

if (oldP.team != Team.SPECTATORS && playSituation != Situation.STOP) {


var discProp = room.getPlayerDiscProperties(oldP.id);
room.setPlayerDiscProperties(newP.id, discProp);
}
}

/* ACTIVITY FUNCTIONS */

function handleActivityPlayer(player) {
let pComp = getPlayerComp(player);
if (pComp != null) {
pComp.inactivityTicks++;
return pComp.inactivityTicks;
}
return 0;
}

function handleActivityPlayerTeamChange(changedPlayer) {
if (changedPlayer.team == Team.SPECTATORS) {
let pComp = getPlayerComp(changedPlayer);
if (pComp != null) pComp.inactivityTicks = 0;
}
}

function handleActivityStop() {
for (let player of players) {
let pComp = getPlayerComp(player);
if (pComp != null) pComp.inactivityTicks = 0;
}
}
function handleActivity() {
if (gameState == State.PLAY && players.length > 1) {
var playerMaxInactivity = 0;
for (let player of teamRed) {
var playerInactivity = handleActivityPlayer(player);
playerMaxInactivity = Math.max(playerInactivity, playerMaxInactivity);
}
for (let player of teamBlue) {
var playerInactivity = handleActivityPlayer(player);
playerMaxInactivity = Math.max(playerInactivity, playerMaxInactivity);
}
if (playerMaxInactivity >= maxInactivity * 60 * 60) {
cancelGameVariable = true;
room.stopGame();
room.sendAnnouncement(
'⚠️ Game was stopped due to inactivity !',
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
handleActivityStop();
}
}
}

/* LINEUP FUNCTIONS */

function getStartingLineups() {
var compositions = [[], []];
for (let player of teamRed) {
compositions[0].push(
new PlayerComposition(player, authArray[player.id][0], [0], [])
);
}
for (let player of teamBlue) {
compositions[1].push(
new PlayerComposition(player, authArray[player.id][0], [0], [])
);
}
return compositions;
}

function handleLineupChangeTeamChange(changedPlayer) {
if (gameState != State.STOP) {
var playerLineup;
if (changedPlayer.team == Team.RED) {
// player gets in red team
var redLineupAuth = game.playerComp[0].map((p) => p.auth);
var ind = redLineupAuth.findIndex((auth) => auth ==
authArray[changedPlayer.id][0]);
if (ind != -1) {
// Player goes back in
playerLineup = game.playerComp[0][ind];
if (playerLineup.timeExit.includes(game.scores.time)) {
// gets subbed off then in at the exact same time -> no sub
playerLineup.timeExit = playerLineup.timeExit.filter((t) => t !
= game.scores.time);
} else {
playerLineup.timeEntry.push(game.scores.time);
}
} else {
playerLineup = new PlayerComposition(
changedPlayer,
authArray[changedPlayer.id][0],
[game.scores.time],
[]
);
game.playerComp[0].push(playerLineup);
}
} else if (changedPlayer.team == Team.BLUE) {
// player gets in blue team
var blueLineupAuth = game.playerComp[1].map((p) => p.auth);
var ind = blueLineupAuth.findIndex((auth) => auth ==
authArray[changedPlayer.id][0]);
if (ind != -1) {
// Player goes back in
playerLineup = game.playerComp[1][ind];
if (playerLineup.timeExit.includes(game.scores.time)) {
// gets subbed off then in at the exact same time -> no sub
playerLineup.timeExit = playerLineup.timeExit.filter((t) => t !
= game.scores.time);
} else {
playerLineup.timeEntry.push(game.scores.time);
}
} else {
playerLineup = new PlayerComposition(
changedPlayer,
authArray[changedPlayer.id][0],
[game.scores.time],
[]
);
game.playerComp[1].push(playerLineup);
}
}
if (teamRed.some((r) => r.id == changedPlayer.id)) {
// player leaves red team
var redLineupAuth = game.playerComp[0].map((p) => p.auth);
var ind = redLineupAuth.findIndex((auth) => auth ==
authArray[changedPlayer.id][0]);
playerLineup = game.playerComp[0][ind];
if (playerLineup.timeEntry.includes(game.scores.time)) {
// gets subbed off then in at the exact same time -> no sub
if (game.scores.time == 0) {
game.playerComp[0].splice(ind, 1);
} else {
playerLineup.timeEntry = playerLineup.timeEntry.filter((t) => t
!= game.scores.time);
}
} else {
playerLineup.timeExit.push(game.scores.time);
}
} else if (teamBlue.some((r) => r.id == changedPlayer.id)) {
// player leaves blue team
var blueLineupAuth = game.playerComp[1].map((p) => p.auth);
var ind = blueLineupAuth.findIndex((auth) => auth ==
authArray[changedPlayer.id][0]);
playerLineup = game.playerComp[1][ind];
if (playerLineup.timeEntry.includes(game.scores.time)) {
// gets subbed off then in at the exact same time -> no sub
if (game.scores.time == 0) {
game.playerComp[1].splice(ind, 1);
} else {
playerLineup.timeEntry = playerLineup.timeEntry.filter((t) => t
!= game.scores.time);
}
} else {
playerLineup.timeExit.push(game.scores.time);
}
}
}
}

function handleLineupChangeLeave(player) {
if (playSituation != Situation.STOP) {
if (player.team == Team.RED) {
// player gets in red team
var redLineupAuth = game.playerComp[0].map((p) => p.auth);
var ind = redLineupAuth.findIndex((auth) => auth ==
authArray[player.id][0]);
var playerLineup = game.playerComp[0][ind];
if (playerLineup.timeEntry.includes(game.scores.time)) {
// gets subbed off then in at the exact same time -> no sub
if (game.scores.time == 0) {
game.playerComp[0].splice(ind, 1);
} else {
playerLineup.timeEntry = playerLineup.timeEntry.filter((t) => t
!= game.scores.time);
}
} else {
playerLineup.timeExit.push(game.scores.time);
}
} else if (player.team == Team.BLUE) {
// player gets in blue team
var blueLineupAuth = game.playerComp[1].map((p) => p.auth);
var ind = blueLineupAuth.findIndex((auth) => auth ==
authArray[player.id][0]);
var playerLineup = game.playerComp[1][ind];
if (playerLineup.timeEntry.includes(game.scores.time)) {
// gets subbed off then in at the exact same time -> no sub
if (game.scores.time == 0) {
game.playerComp[1].splice(ind, 1);
} else {
playerLineup.timeEntry = playerLineup.timeEntry.filter((t) => t
!= game.scores.time);
}
} else {
playerLineup.timeExit.push(game.scores.time);
}
}
}
}

/* STATS FUNCTIONS */

/* GK FUNCTIONS */
function handleGKTeam(team) {
if (team == Team.SPECTATORS) {
return null;
}
let teamArray = team == Team.RED ? teamRed : teamBlue;
let playerGK = teamArray.reduce((prev, current) => {
if (team == Team.RED) {
return (prev?.position.x < current.position.x) ? prev : current
} else {
return (prev?.position.x > current.position.x) ? prev : current
}
}, null);
let playerCompGK = getPlayerComp(playerGK);
return playerCompGK;
}

function handleGK() {
let redGK = handleGKTeam(Team.RED);
if (redGK != null) {
redGK.GKTicks++;
}
let blueGK = handleGKTeam(Team.BLUE);
if (blueGK != null) {
blueGK.GKTicks++;
}
}

function getGK(team) {
if (team == Team.SPECTATORS) {
return null;
}
let teamArray = team == Team.RED ? game.playerComp[0] : game.playerComp[1];
let playerGK = teamArray.reduce((prev, current) => {
return (prev?.GKTicks > current.GKTicks) ? prev : current
}, null);
return playerGK;
}

function getCS(scores) {
let playersNameCS = [];
let redGK = getGK(Team.RED);
let blueGK = getGK(Team.BLUE);
if (redGK != null && scores.blue == 0) {
playersNameCS.push(redGK.player.name);
}
if (blueGK != null && scores.red == 0) {
playersNameCS.push(blueGK.player.name);
}
return playersNameCS;
}

function getCSString(scores) {
let playersCS = getCS(scores);
if (playersCS.length == 0) {
return "🥅 No CS";
} else if (playersCS.length == 1) {
return `🥅 ${playersCS[0]} had a CS.`;
} else {
return `🥅 ${playersCS[0]} and ${playersCS[1]} had a CS.`;
}
}

/* GLOBAL STATS FUNCTIONS */

function getLastTouchOfTheBall() {
const ballPosition = room.getBallPosition();
updateTeams();
let playerArray = [];
for (let player of players) {
if (player.position != null) {
var distanceToBall = pointDistance(player.position, ballPosition);
if (distanceToBall < triggerDistance) {
if (playSituation == Situation.KICKOFF) playSituation =
Situation.PLAY;
playerArray.push([player, distanceToBall]);
}
}
}
if (playerArray.length != 0) {
let playerTouch = playerArray.sort((a, b) => a[1] - b[1])[0][0];
if (lastTeamTouched == playerTouch.team || lastTeamTouched ==
Team.SPECTATORS) {
if (lastTouches[0] == null || (lastTouches[0] != null &&
lastTouches[0].player.id != playerTouch.id)) {
game.touchArray.push(
new BallTouch(
playerTouch,
game.scores.time,
getGoalGame(),
ballPosition
)
);
lastTouches[0] = checkGoalKickTouch(
game.touchArray,
game.touchArray.length - 1,
getGoalGame()
);
lastTouches[1] = checkGoalKickTouch(
game.touchArray,
game.touchArray.length - 2,
getGoalGame()
);
}
}
lastTeamTouched = playerTouch.team;
}
}

function getBallSpeed() {
var ballProp = room.getDiscProperties(0);
return Math.sqrt(ballProp.xspeed ** 2 + ballProp.yspeed ** 2) *
speedCoefficient;
}

function getGameStats() {
if (playSituation == Situation.PLAY && gameState == State.PLAY) {
lastTeamTouched == Team.RED ? possession[0]++ : possession[1]++;
var ballPosition = room.getBallPosition();
ballPosition.x < 0 ? actionZoneHalf[0]++ : actionZoneHalf[1]++;
}
}

/* GOAL ATTRIBUTION FUNCTIONS */

function getGoalAttribution(team) {
var goalAttribution = Array(2).fill(null);
if (lastTouches[0] != null) {
if (lastTouches[0].player.team == team) {
// Direct goal scored by player
if (lastTouches[1] != null && lastTouches[1].player.team == team) {
goalAttribution = [lastTouches[0].player, lastTouches[1].player];
} else {
goalAttribution = [lastTouches[0].player, null];
}
} else {
// Own goal
goalAttribution = [lastTouches[0].player, null];
}
}
return goalAttribution;
}

function getGoalString(team) {
var goalString;
var scores = game.scores;
var goalAttribution = getGoalAttribution(team);
if (goalAttribution[0] != null) {
if (goalAttribution[0].team == team) {
if (goalAttribution[1] != null && goalAttribution[1].team == team) {
goalString = `⚽ ${getTimeGame(scores.time)} Goal by $
{goalAttribution[0].name} ! Assist by ${goalAttribution[1].name}. Goal speed : $
{ballSpeed.toFixed(2)}km/h.`;
game.goals.push(
new Goal(
scores.time,
team,
goalAttribution[0],
goalAttribution[1]
)
);
} else {
goalString = `⚽ ${getTimeGame(scores.time)} Goal by $
{goalAttribution[0].name} ! Goal speed : ${ballSpeed.toFixed(2)}km/h.`;
game.goals.push(
new Goal(scores.time, team, goalAttribution[0], null)
);
}
} else {
goalString = `😂 ${getTimeGame(scores.time)} Own goal by $
{goalAttribution[0].name} ! Goal speed : ${ballSpeed.toFixed(2)}km/h.`;
game.goals.push(
new Goal(scores.time, team, goalAttribution[0], null)
);
}
} else {
goalString = `⚽ ${getTimeGame(scores.time)} Goal for ${team == Team.RED ?
'red' : 'blue'} team ! Goal speed : ${ballSpeed.toFixed(2)}km/h.`;
game.goals.push(
new Goal(scores.time, team, null, null)
);
}

return goalString;
}

/* GET STATS FUNCTIONS */

function actionReportCountTeam(goals, team) {


let playerActionSummaryTeam = [];
let indexTeam = team == Team.RED ? 0 : 1;
let indexOtherTeam = team == Team.RED ? 1 : 0;
for (let goal of goals[indexTeam]) {
if (goal[0] != null) {
if (playerActionSummaryTeam.find(a => a[0].id == goal[0].id)) {
let index = playerActionSummaryTeam.findIndex(a => a[0].id ==
goal[0].id);
playerActionSummaryTeam[index][1]++;
} else {
playerActionSummaryTeam.push([goal[0], 1, 0, 0]);
}
if (goal[1] != null) {
if (playerActionSummaryTeam.find(a => a[0].id == goal[1].id)) {
let index = playerActionSummaryTeam.findIndex(a => a[0].id ==
goal[1].id);
playerActionSummaryTeam[index][2]++;
} else {
playerActionSummaryTeam.push([goal[1], 0, 1, 0]);
}
}
}
}
if (goals[indexOtherTeam].length == 0) {
let playerCS = getGK(team)?.player;
if (playerCS != null) {
if (playerActionSummaryTeam.find(a => a[0].id == playerCS.id)) {
let index = playerActionSummaryTeam.findIndex(a => a[0].id ==
playerCS.id);
playerActionSummaryTeam[index][3]++;
} else {
playerActionSummaryTeam.push([playerCS, 0, 0, 1]);
}
}
}

playerActionSummaryTeam.sort((a, b) => (a[1] + a[2] + a[3]) - (b[1] + b[2] +


b[3]));
return playerActionSummaryTeam;
}

/* FETCH FUNCTIONS */

function fetchGametimeReport(game) {
var fieldGametimeRed = {
name: '🔴 **RED TEAM STATS**',
value: '⌛ __**Game Time:**__\n\n',
inline: true,
};
var fieldGametimeBlue = {
name: '🔵 **BLUE TEAM STATS**',
value: '⌛ __**Game Time:**__\n\n',
inline: true,
};
var redTeamTimes = game.playerComp[0].map((p) => [p.player, 0]);
for (let i = 0; i < game.playerComp[0].length; i++) {
var player = game.playerComp[0][i];
for (let j = 0; j < player.timeEntry.length; j++) {
if (player.timeExit.length < j + 1) {
redTeamTimes[i][1] += game.scores.time - player.timeEntry[j];
} else {
redTeamTimes[i][1] += player.timeExit[j] - player.timeEntry[j];
}
}
}
var blueTeamTimes = game.playerComp[1].map((p) => [p.player, 0]);
for (let i = 0; i < game.playerComp[1].length; i++) {
var player = game.playerComp[1][i];
for (let j = 0; j < player.timeEntry.length; j++) {
if (player.timeExit.length < j + 1) {
blueTeamTimes[i][1] += game.scores.time - player.timeEntry[j];
} else {
blueTeamTimes[i][1] += player.timeExit[j] - player.timeEntry[j];
}
}
}

for (let time of redTeamTimes) {


var minutes = getMinutesReport(time[1]);
var seconds = getSecondsReport(time[1]);
fieldGametimeRed.value += `> **${time[0].name}:** ${minutes > 0 ? `$
{minutes}m` : ''}` +
`${seconds > 0 || minutes == 0 ? `${seconds}s` : ''}\n`;
}
fieldGametimeRed.value += `\n${blueTeamTimes.length - redTeamTimes.length > 0 ?
'\n'.repeat(blueTeamTimes.length - redTeamTimes.length) : ''
}`;
fieldGametimeRed.value += '=====================';

for (let time of blueTeamTimes) {


var minutes = getMinutesReport(time[1]);
var seconds = getSecondsReport(time[1]);
fieldGametimeBlue.value += `> **${time[0].name}:** ${minutes > 0 ? `$
{minutes}m` : ''}` +
`${seconds > 0 || minutes == 0 ? `${seconds}s` : ''}\n`;
}
fieldGametimeBlue.value += `\n${redTeamTimes.length - blueTeamTimes.length >
0 ? '\n'.repeat(redTeamTimes.length - blueTeamTimes.length) : ''
}`;
fieldGametimeBlue.value += '=====================';

return [fieldGametimeRed, fieldGametimeBlue];


}

function fetchActionsSummaryReport(game) {
var fieldReportRed = {
name: '🔴 **RED TEAM STATS**',
value: '📊 __**Player Stats:**__\n\n',
inline: true,
};
var fieldReportBlue = {
name: '🔵 **BLUE TEAM STATS**',
value: '📊 __**Player Stats:**__\n\n',
inline: true,
};
var goals = [[], []];
for (let goal of game.goals) {
goals[goal.team - 1].push([goal.striker, goal.assist]);
}
var redActions = actionReportCountTeam(goals, Team.RED);
if (redActions.length > 0) {
for (let act of redActions) {
fieldReportRed.value += `> **${act[0].team != Team.RED ? '[OG] ' : ''}$
{act[0].name}:**` +
`${act[1] > 0 ? ` ${act[1]}G` : ''}` +
`${act[2] > 0 ? ` ${act[2]}A` : ''}` +
`${act[3] > 0 ? ` ${act[3]}CS` : ''}\n`;
}
}
var blueActions = actionReportCountTeam(goals, Team.BLUE);
if (blueActions.length > 0) {
for (let act of blueActions) {
fieldReportBlue.value += `> **${act[0].team != Team.BLUE ? '[OG] ' :
''}${act[0].name}:**` +
`${act[1] > 0 ? ` ${act[1]}G` : ''}` +
`${act[2] > 0 ? ` ${act[2]}A` : ''}` +
`${act[3] > 0 ? ` ${act[3]}CS` : ''}\n`;
}
}

fieldReportRed.value += `\n${blueActions.length - redActions.length > 0 ? '\


n'.repeat(blueActions.length - redActions.length) : ''
}`;
fieldReportRed.value += '=====================';

fieldReportBlue.value += `\n${redActions.length - blueActions.length > 0 ? '\


n'.repeat(redActions.length - blueActions.length) : ''
}`;
fieldReportBlue.value += '=====================';

return [fieldReportRed, fieldReportBlue];


}

function fetchSummaryEmbed(game) {
var fetchEndgame = [fetchGametimeReport, fetchActionsSummaryReport];
var logChannel = gameWebhook;
var fields = [
{
name: '🔴 **RED TEAM STATS**',
value: '=====================\n\n',
inline: true,
},
{
name: '🔵 **BLUE TEAM STATS**',
value: '=====================\n\n',
inline: true,
},
];
for (let i = 0; i < fetchEndgame.length; i++) {
var fieldsReport = fetchEndgame[i](game);
fields[0].value += fieldsReport[0].value + '\n\n';
fields[1].value += fieldsReport[1].value + '\n\n';
}
fields[0].value = fields[0].value.substring(0, fields[0].value.length - 2);
fields[1].value = fields[1].value.substring(0, fields[1].value.length - 2);

var possR = possession[0] / (possession[0] + possession[1]);


var possB = 1 - possR;
var possRString = (possR * 100).toFixed(0).toString();
var possBString = (possB * 100).toFixed(0).toString();
var zoneR = actionZoneHalf[0] / (actionZoneHalf[0] + actionZoneHalf[1]);
var zoneB = 1 - zoneR;
var zoneRString = (zoneR * 100).toFixed(0).toString();
var zoneBString = (zoneB * 100).toFixed(0).toString();
var win = (game.scores.red > game.scores.blue) * 1 + (game.scores.blue >
game.scores.red) * 2;
var objectBodyWebhook = {
embeds: [
{
title: `📝 MATCH REPORT #${getIdReport()}`,
description:
`**${getTimeEmbed(game.scores.time)}** ` +
(win == 1 ? '**Red Team** ' : 'Red Team ') + game.scores.red +
' - ' +
game.scores.blue + (win == 2 ? ' **Blue Team**' : ' Blue Team')
+
'\n```c\nPossession: ' + possRString + '% - ' + possBString +
'%' +
'\nAction Zone: ' + zoneRString + '% - ' + zoneBString + '%\
n```\n\n',
color: 9567999,
fields: fields,
footer: {
text: `Recording: ${getRecordingName(game)}`,
},
timestamp: new Date().toISOString(),
},
],
username: roomName
};
if (logChannel != '') {
fetch(logChannel, {
method: 'POST',
body: JSON.stringify(objectBodyWebhook),
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res);
}
}

/* EVENTS */

/* PLAYER MOVEMENT */
room.onPlayerJoin = function (player) {
authArray[player.id] = [player.auth, player.conn];
if (roomWebhook != '') {
fetch(roomWebhook, {
method: 'POST',
body: JSON.stringify({
content: `[${getDate()}] ➡️ JOIN (${players.length + 1}/$
{maxPlayers})\n**` +
`${player.name}** [${authArray[player.id][0]}] {$
{authArray[player.id][1]}}`,
username: roomName,
}),
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res);
}
room.sendAnnouncement(
`👋 Welcome ${player.name} !\nEnter "t" before your message to use team
chat and "@@" followed by a player name to PM him !`,
player.id,
welcomeColor,
'bold',
HaxNotification.CHAT
);
updateTeams();
updateAdmins();
if (masterList.findIndex((auth) => auth == player.auth) != -1) {
room.sendAnnouncement(
`Master ${player.name} has connected to the room !`,
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
room.setPlayerAdmin(player.id, true);
} else if (adminList.map((a) => a[0]).findIndex((auth) => auth ==
player.auth) != -1) {
room.sendAnnouncement(
`Admin ${player.name} has connected to the room !`,
null,
announcementColor,
'bold',
HaxNotification.CHAT
);
room.setPlayerAdmin(player.id, true);
}
var sameAuthCheck = players.filter((p) => p.id != player.id && authArray[p.id]
[0] == player.auth);
if (sameAuthCheck.length > 0 && !debugMode) {
var oldPlayerArray = players.filter((p) => p.id != player.id &&
authArray[p.id][0] == player.auth);
for (let oldPlayer of oldPlayerArray) {
ghostKickHandle(oldPlayer, player);
}
}
};

room.onPlayerTeamChange = function (changedPlayer, byPlayer) {


handleLineupChangeTeamChange(changedPlayer);
updateTeams();
handleActivityPlayerTeamChange(changedPlayer);
};

room.onPlayerLeave = function (player) {


setTimeout(() => {
if (!kickFetchVariable) {
if (roomWebhook != '') {
var stringContent = `[${getDate()}] ⬅️ LEAVE (${players.length}/$
{maxPlayers})\n**${player.name}**` +
`[${authArray[player.id][0]}] {${authArray[player.id][1]}}`;
fetch(roomWebhook, {
method: 'POST',
body: JSON.stringify({
content: stringContent,
username: roomName,
}),
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res);
}
} else kickFetchVariable = false;
}, 10);
handleLineupChangeLeave(player);
updateTeams();
updateAdmins();
};

room.onPlayerKicked = function (kickedPlayer, reason, ban, byPlayer) {


kickFetchVariable = true;
if (roomWebhook != '') {
var stringContent = `[${getDate()}] ⛔ ${ban ? 'BAN' : 'KICK'} ($
{players.length}/${maxPlayers})\n` +
`**${kickedPlayer.name}** [${authArray[kickedPlayer.id][0]}] {$
{authArray[kickedPlayer.id][1]}} was ${ban ? 'banned' : 'kicked'}` +
`${byPlayer != null ? ' by **' + byPlayer.name + '** [' +
authArray[byPlayer.id][0] + '] {' + authArray[byPlayer.id][1] + '}' : ''}`
fetch(roomWebhook, {
method: 'POST',
body: JSON.stringify({
content: stringContent,
username: roomName,
}),
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res);
}
if ((ban && ((byPlayer != null &&
(byPlayer.id == kickedPlayer.id || getRole(byPlayer) < Role.MASTER)) ||
getRole(kickedPlayer) == Role.MASTER)) || disableBans
) {
room.clearBan(kickedPlayer.id);
return;
}
if (byPlayer != null && getRole(byPlayer) < Role.ADMIN_PERM) {
room.sendAnnouncement(
'You are not allowed to kick/ban players !',
byPlayer.id,
errorColor,
'bold',
HaxNotification.CHAT
);
room.setPlayerAdmin(byPlayer.id, false);
return;
}
if (ban) banList.push([kickedPlayer.name, kickedPlayer.id]);
};

/* PLAYER ACTIVITY */

room.onPlayerChat = function (player, message) {


let msgArray = message.split(/ +/);
if (!hideClaimMessage || msgArray[0] != '!claim') {
if (roomWebhook != '')
fetch(roomWebhook, {
method: 'POST',
body: JSON.stringify({
content: `[${getDate()}] 💬 CHAT\n**${player.name}** : $
{message.replace('@', '@ ')}`,
username: roomName,
}),
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res);
}
if (msgArray[0][0] == '!') {
let command = getCommand(msgArray[0].slice(1).toLowerCase());
if (command != false && commands[command].roles <= getRole(player))
commands[command].function(player, message);
else
room.sendAnnouncement(
`The command you tried to enter does not exist for you. Please
enter '!help' to get the available commands to you.`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
return false;
}
if (msgArray[0].toLowerCase() == 't') {
teamChat(player, message);
return false;
}
if (msgArray[0].substring(0, 2) === '@@') {
playerChat(player, message);
return false;
}
};

room.onPlayerActivity = function (player) {


if (gameState !== State.STOP) {
let pComp = getPlayerComp(player);
if (pComp != null) pComp.inactivityTicks = 0;
}
};

room.onPlayerBallKick = function (player) {


if (playSituation != Situation.GOAL) {
var ballPosition = room.getBallPosition();
if (game.touchArray.length == 0 || player.id !=
game.touchArray[game.touchArray.length - 1].player.id) {
if (playSituation == Situation.KICKOFF) playSituation = Situation.PLAY;
lastTeamTouched = player.team;
game.touchArray.push(
new BallTouch(
player,
game.scores.time,
getGoalGame(),
ballPosition
)
);
lastTouches[0] = checkGoalKickTouch(
game.touchArray,
game.touchArray.length - 1,
getGoalGame()
);
lastTouches[1] = checkGoalKickTouch(
game.touchArray,
game.touchArray.length - 2,
getGoalGame()
);
}
}
};

/* GAME MANAGEMENT */

room.onGameStart = function (byPlayer) {


clearTimeout(startTimeout);
if (byPlayer != null) clearTimeout(stopTimeout);
game = new Game();
possession = [0, 0];
actionZoneHalf = [0, 0];
gameState = State.PLAY;
endGameVariable = false;
goldenGoal = false;
playSituation = Situation.KICKOFF;
lastTouches = Array(2).fill(null);
lastTeamTouched = Team.SPECTATORS;
calculateStadiumVariables();
};

room.onGamePause = function (byPlayer) {


if (mentionPlayersUnpause && gameState == State.PAUSE) {
if (byPlayer != null) {
room.sendAnnouncement(
`Game paused by ${byPlayer.name} !`,
null,
defaultColor,
'bold',
HaxNotification.NONE
);
} else {
room.sendAnnouncement(
`Game paused !`,
null,
defaultColor,
'bold',
HaxNotification.NONE
);
}
}
clearTimeout(unpauseTimeout);
gameState = State.PAUSE;
};

room.onGameUnpause = function (byPlayer) {


unpauseTimeout = setTimeout(() => {
gameState = State.PLAY;
}, 2000);
if (mentionPlayersUnpause) {
if (byPlayer != null) {
room.sendAnnouncement(
`Game unpaused by ${byPlayer.name} !`,
null,
defaultColor,
'bold',
HaxNotification.NONE
);
} else {
room.sendAnnouncement(
`Game unpaused !`,
null,
defaultColor,
'bold',
HaxNotification.NONE
);
}
}
};

room.onTeamGoal = function (team) {


const scores = room.getScores();
game.scores = scores;
playSituation = Situation.GOAL;
ballSpeed = getBallSpeed();
var goalString = getGoalString(team);
room.sendAnnouncement(
goalString,
null,
team == Team.RED ? redColor : blueColor,
null,
HaxNotification.CHAT
);
if (roomWebhook != '') {
fetch(roomWebhook, {
method: 'POST',
body: JSON.stringify({
content: `[${getDate()}] ${goalString}`,
username: roomName,
}),
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res);
}
if ((scores.scoreLimit != 0 && (scores.red == scores.scoreLimit || scores.blue
== scores.scoreLimit)) || goldenGoal) {
endGame(team);
goldenGoal = false;
stopTimeout = setTimeout(() => {
room.stopGame();
}, 1000);
}
};

room.onPositionsReset = function () {
lastTouches = Array(2).fill(null);
lastTeamTouched = Team.SPECTATORS;
playSituation = Situation.KICKOFF;
};

/* MISCELLANEOUS */

room.onPlayerAdminChange = function (changedPlayer, byPlayer) {


updateTeams();
if (!changedPlayer.admin && getRole(changedPlayer) >= Role.ADMIN_TEMP) {
room.setPlayerAdmin(changedPlayer.id, true);
return;
}
updateAdmins(byPlayer != null && !changedPlayer.admin && changedPlayer.id ==
byPlayer.id ? changedPlayer.id : 0);
};

room.onKickRateLimitSet = function (min, rate, burst, byPlayer) {


if (byPlayer != null) {
room.sendAnnouncement(
`It is not allowed to change the kickrate limit. It must stay at "6-0-
0".`,
player.id,
errorColor,
'bold',
HaxNotification.CHAT
);
room.setKickRateLimit(0, 0, 0);
}
};

room.onStadiumChange = function (newStadiumName, byPlayer) {


checkStadiumVariable = true;
};

room.onGameTick = function () {
checkTime();
getLastTouchOfTheBall();
getGameStats();
handleActivity();
};

You might also like