Compare commits
1 Commits
master
...
no-autocap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c5e8b8b2 |
@ -10,6 +10,5 @@
|
|||||||
"VOICE_ID_PICKING": "1175649967935856680",
|
"VOICE_ID_PICKING": "1175649967935856680",
|
||||||
"VOICE_ID_BLU": "1175649793943552010",
|
"VOICE_ID_BLU": "1175649793943552010",
|
||||||
"VOICE_ID_RED": "1175649777648680971",
|
"VOICE_ID_RED": "1175649777648680971",
|
||||||
"VOICE_ID_FK": "1176396207183106078",
|
"VOICE_ID_FK": "1176396207183106078"
|
||||||
"RANKING_WHITELIST": "233036215610245120"
|
|
||||||
}
|
}
|
||||||
|
|||||||
790
index.js
790
index.js
@ -1,19 +1,13 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { Client, Collection, GatewayIntentBits, Partials } from "discord.js";
|
import { Client, Collection, GatewayIntentBits, Partials } from "discord.js";
|
||||||
import { getApplicableName, findPlayer } from "./utils.js";
|
|
||||||
const {
|
const {
|
||||||
TOKEN,
|
TOKEN,
|
||||||
GUILD_ID,
|
|
||||||
USER_ID_DEBUG,
|
USER_ID_DEBUG,
|
||||||
TEXT_ID_COMMAND,
|
TEXT_ID_COMMAND,
|
||||||
VOICE_ID_PICKING,
|
|
||||||
VOICE_ID_BLU,
|
|
||||||
VOICE_ID_RED,
|
|
||||||
} = require("./config.json");
|
} = require("./config.json");
|
||||||
|
|
||||||
let whitelistStr = require("./config.json").RANKING_WHITELIST;
|
|
||||||
|
|
||||||
export const client = new Client({
|
export const client = new Client({
|
||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
@ -45,132 +39,6 @@ for (const file of commandFiles) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const backticks = "```";
|
|
||||||
const rankingsPath = path.join(__dirname, "rankings.json");
|
|
||||||
|
|
||||||
|
|
||||||
const avgDiff = (arr) => {
|
|
||||||
// average absolute difference between all pairs of rank properties in a given array
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
for (let j = 0; j < arr.length; j++) {
|
|
||||||
if (isNaN(arr[i].rank) || isNaN(arr[j].rank)) {
|
|
||||||
console.error(`Invalid array passed to avgDiff ${arr}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sum += Math.abs(arr[i].rank - arr[j].rank);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum / (arr.length * (arr.length - 1)) / 2;
|
|
||||||
};
|
|
||||||
|
|
||||||
// values used for balancing, modifiable
|
|
||||||
// FUN value: Fluctuating Unfairness Normalization value
|
|
||||||
// PASSION: Player Ability/Skill Separation Index Offset Number
|
|
||||||
let funInput = 4; // 1-10
|
|
||||||
let passionInput = 1; // 1-5
|
|
||||||
|
|
||||||
// balance attempt counter, not modifiable
|
|
||||||
let attempts = 0;
|
|
||||||
|
|
||||||
const balanceArrays = (a1, a2) => {
|
|
||||||
// check to make sure arrays are not empty and that rank properties exist
|
|
||||||
if (a1.length === 0 || a2.length === 0) {
|
|
||||||
console.error(`Empty array(s) passed to balanceArrays ${a1} ${a2}`);
|
|
||||||
return [a1, a2];
|
|
||||||
}
|
|
||||||
if (!a1[0].rank || !a2[0].rank) {
|
|
||||||
console.error(`Rank property missing ${a1} ${a2}`);
|
|
||||||
return [a1, a2];
|
|
||||||
}
|
|
||||||
|
|
||||||
let modArr1 = [...a1];
|
|
||||||
let modArr2 = [...a2];
|
|
||||||
let sum1 = modArr1.reduce((acc, player) => acc + player.rank, 0);
|
|
||||||
let sum2 = modArr2.reduce((acc, player) => acc + player.rank, 0);
|
|
||||||
|
|
||||||
let funValue = funInput * 0.01 + 0.025;
|
|
||||||
let passion = passionInput * 0.1;
|
|
||||||
let unequalAllowance = Math.floor((sum1 + sum2) * funValue);
|
|
||||||
|
|
||||||
// rebalance until teams fall within constraints
|
|
||||||
attempts = 0;
|
|
||||||
while (
|
|
||||||
Math.abs(avgDiff(modArr1) - avgDiff(modArr2)) > passion ||
|
|
||||||
(Math.abs(sum1 - sum2) > unequalAllowance && attempts < 1000)
|
|
||||||
) {
|
|
||||||
attempts++;
|
|
||||||
if (attempts > 1000) {
|
|
||||||
console.log(
|
|
||||||
`Too many attempts to balance teams
|
|
||||||
${attempts} ${unequalAllowance} ${passion}
|
|
||||||
${avgDiff(modArr1)} ${avgDiff(modArr2)}`
|
|
||||||
);
|
|
||||||
return [modArr1, modArr2];
|
|
||||||
} else if (attempts > 600) {
|
|
||||||
unequalAllowance = (sum1 + sum2) * funValue + 2;
|
|
||||||
} else if (attempts > 300) {
|
|
||||||
unequalAllowance = (sum1 + sum2) * funValue + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rerolls = 0;
|
|
||||||
let ran1 = Math.floor(Math.random() * modArr1.length);
|
|
||||||
let ran2 = Math.floor(Math.random() * modArr2.length);
|
|
||||||
// swap randomly if teams seem even
|
|
||||||
if (Math.abs(sum1 - sum2) < unequalAllowance) {
|
|
||||||
while (modArr1[ran1].rank === modArr2[ran2].rank && rerolls < 100) {
|
|
||||||
rerolls++;
|
|
||||||
ran1 = Math.floor(Math.random() * modArr1.length);
|
|
||||||
ran2 = Math.floor(Math.random() * modArr2.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// else swap players to minimize difference
|
|
||||||
else if (sum1 > sum2) {
|
|
||||||
while (modArr1[ran1].rank <= modArr2[ran2].rank && rerolls < 100) {
|
|
||||||
rerolls++;
|
|
||||||
ran1 = Math.floor(Math.random() * modArr1.length);
|
|
||||||
ran2 = Math.floor(Math.random() * modArr2.length);
|
|
||||||
}
|
|
||||||
} else if (sum1 < sum2) {
|
|
||||||
while (modArr1[ran1].rank >= modArr2[ran2].rank && rerolls < 100) {
|
|
||||||
rerolls++;
|
|
||||||
ran1 = Math.floor(Math.random() * modArr1.length);
|
|
||||||
ran2 = Math.floor(Math.random() * modArr2.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make the swap
|
|
||||||
let temp = modArr1[ran1];
|
|
||||||
modArr1[ran1] = modArr2[ran2];
|
|
||||||
modArr2[ran2] = temp;
|
|
||||||
sum1 = modArr1.reduce((acc, player) => acc + player.rank, 0);
|
|
||||||
sum2 = modArr2.reduce((acc, player) => acc + player.rank, 0);
|
|
||||||
|
|
||||||
// if array lengths are uneven, act as if the shorter array is padded with the average of all values
|
|
||||||
// a length difference of more than 1 between the two arrays should be disallowed elsewhere
|
|
||||||
if (modArr1.length > modArr2.length) {
|
|
||||||
let avg = sum2 / modArr2.length;
|
|
||||||
sum2 += avg;
|
|
||||||
} else if (modArr1.length < modArr2.length) {
|
|
||||||
let avg = sum1 / modArr1.length;
|
|
||||||
sum1 += avg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modArr1.sort((a, b) => b.rank - a.rank);
|
|
||||||
modArr2.sort((a, b) => b.rank - a.rank);
|
|
||||||
console.log(
|
|
||||||
`${
|
|
||||||
attempts > 999 ? "forced" : "successful"
|
|
||||||
} sort after ${attempts} attempts:
|
|
||||||
${modArr1.map((i) => i.rank)}
|
|
||||||
${modArr2.map((i) => i.rank)}
|
|
||||||
${sum1} ${sum2} (${unequalAllowance})
|
|
||||||
${avgDiff(modArr1)} ${avgDiff(modArr2)} (${passion})`
|
|
||||||
);
|
|
||||||
|
|
||||||
return [modArr1, modArr2];
|
|
||||||
};
|
|
||||||
|
|
||||||
client.on("ready", () => {
|
client.on("ready", () => {
|
||||||
console.log(`Logged in as ${client.user.tag}!`);
|
console.log(`Logged in as ${client.user.tag}!`);
|
||||||
console.log(
|
console.log(
|
||||||
@ -211,7 +79,10 @@ client.on("interactionCreate", async (interaction) => {
|
|||||||
if (fileCommand.permissions) {
|
if (fileCommand.permissions) {
|
||||||
const member = interaction.member;
|
const member = interaction.member;
|
||||||
for (const permission of fileCommand.permissions) {
|
for (const permission of fileCommand.permissions) {
|
||||||
if (!member.roles.cache.has(permission) && member.id !== USER_ID_DEBUG) {
|
if (
|
||||||
|
!member.roles.cache.has(permission) &&
|
||||||
|
member.id !== USER_ID_DEBUG
|
||||||
|
) {
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: "You lack the required permissions",
|
content: "You lack the required permissions",
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
@ -228,657 +99,6 @@ client.on("interactionCreate", async (interaction) => {
|
|||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (interaction.channelId !== TEXT_ID_COMMAND) {
|
|
||||||
//let isRunner = await interaction.member.roles.cache.has(ROLE_ID_RUNNER);
|
|
||||||
//if (!isRunner) {
|
|
||||||
await interaction.reply({
|
|
||||||
content: "Wrong channel, or you lack the required permissions",
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command === "pick" || command === "autocaptain") {
|
|
||||||
await interaction.reply("Picking teams...");
|
|
||||||
|
|
||||||
// get voice channels
|
|
||||||
const picking = interaction.guild.channels.cache.find(
|
|
||||||
(channel) => channel.name === "picking" || channel.id === VOICE_ID_PICKING
|
|
||||||
);
|
|
||||||
if (!picking) return console.error("Can't find channel 'picking'!");
|
|
||||||
const blu = interaction.guild.channels.cache.find(
|
|
||||||
(channel) => channel.name === "blu" || channel.id === VOICE_ID_BLU
|
|
||||||
);
|
|
||||||
if (!blu) return console.error("Can't find channel 'blu'!");
|
|
||||||
const red = interaction.guild.channels.cache.find(
|
|
||||||
(channel) => channel.name === "red" || channel.id === VOICE_ID_RED
|
|
||||||
);
|
|
||||||
if (!red) return console.error("Can't find channel 'red'!");
|
|
||||||
|
|
||||||
// get players in voice channel
|
|
||||||
const players = picking.members;
|
|
||||||
if (players.size !== 18) {
|
|
||||||
return await interaction.followUp(
|
|
||||||
`Found ${players.size} players in picking, expected 18`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get rankings
|
|
||||||
let rankings = {};
|
|
||||||
try {
|
|
||||||
console.log(`Getting rankings at ${rankingsPath}...`);
|
|
||||||
if (!fs.existsSync(rankingsPath)) {
|
|
||||||
fs.writeFileSync(rankingsPath, "{}");
|
|
||||||
}
|
|
||||||
rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return await message.reply("Error sorting teams");
|
|
||||||
}
|
|
||||||
|
|
||||||
// distribute unranked players evenly, then randomly assign ranked players
|
|
||||||
let rankedPlayers = [],
|
|
||||||
bluPlayers = [],
|
|
||||||
redPlayers = [],
|
|
||||||
rng = 0;
|
|
||||||
|
|
||||||
for (const [playerId, player] of players) {
|
|
||||||
if (!rankings[playerId]) {
|
|
||||||
rng = Math.random();
|
|
||||||
// if teams are even, randomly assign
|
|
||||||
if (bluPlayers.length === redPlayers.length) {
|
|
||||||
if (rng < 0.5) {
|
|
||||||
bluPlayers.push({ player, rank: 0 });
|
|
||||||
} else {
|
|
||||||
redPlayers.push({ player, rank: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// otherwise, equalize teams
|
|
||||||
else if (bluPlayers.length < redPlayers.length) {
|
|
||||||
bluPlayers.push({ player, rank: 0 });
|
|
||||||
} else {
|
|
||||||
redPlayers.push({ player, rank: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// prepare ranked players for random assignment
|
|
||||||
else {
|
|
||||||
rankedPlayers.push(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// slots for ranked players are limited by unranked players, who are sorted differently
|
|
||||||
let bluRankedPlayers = [],
|
|
||||||
redRankedPlayers = [],
|
|
||||||
bluRankSlots = 9 - bluPlayers.length,
|
|
||||||
redRankSlots = 9 - redPlayers.length;
|
|
||||||
|
|
||||||
// create array with players and their ranks
|
|
||||||
for (const player of rankedPlayers) {
|
|
||||||
rng = Math.random();
|
|
||||||
if (rng < 0.5) {
|
|
||||||
if (bluRankedPlayers.length < bluRankSlots) {
|
|
||||||
bluRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
} else {
|
|
||||||
redRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (redRankedPlayers.length < redRankSlots) {
|
|
||||||
redRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
} else {
|
|
||||||
bluRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
bluPlayers.length + bluRankedPlayers.length !== 9 ||
|
|
||||||
redPlayers.length + redRankedPlayers.length !== 9
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
`Invalid number of players: ${bluPlayers.length} ${bluRankedPlayers.length} ${redPlayers.length} ${redRankedPlayers.length}`
|
|
||||||
);
|
|
||||||
return await message.reply("Error sorting teams");
|
|
||||||
}
|
|
||||||
|
|
||||||
// bring teams to reasonable balance
|
|
||||||
let [bluBalanced, redBalanced] = balanceArrays(
|
|
||||||
bluRankedPlayers,
|
|
||||||
redRankedPlayers
|
|
||||||
);
|
|
||||||
bluPlayers = bluPlayers.concat(bluBalanced);
|
|
||||||
redPlayers = redPlayers.concat(redBalanced);
|
|
||||||
|
|
||||||
// sort alphebetically then build string
|
|
||||||
bluPlayers.sort((a, b) =>
|
|
||||||
a.player.displayName.localeCompare(b.player.displayName)
|
|
||||||
);
|
|
||||||
redPlayers.sort((a, b) =>
|
|
||||||
a.player.displayName.localeCompare(b.player.displayName)
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let teamStr = `${backticks}BLU:`;
|
|
||||||
for (const player of bluPlayers) {
|
|
||||||
teamStr += `\n${player.player.displayName}`;
|
|
||||||
}
|
|
||||||
teamStr += `\n\nRED:`;
|
|
||||||
for (const player of redPlayers) {
|
|
||||||
teamStr += `\n${player.player.displayName}`;
|
|
||||||
}
|
|
||||||
teamStr += backticks;
|
|
||||||
|
|
||||||
await interaction.followUp(teamStr);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
await interaction.followUp("Couldn't print teams before moving players");
|
|
||||||
}
|
|
||||||
|
|
||||||
let moveErr = 0;
|
|
||||||
|
|
||||||
// move to team voice channels
|
|
||||||
while (bluPlayers.length > 0) {
|
|
||||||
const idx = Math.floor(Math.random() * bluPlayers.length);
|
|
||||||
const player = bluPlayers.splice(idx, 1)[0];
|
|
||||||
try {
|
|
||||||
await player.player.voice.setChannel(blu);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
moveErr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (redPlayers.length > 0) {
|
|
||||||
const idx = Math.floor(Math.random() * redPlayers.length);
|
|
||||||
const player = redPlayers.splice(idx, 1)[0];
|
|
||||||
try {
|
|
||||||
await player.player.voice.setChannel(red);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
moveErr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interaction.followUp(
|
|
||||||
`Players moved into teams${
|
|
||||||
moveErr > 0 ? ` (error moving ${moveErr} members)` : ""
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* DM commands
|
|
||||||
* - setrank: saves a player's rank
|
|
||||||
* - getrank: prints a player's rank
|
|
||||||
* - rankings: prints all players' ranks
|
|
||||||
* - fun: prints or sets the FUN value
|
|
||||||
* - passion: prints or sets the PASSION value
|
|
||||||
* - simulateteams: simulates autocaptain results
|
|
||||||
* - whitelist: adds an admin to the whitelist to use DM commands
|
|
||||||
* - getwhitelist: prints the whitelist
|
|
||||||
* - clearwhitelist: clears the whitelist completely, security measure
|
|
||||||
*/
|
|
||||||
client.on("messageCreate", async (message) => {
|
|
||||||
if (message.author.bot || message.guild) return;
|
|
||||||
|
|
||||||
// check if user is whitelisted
|
|
||||||
if (!whitelistStr.includes(message.author.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.content.toLowerCase().includes("hello penguin")) {
|
|
||||||
await message.reply(`Hello ${message.author.username}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pickupGuild = client.guilds.cache.get(GUILD_ID);
|
|
||||||
if (!pickupGuild) {
|
|
||||||
await message.reply("Could not find guild");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = message.content.toLowerCase().split(" ");
|
|
||||||
|
|
||||||
if (args[0] === "setrank") {
|
|
||||||
if (args.length < 3) {
|
|
||||||
await message.reply(
|
|
||||||
"Invalid number of arguments. usage: `setrank <player name or id> <rank (0-5)>`"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const player = findPlayer(pickupGuild, args[1]);
|
|
||||||
if (!player) {
|
|
||||||
await message.reply(
|
|
||||||
`Could not find player ${args[1]}. If this issue persists, try copy/pasting the player's Discord handle or user ID`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const playerId = player.id;
|
|
||||||
const applicableName = getApplicableName(player);
|
|
||||||
|
|
||||||
const rank = parseInt(args[2]);
|
|
||||||
if (isNaN(rank) || rank < 0 || rank > 5) {
|
|
||||||
await message.reply(
|
|
||||||
`Invalid rank ${args[2]}. Supply an integer between 0 and 5`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create rankings.json if it doesn't exist
|
|
||||||
const rankingsPath = path.join(__dirname, "rankings.json");
|
|
||||||
if (!fs.existsSync(rankingsPath)) {
|
|
||||||
fs.writeFileSync(rankingsPath, "{}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// rankings.json is a map of player's id to rank
|
|
||||||
const rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
||||||
rankings[playerId] = rank;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Setting rank of ${applicableName}, ${playerId} to ${rank} at ${rankingsPath}...`
|
|
||||||
);
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
fs.writeFile(rankingsPath, JSON.stringify(rankings), (err) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve();
|
|
||||||
});
|
|
||||||
}).then((res, err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
message.reply(`Error setting rank: ${err.message}`);
|
|
||||||
} else {
|
|
||||||
message.reply(`Set rank of ${applicableName} to ${rank}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "getrank") {
|
|
||||||
if (args.length < 2) {
|
|
||||||
await message.reply(
|
|
||||||
"Invalid number of arguments. Usage: `getrank <player name or id>`"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const player = findPlayer(pickupGuild, args[1]);
|
|
||||||
if (!player) {
|
|
||||||
await message.reply(
|
|
||||||
`Could not find player ${args[1]}. If this issue persists, try copy/pasting the player's Discord handle or user ID`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const playerId = player.id;
|
|
||||||
const applicableName = getApplicableName(player);
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(
|
|
||||||
`Getting rank of ${applicableName}, ${playerId} at ${rankingsPath}...`
|
|
||||||
);
|
|
||||||
if (!fs.existsSync(rankingsPath)) {
|
|
||||||
fs.writeFileSync(rankingsPath, "{}");
|
|
||||||
}
|
|
||||||
const rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
||||||
await message.reply(
|
|
||||||
`${backticks}${applicableName} - ${"*".repeat(
|
|
||||||
rankings[playerId]
|
|
||||||
)}${backticks}`
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
await message.reply(`Error getting rank: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "rankings") {
|
|
||||||
await message.reply("Getting rankings...");
|
|
||||||
try {
|
|
||||||
console.log(`Getting rankings at ${rankingsPath}...`);
|
|
||||||
if (!fs.existsSync(rankingsPath)) {
|
|
||||||
fs.writeFileSync(rankingsPath, "{}");
|
|
||||||
}
|
|
||||||
const rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
||||||
let players = [];
|
|
||||||
for (const [playerId, rank] of Object.entries(rankings)) {
|
|
||||||
if (rank > 0) {
|
|
||||||
const player = await pickupGuild.members.fetch(playerId);
|
|
||||||
if (!player) {
|
|
||||||
console.error(`Could not find player ${playerId}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const applicableName = getApplicableName(player);
|
|
||||||
players.push({ name: applicableName, rank });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by rank, then name
|
|
||||||
players.sort((a, b) => {
|
|
||||||
if (a.rank === b.rank) {
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
}
|
|
||||||
return b.rank - a.rank;
|
|
||||||
});
|
|
||||||
|
|
||||||
// build string
|
|
||||||
let str = "";
|
|
||||||
const maxNameLength = Math.max(...players.map((p) => p.name.length));
|
|
||||||
for (const { name, rank } of players) {
|
|
||||||
str += `${name.padEnd(maxNameLength, " ")} - ${"*".repeat(rank)}\n`;
|
|
||||||
}
|
|
||||||
if (str === "") str = "No rankings found";
|
|
||||||
if (str.length > 2000) {
|
|
||||||
let chunks = str.match(/[\s\S]{1,1990}/g);
|
|
||||||
for (let chunk of chunks) {
|
|
||||||
await message.reply(`${backticks}${chunk}${backticks}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await message.reply(`${backticks}${str}${backticks}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
await message.reply(`Error getting rankings: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "fun") {
|
|
||||||
if (
|
|
||||||
args.length < 2 ||
|
|
||||||
isNaN(args[1]) ||
|
|
||||||
parseInt(args[1]) < 1 ||
|
|
||||||
parseInt(args[1]) > 10
|
|
||||||
)
|
|
||||||
return await message.reply(
|
|
||||||
`Current FUN value: ${funInput}\nUsage: \`fun <1-10>\``
|
|
||||||
);
|
|
||||||
funInput = parseInt(args[1]);
|
|
||||||
return await message.reply(`FUN value set to ${funInput}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "passion") {
|
|
||||||
if (
|
|
||||||
args.length < 2 ||
|
|
||||||
isNaN(args[1]) ||
|
|
||||||
parseInt(args[1]) < 1 ||
|
|
||||||
parseInt(args[1]) > 5
|
|
||||||
)
|
|
||||||
return await message.reply(
|
|
||||||
`Current PASSION: ${passionInput}\nUsage: \`passion <1-5>\``
|
|
||||||
);
|
|
||||||
passionInput = parseInt(args[1]);
|
|
||||||
return await message.reply(`PASSION set to ${passionInput}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "simulateteams") {
|
|
||||||
await message.reply("Simulating teams...");
|
|
||||||
|
|
||||||
// get players in voice channel
|
|
||||||
// const players = picking.members;
|
|
||||||
// if (players.size !== 18) {
|
|
||||||
// return await message.reply(
|
|
||||||
// `Found ${players.size} players in picking, expected 18`
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// simulate input is 18 player display names separated by commas
|
|
||||||
const playerInputs = args[1].split(",");
|
|
||||||
if (playerInputs.length !== 18) {
|
|
||||||
return await message.reply(
|
|
||||||
`Found ${playerInputs.length} players in input, expected 18
|
|
||||||
\nUsage: \`simulateteams <player1>,<player2>,...,<player18>\``
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get players from input
|
|
||||||
let players = new Map();
|
|
||||||
for (const input of playerInputs) {
|
|
||||||
const player = findPlayer(pickupGuild, input);
|
|
||||||
if (!player) {
|
|
||||||
await message.reply(`Could not find player ${input}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
players.set(player.id, player);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get rankings
|
|
||||||
let rankings = {};
|
|
||||||
try {
|
|
||||||
console.log(`Getting rankings at ${rankingsPath}...`);
|
|
||||||
if (!fs.existsSync(rankingsPath)) {
|
|
||||||
fs.writeFileSync(rankingsPath, "{}");
|
|
||||||
}
|
|
||||||
rankings = JSON.parse(fs.readFileSync(rankingsPath));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return await message.reply("Error sorting teams");
|
|
||||||
}
|
|
||||||
|
|
||||||
// distribute unranked players evenly, then randomly assign ranked players
|
|
||||||
let rankedPlayers = [],
|
|
||||||
bluPlayers = [],
|
|
||||||
redPlayers = [],
|
|
||||||
rng = 0;
|
|
||||||
|
|
||||||
for (const [playerId, player] of players) {
|
|
||||||
if (!rankings[playerId]) {
|
|
||||||
rng = Math.random();
|
|
||||||
// if teams are even, randomly assign
|
|
||||||
if (bluPlayers.length === redPlayers.length) {
|
|
||||||
if (rng < 0.5) {
|
|
||||||
bluPlayers.push({ player, rank: 0 });
|
|
||||||
} else {
|
|
||||||
redPlayers.push({ player, rank: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// otherwise, equalize teams
|
|
||||||
else if (bluPlayers.length < redPlayers.length) {
|
|
||||||
bluPlayers.push({ player, rank: 0 });
|
|
||||||
} else {
|
|
||||||
redPlayers.push({ player, rank: 0 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// prepare ranked players for random assignment
|
|
||||||
else {
|
|
||||||
rankedPlayers.push(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
`bluUnranked: ${bluPlayers.length} redUnranked: ${redPlayers.length}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// slots for ranked players are limited by unranked players, who are sorted differently
|
|
||||||
let bluRankedPlayers = [],
|
|
||||||
redRankedPlayers = [],
|
|
||||||
bluRankSlots = 9 - bluPlayers.length,
|
|
||||||
redRankSlots = 9 - redPlayers.length;
|
|
||||||
|
|
||||||
console.log(`bluRankSlots: ${bluRankSlots} redRankSlots: ${redRankSlots}`);
|
|
||||||
|
|
||||||
// create array with players and their ranks
|
|
||||||
for (const player of rankedPlayers) {
|
|
||||||
rng = Math.random();
|
|
||||||
console.log(
|
|
||||||
`rng: ${rng}, bluRankedPlayers: ${bluRankedPlayers.length}, redRankedPlayers: ${redRankedPlayers.length}`
|
|
||||||
);
|
|
||||||
if (rng < 0.5) {
|
|
||||||
if (bluRankedPlayers.length < bluRankSlots) {
|
|
||||||
bluRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
} else {
|
|
||||||
redRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (redRankedPlayers.length < redRankSlots) {
|
|
||||||
redRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
} else {
|
|
||||||
bluRankedPlayers.push({ player, rank: rankings[player.id] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
bluPlayers.length + bluRankedPlayers.length !== 9 ||
|
|
||||||
redPlayers.length + redRankedPlayers.length !== 9
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
`Invalid number of players: ${bluPlayers.length} ${bluRankedPlayers.length} ${redPlayers.length} ${redRankedPlayers.length}`
|
|
||||||
);
|
|
||||||
return await message.reply("Error sorting teams");
|
|
||||||
}
|
|
||||||
|
|
||||||
// bring teams to reasonable balance
|
|
||||||
let [bluBalanced, redBalanced] = balanceArrays(
|
|
||||||
bluRankedPlayers,
|
|
||||||
redRankedPlayers
|
|
||||||
);
|
|
||||||
bluPlayers = bluPlayers.concat(bluBalanced);
|
|
||||||
redPlayers = redPlayers.concat(redBalanced);
|
|
||||||
|
|
||||||
let moveErr = 0;
|
|
||||||
|
|
||||||
// for sim, print teams after sorting by rank then name
|
|
||||||
bluPlayers.sort((a, b) => {
|
|
||||||
if (a.rank === b.rank) {
|
|
||||||
return a.player.displayName.localeCompare(b.player.displayName);
|
|
||||||
}
|
|
||||||
return b.rank - a.rank;
|
|
||||||
});
|
|
||||||
redPlayers.sort((a, b) => {
|
|
||||||
if (a.rank === b.rank) {
|
|
||||||
return a.player.displayName.localeCompare(b.player.displayName);
|
|
||||||
}
|
|
||||||
return b.rank - a.rank;
|
|
||||||
});
|
|
||||||
let simString = `${backticks}BLU:`;
|
|
||||||
|
|
||||||
// move to team voice channels
|
|
||||||
while (bluPlayers.length > 0) {
|
|
||||||
//let idx = Math.floor(Math.random() * bluPlayers.length);
|
|
||||||
//if (idx === bluPlayers.length) idx--;
|
|
||||||
let idx = 0;
|
|
||||||
const player = bluPlayers.splice(idx, 1)[0];
|
|
||||||
try {
|
|
||||||
//await player.voice.setChannel(blu);
|
|
||||||
simString += `\n${getApplicableName(player.player)} - ${"*".repeat(
|
|
||||||
player.rank
|
|
||||||
)}`;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
moveErr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
simString += `\n\nRED:`;
|
|
||||||
while (redPlayers.length > 0) {
|
|
||||||
//let idx = Math.floor(Math.random() * redPlayers.length);
|
|
||||||
//if (idx === redPlayers.length) idx--;
|
|
||||||
let idx = 0;
|
|
||||||
const player = redPlayers.splice(idx, 1)[0];
|
|
||||||
try {
|
|
||||||
//await player.voice.setChannel(red);
|
|
||||||
simString += `\n${getApplicableName(player.player)} - ${"*".repeat(
|
|
||||||
player.rank
|
|
||||||
)}`;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
moveErr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message.reply(
|
|
||||||
`Simulated teams:\n${simString}${backticks}${
|
|
||||||
moveErr > 0 ? ` (error moving ${moveErr} members)` : ""
|
|
||||||
}${attempts > 999 ? "(forced)" : ""}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "whitelist") {
|
|
||||||
// add user to config.json whitelist
|
|
||||||
if (args.length < 2) {
|
|
||||||
await message.reply(
|
|
||||||
"Invalid number of arguments. Usage: `whitelist <name>`"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = args[1];
|
|
||||||
const player = findPlayer(pickupGuild, name);
|
|
||||||
if (!player) {
|
|
||||||
await message.reply(
|
|
||||||
`Could not find player ${name}. If this issue persists, try copy/pasting the player's Discord handle or user ID`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const playerId = player.id;
|
|
||||||
const applicableName = getApplicableName(player);
|
|
||||||
|
|
||||||
if (whitelistStr.includes(playerId)) {
|
|
||||||
await message.reply(
|
|
||||||
`User ${applicableName} (${playerId}) is already whitelisted`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configPath = path.join(__dirname, "config.json");
|
|
||||||
const config = JSON.parse(fs.readFileSync(configPath));
|
|
||||||
whitelistStr += `,${playerId}`;
|
|
||||||
config.RANKING_WHITELIST = whitelistStr;
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
fs.writeFile(configPath, JSON.stringify(config), (err) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve();
|
|
||||||
});
|
|
||||||
}).then((res, err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
message.reply(`Error whitelisting user: ${err.message}`);
|
|
||||||
} else {
|
|
||||||
message.reply(
|
|
||||||
`Whitelisted user ${applicableName} (${playerId}). If this was done in error, please contact an admin`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "getwhitelist") {
|
|
||||||
let str = `${backticks}${whitelistStr}${backticks}${backticks}`;
|
|
||||||
const whitelistIds = whitelistStr.split(",");
|
|
||||||
for (const id of whitelistIds) {
|
|
||||||
const player = findPlayer(pickupGuild, id);
|
|
||||||
if (player) {
|
|
||||||
str += `\n${getApplicableName(player)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str += `${backticks}`;
|
|
||||||
await message.reply(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[0] === "clearwhitelist") {
|
|
||||||
if (args[1] !== "confirm") {
|
|
||||||
await message.reply(
|
|
||||||
"This command will clear the whitelist and prevent further DM commands. This will require manual intervention to undo. To confirm, use `clearwhitelist confirm`"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const configPath = path.join(__dirname, "config.json");
|
|
||||||
const config = JSON.parse(fs.readFileSync(configPath));
|
|
||||||
whitelistStr = "";
|
|
||||||
config.RANKING_WHITELIST = whitelistStr;
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
fs.writeFile(configPath, JSON.stringify(config), (err) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve();
|
|
||||||
});
|
|
||||||
}).then((res, err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
message.reply(`Error clearing whitelist: ${err.message}`);
|
|
||||||
} else {
|
|
||||||
message.reply("Whitelist cleared, please alert an admin");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user