mirror of
https://github.com/AsgardEternal/SquadJS.git
synced 2024-09-28 09:34:23 -05:00
Rewrite of plugin system
This commit is contained in:
parent
86d0878174
commit
dd685ecd80
17
README.md
17
README.md
@ -384,23 +384,6 @@ The following is a list of plugins built into SquadJS, you can click their title
|
||||
]</code></pre>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DiscordPlaceholder</summary>
|
||||
<h2>DiscordPlaceholder</h2>
|
||||
<p>The <code>DiscordPlaceholder</code> plugin can be used to create placeholder messages in Discord for use by other plugins.</p>
|
||||
<h3>Options</h3>
|
||||
<h4>discordClient (Required)</h4>
|
||||
<h6>Description</h6>
|
||||
<p>Discord connector name.</p>
|
||||
<h6>Default</h6>
|
||||
<pre><code>discord</code></pre>
|
||||
<h4>command</h4>
|
||||
<h6>Description</h6>
|
||||
<p>Command that triggers the placeholder message.</p>
|
||||
<h6>Default</h6>
|
||||
<pre><code>!placeholder</code></pre>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DiscordRcon</summary>
|
||||
<h2>DiscordRcon</h2>
|
||||
|
@ -123,12 +123,6 @@
|
||||
"channelID": "",
|
||||
"events": []
|
||||
},
|
||||
{
|
||||
"plugin": "DiscordPlaceholder",
|
||||
"enabled": true,
|
||||
"discordClient": "discord",
|
||||
"command": "!placeholder"
|
||||
},
|
||||
{
|
||||
"plugin": "DiscordRcon",
|
||||
"enabled": false,
|
||||
|
5
index.js
5
index.js
@ -8,11 +8,16 @@ async function main() {
|
||||
const configPath = process.argv[2];
|
||||
if (config && configPath) throw new Error('Cannot accept both a config and config path.');
|
||||
|
||||
// create a SquadServer instance
|
||||
const server = config
|
||||
? await SquadServerFactory.buildFromConfigString(config)
|
||||
: await SquadServerFactory.buildFromConfigFile(configPath || './config.json');
|
||||
|
||||
// watch the server
|
||||
await server.watch();
|
||||
|
||||
// now mount the plugins
|
||||
server.plugins.forEach(plugin => plugin.mount());
|
||||
}
|
||||
|
||||
main();
|
||||
|
@ -14,17 +14,19 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default class SquadServerFactory {
|
||||
static async buildFromConfig(config) {
|
||||
// Setup logging levels
|
||||
// setup logging levels
|
||||
for (const [module, verboseness] of Object.entries(config.verboseness)) {
|
||||
Logger.setVerboseness(module, verboseness);
|
||||
}
|
||||
|
||||
// create SquadServer
|
||||
Logger.verbose('SquadServerFactory', 1, 'Creating SquadServer...');
|
||||
const server = new SquadServer(config.server);
|
||||
|
||||
// pull layers read to use to create layer filter connectors
|
||||
await server.squadLayers.pull();
|
||||
|
||||
// initialise connectors
|
||||
Logger.verbose('SquadServerFactory', 1, 'Preparing connectors...');
|
||||
const connectors = {};
|
||||
for (const pluginConfig of config.plugins) {
|
||||
@ -36,82 +38,63 @@ export default class SquadServerFactory {
|
||||
// ignore non connectors
|
||||
if (!option.connector) continue;
|
||||
|
||||
if (!(optionName in pluginConfig))
|
||||
throw new Error(
|
||||
`${Plugin.name}: ${optionName} (${option.connector} connector) is missing.`
|
||||
);
|
||||
// check the connector is listed in the options
|
||||
if (!(optionName in pluginConfig)) throw new Error(`${Plugin.name}: ${optionName} (${option.connector} connector) is missing.`);
|
||||
|
||||
// get the name of the connector
|
||||
const connectorName = pluginConfig[optionName];
|
||||
|
||||
// skip already created connectors
|
||||
if (connectors[connectorName]) continue;
|
||||
|
||||
const connectorConfig = config.connectors[connectorName];
|
||||
|
||||
if (option.connector === 'discord') {
|
||||
Logger.verbose('SquadServerFactory', 1, `Starting discord connector ${connectorName}...`);
|
||||
connectors[connectorName] = new Discord.Client();
|
||||
await connectors[connectorName].login(connectorConfig);
|
||||
} else if (option.connector === 'mysql') {
|
||||
Logger.verbose(
|
||||
'SquadServerFactory',
|
||||
1,
|
||||
`Starting mysqlPool connector ${connectorName}...`
|
||||
);
|
||||
connectors[connectorName] = mysql.createPool(connectorConfig);
|
||||
} else if (option.connector === 'squadlayerpool') {
|
||||
Logger.verbose(
|
||||
'SquadServer',
|
||||
1,
|
||||
`Starting squadlayerfilter connector ${connectorName}...`
|
||||
);
|
||||
connectors[connectorName] = server.squadLayers[connectorConfig.type](
|
||||
connectorConfig.filter,
|
||||
connectorConfig.activeLayerFilter
|
||||
);
|
||||
} else {
|
||||
throw new Error(`${option.connector} is an unsupported connector type.`);
|
||||
}
|
||||
// create the connector
|
||||
connectors[connectorName] = await SquadServerFactory.createConnector(server, option.connector, connectorName, config.connectors[connectorName])
|
||||
}
|
||||
}
|
||||
|
||||
Logger.verbose('SquadServerFactory', 1, 'Applying plugins to SquadServer...');
|
||||
for (const pluginConfig of config.plugins) {
|
||||
// initialise plugins
|
||||
Logger.verbose('SquadServerFactory', 1, 'Initialising plugins...');
|
||||
|
||||
for(const pluginConfig of config.plugins) {
|
||||
if (!pluginConfig.enabled) continue;
|
||||
|
||||
if (!plugins[pluginConfig.plugin])
|
||||
throw new Error(`Plugin ${pluginConfig.plugin} does not exist.`);
|
||||
if (!plugins[pluginConfig.plugin]) throw new Error(`Plugin ${pluginConfig.plugin} does not exist.`);
|
||||
|
||||
const Plugin = plugins[pluginConfig.plugin];
|
||||
|
||||
Logger.verbose('SquadServerFactory', 1, `Initialising ${Plugin.name}...`);
|
||||
|
||||
const options = {};
|
||||
for (const [optionName, option] of Object.entries(Plugin.optionsSpecification)) {
|
||||
if (option.connector) {
|
||||
options[optionName] = connectors[pluginConfig[optionName]];
|
||||
} else {
|
||||
if (option.required) {
|
||||
if (!(optionName in pluginConfig))
|
||||
throw new Error(`${Plugin.name}: ${optionName} is required but missing.`);
|
||||
if (option.default === pluginConfig[optionName])
|
||||
throw new Error(
|
||||
`${Plugin.name}: ${optionName} is required but is the default value.`
|
||||
);
|
||||
}
|
||||
const plugin = new Plugin(server, pluginConfig, connectors);
|
||||
|
||||
options[optionName] = pluginConfig[optionName] || option.default;
|
||||
}
|
||||
}
|
||||
// allow the plugin to do any asynchronous work needed before it can be mounted
|
||||
await plugin.prepareToMount();
|
||||
|
||||
server.plugins.push(new Plugin(server, options, pluginConfig));
|
||||
server.plugins.push(plugin);
|
||||
}
|
||||
|
||||
Logger.verbose('SquadServerFactory', 1, 'SquadServer built.');
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
static async createConnector(server, type, connectorName, connectorConfig) {
|
||||
Logger.verbose('SquadServerFactory', 1, `Starting ${type} connector ${connectorName}...`);
|
||||
|
||||
if (type === 'discord') {
|
||||
const connector = new Discord.Client();
|
||||
await connector.login(connectorConfig);
|
||||
return connector;
|
||||
}
|
||||
|
||||
if (type === 'mysql') {
|
||||
return mysql.createPool(connectorConfig);
|
||||
}
|
||||
|
||||
if (type === 'squadlayerpool') {
|
||||
return server.squadLayers[connectorConfig.type](connectorConfig.filter, connectorConfig.activeLayerFilter);
|
||||
}
|
||||
|
||||
throw new Error(`${type.connector} is an unsupported connector type.`);
|
||||
}
|
||||
|
||||
static parseConfig(configString) {
|
||||
try {
|
||||
return JSON.parse(configString);
|
||||
@ -182,18 +165,18 @@ export default class SquadServerFactory {
|
||||
<p>${option.description}</p>
|
||||
<h6>Default</h6>
|
||||
<pre><code>${
|
||||
typeof option.default === 'object'
|
||||
? JSON.stringify(option.default, null, 2)
|
||||
: option.default
|
||||
}</code></pre>`;
|
||||
typeof option.default === 'object'
|
||||
? JSON.stringify(option.default, null, 2)
|
||||
: option.default
|
||||
}</code></pre>`;
|
||||
|
||||
if (option.example)
|
||||
optionInfo += `<h6>Example</h6>
|
||||
<pre><code>${
|
||||
typeof option.example === 'object'
|
||||
? JSON.stringify(option.example, null, 2)
|
||||
: option.example
|
||||
}</code></pre>`;
|
||||
typeof option.example === 'object'
|
||||
? JSON.stringify(option.example, null, 2)
|
||||
: option.example
|
||||
}</code></pre>`;
|
||||
|
||||
options.push(optionInfo);
|
||||
}
|
||||
|
@ -19,11 +19,21 @@ export default class AutoTKWarn extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.server.on('TEAMKILL', async (info) => {
|
||||
await this.server.rcon.warn(info.attacker.steamID, this.options.message);
|
||||
});
|
||||
this.onTeamkill = this.onTeamkill.bind(this);
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.server.on('TEAMKILL', this.onTeamkill);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.server.removeEventListener('TEAMKILL', this.onTeamkill);
|
||||
}
|
||||
|
||||
async onTeamkill(info) {
|
||||
await this.server.rcon.warn(info.attacker.steamID, this.options.message);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,31 @@
|
||||
import Logger from 'core/logger';
|
||||
|
||||
export default class BasePlugin {
|
||||
constructor(server, options, connectors) {
|
||||
this.server = server;
|
||||
this.options = {};
|
||||
this.rawOptions = options;
|
||||
|
||||
for (const [optionName, option] of Object.entries(this.constructor.optionsSpecification)) {
|
||||
if (option.connector) {
|
||||
this.options[optionName] = connectors[this.rawOptions[optionName]];
|
||||
} else {
|
||||
if (option.required) {
|
||||
if (!(optionName in this.rawOptions)) throw new Error(`${this.constructor.name}: ${optionName} is required but missing.`);
|
||||
if (option.default === this.rawOptions[optionName]) throw new Error(`${this.constructor.name}: ${optionName} is required but is the default value.`);
|
||||
}
|
||||
|
||||
this.options[optionName] = this.rawOptions[optionName] || option.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async prepareToMount() {}
|
||||
|
||||
mount() {}
|
||||
|
||||
unmount() {}
|
||||
|
||||
static get description() {
|
||||
throw new Error('Plugin missing "static get description()" method.');
|
||||
}
|
||||
@ -13,13 +38,7 @@ export default class BasePlugin {
|
||||
throw new Error('Plugin missing "static get optionSpecification()" method.');
|
||||
}
|
||||
|
||||
constructor(server, options = {}, optionsRaw = {}) {
|
||||
this.server = server;
|
||||
this.options = options;
|
||||
this.optionsRaw = optionsRaw;
|
||||
}
|
||||
|
||||
verbose(...args) {
|
||||
Logger.verbose(this.constructor.name, ...args);
|
||||
}
|
||||
}
|
||||
}
|
@ -36,9 +36,7 @@ export default class ChatCommands extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
|
||||
mount() {
|
||||
for (const command of this.options.commands) {
|
||||
this.server.on(`CHAT_COMMAND:${command.command}`, async (data) => {
|
||||
if (command.ignoreChats.includes(data.chat)) return;
|
||||
|
@ -29,23 +29,33 @@ export default class DiscordAdminBroadcast extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.server.on('ADMIN_BROADCAST', async (info) => {
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: 'Admin Broadcast',
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Message',
|
||||
value: `${info.message}`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
this.onAdminBroadcast = this.onAdminBroadcast.bind(this);
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.server.on('ADMIN_BROADCAST', this.onAdminBroadcast);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.server.removeEventListener('ADMIN_BROADCAST', this.onAdminBroadcast);
|
||||
}
|
||||
|
||||
async onAdminBroadcast(info) {
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: 'Admin Broadcast',
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Message',
|
||||
value: `${info.message}`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -26,72 +26,80 @@ export default class DiscordAdminCamLogs extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.adminsInCam = {};
|
||||
|
||||
this.server.on('PLAYER_POSSESS', async (info) => {
|
||||
if (info.player === null || info.possessClassname !== 'CameraMan') return;
|
||||
this.onPlayerPossess = this.onPlayerPossess.bind(this);
|
||||
this.onPlayerUnPossess = this.onPlayerUnPossess.bind(this);
|
||||
}
|
||||
|
||||
this.adminsInCam[info.player.steamID] = info.time;
|
||||
mount() {
|
||||
this.server.on('PLAYER_POSSESS', this.onPlayerPossess);
|
||||
this.server.on('PLAYER_UNPOSSESS', this.onPlayerUnPossess);
|
||||
}
|
||||
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: `Admin Entered Admin Camera`,
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: "Admin's Name",
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Admin's SteamID",
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`,
|
||||
inline: true
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
});
|
||||
unmount() {
|
||||
this.server.removeEventListener('PLAYER_POSSESS', this.onPlayerPossess);
|
||||
this.server.removeEventListener('PLAYER_UNPOSSESS', this.onPlayerUnPossess);
|
||||
}
|
||||
|
||||
this.server.on('PLAYER_UNPOSSESS', async (info) => {
|
||||
if (
|
||||
info.player === null ||
|
||||
info.switchPossess === true ||
|
||||
!(info.player.steamID in this.adminsInCam)
|
||||
)
|
||||
return;
|
||||
async onPlayerPossess(info) {
|
||||
if (info.player === null || info.possessClassname !== 'CameraMan') return;
|
||||
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: `Admin Left Admin Camera`,
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: "Admin's Name",
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Admin's SteamID",
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Time in Admin Camera',
|
||||
value: `${Math.round(
|
||||
(info.time.getTime() - this.adminsInCam[info.player.steamID].getTime()) / 60000
|
||||
)} mins`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
this.adminsInCam[info.player.steamID] = info.time;
|
||||
|
||||
delete this.adminsInCam[info.player.steamID];
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: `Admin Entered Admin Camera`,
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: "Admin's Name",
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Admin's SteamID",
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`,
|
||||
inline: true
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onPlayerUnPossess(info) {
|
||||
if (info.player === null || info.switchPossess === true || !(info.player.steamID in this.adminsInCam)) return;
|
||||
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: `Admin Left Admin Camera`,
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: "Admin's Name",
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Admin's SteamID",
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Time in Admin Camera',
|
||||
value: `${Math.round(
|
||||
(info.time.getTime() - this.adminsInCam[info.player.steamID].getTime()) / 60000
|
||||
)} mins`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
|
||||
delete this.adminsInCam[info.player.steamID];
|
||||
}
|
||||
}
|
||||
|
@ -57,68 +57,78 @@ export default class DiscordAdminRequest extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.lastPing = Date.now() - this.pingDelay;
|
||||
this.lastPing = Date.now() - this.options.pingDelay;
|
||||
|
||||
this.server.on(`CHAT_COMMAND:${this.options.command}`, async (info) => {
|
||||
if (this.options.ignoreChats.includes(info.chat)) return;
|
||||
this.onChatCommand = this.onChatCommand.bind(this);
|
||||
}
|
||||
|
||||
for (const ignorePhrase of this.options.ignorePhrases) {
|
||||
if (info.message.includes(ignorePhrase)) return;
|
||||
}
|
||||
mount() {
|
||||
this.server.on(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand);
|
||||
}
|
||||
|
||||
if (info.message.length === 0) {
|
||||
await this.server.rcon.warn(
|
||||
info.player.steamID,
|
||||
`Please specify what you would like help with when requesting an admin.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
unmount() {
|
||||
this.server.removeEventListener(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand)
|
||||
}
|
||||
|
||||
const message = {
|
||||
embed: {
|
||||
title: `${info.player.name} has requested admin support!`,
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Player',
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'SteamID',
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Team & Squad',
|
||||
value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}`
|
||||
},
|
||||
{
|
||||
name: 'Message',
|
||||
value: info.message
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
};
|
||||
async onChatCommand(info) {
|
||||
if (this.options.ignoreChats.includes(info.chat)) return;
|
||||
|
||||
if (
|
||||
this.options.pingGroups.length > 0 &&
|
||||
Date.now() - this.options.pingDelay > this.lastPing
|
||||
) {
|
||||
message.content = this.options.pingGroups.map((groupID) => `<@&${groupID}>`).join(' ');
|
||||
this.lastPing = Date.now();
|
||||
}
|
||||
|
||||
await this.sendDiscordMessage(message);
|
||||
for (const ignorePhrase of this.options.ignorePhrases) {
|
||||
if (info.message.includes(ignorePhrase)) return;
|
||||
}
|
||||
|
||||
if (info.message.length === 0) {
|
||||
await this.server.rcon.warn(
|
||||
info.player.steamID,
|
||||
`An admin has been notified, please wait for us to get back to you.`
|
||||
`Please specify what you would like help with when requesting an admin.`
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const message = {
|
||||
embed: {
|
||||
title: `${info.player.name} has requested admin support!`,
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Player',
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'SteamID',
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.player.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Team & Squad',
|
||||
value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}`
|
||||
},
|
||||
{
|
||||
name: 'Message',
|
||||
value: info.message
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
this.options.pingGroups.length > 0 &&
|
||||
Date.now() - this.options.pingDelay > this.lastPing
|
||||
) {
|
||||
message.content = this.options.pingGroups.map((groupID) => `<@&${groupID}>`).join(' ');
|
||||
this.lastPing = Date.now();
|
||||
}
|
||||
|
||||
await this.sendDiscordMessage(message);
|
||||
|
||||
await this.server.rcon.warn(
|
||||
info.player.steamID,
|
||||
`An admin has been notified, please wait for us to get back to you.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,18 +14,12 @@ export default class DiscordBasePlugin extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
|
||||
this.channel = null;
|
||||
async prepareToMount() {
|
||||
this.channel = await this.options.discordClient.channels.fetch(this.options.channelID);
|
||||
}
|
||||
|
||||
async sendDiscordMessage(message, channelID = this.options.channelID) {
|
||||
if (this.channel === null)
|
||||
this.channel = await this.options.discordClient.channels.fetch(channelID);
|
||||
|
||||
if (typeof message === 'object' && 'embed' in message)
|
||||
message.embed.footer = { text: COPYRIGHT_MESSAGE };
|
||||
async sendDiscordMessage(message) {
|
||||
if (typeof message === 'object' && 'embed' in message) message.embed.footer = { text: COPYRIGHT_MESSAGE };
|
||||
|
||||
await this.channel.send(message);
|
||||
}
|
||||
|
@ -37,39 +37,49 @@ export default class DiscordChat extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.server.on('CHAT_MESSAGE', async (info) => {
|
||||
if (this.options.ignoreChats.includes(info.chat)) return;
|
||||
this.onChatMessage = this.onChatMessage.bind(this);
|
||||
}
|
||||
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: info.chat,
|
||||
color: this.options.chatColors[info.chat] || this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Player',
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'SteamID',
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Team & Squad',
|
||||
value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}`
|
||||
},
|
||||
{
|
||||
name: 'Message',
|
||||
value: `${info.message}`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
mount() {
|
||||
this.server.on('CHAT_MESSAGE', this.onChatMessage);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.server.removeEventListener('CHAT_MESSAGE', this.onChatMessage);
|
||||
}
|
||||
|
||||
async onChatMessage(info) {
|
||||
if (this.options.ignoreChats.includes(info.chat)) return;
|
||||
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: info.chat,
|
||||
color: this.options.chatColors[info.chat] || this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Player',
|
||||
value: info.player.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'SteamID',
|
||||
value: `[${info.player.steamID}](https://steamcommunity.com/profiles/${info.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Team & Squad',
|
||||
value: `Team: ${info.player.teamID}, Squad: ${info.player.squadID || 'Unassigned'}`
|
||||
},
|
||||
{
|
||||
name: 'Message',
|
||||
value: `${info.message}`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -30,9 +30,7 @@ export default class DiscordDebug extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
|
||||
mount() {
|
||||
for (const event of this.options.events) {
|
||||
this.server.on(event, async (info) => {
|
||||
await this.sendDiscordMessage(`\`\`\`${JSON.stringify({ ...info, event }, null, 2)}\`\`\``);
|
||||
|
@ -1,42 +0,0 @@
|
||||
import BasePlugin from './base-plugin.js';
|
||||
|
||||
export default class DiscordPlaceholder extends BasePlugin {
|
||||
static get description() {
|
||||
return (
|
||||
'The <code>DiscordPlaceholder</code> plugin can be used to create placeholder messages in Discord for use by ' +
|
||||
'other plugins.'
|
||||
);
|
||||
}
|
||||
|
||||
static get defaultEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static get optionsSpecification() {
|
||||
return {
|
||||
discordClient: {
|
||||
required: true,
|
||||
description: 'Discord connector name.',
|
||||
connector: 'discord',
|
||||
default: 'discord'
|
||||
},
|
||||
command: {
|
||||
required: false,
|
||||
description: 'Command that triggers the placeholder message.',
|
||||
default: '!placeholder'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
|
||||
this.options.discordClient.on('message', async (message) => {
|
||||
// check the author of the message is not a bot
|
||||
if (message.author.bot || !message.content.toLowerCase().startsWith(this.options.command))
|
||||
return;
|
||||
|
||||
await message.channel.send('Placeholder.');
|
||||
});
|
||||
}
|
||||
}
|
@ -47,44 +47,54 @@ export default class DiscordRcon extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.options.discordClient.on('message', async (message) => {
|
||||
// check the author of the message is not a bot and that the channel is the RCON console channel
|
||||
if (message.author.bot || message.channel.id !== this.channelID) return;
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
}
|
||||
|
||||
let command = message.content;
|
||||
mount() {
|
||||
this.options.discordClient.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
// write admin's name into broadcast command if prependAdminNameInBroadcast is enabled
|
||||
if (this.options.prependAdminNameInBroadcast)
|
||||
command = command.replace(
|
||||
/^AdminBroadcast /i,
|
||||
`AdminBroadcast ${message.member.displayName}: `
|
||||
);
|
||||
unmount() {
|
||||
this.options.discordClient.removeEventListener('message', this.onMessage);
|
||||
}
|
||||
|
||||
// check the admin has permissions
|
||||
if (Object.keys(this.options.permissions).length !== 0) {
|
||||
const commandPrefix = command.match(/([^ ]+)/);
|
||||
async onMessage(message) {
|
||||
// check the author of the message is not a bot and that the channel is the RCON console channel
|
||||
if (message.author.bot || message.channel.id !== this.channelID) return;
|
||||
|
||||
let hasPermission = false;
|
||||
for (const [role, allowedCommands] of Object.entries(this.options.permissions)) {
|
||||
if (!message.member._roles.includes(role)) continue;
|
||||
let command = message.content;
|
||||
|
||||
for (const allowedCommand of allowedCommands)
|
||||
if (commandPrefix[1].toLowerCase() === allowedCommand.toLowerCase())
|
||||
hasPermission = true;
|
||||
}
|
||||
// write admin's name into broadcast command if prependAdminNameInBroadcast is enabled
|
||||
if (this.options.prependAdminNameInBroadcast)
|
||||
command = command.replace(
|
||||
/^AdminBroadcast /i,
|
||||
`AdminBroadcast ${message.member.displayName}: `
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
await message.reply('you do not have permission to run that command.');
|
||||
return;
|
||||
}
|
||||
// check the admin has permissions
|
||||
if (Object.keys(this.options.permissions).length !== 0) {
|
||||
const commandPrefix = command.match(/([^ ]+)/);
|
||||
|
||||
let hasPermission = false;
|
||||
for (const [role, allowedCommands] of Object.entries(this.options.permissions)) {
|
||||
if (!message.member._roles.includes(role)) continue;
|
||||
|
||||
for (const allowedCommand of allowedCommands)
|
||||
if (commandPrefix[1].toLowerCase() === allowedCommand.toLowerCase())
|
||||
hasPermission = true;
|
||||
}
|
||||
|
||||
// execute command and print response
|
||||
await this.respondToMessage(message, await this.server.rcon.execute(command));
|
||||
});
|
||||
if (!hasPermission) {
|
||||
await message.reply('you do not have permission to run that command.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// execute command and print response
|
||||
await this.respondToMessage(message, await this.server.rcon.execute(command));
|
||||
}
|
||||
|
||||
async respondToMessage(message, response) {
|
||||
|
@ -26,23 +26,33 @@ export default class DiscordRoundWinner extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.server.on('NEW_GAME', async (info) => {
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: 'Round Winner',
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Message',
|
||||
value: `${info.winner} won on ${info.layer}.`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
this.onNewGame = this.onNewGame.bind(this);
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.server.on('NEW_GAME', this.onNewGame);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.server.removeEventListener('NEW_GAME', this.onNewGame);
|
||||
}
|
||||
|
||||
async onNewGame(info) {
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: 'Round Winner',
|
||||
color: this.options.color,
|
||||
fields: [
|
||||
{
|
||||
name: 'Message',
|
||||
value: `${info.winner} won on ${info.layer}.`
|
||||
}
|
||||
],
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -41,28 +41,38 @@ export default class DiscordServerStatus extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
setInterval(async () => {
|
||||
for (const messageID of this.options.messageIDs) {
|
||||
try {
|
||||
const channel = await this.options.discordClient.channels.fetch(messageID.channelID);
|
||||
const message = await channel.messages.fetch(messageID.messageID);
|
||||
this.update = this.update.bind(this);
|
||||
}
|
||||
|
||||
await message.edit(this.getEmbed());
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
mount() {
|
||||
this.interval = setInterval(this.update, this.options.updateInterval);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
async update() {
|
||||
for (const messageID of this.options.messageIDs) {
|
||||
try {
|
||||
const channel = await this.options.discordClient.channels.fetch(messageID.channelID);
|
||||
const message = await channel.messages.fetch(messageID.messageID);
|
||||
|
||||
await message.edit(this.getEmbed());
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
await this.options.discordClient.user.setActivity(
|
||||
`(${this.server.a2sPlayerCount}/${this.server.publicSlots}) ${
|
||||
this.server.layerHistory[0].layer || 'Unknown'
|
||||
}`,
|
||||
{ type: 'WATCHING' }
|
||||
);
|
||||
}, this.options.updateInterval);
|
||||
await this.options.discordClient.user.setActivity(
|
||||
`(${this.server.a2sPlayerCount}/${this.server.publicSlots}) ${
|
||||
this.server.layerHistory[0].layer || 'Unknown'
|
||||
}`,
|
||||
{ type: 'WATCHING' }
|
||||
);
|
||||
}
|
||||
|
||||
getEmbed() {
|
||||
|
@ -33,30 +33,42 @@ export default class DiscordSubsystemRestarter extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.options.discordClient.on('message', async (message) => {
|
||||
// check the author of the message is not a bot
|
||||
if (message.author.bot) return;
|
||||
|
||||
if (message.content.match(/!squadjs restartsubsystem rcon/i)) {
|
||||
if (!message.member._roles.includes(this.options.role)) {
|
||||
message.reply('you do not have permission to do that.');
|
||||
}
|
||||
|
||||
await this.server.restartRCON();
|
||||
message.reply('restarted the SquadJS RCON subsystem.');
|
||||
}
|
||||
|
||||
if (message.content.match(/!squadjs restartsubsystem logparser/i)) {
|
||||
if (!message.member._roles.includes(this.options.role)) {
|
||||
message.reply('you do not have permission to do that.');
|
||||
}
|
||||
|
||||
await this.server.restartLogParser();
|
||||
message.reply('restarted the SquadJS LogParser subsystem.');
|
||||
}
|
||||
});
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
}
|
||||
|
||||
mount(){
|
||||
this.options.discordClient.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.options.discordClient.removeEventListener('message', this.onMessage);
|
||||
}
|
||||
|
||||
async onMessage(message) {
|
||||
// check the author of the message is not a bot
|
||||
if (message.author.bot) return;
|
||||
|
||||
if (message.content.match(/!squadjs restartsubsystem rcon/i)) {
|
||||
if (!message.member._roles.includes(this.options.role)) {
|
||||
message.reply('you do not have permission to do that.');
|
||||
}
|
||||
|
||||
await this.server.restartRCON();
|
||||
message.reply('restarted the SquadJS RCON subsystem.');
|
||||
}
|
||||
|
||||
if (message.content.match(/!squadjs restartsubsystem logparser/i)) {
|
||||
if (!message.member._roles.includes(this.options.role)) {
|
||||
message.reply('you do not have permission to do that.');
|
||||
}
|
||||
|
||||
await this.server.restartLogParser();
|
||||
message.reply('restarted the SquadJS LogParser subsystem.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -34,53 +34,63 @@ export default class DiscordTeamkill extends DiscordBasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.server.on('TEAMKILL', async (info) => {
|
||||
if (!info.attacker) return;
|
||||
this.onTeamkill = this.onTeamkill.bind(this);
|
||||
}
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: "Attacker's Name",
|
||||
value: info.attacker.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Attacker's SteamID",
|
||||
value: `[${info.attacker.steamID}](https://steamcommunity.com/profiles/${info.attacker.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Weapon',
|
||||
value: info.weapon
|
||||
},
|
||||
{
|
||||
name: "Victim's Name",
|
||||
value: info.victim.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Victim's SteamID",
|
||||
value: `[${info.victim.steamID}](https://steamcommunity.com/profiles/${info.victim.steamID})`,
|
||||
inline: true
|
||||
}
|
||||
];
|
||||
mount() {
|
||||
this.server.on('TEAMKILL', this.onTeamkill);
|
||||
}
|
||||
|
||||
if (!this.options.disableSCBL)
|
||||
fields.push({
|
||||
name: 'Squad Community Ban List',
|
||||
value: `[Attacker's Bans](https://squad-community-ban-list.com/search/${info.attacker.steamID})`
|
||||
});
|
||||
unmount() {
|
||||
this.server.removeEventListener('TEAMKILL', this.onTeamkill);
|
||||
}
|
||||
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: `Teamkill: ${info.attacker.name}`,
|
||||
color: this.options.color,
|
||||
fields: fields,
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
async onTeamkill(info) {
|
||||
if (!info.attacker) return;
|
||||
|
||||
const fields = [
|
||||
{
|
||||
name: "Attacker's Name",
|
||||
value: info.attacker.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Attacker's SteamID",
|
||||
value: `[${info.attacker.steamID}](https://steamcommunity.com/profiles/${info.attacker.steamID})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: 'Weapon',
|
||||
value: info.weapon
|
||||
},
|
||||
{
|
||||
name: "Victim's Name",
|
||||
value: info.victim.name,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Victim's SteamID",
|
||||
value: `[${info.victim.steamID}](https://steamcommunity.com/profiles/${info.victim.steamID})`,
|
||||
inline: true
|
||||
}
|
||||
];
|
||||
|
||||
if (!this.options.disableSCBL)
|
||||
fields.push({
|
||||
name: 'Squad Community Ban List',
|
||||
value: `[Attacker's Bans](https://squad-community-ban-list.com/search/${info.attacker.steamID})`
|
||||
});
|
||||
|
||||
await this.sendDiscordMessage({
|
||||
embed: {
|
||||
title: `Teamkill: ${info.attacker.name}`,
|
||||
color: this.options.color,
|
||||
fields: fields,
|
||||
timestamp: info.time.toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import DiscordAdminCamLogs from './discord-admin-cam-logs.js';
|
||||
import DiscordAdminRequest from './discord-admin-request.js';
|
||||
import DiscordChat from './discord-chat.js';
|
||||
import DiscordDebug from './discord-debug.js';
|
||||
import DiscordPlaceholder from './discord-placeholder.js';
|
||||
import DiscordRcon from './discord-rcon.js';
|
||||
import DiscordRoundWinner from './discord-round-winner.js';
|
||||
import DiscordServerStatus from './discord-server-status.js';
|
||||
@ -22,7 +21,6 @@ const plugins = [
|
||||
DiscordAdminRequest,
|
||||
DiscordChat,
|
||||
DiscordDebug,
|
||||
DiscordPlaceholder,
|
||||
DiscordRcon,
|
||||
DiscordRoundWinner,
|
||||
DiscordServerStatus,
|
||||
|
@ -28,12 +28,22 @@ export default class IntervalledBroadcasts extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
setInterval(async () => {
|
||||
await this.server.rcon.broadcast(this.options.broadcasts[0]);
|
||||
this.broadcasts.push(this.options.broadcasts.shift());
|
||||
}, this.options.interval);
|
||||
this.broadcast = this.broadcast.bind(this);
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.interval = setInterval(this.broadcast, this.options.interval);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
async broadcast() {
|
||||
await this.server.rcon.broadcast(this.options.broadcasts[0]);
|
||||
this.broadcasts.push(this.options.broadcasts.shift());
|
||||
}
|
||||
}
|
||||
|
@ -48,21 +48,31 @@ export default class SeedingMode extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
setInterval(async () => {
|
||||
if (
|
||||
this.server.a2sPlayerCount !== 0 &&
|
||||
this.server.a2sPlayerCount < this.options.liveThreshold
|
||||
)
|
||||
await this.server.rcon.broadcast(this.options.seedingMessage);
|
||||
else if (
|
||||
this.server.a2sPlayerCount !== 0 &&
|
||||
this.options.liveEnabled &&
|
||||
this.server.a2sPlayerCount < this.options.liveThreshold
|
||||
)
|
||||
await this.server.rcon.broadcast(this.options.liveMessage);
|
||||
}, this.options.interval);
|
||||
this.broadcast = this.broadcast.bind(this);
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.interval = setInterval(this.broadcast, this.options.interval);
|
||||
}
|
||||
|
||||
unmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
async broadcast() {
|
||||
if (
|
||||
this.server.a2sPlayerCount !== 0 &&
|
||||
this.server.a2sPlayerCount < this.options.liveThreshold
|
||||
)
|
||||
await this.server.rcon.broadcast(this.options.seedingMessage);
|
||||
else if (
|
||||
this.server.a2sPlayerCount !== 0 &&
|
||||
this.options.liveEnabled &&
|
||||
this.server.a2sPlayerCount < this.options.liveThreshold
|
||||
)
|
||||
await this.server.rcon.broadcast(this.options.liveMessage);
|
||||
}
|
||||
}
|
||||
|
@ -22,34 +22,44 @@ export default class TeamRandomizer extends BasePlugin {
|
||||
};
|
||||
}
|
||||
|
||||
constructor(server, options, optionsRaw) {
|
||||
super(server, options, optionsRaw);
|
||||
constructor(server, options, connectors) {
|
||||
super(server, options, connectors);
|
||||
|
||||
this.server.on(`CHAT_COMMAND:${this.options.command}`, async (info) => {
|
||||
if (info.chat !== 'ChatAdmin') return;
|
||||
this.onChatCommand = this.onChatCommand.bind(this);
|
||||
}
|
||||
|
||||
const players = this.server.players.slice(0);
|
||||
mount() {
|
||||
this.server.on(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand);
|
||||
}
|
||||
|
||||
let currentIndex = players.length;
|
||||
let temporaryValue;
|
||||
let randomIndex;
|
||||
unmount() {
|
||||
this.server.removeEventListener(`CHAT_COMMAND:${this.options.command}`, this.onChatCommand);
|
||||
}
|
||||
|
||||
while (currentIndex !== 0) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
async onChatCommand(info) {
|
||||
if (info.chat !== 'ChatAdmin') return;
|
||||
|
||||
temporaryValue = players[currentIndex];
|
||||
players[currentIndex] = players[randomIndex];
|
||||
players[randomIndex] = temporaryValue;
|
||||
}
|
||||
const players = this.server.players.slice(0);
|
||||
|
||||
let team = '1';
|
||||
let currentIndex = players.length;
|
||||
let temporaryValue;
|
||||
let randomIndex;
|
||||
|
||||
for (const player of players) {
|
||||
if (player.teamID !== team) await this.server.rcon.switchTeam(player.steamID);
|
||||
while (currentIndex !== 0) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
|
||||
team = team === '1' ? '2' : '1';
|
||||
}
|
||||
});
|
||||
temporaryValue = players[currentIndex];
|
||||
players[currentIndex] = players[randomIndex];
|
||||
players[randomIndex] = temporaryValue;
|
||||
}
|
||||
|
||||
let team = '1';
|
||||
|
||||
for (const player of players) {
|
||||
if (player.teamID !== team) await this.server.rcon.switchTeam(player.steamID);
|
||||
|
||||
team = team === '1' ? '2' : '1';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user