From 1aa6c3fa6ef68e117082a74e3a4825996d3b956d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Sun, 15 Jun 2025 16:39:46 +0200 Subject: [PATCH 01/71] Fix container failing to start after docker daemon restart --- docker_run.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker_run.sh b/docker_run.sh index 1b2ede52..78abe33d 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -36,8 +36,7 @@ fi if find /app/server/trakman -mindepth 1 -maxdepth 1 | read; then echo 'Trakman exists. Attempting update...' cd trakman || exit - cp ../trakmanbk/Update.js . - node Update.js /app/server/trakmanbk/.hashes.json + cp ../trakmanbk/Update.js . && node Update.js /app/server/trakmanbk/.hashes.json || echo "Update not available." if [ $? -gt 0 ]; then chown server:server update.log echo 'Update not fully successful, please stop the container.' From 9ae0ca8fe7966b47ea798ab3c06ea3d8a9283c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Wed, 18 Jun 2025 19:43:58 +0200 Subject: [PATCH 02/71] docker run script: added proper error handling, support for relaunching container without removing, log file for server restarts, fixed unescaped dollar signs in run.sh --- docker_run.sh | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/docker_run.sh b/docker_run.sh index 78abe33d..4556f9b7 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -5,7 +5,7 @@ # create and copy dedicated config if find /app/server/GameData/Config -mindepth 1 -maxdepth 1 | read; then echo 'Server config exists, skipping initial setup.' - rm dedicated_cfg.txt.bk + rm dedicated_cfg.txt.bk 2>/dev/null else echo 'Setting up server...' # ugly xml replacement @@ -17,17 +17,30 @@ else xml ed -L -u "/dedicated/masterserver_account/password" -v "$SERVER_ACC_PASSWORD" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/masterserver_account/validation_key" -v "$SERVER_ACC_KEY" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/server_options/name" -v "$SERVER_NAME" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/comment" -v "$SERVER_COMMENT" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/hide_server" -v "${SERVER_HIDE_SERVER=0}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/max_players" -v "${SERVER_MAX_PLAYERS=32}" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/server_options/password" -v "$SERVER_PASSWORD" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/max_spectators" -v "${SERVER_MAX_SPECTATORS=32}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/password_spectator" -v "$SERVER_PASSWORD_SPECTATOR" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/ladder_mode" -v "${SERVER_LADDER_MODE=1}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/ladder_serverlimit_min" -v "${SERVER_LADDER_SERVERLIMIT_MIN=0}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/ladder_serverlimit_max" -v "${SERVER_LADDER_SERVERLIMIT_MAX=50000}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/enable_p2p_upload" -v "${SERVER_ENABLE_P2P_UPLOAD=True}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/server_options/enable_p2p_download" -v "${SERVER_ENABLE_P2P_DOWNLOAD=True}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/connection_uploadrate" -v "${SERVER_CONNECTION_UPLOADRATE=512}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/connection_downloadrate" -v "${SERVER_CONNECTION_DOWNLOADRATE=8192}" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/server_port" -v "$SERVER_NET_PORT" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/server_p2p_port" -v "$SERVER_P2P_PORT" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/xmlrpc_port" -v "$SERVER_PORT" dedicated_cfg.txt.bk - xml ed -L -u "/dedicated/system_config/packmask" -v "$SERVER_PACKMASK" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/packmask" -v "${SERVER_PACKMASK=nations}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/disable_coherence_checks" -v "${SERVER_DISABLE_COHERENCE_CHECKS=laps}" dedicated_cfg.txt.bk mv /app/server/dedicated_cfg.txt.bk /app/server/GameData/Config/dedicated_cfg.txt fi # copy over default tracks if find /app/server/GameData/Tracks -mindepth 1 -maxdepth 1 | read; then echo 'Tracks exist, skipping initial setup.' - rm -r Tracksbk + rm -r Tracksbk 2>/dev/null else echo 'Setting up tracks...' mv /app/server/Tracksbk/* /app/server/GameData/Tracks/ @@ -35,16 +48,27 @@ fi # update and copy over Trakman directory if find /app/server/trakman -mindepth 1 -maxdepth 1 | read; then echo 'Trakman exists. Attempting update...' - cd trakman || exit - cp ../trakmanbk/Update.js . && node Update.js /app/server/trakmanbk/.hashes.json || echo "Update not available." - if [ $? -gt 0 ]; then - chown server:server update.log - echo 'Update not fully successful, please stop the container.' - sleep 1m # wait a minute for user to read the message, or to realise something's wrong + if ! cd trakman 2>/dev/null + then + echo 'Trakman actually does not exist. This makes no sense. Aborting and trying again.' exit fi + if ! cp ../trakmanbk/Update.js . 2>/dev/null + then + # this runs if trakmanbk doesn't exist so we have nothing to update from + echo "Update not available." + else + if ! node Update.js /app/server/trakmanbk/.hashes.json + then + # this runs if trakmanbk exists and the update script fails + chown server:server update.log + echo 'Update not fully successful, please stop the container.' + sleep 1m # wait a minute for the user to read the message, or to realise something's wrong + exit 1 + fi + fi cd .. - rm -r trakmanbk + rm -r trakmanbk 2>/dev/null else echo 'Setting up Trakman...' mv /app/server/trakmanbk/* /app/server/trakman/ @@ -62,6 +86,7 @@ touch trakman/logs/trace.log mkdir -p .pm2/logs touch .pm2/logs/Trakman-error.log touch .pm2/logs/Trakman-out.log +touch .pm2/logs/docker.log mkdir -p trakman/temp touch trakman/temp/rank_coherence.txt mkdir -p trakman/plugins/server_links/temp @@ -71,7 +96,7 @@ chown -R server:server /app/server echo "#!/bin/sh (while true; do /app/server/TrackmaniaServer /game_settings=MatchSettings/MatchSettings.txt /dedicated_cfg=dedicated_cfg.txt /nodaemon - echo 'Server exited with code ' $? + echo [\$(date +'%d %b %Y %T')] Server exited with code \$? | tee -a /app/server/.pm2/logs/docker.log echo 'Restarting...' done) & npm i --prefix /app/server/trakman @@ -80,7 +105,7 @@ chmod -R a+w /app/server cd trakman trap 'echo Terminating; npx pm2 stop 0; npx pm2 kill; exit' SIGTERM SIGINT npx pm2 start ./built/src/Main.js --name Trakman -wait $!" > run.sh +wait \$!" > run.sh chown server:server run.sh chmod 766 run.sh exec su-exec server ./run.sh From 9e5b62116d377d8944a762901b1e021be8794fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Thu, 19 Jun 2025 13:52:47 +0200 Subject: [PATCH 03/71] moved version and repo information to package.json, improved update script --- Update.js | 12 +++++-- config/Config.js | 8 ++--- package-lock.json | 86 +++++++++++++++++++++++++---------------------- package.json | 7 +++- src/Logger.ts | 2 +- src/Trakman.ts | 11 +++++- 6 files changed, 73 insertions(+), 53 deletions(-) diff --git a/Update.js b/Update.js index c66d182a..4c1d876d 100644 --- a/Update.js +++ b/Update.js @@ -118,6 +118,7 @@ async function doUpdate(fromPath, newHashes, oldHashes = null) { errorOccurred = true } } + // copy new hashes so you only have to update once try { await fs.copyFile(process.argv[2], '.hashes.json') @@ -130,27 +131,32 @@ async function doUpdate(fromPath, newHashes, oldHashes = null) { console.log('Errors occurred during update, exiting...') process.exit(1) } - let log = 'Update successful!' + + let log = `Update log to version ${JSON.parse(await fs.readFile(path.join(fromPath, 'package.json'), { encoding: 'utf-8' }))['version']}\n\n` if (conflicts.length > 0) { console.log('!!! Update conflicts, make sure to fix them by comparing your files to files ending in .new !!!') console.log('If you do not fix these conflicts, the controller might fail to start or crash.') console.log('Affected files: ') - log = 'Update did not succeed because of conflicts. Please merge the following files manually:\n' + log += 'Update did not succeed because of conflicts. Please merge the following files manually:\n' conflicts.forEach(name => { console.log(name) log += name + '\n' }) console.log('_____________________________________') + } else { + log += 'Update successful!' } + try { await fs.writeFile('update.log', log, { encoding: 'utf-8' }) } catch(e) { console.log('Failed to write update log.') console.log(e) } + if (conflicts.length > 0) { process.exit(2) } if (updatePerformed) { - console.log('Update successful.') + console.log('Update successful!') } else { console.log('Update not necessary.') } diff --git a/config/Config.js b/config/Config.js index 36857885..7a65671c 100644 --- a/config/Config.js +++ b/config/Config.js @@ -81,7 +81,7 @@ export default { blacklistFile: "blacklist.txt", /** Relative path (/GameData/Config/) to the guestlist file */ guestlistFile: "guestlist.txt", - /** Relative path (/GameData/Tracks/) to the matchsettings file */ + /** Relative path (/GameData/Tracks/) to the match settings file, ignored if manualMapLoading is enabled */ matchSettingsFile: "MatchSettings/MatchSettings.txt", /** Default message sent as the reason for administrative actions if nothing was specified by the admin */ defaultReasonMessage: 'No reason specified', @@ -93,9 +93,5 @@ export default { searchOptions: { intraMode: 1, alpha: "a-zа-яё" - }, - /** Current controller version */ - version: "1.6.1", - /** Controller repository link */ - repo: "github.com/lythx/trakman", + } } diff --git a/package-lock.json b/package-lock.json index d47eadc8..6ffad55d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,20 @@ { "name": "trakman", + "version": "1.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "trakman", + "version": "1.6.1", + "license": "MIT", "dependencies": { "@leeoniya/ufuzzy": "^1.0.18", "dotenv": "^16.5.0", "node-fetch": "^3.3.2", "pg": "^8.16.0", "pg-copy-streams": "^6.0.6", - "pm2": "^6.0.6", + "pm2": "^6.0.8", "typescript": "^5.8.3", "xml2js": "^0.5.0" }, @@ -309,9 +313,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.110", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.110.tgz", - "integrity": "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q==", + "version": "18.19.112", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.112.tgz", + "integrity": "sha512-i+Vukt9POdS/MBI7YrrkkI5fMfwFtOjphSmt4WXYLfwqsfr6z/HdCx7LqT9M7JktGob8WNgj8nFB4TbGNE4Cog==", "dev": true, "license": "MIT", "dependencies": { @@ -352,9 +356,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -426,12 +430,12 @@ } }, "node_modules/ansis": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0.tgz", - "integrity": "sha512-P8nrHI1EyW9OfBt1X7hMSwGN2vwRuqHSKJAT1gbLWZRzDa24oHjYwGHvEgHeBepupzk878yS/HBZ0NMPYtbolw==", + "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", "license": "ISC", "engines": { - "node": ">=14" + "node": ">=10" } }, "node_modules/anymatch": { @@ -1309,22 +1313,22 @@ "license": "MIT" }, "node_modules/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.1.tgz", + "integrity": "sha512-5n6e7MgF5ABRsssOsX9xC95p+NUuhgDQDBSsrKSZJjYVqZHGyrmJuknym2IbVhGtzV9siBdzH9SEIQAuWF+sdg==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.9.0", - "pg-pool": "^3.10.0", - "pg-protocol": "^1.10.0", + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.1", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.2.5" + "pg-cloudflare": "^1.2.6" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -1336,16 +1340,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", - "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz", + "integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", - "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", "license": "MIT" }, "node_modules/pg-copy-streams": { @@ -1367,18 +1371,18 @@ } }, "node_modules/pg-pool": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", - "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", - "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.1.tgz", + "integrity": "sha512-9YS3ZonDj0Lxny//aF0ITPdfrEPgKWCJvONsSXAaIUhgpzlzl5JgaZNlbTFxvYNfm2terGEnHeOSUlF6qRGBzw==", "license": "MIT" }, "node_modules/pg-types": { @@ -1431,16 +1435,16 @@ } }, "node_modules/pm2": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.6.tgz", - "integrity": "sha512-6t+I+Gc232dKe0OjwFYlU59JoY8MWBqsBmcZMSfKollByL4q8APUFwWOt7OtPH4lGO4Sb6GiFAqu7xT42QeRBA==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.8.tgz", + "integrity": "sha512-y7sO+UuGjfESK/ChRN+efJKAsHrBd95GY2p1GQfjVTtOfFtUfiW0NOuUhP5dN5QTF2F0EWcepgkLqbF32j90Iw==", "license": "AGPL-3.0", "dependencies": { "@pm2/agent": "~2.1.1", "@pm2/io": "~6.1.0", "@pm2/js-api": "~0.8.0", "@pm2/pm2-version-check": "latest", - "ansis": "4.0.0", + "ansis": "4.0.0-node10", "async": "~3.2.6", "blessed": "0.1.81", "chokidar": "^3.5.3", @@ -1773,9 +1777,9 @@ } }, "node_modules/socks": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", - "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", "license": "MIT", "dependencies": { "ip-address": "^9.0.5", @@ -1859,9 +1863,9 @@ } }, "node_modules/systeminformation": { - "version": "5.27.1", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.1.tgz", - "integrity": "sha512-FgkVpT6GgATtNvADgtEzDxI/SVaBisfnQ4fmgQZhCJ4335noTgt9q6O81ioHwzs9HgnJaaFSdHSEMIkneZ55iA==", + "version": "5.27.5", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.5.tgz", + "integrity": "sha512-Kw8gAew7cvOMbCYISLeyf7e18p8/XaMz9lxARPlIsDatROB3yIS3xulfzD+lYHWe63vsddOCzDZBHt4E2yuq0Q==", "license": "MIT", "optional": true, "os": [ diff --git a/package.json b/package.json index b5c05dfc..fbfa5c03 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,16 @@ { + "name": "trakman", + "version": "1.6.1", + "private": "true", + "license": "MIT", + "homepage": "http://github.com/lythx/trakman", "dependencies": { "@leeoniya/ufuzzy": "^1.0.18", "dotenv": "^16.5.0", "node-fetch": "^3.3.2", "pg": "^8.16.0", "pg-copy-streams": "^6.0.6", - "pm2": "^6.0.6", + "pm2": "^6.0.8", "typescript": "^5.8.3", "xml2js": "^0.5.0" }, diff --git a/src/Logger.ts b/src/Logger.ts index 9113f94e..470ad554 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -212,10 +212,10 @@ export abstract class Logger { private static async writeLog(tag: Tag, location: string, date: string, lines: any[], force = false): Promise { if (lines.length === 0 || this.logTypes[tag].level > this.logLevel) { return } - const logStr: string = this.getLogfileString(tag, lines, location, date) if (!this.consoleDisabled) { this.consoleLog(this.getConsoleString(tag, lines, location, date), force) } + const logStr: string = this.getLogfileString(tag, lines, location, date) for (const file of this.logTypes[tag].files) { await fs.appendFile(file, logStr) } diff --git a/src/Trakman.ts b/src/Trakman.ts index c9a27086..b969b259 100644 --- a/src/Trakman.ts +++ b/src/Trakman.ts @@ -18,12 +18,17 @@ import prefixes from '../config/PrefixesAndPalette.js' import controllerConfig from '../config/Config.js' import { RoundsService } from './services/RoundsService.js' import { forceFixRankCoherence } from './FixRankCoherence.js' +import fs from 'node:fs/promises' const playersRepo: PlayerRepository = new PlayerRepository() const mapIdsRepo: MapIdsRepository = new MapIdsRepository() const DB: Database = new Database() +// parsing this ourselves instead of using npm_package_version or _homepage because those don't work half the time +// crashes the controller if package.json doesn't exist, Too Bad! +const packageObject = JSON.parse(await fs.readFile('package.json', { encoding: 'utf-8' })) + namespace trakman { export const utils = Utils @@ -730,7 +735,11 @@ namespace trakman { /** * Controller config. */ - controller: controllerConfig, + controller: { + ...controllerConfig, + version: packageObject.version, + repo: packageObject.homepage + }, /** * Current dedicated server config. From d616dd32fa4c5544fc72526941adba48756c71d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Sat, 21 Jun 2025 12:58:46 +0200 Subject: [PATCH 04/71] db auto-reconnect and timeout for failing --- docker_run.sh | 2 +- src/Logger.ts | 4 +-- src/Main.ts | 4 +-- src/database/DB.ts | 58 +++++++++++++++++++++++++++++++++-- src/database/MapRepository.ts | 6 +++- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/docker_run.sh b/docker_run.sh index 4556f9b7..b8107461 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -104,7 +104,7 @@ npm run build --prefix /app/server/trakman chmod -R a+w /app/server cd trakman trap 'echo Terminating; npx pm2 stop 0; npx pm2 kill; exit' SIGTERM SIGINT -npx pm2 start ./built/src/Main.js --name Trakman +npm run daemon wait \$!" > run.sh chown server:server run.sh chmod 766 run.sh diff --git a/src/Logger.ts b/src/Logger.ts index 470ad554..49352829 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -85,10 +85,10 @@ export abstract class Logger { } }) process.on('uncaughtException', (err: Error): void => { - void this.fatal('Uncaught exception occured: ', err.message, ...(err.stack === undefined ? '' : err.stack.split('\n'))) // indent fix + void this.fatal('Uncaught exception occurred: ', err.message, ...(err.stack === undefined ? '' : err.stack.split('\n'))) // indent fix }) process.on('unhandledRejection', (err: Error): void => { - void this.fatal('Unhandled rejection occured: ', err.message, ...(err.stack === undefined ? '' : err.stack.split('\n'))) + void this.fatal('Unhandled rejection occurred: ', err.message, ...(err.stack === undefined ? '' : err.stack.split('\n'))) }) if (this.useDiscord) { const envDcLog = Number(process.env.DISCORD_LOG_LEVEL) diff --git a/src/Main.ts b/src/Main.ts index ace5c405..c637a5d0 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -36,13 +36,13 @@ const authenticationStatus: any | Error = await Client.call('Authenticate', [ if (authenticationStatus instanceof Error) { await Logger.fatal('Authentication failed. Server responded with an error:', authenticationStatus.message) } Logger.trace('Authentication success') Logger.trace('Initializing database...') -Database.initialize() +await Database.initialize() Logger.trace('Database initialized...') await fixRankCoherence() Logger.trace('Retrieving game info...') await GameService.initialize() Logger.trace('Game info fetched') -// import plugins after initializing database to avoid process exiting with no error in case of query on inexistent table +// import plugins after initializing database to avoid process exiting with no error in case of query on non-existent table await import('../Plugins.js') Logger.trace('Fetching player info...') await PlayerService.initialize() diff --git a/src/database/DB.ts b/src/database/DB.ts index 959c282f..6471506b 100644 --- a/src/database/DB.ts +++ b/src/database/DB.ts @@ -13,24 +13,73 @@ export class Database { password: process.env.DB_PASSWORD, database: process.env.DB_NAME, host: process.env.DB_IP, - port: Number(process.env.DB_PORT) + port: Number(process.env.DB_PORT), + connectionTimeoutMillis: 15000 }) + private static reconnectingPool: boolean = false + private static reconnectTimeout: number private client: postgres.Pool | postgres.PoolClient = Database.pool static dbVersion: string static dbSize: string - static async initialize(): Promise { + static async initialize(reconnectTimeout: number = 1000): Promise { + this.reconnectTimeout = reconnectTimeout for (const e of createQueries) { await this.pool.query(e).catch(async (err: Error) => { await Logger.fatal(`Database create query failed.`, `Error: ${err.message}`, err.stack, `Query:`, e) }) } + await this.getDBInfo() + + this.pool.on('error', (err: Error) => { + if (err instanceof postgres.DatabaseError && err.code === '57P01') { + if (this.reconnectingPool) { + return + } + this.reconnectingPool = true + Logger.error(`Lost connection to database, attempting to reconnect in ${this.reconnectTimeout/1000} second(s)`) + setTimeout(async () => { + Logger.debug('Reconnecting to database...') + await this.pool.query(`select version();`).catch(async (err: Error) => { + await Logger.fatal('Failed to reconnect to database', err.message) + }) + await this.getDBInfo() + Logger.info('Reconnected to database successfully') + this.reconnectingPool = false + }, this.reconnectTimeout) + } else { + Logger.error(err) + } + }) + } + + private static async getDBInfo(): Promise { this.dbVersion = String((await this.pool.query(`select version();`) as any)?.rows[0]?.version?.split(` `, 2)[1]) this.dbSize = String((await this.pool.query(`select pg_size_pretty(pg_database_size('${process.env.DB_NAME}'));`) as any)?.rows[0]?.pg_size_pretty) } async enableClient(): Promise { this.client = await Database.pool.connect() + this.client.on('error', (err: Error) => { + if (this.client instanceof postgres.Pool) { + Logger.error('handleClientError() called on a non-client') + return + } + // this error code occurs when the database shuts down and must be re-enabled + if (err instanceof postgres.DatabaseError && err.code === '57P01') { + this.client.release(true) + setTimeout(async () => { + await this.enableClient() + // no point handling errors here because + // it just fires somewhere internally, and we have no way of catching it... + // thankfully the timeout helps here + await this.client.query(`select version();`) + Logger.debug('Re-enabled DB client') + }, Database.reconnectTimeout) + } else { + Logger.error(err) + } + }) } /** @@ -52,7 +101,10 @@ export class Database { * @param q table name and column names in parentheses, ex. "maps(id, name, etc.)" * @returns a stream that can be piped into */ - stream(q: string): CopyStreamQuery { + stream(q: string): CopyStreamQuery | Error { + if (this.client instanceof postgres.Pool) { + return new Error('You must first call enableClient() on this instance before using this method') + } return this.client.query(from(`COPY ${q} FROM STDIN;`)) } diff --git a/src/database/MapRepository.ts b/src/database/MapRepository.ts index 6e0cfa63..2fa02d73 100644 --- a/src/database/MapRepository.ts +++ b/src/database/MapRepository.ts @@ -73,8 +73,12 @@ export class MapRepository extends Repository { } }) const bulk = values.join('\n') - const stream: CopyStreamQuery = this.db.stream( + const stream: CopyStreamQuery | Error = this.db.stream( 'maps(id, name, filename, author, environment, mood, bronze_time, silver_time, ' + 'gold_time,author_time, copper_price, is_lap_race, laps_amount, checkpoints_amount, add_date, leaderboard_rating, awards)') + if (stream instanceof Error) { + await Logger.fatal('Failed to mass-add maps to database', stream) + return + } const src = new Readable() src.readable = true From f3a7b6ab920ac2885b8e8f03ed59396b7276df13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Sat, 21 Jun 2025 13:10:20 +0200 Subject: [PATCH 05/71] added option to disable update utility --- docker-compose.yml | 2 ++ docker_run.sh | 38 +++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fd78efad..767b725d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -53,6 +53,8 @@ services: DISCORD_EMBED_TITLE_URL: "" DISCORD_EMBED_TITLE_ICON_URL: "" DISCORD_TAGGED_USERS: "" + # Setup + UPDATE_UTILITY_DISABLED: "NO" ports: # Don't forget to change the ports # if you already have another server running # **The ports MUST match** because of the way the server works diff --git a/docker_run.sh b/docker_run.sh index b8107461..5faba396 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -47,27 +47,31 @@ else fi # update and copy over Trakman directory if find /app/server/trakman -mindepth 1 -maxdepth 1 | read; then - echo 'Trakman exists. Attempting update...' - if ! cd trakman 2>/dev/null - then - echo 'Trakman actually does not exist. This makes no sense. Aborting and trying again.' - exit - fi - if ! cp ../trakmanbk/Update.js . 2>/dev/null - then - # this runs if trakmanbk doesn't exist so we have nothing to update from - echo "Update not available." + if [ "$UPDATE_UTILITY_DISABLED" = "YES" ]; then + echo 'Trakman exists, skipping initial setup.' else - if ! node Update.js /app/server/trakmanbk/.hashes.json + echo 'Trakman exists. Attempting update...' + if ! cd trakman 2>/dev/null + then + echo 'Trakman actually does not exist. This makes no sense. Aborting and trying again.' + exit + fi + if ! cp ../trakmanbk/Update.js . 2>/dev/null then - # this runs if trakmanbk exists and the update script fails - chown server:server update.log - echo 'Update not fully successful, please stop the container.' - sleep 1m # wait a minute for the user to read the message, or to realise something's wrong - exit 1 + # this runs if trakmanbk doesn't exist so we have nothing to update from + echo "Update not available." + else + if ! node Update.js /app/server/trakmanbk/.hashes.json + then + # this runs if trakmanbk exists and the update script fails + chown server:server update.log + echo 'Update not fully successful, please stop the container.' + sleep 1m # wait a minute for the user to read the message, or to realise something's wrong + exit 1 + fi fi + cd .. fi - cd .. rm -r trakmanbk 2>/dev/null else echo 'Setting up Trakman...' From 8ade2cc32985ab56276f3dede940e1fae15e4e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Tue, 24 Jun 2025 12:45:20 +0200 Subject: [PATCH 06/71] default values for version and homepage, logs display milliseconds for more accuracy --- docker_run.sh | 2 +- src/Logger.ts | 34 ++++++++++++++-------------------- src/Trakman.ts | 4 ++-- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/docker_run.sh b/docker_run.sh index 5faba396..0bb14e3a 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -100,7 +100,7 @@ chown -R server:server /app/server echo "#!/bin/sh (while true; do /app/server/TrackmaniaServer /game_settings=MatchSettings/MatchSettings.txt /dedicated_cfg=dedicated_cfg.txt /nodaemon - echo [\$(date +'%d %b %Y %T')] Server exited with code \$? | tee -a /app/server/.pm2/logs/docker.log + echo [\$(date +'%d %b %Y %T.%3N')] Server exited with code \$? | tee -a /app/server/.pm2/logs/docker.log echo 'Restarting...' done) & npm i --prefix /app/server/trakman diff --git a/src/Logger.ts b/src/Logger.ts index 49352829..c1fa2bce 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -116,11 +116,9 @@ export abstract class Logger { static async fatal(...lines: any[]): Promise { if (this.crashed) { return } this.crashed = true - const date: string = new Date().toUTCString() - const location: string = this.getLocation() // In case discord message hangs the process it exits after 10 seconds anyway setTimeout(() => process.exit(1), 10000) - await this.writeLog('fatal', location, date, lines, true) + void this.writeLog('fatal', this.getLocation(), this.getDateString(), lines, true) process.exit(1) } @@ -130,9 +128,7 @@ export abstract class Logger { */ static error(...lines: any[]): void { if (this.crashed) { return } - const date: string = new Date().toUTCString() - const location: string = this.getLocation() - void this.writeLog('error', location, date, lines) + void this.writeLog('error', this.getLocation(), this.getDateString(), lines) } /** @@ -141,9 +137,7 @@ export abstract class Logger { */ static warn(...lines: any[]): void { if (this.crashed) { return } - const date: string = new Date().toUTCString() - const location: string = this.getLocation() - void this.writeLog('warn', location, date, lines) + void this.writeLog('warn', this.getLocation(), this.getDateString(), lines) } /** @@ -152,9 +146,7 @@ export abstract class Logger { */ static info(...lines: any[]): void { if (this.crashed) { return } - const date: string = new Date().toUTCString() - const location: string = this.getLocation() - void this.writeLog('info', location, date, lines) + void this.writeLog('info', this.getLocation(), this.getDateString(), lines) } /** @@ -163,9 +155,7 @@ export abstract class Logger { */ static debug(...lines: any[]): void { if (this.crashed) { return } - const date: string = new Date().toUTCString() - const location: string = this.getLocation() - void this.writeLog('debug', location, date, lines) + void this.writeLog('debug', this.getLocation(), this.getDateString(), lines) } /** @@ -174,9 +164,7 @@ export abstract class Logger { */ static trace(...lines: any[]): void { if (this.crashed) { return } - const date: string = new Date().toUTCString() - const location: string = this.getLocation() - void this.writeLog('trace', location, date, lines) + void this.writeLog('trace', this.getLocation(), this.getDateString(), lines) } /** @@ -210,6 +198,12 @@ export abstract class Logger { } } + private static getDateString(): string { + const d = new Date() + const ret = d.toUTCString() + return ret.substring(5, ret.length - 4) + `.${String(d.getMilliseconds()).padStart(3, "0")}` + } + private static async writeLog(tag: Tag, location: string, date: string, lines: any[], force = false): Promise { if (lines.length === 0 || this.logTypes[tag].level > this.logLevel) { return } if (!this.consoleDisabled) { @@ -271,7 +265,7 @@ export abstract class Logger { } private static getLogfileString(tag: Tag, lines: string[], location: string, date: string): string { - let ret: string = `<${tag.toUpperCase()}> [${date.substring(5, date.length - 4)}] (${location}) ${lines[0]}\n` + let ret: string = `<${tag.toUpperCase()}> [${date}] (${location}) ${lines[0]}\n` for (let i: number = 1; i < lines.length; i++) { ret += `\t${lines[i]}\n` } @@ -281,7 +275,7 @@ export abstract class Logger { private static getConsoleString(tag: Tag, lines: string[], location: string, date: string): string { const colour: string = this.logTypes[tag].colour const colourString: string = `\u001b${colour}` - let ret: string = `<${colourString}${tag.toUpperCase()}\x1b[0m> [\u001b[34m${date.substring(5, date.length - 4)}\x1b[0m] (\u001b[36m${location}\x1b[0m) ${lines[0]}` + let ret: string = `<${colourString}${tag.toUpperCase()}\x1b[0m> [\u001b[34m${date}\x1b[0m] (\u001b[36m${location}\x1b[0m) ${lines[0]}` for (let i: number = 1; i < lines.length; i++) { ret += `\n\t${lines[i]}` } diff --git a/src/Trakman.ts b/src/Trakman.ts index b969b259..67796055 100644 --- a/src/Trakman.ts +++ b/src/Trakman.ts @@ -737,8 +737,8 @@ namespace trakman { */ controller: { ...controllerConfig, - version: packageObject.version, - repo: packageObject.homepage + version: packageObject.version ??= "unknown", + repo: packageObject.homepage ??= "http://github.com/lythx/trakman" }, /** From 711557f8645e5aee2f5d7073a01995aed3751b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Tue, 24 Jun 2025 14:04:22 +0200 Subject: [PATCH 07/71] initialise best checkpoints to prevent crashes --- plugins/checkpoint_records/CheckpointListeners.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/checkpoint_records/CheckpointListeners.ts b/plugins/checkpoint_records/CheckpointListeners.ts index a8ecdad2..087c00cf 100644 --- a/plugins/checkpoint_records/CheckpointListeners.ts +++ b/plugins/checkpoint_records/CheckpointListeners.ts @@ -3,7 +3,7 @@ import { bestCpsDB, allCpsDB } from './CheckpointDB.js' import { emitEvent } from './CheckpointEvents.js' import config from './Config.js' -let currentBestCps: BestCheckpoints +let currentBestCps: BestCheckpoints = [] let currentMapDBId: number const currentPlayerCps: PlayerCheckpoints[] = [] @@ -65,6 +65,10 @@ if (config.isEnabled) { }) tm.addListener('PlayerCheckpoint', (info: tm.CheckpointInfo) => { + if (currentMapDBId === undefined) { + tm.log.error(`Player ${info.player.login} got a checkpoint before the map started`) + return + } const date = new Date() const playerCheckpoints = currentPlayerCps.find(a => a.login === info.player.login) const isStunts = tm.getGameMode() === 'Stunts' From 16e2ff1beeb55983072c412073abe0df108265c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Fri, 27 Jun 2025 17:48:04 +0200 Subject: [PATCH 08/71] change default tmx fetcher site to tmuf if non-nations packmask --- src/TMXFetcher.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/TMXFetcher.ts b/src/TMXFetcher.ts index 9a19f249..61bcc220 100644 --- a/src/TMXFetcher.ts +++ b/src/TMXFetcher.ts @@ -9,6 +9,9 @@ export abstract class TMXFetcher { private static readonly prefixes: TMXPrefix[] = ['tmnforever', 'united', 'nations', 'original', 'sunrise'] private static readonly sites: tm.TMXSite[] = ['TMNF', 'TMU', 'TMN', 'TMO', 'TMS'] + private static readonly packmaskSite: tm.TMXSite = (config.manualMapLoading.stadiumOnly !== undefined + ? config.manualMapLoading.stadiumOnly : process.env.SERVER_PACKMASK === 'nations') + ? 'TMNF' : 'TMU' private static readonly environments: Record = { 1: 'Snow', 2: 'Desert', @@ -77,11 +80,11 @@ export abstract class TMXFetcher { /** * Fetches map file from TMX via its TMX ID. * @param tmxId Map TMX ID - * @param site Optional TMX site (TMNF by default) + * @param site Optional TMX site * @returns Object containing map name and file content, or Error if unsuccessful */ static async fetchMapFile(tmxId: number, site?: tm.TMXSite): Promise<{ name: string, content: Buffer } | Error> - static async fetchMapFile(id: number | string, site: tm.TMXSite = 'TMNF'): Promise<{ name: string, content: Buffer } | Error> { + static async fetchMapFile(id: number | string, site: tm.TMXSite = this.packmaskSite): Promise<{ name: string, content: Buffer } | Error> { let prefix: TMXPrefix = this.siteToPrefix(site) if (typeof id === 'string') { const res = await this.getTMXId(id) @@ -287,7 +290,7 @@ export abstract class TMXFetcher { * @param count Number of maps to fetch * @returns An array of searched map objects or Error if unsuccessful */ - static async searchForMap(query?: string, author?: string, site: tm.TMXSite = 'TMNF', + static async searchForMap(query?: string, author?: string, site: tm.TMXSite = this.packmaskSite, count: number = config.defaultTMXSearchLimit): Promise { const params: [string, string][] = [['count', count.toString()], ['name', (query ?? '').trim()], ['author', (author ?? '').trim()]] if (author === undefined) { params.pop() } @@ -321,10 +324,10 @@ export abstract class TMXFetcher { /** * Fetches a random map file from TMX. - * @param site Optional TMX site (TMNF by default) + * @param site Optional TMX site * @returns Object containing map name and file content, or Error if unsuccessful */ - static async fetchRandomMapFile(site: tm.TMXSite = 'TMNF'): Promise<{ name: string, content: Buffer } | Error> { + static async fetchRandomMapFile(site: tm.TMXSite = this.packmaskSite): Promise<{ name: string, content: Buffer } | Error> { const prefix = this.siteToPrefix(site) const res = await fetch(`https://${prefix}.tm-exchange.com/trackrandom`).catch((err: Error) => err) if (res instanceof Error) { From 5fd8b629c2e27a80971cd2baa4bf4ba269bc92ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Fri, 27 Jun 2025 17:50:25 +0200 Subject: [PATCH 09/71] tmxfetcher docs --- src/TMXFetcher.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/TMXFetcher.ts b/src/TMXFetcher.ts index 61bcc220..85da7124 100644 --- a/src/TMXFetcher.ts +++ b/src/TMXFetcher.ts @@ -80,7 +80,7 @@ export abstract class TMXFetcher { /** * Fetches map file from TMX via its TMX ID. * @param tmxId Map TMX ID - * @param site Optional TMX site + * @param site Optional TMX site (default depends on packmask) * @returns Object containing map name and file content, or Error if unsuccessful */ static async fetchMapFile(tmxId: number, site?: tm.TMXSite): Promise<{ name: string, content: Buffer } | Error> @@ -157,6 +157,7 @@ export abstract class TMXFetcher { /** * Fetches TMX for map information. * @param tmxId Map TMX ID + * @param prefix Map TMX Prefix * @returns Map info from TMX or error if unsuccessful */ static async fetchMapInfo(tmxId: number, prefix: TMXPrefix): Promise | Error> @@ -286,7 +287,7 @@ export abstract class TMXFetcher { * Searches for maps matching the specified name on TMX. * @param query Search query * @param author Map author to look for - * @param site TMX Site to fetch from + * @param site TMX Site to fetch from (default depends on packmask) * @param count Number of maps to fetch * @returns An array of searched map objects or Error if unsuccessful */ @@ -324,7 +325,7 @@ export abstract class TMXFetcher { /** * Fetches a random map file from TMX. - * @param site Optional TMX site + * @param site Optional TMX site (default depends on packmask) * @returns Object containing map name and file content, or Error if unsuccessful */ static async fetchRandomMapFile(site: tm.TMXSite = this.packmaskSite): Promise<{ name: string, content: Buffer } | Error> { From ee6d505e0127270dcafa96d57491b48d1069ae01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Sat, 5 Jul 2025 00:42:38 +0200 Subject: [PATCH 10/71] speed up /best to O(n) average case, O(log n) worst case, make long-taking commands async, fix docker for volumeless setups --- Dockerfile | 3 +- docker_run.sh | 2 + plugins/maplist/Maplist.ts | 118 ++++++++++-------------- plugins/maplist/ui/Maplist.component.ts | 8 +- src/services/RecordService.ts | 14 +-- 5 files changed, 66 insertions(+), 79 deletions(-) diff --git a/Dockerfile b/Dockerfile index 89338e1c..a65a86f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,8 @@ RUN mv GameData/Config/dedicated_cfg.txt dedicated_cfg.txt.bk && \ mkdir -p Tracksbk/MatchSettings && \ mkdir -p Tracksbk/Campaigns/Nations && \ mv GameData/Tracks/MatchSettings/Nations/NationsBlue.txt Tracksbk/MatchSettings/MatchSettings.txt && \ - mv GameData/Tracks/Campaigns/Nations/Blue Tracksbk/Campaigns/Nations/ + mv GameData/Tracks/Campaigns/Nations/Blue Tracksbk/Campaigns/Nations/ && \ + rm -rf GameData/Tracks # expose volumes VOLUME /app/server/GameData/Config VOLUME /app/server/GameData/Tracks diff --git a/docker_run.sh b/docker_run.sh index 0bb14e3a..8e1ae5b7 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -33,6 +33,8 @@ else xml ed -L -u "/dedicated/system_config/server_port" -v "$SERVER_NET_PORT" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/server_p2p_port" -v "$SERVER_P2P_PORT" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/xmlrpc_port" -v "$SERVER_PORT" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/xmlrpc_allowremote" -v "${REMOTE_XMLRPC=False}" dedicated_cfg.txt.bk + xml ed -L -u "/dedicated/system_config/force_ip_address" -v "$SERVER_IP" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/packmask" -v "${SERVER_PACKMASK=nations}" dedicated_cfg.txt.bk xml ed -L -u "/dedicated/system_config/disable_coherence_checks" -v "${SERVER_DISABLE_COHERENCE_CHECKS=laps}" dedicated_cfg.txt.bk mv /app/server/dedicated_cfg.txt.bk /app/server/GameData/Config/dedicated_cfg.txt diff --git a/plugins/maplist/Maplist.ts b/plugins/maplist/Maplist.ts index 8adb0cd5..41493203 100644 --- a/plugins/maplist/Maplist.ts +++ b/plugins/maplist/Maplist.ts @@ -18,8 +18,7 @@ const updateListeners: ((action: 'add' | 'remove', addedOrRemovedMap: Readonly[]) => void)[] = [] function initialize(): void { - const arr: tm.Map[] = tm.maps.list.sort((a, b): number => a.name.localeCompare(b.name)) - authorSort = arr.sort((a, b): number => a.author.localeCompare(b.author)) + authorSort = tm.maps.list.sort((a, b): number => a.author.localeCompare(b.author)) nameSort = [...authorSort].sort((a, b): number => a.name.localeCompare(b.name)) karmaSort = [...authorSort].sort((a, b): number => { return a.voteRatio ?? 0 - b.voteRatio ?? 0 @@ -31,35 +30,29 @@ function initialize(): void { newestSort = [...oldestSort].reverse() } -function insertMap(map: tm.MapAddedInfo): void { - authorSort.splice(authorSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name)), 0, map) - nameSort.splice(nameSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name)), 0, map) +async function insertMap(map: tm.MapAddedInfo): Promise { + authorSort.splice(authorSort.findIndex(a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name)), 0, + map) + nameSort.splice(nameSort.findIndex(a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name)), 0, map) const ratio: number = map.voteRatio - karmaSort.splice(karmaSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name) - && ratio > map.voteRatio), 0, map) - worstKarmaSort.splice(worstKarmaSort.findIndex(a => !map.author.localeCompare(a.author) - && !map.name.localeCompare(a.name) - && ratio <= map.voteRatio), 0, map) - atSort.splice(atSort.findIndex(a => map.author.localeCompare(a.author) - && map.name.localeCompare(a.name) - && map.authorTime < a.authorTime), 0, map) - worstAtSort.splice(worstAtSort.findIndex(a => !map.author.localeCompare(a.author) - && !map.name.localeCompare(a.name) - && map.authorTime >= a.authorTime), 0, map) - newestSort.splice(newestSort.findIndex(a => !map.author.localeCompare(a.author) - && map.name.localeCompare(a.name) - && map.addDate.getTime() < a.addDate.getTime()), 0, map) - oldestSort.splice(oldestSort.findIndex(a => !map.author.localeCompare(a.author) - && !map.name.localeCompare(a.name) - && map.addDate.getTime() >= a.addDate.getTime()), 0, map) + karmaSort.splice(karmaSort.findIndex( + a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name) && ratio > map.voteRatio), 0, map) + worstKarmaSort.splice(worstKarmaSort.findIndex( + a => !map.author.localeCompare(a.author) && !map.name.localeCompare(a.name) && ratio <= map.voteRatio), 0, map) + atSort.splice(atSort.findIndex( + a => map.author.localeCompare(a.author) && map.name.localeCompare(a.name) && map.authorTime < a.authorTime), 0, map) + worstAtSort.splice(worstAtSort.findIndex( + a => !map.author.localeCompare(a.author) && !map.name.localeCompare(a.name) && map.authorTime >= a.authorTime), 0, + map) + newestSort.splice(newestSort.findIndex(a => !map.author.localeCompare(a.author) && map.name.localeCompare( + a.name) && map.addDate.getTime() < a.addDate.getTime()), 0, map) + oldestSort.splice(oldestSort.findIndex(a => !map.author.localeCompare(a.author) && !map.name.localeCompare( + a.name) && map.addDate.getTime() >= a.addDate.getTime()), 0, map) cache.length = 0 for (const e of updateListeners) { e('add', map) } } -function removeMap(map: tm.MapRemovedInfo): void { +async function removeMap(map: tm.MapRemovedInfo): Promise { authorSort.splice(authorSort.findIndex(a => a.id === map.id), 1) nameSort.splice(nameSort.findIndex(a => a.id === map.id), 1) karmaSort.splice(karmaSort.findIndex(a => a.id === map.id), 1) @@ -74,7 +67,7 @@ function removeMap(map: tm.MapRemovedInfo): void { function addOrRemove(map: tm.MapAddedInfo | tm.MapAddedInfo[], fun: Function): void { if (!Array.isArray(map)) { - fun(map) + void fun(map) return } // If the list of added or removed maps is small enough, use insertion/deletion; @@ -92,7 +85,6 @@ tm.addListener('JukeboxChanged', (list): void => { for (const e of jukeboxUpdateListeners) { e(list) } }) - tm.addListener('Startup', initialize) tm.addListener('MapAdded', (map: tm.MapAddedInfo | tm.MapAddedInfo[]): void => { @@ -124,7 +116,7 @@ export const maplist = { /** * Add a callback function to execute on list update - * @param callback Functon to execute on event, it takes action type and added od removed map as parameters + * @param callback Function to execute on event, it takes action type and added od removed map as parameters */ onListUpdate(callback: (action: 'add' | 'remove', addedOrRemovedMap: Readonly) => void) { updateListeners.push(callback) @@ -132,7 +124,7 @@ export const maplist = { /** * Add a callback function to execute on jukebox update - * @param callback Functon to execute on event, it takes updated jukebox as parameter + * @param callback Function to execute on event, it takes updated jukebox as parameter */ onJukeboxUpdate(callback: (jukebox: readonly Readonly[]) => void) { jukeboxUpdateListeners.push(callback) @@ -144,7 +136,7 @@ export const maplist = { * @returns Array of map objects */ get: (sort?: 'jukebox' | 'name' | 'karma' | 'long' | 'short' | 'worstkarma' | 'author' | 'oldest' | 'newest'): readonly Readonly[] => { - switch (sort) { + switch(sort) { case 'jukebox': return jukebox case 'name': @@ -161,7 +153,8 @@ export const maplist = { return oldestSort case 'newest': return newestSort - case 'author': default: + case 'author': + default: return authorSort } }, @@ -172,39 +165,28 @@ export const maplist = { * @param sort Sorting criteria * @returns Array of map objects */ - getByPosition: (login: string, sort: 'best' | 'worst'): Readonly[] => { - if (sort === 'best') { - let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'best')?.list - if (list === undefined) { - list = [] - const ranks: { mapId: string, rank: number }[] = - tm.records.getRank(login, tm.maps.list.map(a => a.id)) - .sort((a, b): number => a.rank - b.rank) - for (let i: number = 0; i < ranks.length; i++) { - const entry: tm.Map | undefined = authorSort.find(a => a.id === ranks[i].mapId) - if (entry !== undefined) { list.push(entry) } - } - cache.unshift({ query: login, list, type: 'best' }) - cache.length = Math.min(config.cacheSize, cache.length) - } - return list - } - else { - let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'worst')?.list - if (list === undefined) { - list = [] - const ranks: { mapId: string, rank: number }[] = - tm.records.getRank(login, tm.maps.list.map(a => a.id)) - .sort((a, b): number => b.rank - a.rank) - for (let i: number = 0; i < ranks.length; i++) { - const entry: tm.Map | undefined = authorSort.find(a => a.id === ranks[i].mapId) - if (entry !== undefined) { list.push(entry) } + getByPosition: async (login: string, sort: 'best' | 'worst'): Promise[]> => { + let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === sort)?.list + if (list === undefined) { + const order = sort === 'best' ? 1 : -1 + const ranks: { mapId: string, rank: number }[] = (await tm.records.getRank(login, tm.maps.list.map(a => a.id))) + .sort((a, b): number => (a.rank - b.rank) * order) + const rankMap = new Map(ranks.map((e, i) => [e.mapId, i])) + list = Array(rankMap.size) + // theoretically, the list cannot contain empty spots after this loop + // practically, im not sure, because a map might magically get deleted out of the authorsort array, TODO: see if it works + for (const map of authorSort) { + const i = rankMap.get(map.id) + if (i !== undefined) { + list[i] = map } - cache.unshift({ query: login, list, type: 'worst' }) - cache.length = Math.min(config.cacheSize, cache.length) } - return list + // in case it doesn't work just uncomment this, it's not much overhead but its overhead nonetheless + // list.filter(a => a) // delete empty slots from the list + cache.unshift({ query: login, list, type: sort }) + cache.length = Math.min(config.cacheSize, cache.length) } + return list }, /** @@ -212,7 +194,7 @@ export const maplist = { * @param query Search query * @returns Array of map objects */ - searchByName: (query: string): Readonly[] => { + searchByName: async (query: string): Promise[]> => { let list: tm.Map[] | undefined = cache.find(a => a.query === query && a.type === 'name')?.list let indices: number[] if (list === undefined) { @@ -229,7 +211,7 @@ export const maplist = { * @param query Search query * @returns Array of map objects */ - searchByAuthor: (query: string): Readonly[] => { + searchByAuthor: async (query: string): Promise[]> => { let list: tm.Map[] | undefined = cache.find(a => a.query === query && a.type === 'author')?.list let indices: number[] if (list === undefined) { @@ -282,8 +264,8 @@ export const maplist = { let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'norank')?.list if (list === undefined) { const mapsWithAuthor: string[] = (await tm.records.fetchByLogin(login)) - .filter(a => authorSort.find(b => b.id === a.map)?.authorTime ?? Infinity < a.time) - .map(a => a.map) + .filter(a => authorSort.find(b => b.id === a.map)?.authorTime ?? Infinity < a.time) + .map(a => a.map) list = authorSort.filter(a => !mapsWithAuthor.includes(a.id)) cache.unshift({ query: login, list, type: 'norank' }) cache.length = Math.min(config.cacheSize, cache.length) @@ -299,8 +281,8 @@ export const maplist = { filterNoAuthor: async (login: string): Promise[]> => { let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === 'noauthor')?.list if (list === undefined) { - const ranks = (tm.records.getRank(login, authorSort.map(a => a.id))) - .filter(a => a.rank <= tm.records.maxLocalsAmount) + const ranks = (await (tm.records.getRank(login, authorSort.map(a => a.id)))) + .filter(a => a.rank <= tm.records.maxLocalsAmount) list = authorSort.filter(a => !ranks.some(b => a.id === b.mapId)) cache.unshift({ query: login, list, type: 'noauthor' }) cache.length = Math.min(config.cacheSize, cache.length) diff --git a/plugins/maplist/ui/Maplist.component.ts b/plugins/maplist/ui/Maplist.component.ts index 08a0aae4..d5275090 100644 --- a/plugins/maplist/ui/Maplist.component.ts +++ b/plugins/maplist/ui/Maplist.component.ts @@ -141,7 +141,7 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi | 'norank' | 'noauthor' | 'oldest' | 'newest', page: number): Promise { let list: readonly Readonly[] = [] if (option === 'best' || option === 'worst') { - list = maplist.getByPosition(login, option) + list = await maplist.getByPosition(login, option) } else if (option === 'nofinish' || option === 'norank' || option === 'noauthor') { list = await maplist.getFiltered(login, option) } else { @@ -154,8 +154,8 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi undefined, config.optionTitles[option as keyof typeof config.optionTitles]) } - openWithQuery(login: string, query: string, page: number, searchByAuthor?: true): void { - const list: Readonly[] = searchByAuthor === true ? maplist.searchByAuthor(query) : maplist.searchByName(query) + async openWithQuery(login: string, query: string, page: number, searchByAuthor?: true): Promise { + const list: Readonly[] = searchByAuthor === true ? await maplist.searchByAuthor(query) : await maplist.searchByName(query) const paginator = this.getPaginator(login, list, query) page = Math.max(1, Math.min(Math.ceil(list.length / (config.rows * config.columns)), page)) paginator.setPageForLogin(login, page) @@ -346,7 +346,7 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi } private async getRecordIndexStrings(login: string, ...mapIds: string[]): Promise { - const ranks = tm.records.getRank(login, mapIds) + const ranks = await tm.records.getRank(login, mapIds) const ret: string[] = [] for (let i = 0; i < mapIds.length; i++) { const r = ranks.find(a => a.mapId === mapIds[i]) diff --git a/src/services/RecordService.ts b/src/services/RecordService.ts index a92d8969..12a65cb0 100644 --- a/src/services/RecordService.ts +++ b/src/services/RecordService.ts @@ -442,6 +442,7 @@ export class RecordService { * @param time Record time * @param player Player object containing login and nickname * @param recordType Record type ('live' or 'local') + * @param isStunts The record is not a time but a stunts score * @returns Array of strings formatted for Logger output */ private static getLogString(previousPosition: number | undefined, position: number, @@ -461,29 +462,30 @@ export class RecordService { * @param mapId Map uid * @returns Rank or undefined if player doesn't have a record */ - static getRank(login: string, mapId: string): number | undefined + static async getRank(login: string, mapId: string): Promise /** * Gets given player local ranks on given maps * @param login Player login * @param mapIds Array of map uids * @returns Array of objects with player ranks and map uids */ - static getRank(login: string, mapIds?: string[]): { mapId: string, rank: number }[] + static async getRank(login: string, mapIds?: string[]): Promise<{ mapId: string; rank: number }[]> /** * Gets given player local ranks on all maps * @param login Player login * @returns Array of objects with player ranks and map uids */ - static getRank(login: string): { mapId: string, rank: number }[] - static getRank(login: string, mapIds?: string | string[]): number | undefined | { mapId: string, rank: number }[] { + static async getRank(login: string): Promise<{ mapId: string; rank: number }[]> + static async getRank(login: string, mapIds?: string | string[]): Promise { if (mapIds === undefined) { return this._playerRanks.filter(a => login === a.login && a.rank !== -1) } if (typeof mapIds === 'string') { - const find = this._playerRanks.find(a => mapIds === a.mapId && a.rank !== -1 && a.login === login) + const find = this._playerRanks.find(a => a.login === login && a.rank !== -1 && mapIds === a.mapId) return find?.rank } - return this._playerRanks.filter(a => mapIds.includes(a.mapId) && a.rank !== -1 && a.login === login) + const mapIdSet = new Set(mapIds) + return this._playerRanks.filter(a => a.login === login && a.rank !== -1 && mapIdSet.has(a.mapId)) } /** From 1e9ae23b9db40aa7564dad73bc41e3ee3817c8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Sat, 5 Jul 2025 01:36:33 +0200 Subject: [PATCH 11/71] pretty sure I fixed the checkpoint crashing controller during startup bug --- plugins/maplist/ui/Maplist.component.ts | 2 +- plugins/sector_records/SectorListeners.ts | 247 ++++++++++++---------- 2 files changed, 140 insertions(+), 109 deletions(-) diff --git a/plugins/maplist/ui/Maplist.component.ts b/plugins/maplist/ui/Maplist.component.ts index d5275090..94da2efd 100644 --- a/plugins/maplist/ui/Maplist.component.ts +++ b/plugins/maplist/ui/Maplist.component.ts @@ -139,7 +139,7 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi async openWithOption(login: string, option: 'jukebox' | 'name' | 'karma' | 'short' | 'long' | 'best' | 'worst' | 'worstkarma' | 'nofinish' | 'norank' | 'noauthor' | 'oldest' | 'newest', page: number): Promise { - let list: readonly Readonly[] = [] + let list: readonly Readonly[] if (option === 'best' || option === 'worst') { list = await maplist.getByPosition(login, option) } else if (option === 'nofinish' || option === 'norank' || option === 'noauthor') { diff --git a/plugins/sector_records/SectorListeners.ts b/plugins/sector_records/SectorListeners.ts index 901953ec..460ff446 100644 --- a/plugins/sector_records/SectorListeners.ts +++ b/plugins/sector_records/SectorListeners.ts @@ -1,6 +1,6 @@ import type { BestSectors, PlayerSectors } from './SectorTypes.js' -import { bestSecsDB, allSecsDB } from './SectorDB.js' -import { emitEvent } from './SectorEvents.js' +import { allSecsDB, bestSecsDB } from './SectorDB.js' +import { addListener, emitEvent } from './SectorEvents.js' import config from './Config.js' let currentBestSecs: BestSectors @@ -19,7 +19,9 @@ const onMapStart = async (): Promise => { await tm.log.fatal(`Failed to fetch best sectors for map ${tm.maps.current.id}`, res.message) return } - if (res === undefined) return + if (res === undefined) { + return + } currentBestSecs = res const playerSecs = await allSecsDB.get(currentMapDBId, ...tm.players.list.map(a => a.login)) if (playerSecs instanceof Error) { @@ -28,7 +30,7 @@ const onMapStart = async (): Promise => { } currentPlayerSecs.length = 0 currentPlayerSecs.push(...playerSecs) - emitEvent('SectorsFetch', currentBestSecs, currentPlayerSecs) + void emitEvent('SectorsFetch', currentBestSecs, currentPlayerSecs) } if (config.isEnabled) { @@ -41,7 +43,16 @@ if (config.isEnabled) { await onMapStart() }, true) - tm.addListener('PlayerDataUpdated', (info) => { + const playerDataUpdated = (info: readonly tm.PlayerDataUpdatedInfo[], fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerDataUpdated event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerDataUpdated(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } const changedObjects: { login: string; nickname: string; }[] = [] for (const e of currentBestSecs) { if (e === undefined) { continue } @@ -60,11 +71,21 @@ if (config.isEnabled) { } } if (changedObjects.length !== 0) { - emitEvent('NicknameUpdated', changedObjects) + void emitEvent('NicknameUpdated', changedObjects) } - }) + } + tm.addListener('PlayerDataUpdated', playerDataUpdated) - tm.addListener('PlayerCheckpoint', (info: tm.CheckpointInfo) => { + const playerCheckpoint = (info: tm.CheckpointInfo, fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerCheckpoint event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerCheckpoint(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } const date = new Date() const playerSectors = currentPlayerSecs.find(a => a.login === info.player.login) const time = info.time - (info.player.currentCheckpoints[info.index - 1]?.time ?? 0) @@ -73,29 +94,33 @@ if (config.isEnabled) { currentPlayerSecs.push({ login: info.player.login, sectors: [time], nickname: info.player.nickname }) void allSecsDB.add(currentMapDBId, info.player.login, [time]) emitEvent('PlayerSector', { login: info.player.login, nickname: info.player.nickname, index: info.index }) - } else if (((playerSectors.sectors[info.index] ?? Infinity) > time && !isStunts) || - ((playerSectors.sectors[info.index] ?? -1) < time && isStunts)) { + } else if (((playerSectors.sectors[info.index] ?? Infinity) > time && !isStunts) || ((playerSectors.sectors[info.index] ?? -1) < time && isStunts)) { playerSectors.sectors[info.index] = time void allSecsDB.update(currentMapDBId, info.player.login, playerSectors.sectors.map(a => a === undefined ? -1 : a)) emitEvent('PlayerSector', { login: info.player.login, nickname: info.player.nickname, index: info.index }) } const sector = currentBestSecs[info.index]?.sector - if (sector === undefined || - (sector > time && !isStunts) || - (sector < time && isStunts)) { + if (sector === undefined || (sector > time && !isStunts) || (sector < time && isStunts)) { currentBestSecs[info.index] = { - login: info.player.login, - nickname: info.player.nickname, - sector: time, - date: date + login: info.player.login, nickname: info.player.nickname, sector: time, date: date } - sector === undefined ? void bestSecsDB.add(currentMapDBId, info.player.login, info.index, time, date) - : void bestSecsDB.update(currentMapDBId, info.player.login, info.index, time, date) + sector === undefined ? void bestSecsDB.add(currentMapDBId, info.player.login, info.index, time, + date) : void bestSecsDB.update(currentMapDBId, info.player.login, info.index, time, date) emitEvent('BestSector', { login: info.player.login, nickname: info.player.nickname, index: info.index, date }) } - }) + } + tm.addListener('PlayerCheckpoint', playerCheckpoint) - tm.addListener('PlayerFinish', (info: tm.FinishInfo) => { + const playerFinish = (info: tm.FinishInfo, fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerCheckpoint event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerFinish(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } const date = new Date() const index = info.checkpoints.length const playerSectors = currentPlayerSecs.find(a => a.login === info.login) @@ -105,112 +130,119 @@ if (config.isEnabled) { currentPlayerSecs.push({ login: info.login, sectors: [time], nickname: info.nickname }) void allSecsDB.add(currentMapDBId, info.login, [time]) emitEvent('PlayerSector', { login: info.login, nickname: info.nickname, index: index }) - } else if (((playerSectors.sectors[index] ?? Infinity) > time && !isStunts) || - ((playerSectors.sectors[index] ?? -1) < time && isStunts)) { + } else if (((playerSectors.sectors[index] ?? Infinity) > time && !isStunts) || ((playerSectors.sectors[index] ?? -1) < time && isStunts)) { playerSectors.sectors[index] = time void allSecsDB.update(currentMapDBId, info.login, playerSectors.sectors.map(a => a === undefined ? -1 : a)) emitEvent('PlayerSector', { login: info.login, nickname: info.nickname, index: index }) } const sector = currentBestSecs[index]?.sector - if (sector === undefined || - (sector > time && !isStunts) || - (sector < time && isStunts)) { + if (sector === undefined || (sector > time && !isStunts) || (sector < time && isStunts)) { currentBestSecs[index] = { - login: info.login, - nickname: info.nickname, - sector: time, - date: date + login: info.login, nickname: info.nickname, sector: time, date: date } - sector === undefined ? void bestSecsDB.add(currentMapDBId, info.login, index, time, date) - : void bestSecsDB.update(currentMapDBId, info.login, index, time, date) + sector === undefined ? void bestSecsDB.add(currentMapDBId, info.login, index, time, + date) : void bestSecsDB.update(currentMapDBId, info.login, index, time, date) emitEvent('BestSector', { login: info.login, nickname: info.nickname, index: index, date }) } - }) + } + tm.addListener('PlayerFinish', playerFinish) - tm.addListener('PlayerJoin', async (info) => { + const playerJoin = async (info: Readonly>, fromQueue: boolean = false) => { + if (currentBestSecs === undefined) { + if (!fromQueue) { + tm.log.debug('PlayerCheckpoint event before sectors initialised, adding to queue') + addListener('SectorsFetch', () => playerJoin(info, true)) + } else { + void tm.log.fatal('currentBestSecs is still undefined after sectors plugin initialisation') + } + return + } const playerSecs = await allSecsDB.get(currentMapDBId, info.login) if (playerSecs instanceof Error) { - await tm.log.fatal(`Failed to fetch player ${info.login} sectors for map ${tm.maps.current.id}`, playerSecs.message) + await tm.log.fatal(`Failed to fetch player ${info.login} sectors for map ${tm.maps.current.id}`, + playerSecs.message) return } currentPlayerSecs.push(...playerSecs) - }) + } + tm.addListener('PlayerJoin', playerJoin) - tm.commands.add( - { - aliases: config.commands.deletemysector.aliases, - help: config.commands.deletemysector.help, - params: [{ name: 'sectorIndex', type: 'int', optional: true }], - callback(info, sectorIndex?: number) { - const secs = currentPlayerSecs.find(a => a.login === info.login) - if (secs === undefined) { - tm.sendMessage(config.noSectorRecords, info.login) + tm.commands.add({ + aliases: config.commands.deletemysector.aliases, help: config.commands.deletemysector.help, + params: [{ name: 'sectorIndex', type: 'int', optional: true }], callback(info, sectorIndex?: number) { + if (currentBestSecs === undefined) { + tm.sendMessage(config.noSectorRecords, info.login) + return + } + const secs = currentPlayerSecs.find(a => a.login === info.login) + if (secs === undefined) { + tm.sendMessage(config.noSectorRecords, info.login) + return + } + if (sectorIndex === undefined) { + const arr: number[] = secs.sectors.filter(a => a !== undefined) as any + secs.sectors.length = 0 + tm.sendMessage(config.allPlayerSectorsRemoved, info.login) + emitEvent('DeletePlayerSector', { ...info, deletedSectors: arr.map((a, i) => ({ time: a, index: i })) }) + void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) + } else { + if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount) { + tm.sendMessage(config.outOfRange, info.login) return } - if (sectorIndex === undefined) { - const arr: number[] = secs.sectors.filter(a => a !== undefined) as any - secs.sectors.length = 0 - tm.sendMessage(config.allPlayerSectorsRemoved, info.login) - emitEvent('DeletePlayerSector', { ...info, deletedSectors: arr.map((a, i) => ({ time: a, index: i })) }) - void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) - } else { - if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = secs.sectors[sectorIndex - 1] - secs.sectors[sectorIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.playerSectorRemoved, { index: tm.utils.getOrdinalSuffix(sectorIndex) }), info.login) - if (deleted !== undefined) { - emitEvent('DeletePlayerSector', { ...info, deletedSectors: [{ time: deleted, index: sectorIndex }] }) - } - void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) + const deleted = secs.sectors[sectorIndex - 1] + secs.sectors[sectorIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.playerSectorRemoved, { index: tm.utils.getOrdinalSuffix(sectorIndex) }), + info.login) + if (deleted !== undefined) { + emitEvent('DeletePlayerSector', { ...info, deletedSectors: [{ time: deleted, index: sectorIndex }] }) } - }, - privilege: config.commands.deletemysector.privilege - }, - { - aliases: config.commands.deletesector.aliases, - help: config.commands.deletesector.help, - params: [{ name: 'sectorIndex', type: 'int', optional: true }], - callback(info, sectorIndex?: number) { - if (sectorIndex === undefined) { - const arr: { - login: string, - nickname: string, - sector: number, - date: Date - }[] = currentBestSecs.filter(a => a !== undefined) as any - currentBestSecs.length = 0 - tm.sendMessage(tm.utils.strVar(config.allBestSectorsRemoved, - { title: info.title, nickname: tm.utils.strip(info.nickname, true) })) - emitEvent('DeleteBestSector', arr.map((a, i) => ({ ...a, index: i }))) - void bestSecsDB.delete(currentMapDBId) - } else { - if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount + 1) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = currentBestSecs[sectorIndex - 1] - currentBestSecs[sectorIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.bestSectorRemoved, { - title: info.title, - nickname: tm.utils.strip(info.nickname, true), - index: tm.utils.getOrdinalSuffix(sectorIndex) - }), config.commands.deletesector.public ? undefined : info.login) - if (deleted !== undefined) { - emitEvent('DeleteBestSector', [{ ...deleted, index: sectorIndex }]) - } - void bestSecsDB.delete(currentMapDBId, sectorIndex - 1) + void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) + } + }, privilege: config.commands.deletemysector.privilege + }, { + aliases: config.commands.deletesector.aliases, help: config.commands.deletesector.help, + params: [{ name: 'sectorIndex', type: 'int', optional: true }], callback(info, sectorIndex?: number) { + if (currentBestSecs === undefined) { + tm.sendMessage(config.noSectorRecords, info.login) + return + } + if (sectorIndex === undefined) { + const arr: { + login: string, nickname: string, sector: number, date: Date + }[] = currentBestSecs.filter(a => a !== undefined) as any + currentBestSecs.length = 0 + tm.sendMessage(tm.utils.strVar(config.allBestSectorsRemoved, + { title: info.title, nickname: tm.utils.strip(info.nickname, true) })) + emitEvent('DeleteBestSector', arr.map((a, i) => ({ ...a, index: i }))) + void bestSecsDB.delete(currentMapDBId) + } else { + if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount + 1) { + tm.sendMessage(config.outOfRange, info.login) + return } - }, - privilege: config.commands.deletesector.privilege - } - ) + const deleted = currentBestSecs[sectorIndex - 1] + currentBestSecs[sectorIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.bestSectorRemoved, { + title: info.title, nickname: tm.utils.strip(info.nickname, true), + index: tm.utils.getOrdinalSuffix(sectorIndex) + }), config.commands.deletesector.public ? undefined : info.login) + if (deleted !== undefined) { + emitEvent('DeleteBestSector', [{ ...deleted, index: sectorIndex }]) + } + void bestSecsDB.delete(currentMapDBId, sectorIndex - 1) + } + }, privilege: config.commands.deletesector.privilege + }) } const getMapSectors = (): ({ login: string, nickname: string, sector: number, date: Date } | null)[] => { - const arr: ({ login: string, nickname: string, sector: number, date: Date } | null)[] = new Array(tm.maps.current.checkpointsAmount).fill(null) + if (currentBestSecs === undefined) { + tm.log.debug('getMapSectors called before initialisation, aborting') + return [] + } + const arr: ({ login: string, nickname: string, sector: number, date: Date } | null)[] = new Array( + tm.maps.current.checkpointsAmount).fill(null) for (const [i, e] of currentBestSecs.entries()) { arr[i] = e ?? null } @@ -221,12 +253,11 @@ const getPlayerSectors = (): ({ login: string, nickname: string, sectors: (numbe const arr: ({ login: string, nickname: string, sectors: (number | null)[] })[] = [] for (const [i, e] of tm.players.list.entries()) { arr[i] = { - login: e.login, - nickname: e.nickname, - sectors: new Array(tm.maps.current.checkpointsAmount).fill(null).map((a, i) => currentPlayerSecs.find(a => a.login === e.login)?.sectors[i] ?? null) + login: e.login, nickname: e.nickname, sectors: new Array(tm.maps.current.checkpointsAmount).fill(null) + .map((a, i) => currentPlayerSecs.find(a => a.login === e.login)?.sectors[i] ?? null) } } return arr } -export { getMapSectors, getPlayerSectors } \ No newline at end of file +export { getMapSectors, getPlayerSectors } From 68df7941a03e4437be17028aca33d26dc1f1eaea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Sat, 5 Jul 2025 01:48:03 +0200 Subject: [PATCH 12/71] added env variable to disable re-installing npm packages on container startup (INSTALL_ON_STARTUP=NO) --- docker_run.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docker_run.sh b/docker_run.sh index 8e1ae5b7..6a7789b5 100644 --- a/docker_run.sh +++ b/docker_run.sh @@ -104,14 +104,16 @@ echo "#!/bin/sh /app/server/TrackmaniaServer /game_settings=MatchSettings/MatchSettings.txt /dedicated_cfg=dedicated_cfg.txt /nodaemon echo [\$(date +'%d %b %Y %T.%3N')] Server exited with code \$? | tee -a /app/server/.pm2/logs/docker.log echo 'Restarting...' -done) & -npm i --prefix /app/server/trakman -npm run build --prefix /app/server/trakman +done) &" > run.sh +if [ "$INSTALL_ON_STARTUP" != "NO" ]; then + echo "npm i --prefix /app/server/trakman" >> run.sh +fi +echo "npm run build --prefix /app/server/trakman chmod -R a+w /app/server cd trakman trap 'echo Terminating; npx pm2 stop 0; npx pm2 kill; exit' SIGTERM SIGINT npm run daemon -wait \$!" > run.sh +wait \$!" >> run.sh chown server:server run.sh chmod 766 run.sh exec su-exec server ./run.sh From e1a8d42e473227e9acda652d85741986df8faccc Mon Sep 17 00:00:00 2001 From: wsrvn Date: Tue, 8 Jul 2025 14:48:32 +0300 Subject: [PATCH 13/71] fix cp counter width in rounds mode --- plugins/ui/static_components/race/CpCounter.component.ts | 4 ++-- plugins/ui/static_components/race/CpCounter.config.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/ui/static_components/race/CpCounter.component.ts b/plugins/ui/static_components/race/CpCounter.component.ts index 97bf09ca..aa8c83e4 100644 --- a/plugins/ui/static_components/race/CpCounter.component.ts +++ b/plugins/ui/static_components/race/CpCounter.component.ts @@ -87,7 +87,7 @@ export default class CpCounter extends StaticComponent { } const cpInfo = target.currentCheckpoints.at(-1) if (cpInfo === undefined) { return } - const ret = this.calculateData({...cpInfo, player: target}) + const ret = this.calculateData({ ...cpInfo, player: target }) if (ret === undefined) { return } tm.sendManialink(ret.xml, player.login) }) @@ -189,7 +189,7 @@ export default class CpCounter extends StaticComponent { const timeColour: string = '$' + (isFinish === true ? config.colours.finish : config.colours.default) return `${this.header.constructXml(timeColour + tm.utils.getTimeString(currentTime), icon, config.side, { rectangleWidth: w, centerText: true })} - + ${centeredText('$' + config.colours.default + differenceString, w, h.height, h)} ` diff --git a/plugins/ui/static_components/race/CpCounter.config.js b/plugins/ui/static_components/race/CpCounter.config.js index 3ba30f14..617bc5ff 100644 --- a/plugins/ui/static_components/race/CpCounter.config.js +++ b/plugins/ui/static_components/race/CpCounter.config.js @@ -9,7 +9,7 @@ export default { useRelative: false, posX: 12, posY: -37, - margin: cfg.marginBig, + margin: cfg.margin, side: true, rectangleWidth: 7.7, icon: icons.waypoint, @@ -32,10 +32,10 @@ export default { iconBottom: icons.lap, iconTop: icons.clockLap, lapText: 'Lap', - lapTextWidth: 2.8, - lapCounterWidth: 3.1, + lapTextWidth: 2.7, + lapCounterWidth: 3.2, checkpointText: 'CP', - checkpointTextWidth: 2.8, + checkpointTextWidth: 2, }, background: cfg.background, finishTextDuration: 3000, From e4a4edb0bfbba3f654bd457119127ba1a3d84bf9 Mon Sep 17 00:00:00 2001 From: AroPix Date: Mon, 18 Aug 2025 21:40:52 +0200 Subject: [PATCH 14/71] Fix Dockerfile build --- plugins/maplist/Maplist.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/maplist/Maplist.ts b/plugins/maplist/Maplist.ts index 8adb0cd5..68eb6b2a 100644 --- a/plugins/maplist/Maplist.ts +++ b/plugins/maplist/Maplist.ts @@ -22,7 +22,7 @@ function initialize(): void { authorSort = arr.sort((a, b): number => a.author.localeCompare(b.author)) nameSort = [...authorSort].sort((a, b): number => a.name.localeCompare(b.name)) karmaSort = [...authorSort].sort((a, b): number => { - return a.voteRatio ?? 0 - b.voteRatio ?? 0 + return a.voteRatio ?? 0 - (b.voteRatio ?? 0) }) worstKarmaSort = [...karmaSort].reverse() atSort = [...authorSort].sort((a, b): number => a.authorTime - b.authorTime) From 74938b65e1dbafde4de62d9250ab7df61ca1a27f Mon Sep 17 00:00:00 2001 From: AuthBypass Date: Wed, 20 Aug 2025 03:28:25 +0200 Subject: [PATCH 15/71] Add authorTimeMultiplier and toggle --- .../race/TimerWidget.component.ts | 36 ++++++++++++++++--- .../race/TimerWidget.config.js | 4 ++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/plugins/ui/static_components/race/TimerWidget.component.ts b/plugins/ui/static_components/race/TimerWidget.component.ts index f0da3847..ce0fcfe7 100644 --- a/plugins/ui/static_components/race/TimerWidget.component.ts +++ b/plugins/ui/static_components/race/TimerWidget.component.ts @@ -5,6 +5,7 @@ import { componentIds, StaticHeader, StaticComponent, centeredText, addManialinkListener } from '../../UI.js' import config from './TimerWidget.config.js' +import globalconfig from '../../../../config/Config.js' export default class TimerWidget extends StaticComponent { @@ -44,12 +45,37 @@ export default class TimerWidget extends StaticComponent { return this.display() } }) + const setAdjustedTimeLimit = () => { + if (tm.getGameMode() !== 'TimeAttack') return; + const authorTime = tm.maps.current.authorTime; + let newLimit = globalconfig.defaultTimeAttackTimeLimit; + if (config.useAuthorTimeMultiplier) { + newLimit = Math.max( + authorTime * config.authorTimeMultiplier, + globalconfig.defaultTimeAttackTimeLimit, + globalconfig.dynamicTimerSubtractionLimit + ); + } else { + newLimit = Math.max( + globalconfig.defaultTimeAttackTimeLimit, + globalconfig.dynamicTimerSubtractionLimit + ); + } + }; + tm.addListener('ServerStateChanged', state => { + if (state === 'race') setAdjustedTimeLimit(); + }); + tm.addListener('ServerStateChanged', (state) => { + if (state === 'race') { + setAdjustedTimeLimit(); + } + }); this.renderOnEvent('BeginMap', () => { - this.isOnRestart = false - this.noButtonXml = this.constructXml(false) - this.xmlWithButtons = this.constructXml(true) - return this.display() - }) + this.isOnRestart = false; + this.noButtonXml = this.constructXml(false); + this.xmlWithButtons = this.constructXml(true); + return this.display(); + }); addManialinkListener(this.pauseButtonId, (info) => { if (info.privilege < config.timerActionsPrivilege) { return } if (!tm.timer.isDynamic) { diff --git a/plugins/ui/static_components/race/TimerWidget.config.js b/plugins/ui/static_components/race/TimerWidget.config.js index f5cba7ab..5623b0e0 100644 --- a/plugins/ui/static_components/race/TimerWidget.config.js +++ b/plugins/ui/static_components/race/TimerWidget.config.js @@ -17,6 +17,8 @@ export default { buttonOrder: ['pause', 'add', 'subtract'], pausedText: 'PAUSED', textYOffset: -0.3, + useAuthorTimeMultiplier: false, + authorTimeMultiplier: 3, icons: { pause: icons.pause, resume: icons.play, @@ -37,4 +39,4 @@ export default { pause: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has ${p.highlight}paused ${p.admin}the timer.`, resume: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has ${p.highlight}resumed ${p.admin}the timer.`, set: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has set the remaining time to ${p.highlight}#{time}${p.admin}.`, -} \ No newline at end of file +} From 788f10450d267a87959ac48df8a873e4282fbd62 Mon Sep 17 00:00:00 2001 From: AuthBypass Date: Wed, 20 Aug 2025 04:14:19 +0200 Subject: [PATCH 16/71] Remove duplicate ServerStateChanged event listener --- plugins/ui/static_components/race/TimerWidget.component.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/ui/static_components/race/TimerWidget.component.ts b/plugins/ui/static_components/race/TimerWidget.component.ts index ce0fcfe7..7ba59579 100644 --- a/plugins/ui/static_components/race/TimerWidget.component.ts +++ b/plugins/ui/static_components/race/TimerWidget.component.ts @@ -61,15 +61,11 @@ export default class TimerWidget extends StaticComponent { globalconfig.dynamicTimerSubtractionLimit ); } + tm.timer.setTimeLimit(newLimit); }; tm.addListener('ServerStateChanged', state => { if (state === 'race') setAdjustedTimeLimit(); }); - tm.addListener('ServerStateChanged', (state) => { - if (state === 'race') { - setAdjustedTimeLimit(); - } - }); this.renderOnEvent('BeginMap', () => { this.isOnRestart = false; this.noButtonXml = this.constructXml(false); From b135e37ab1aae94413260b0c4469fd5805d2badf Mon Sep 17 00:00:00 2001 From: AuthBypass Date: Wed, 20 Aug 2025 12:22:57 +0200 Subject: [PATCH 17/71] Refactored for styling consistency --- .../race/TimerWidget.component.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/ui/static_components/race/TimerWidget.component.ts b/plugins/ui/static_components/race/TimerWidget.component.ts index 7ba59579..68b3eee3 100644 --- a/plugins/ui/static_components/race/TimerWidget.component.ts +++ b/plugins/ui/static_components/race/TimerWidget.component.ts @@ -5,7 +5,6 @@ import { componentIds, StaticHeader, StaticComponent, centeredText, addManialinkListener } from '../../UI.js' import config from './TimerWidget.config.js' -import globalconfig from '../../../../config/Config.js' export default class TimerWidget extends StaticComponent { @@ -46,32 +45,33 @@ export default class TimerWidget extends StaticComponent { } }) const setAdjustedTimeLimit = () => { - if (tm.getGameMode() !== 'TimeAttack') return; - const authorTime = tm.maps.current.authorTime; - let newLimit = globalconfig.defaultTimeAttackTimeLimit; + if (tm.getGameMode() !== 'TimeAttack') { return } + const authorTime = tm.maps.current.authorTime + const globalconfig = tm.config.controller + let newLimit = globalconfig.defaultTimeAttackTimeLimit if (config.useAuthorTimeMultiplier) { newLimit = Math.max( authorTime * config.authorTimeMultiplier, globalconfig.defaultTimeAttackTimeLimit, globalconfig.dynamicTimerSubtractionLimit - ); + ) } else { newLimit = Math.max( globalconfig.defaultTimeAttackTimeLimit, globalconfig.dynamicTimerSubtractionLimit - ); + ) } - tm.timer.setTimeLimit(newLimit); - }; + tm.timer.setTimeLimit(newLimit) + } tm.addListener('ServerStateChanged', state => { - if (state === 'race') setAdjustedTimeLimit(); - }); + if (state === 'race') { setAdjustedTimeLimit() } + }) this.renderOnEvent('BeginMap', () => { - this.isOnRestart = false; - this.noButtonXml = this.constructXml(false); - this.xmlWithButtons = this.constructXml(true); - return this.display(); - }); + this.isOnRestart = false + this.noButtonXml = this.constructXml(false) + this.xmlWithButtons = this.constructXml(true) + return this.display() + }) addManialinkListener(this.pauseButtonId, (info) => { if (info.privilege < config.timerActionsPrivilege) { return } if (!tm.timer.isDynamic) { From b058f9df67c054678f38231b8a82d277ec302893 Mon Sep 17 00:00:00 2001 From: AuthBypass Date: Wed, 20 Aug 2025 12:59:02 +0200 Subject: [PATCH 18/71] Added chat message with the new time + multiplier --- plugins/ui/static_components/race/TimerWidget.component.ts | 7 ++++++- plugins/ui/static_components/race/TimerWidget.config.js | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/ui/static_components/race/TimerWidget.component.ts b/plugins/ui/static_components/race/TimerWidget.component.ts index 68b3eee3..89208bac 100644 --- a/plugins/ui/static_components/race/TimerWidget.component.ts +++ b/plugins/ui/static_components/race/TimerWidget.component.ts @@ -49,12 +49,17 @@ export default class TimerWidget extends StaticComponent { const authorTime = tm.maps.current.authorTime const globalconfig = tm.config.controller let newLimit = globalconfig.defaultTimeAttackTimeLimit + let multiplier = config.authorTimeMultiplier if (config.useAuthorTimeMultiplier) { newLimit = Math.max( - authorTime * config.authorTimeMultiplier, + authorTime * multiplier, globalconfig.defaultTimeAttackTimeLimit, globalconfig.dynamicTimerSubtractionLimit ) + tm.sendMessage(tm.utils.strVar(config.newTimeLimitMessage, { + limit: Math.floor(newLimit / 1000), + multiplier + })) } else { newLimit = Math.max( globalconfig.defaultTimeAttackTimeLimit, diff --git a/plugins/ui/static_components/race/TimerWidget.config.js b/plugins/ui/static_components/race/TimerWidget.config.js index 5623b0e0..69b95949 100644 --- a/plugins/ui/static_components/race/TimerWidget.config.js +++ b/plugins/ui/static_components/race/TimerWidget.config.js @@ -17,8 +17,9 @@ export default { buttonOrder: ['pause', 'add', 'subtract'], pausedText: 'PAUSED', textYOffset: -0.3, - useAuthorTimeMultiplier: false, - authorTimeMultiplier: 3, + useAuthorTimeMultiplier: true, + authorTimeMultiplier: 3, + newTimeLimitMessage: `${p.admin}TimeAttack limit set to ${p.highlight}#{limit}${p.admin} seconds (Multiplier: ${p.highlight}#{multiplier}${p.admin}).`, icons: { pause: icons.pause, resume: icons.play, From dcd9e1974cb3a27ac590f72af58f4c41fdf4b141 Mon Sep 17 00:00:00 2001 From: AuthBypass Date: Wed, 20 Aug 2025 13:05:43 +0200 Subject: [PATCH 19/71] Changed the chatmessage to be toggable --- .../ui/static_components/race/TimerWidget.component.ts | 10 ++++++---- .../ui/static_components/race/TimerWidget.config.js | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/ui/static_components/race/TimerWidget.component.ts b/plugins/ui/static_components/race/TimerWidget.component.ts index 89208bac..617c6003 100644 --- a/plugins/ui/static_components/race/TimerWidget.component.ts +++ b/plugins/ui/static_components/race/TimerWidget.component.ts @@ -56,10 +56,12 @@ export default class TimerWidget extends StaticComponent { globalconfig.defaultTimeAttackTimeLimit, globalconfig.dynamicTimerSubtractionLimit ) - tm.sendMessage(tm.utils.strVar(config.newTimeLimitMessage, { - limit: Math.floor(newLimit / 1000), - multiplier - })) + if (config.sendTimeLimitMessage) { + tm.sendMessage(tm.utils.strVar(config.newTimeLimitMessage, { + limit: Math.floor(newLimit / 1000), + multiplier + })) + } } else { newLimit = Math.max( globalconfig.defaultTimeAttackTimeLimit, diff --git a/plugins/ui/static_components/race/TimerWidget.config.js b/plugins/ui/static_components/race/TimerWidget.config.js index 69b95949..335270e6 100644 --- a/plugins/ui/static_components/race/TimerWidget.config.js +++ b/plugins/ui/static_components/race/TimerWidget.config.js @@ -20,6 +20,7 @@ export default { useAuthorTimeMultiplier: true, authorTimeMultiplier: 3, newTimeLimitMessage: `${p.admin}TimeAttack limit set to ${p.highlight}#{limit}${p.admin} seconds (Multiplier: ${p.highlight}#{multiplier}${p.admin}).`, + sendTimeLimitMessage: true, icons: { pause: icons.pause, resume: icons.play, From c438aeb6fa77b9f97fb75b365eb23e2153736789 Mon Sep 17 00:00:00 2001 From: AroPix Date: Wed, 20 Aug 2025 18:07:51 +0200 Subject: [PATCH 20/71] Move function call to correct spot --- plugins/music/Music.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/music/Music.ts b/plugins/music/Music.ts index b0419434..5d5517dd 100644 --- a/plugins/music/Music.ts +++ b/plugins/music/Music.ts @@ -348,7 +348,6 @@ function shuffleQueue(caller?: Caller): boolean { .sort((a, b): number => a.rand - b.rand) .map(a => a.song) listUi.updateSongs(current, queue) - updateNextSong(queue[0]?.url) emitEvent(queueChangeCallbacks, queue) if (caller !== undefined) { tm.log.info(`${tm.utils.strip(caller.nickname)} (${caller.login}) shuffled the song list`) From 63b49f26dd5e7e8aa89b34ea03be60de1eec6cec Mon Sep 17 00:00:00 2001 From: AroPix Date: Wed, 20 Aug 2025 18:08:06 +0200 Subject: [PATCH 21/71] Call nextsong update on EndMap and Startup only --- plugins/music/Music.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/plugins/music/Music.ts b/plugins/music/Music.ts index 5d5517dd..dc872de5 100644 --- a/plugins/music/Music.ts +++ b/plugins/music/Music.ts @@ -90,9 +90,6 @@ export const music = { return false } const song = queue.splice(index, 1)[0] - if (index === 0) { - updateNextSong(queue[0]?.url) - } listUi.updateSongs(current, queue) emitEvent(songRemoveCallbacks, song, caller) emitEvent(queueChangeCallbacks, queue, { @@ -175,7 +172,7 @@ if (config.isEnabled) { emitEvent(queueChangeCallbacks, queue) }) - tm.addListener('BeginMap', () => { + tm.addListener('EndMap', () => { if (current !== undefined) { current.isJuked = false current.caller = undefined @@ -187,11 +184,11 @@ if (config.isEnabled) { } } current = queue[0] + updateNextSong(current?.url) queue.shift() listUi.updateSongs(current, queue) listUi.updatePreviousSongs(history) widgetUi.setCurrentSong(current) - updateNextSong(queue[0]?.url) emitEvent(queueChangeCallbacks, queue) }, true) const msg = config.messages @@ -293,9 +290,6 @@ function addToQueue(songName: string, emitEvents: boolean, caller?: Caller): song.caller = caller const newIndex = queue.findIndex(a => a.isJuked === false) queue.splice(newIndex, 0, song) - if (newIndex === 0) { - updateNextSong(song.url) - } listUi.updateSongs(current, queue) if (caller !== undefined) { tm.log.trace(`${tm.utils.strip(caller.nickname)} (${caller.login}) queued song ${song.name} by ${song.author}`) @@ -326,9 +320,6 @@ function removeFromQueue(name: string, caller?: Caller): Readonly | 'not q tm.log.trace(`Song ${tm.utils.strip(queue[index].name)} by ${queue[index].author} has been removed from the queue`) } const song = queue.splice(index, 1)[0] - if (index === 0) { - updateNextSong(queue[0].url) - } song.isJuked = false song.caller = undefined queue.push(song) From 239eb654bb668699842b1625795cd40ab779b293 Mon Sep 17 00:00:00 2001 From: AroPix Date: Wed, 20 Aug 2025 18:37:11 +0200 Subject: [PATCH 22/71] Add favourite button in code --- .../race/buttons/ButtonsWidget.component.ts | 2 + .../race/buttons/ButtonsWidget.config.js | 359 +++++++++--------- 2 files changed, 185 insertions(+), 176 deletions(-) diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts index ffb6443b..12725660 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts @@ -12,6 +12,7 @@ import { StatsButton } from './StatsButton.js' import { CommandListButton } from './CommandListButton.js' import { SectorsButton } from './SectorsButton.js' import { UiButton } from './UiButton.js' +import { FavouriteButton } from "./FavouriteButton.js"; import config from './ButtonsWidget.config.js' export default class ButtonsWidget extends StaticComponent { @@ -31,6 +32,7 @@ export default class ButtonsWidget extends StaticComponent { new TimeButton(), new PlayerCounter(), new VersionButton(), + new FavouriteButton(), new MapsButton(), new StatsButton(), new CommandListButton(), diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js index 3c7b255e..5d21b792 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js @@ -2,183 +2,190 @@ import cfg from '../../../config/RaceUi.js' import icons from '../../../config/Icons.js' export default { - height: 14.5, - width: cfg.width, - margin: cfg.margin, - rows: 3, - columns: 4, - order: [ - 'VisitCounter', - 'PlayerCounter', - 'VersionButton', - 'TimeButton', - 'MapsButton', - 'StatsButton', - 'SectorsButton', - 'CommandListButton', - 'VoteSkip', - 'VoteReplay', - 'PaySkip', - 'PayReplay' - ], - visitCounter: { - texts: { 0: "#{count}", 1: "$AF4VISITOR#{plural}", equal: false }, - icon: icons.people, - width: 2, - height: 2, - padding: 0.3 - }, - playerCounter: { - texts: { 0: "#{count} $AF4SPEC#{plural}", 1: "#{count} $AF4PLAYER#{plural}", equal: true }, - icon: icons.peopleGaming, - width: 2, - height: 2, - padding: 0.3 - }, - versionButton: { - texts: { 0: "#{version}", 1: "$AF4VERSION", equal: false }, - icon: icons.codeBranch, - width: 1.8, - height: 1.8, - padding: 0.3 - }, - timeButton: { - texts: { 0: "#{time}", 1: "$AF4TIME", equal: false }, - icon: icons.clock, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - mapsButton: { - texts: { 0: "#{count}", 1: "$AF4MAPS", equal: false }, - icon: icons.numberedList, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - statsButton: { - texts: { 0: "PLAYER", 1: "$AF4STATS", equal: true }, - icon: icons.stats, - width: 1.9, - height: 1.9, - padding: 0.2 - }, - sectorsButton: { - texts: { 0: "SECTOR", 1: "$AF4RECORDS", equal: true }, - icon: icons.clockList, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - commandListButton: { - texts: { 0: "COMMAND", 1: "$AF4LIST", equal: true }, - icon: icons.infoCircle, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - voteSkip: { - texts: [ - { 0: " VOTE ", 1: "TO $E22SKIP", equal: false }, - { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, - { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, + height: 14.5, + width: cfg.width, + margin: cfg.margin, + rows: 3, + columns: 4, + order: [ + 'VisitCounter', + 'PlayerCounter', + 'VersionButton', + 'TimeButton', + 'MapsButton', + 'StatsButton', + 'SectorsButton', + 'CommandListButton', + 'VoteSkip', + 'VoteReplay', + 'PaySkip', + 'PayReplay' ], - header: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.red}SKIP${tm.utils.palette.highlight} the ongoing map`, - icon: icons.voteSkip, - voteIcon: icons.voteSkip, - triesLimit: 3, - timeout: 30, - time: 30, - minimumRemainingTime: 30000, // msec - goal: 0.51, - width: 1.8, - height: 1.8, - padding: 0.2, - countdown: 5, - actionId: 1, - command: { - aliases: ['s', 'skip'], - help: `Start a vote to skip the ongoing map.`, - privilege: 0 - } - }, - voteReplay: { - texts: [ - { 0: " VOTE ", 1: "TO $AF4RES", equal: false }, - { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, - { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, - { 0: "VOTE TO", 1: "$AF4EXTEND", equal: true }, - { 0: "LIMIT", 1: "$AF4REACHED", equal: true } - ], - timeExtension: 1000 * 60 * 5, // 5 minutes - extensionsLimit: 2, // 0 to disable limit - resHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}REPLAY${tm.utils.palette.highlight} the ongoing map`, - extendHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}EXTEND${tm.utils.palette.highlight} the time on the ongoing map`, - icon: icons.voteReplay, - voteIcon: icons.voteReplay, - replayLimit: 5, - triesLimit: 3, - timeout: 30, - time: 30, - minimumRemainingTime: 30000, //msec - goal: 0.5, - width: 1.8, - height: 1.8, - padding: 0.2, - actionId: 2, - command: { - aliases: ['r', 'res', 'replay'], - help: `Start a vote to replay the ongoing map.`, - privilege: 0 + visitCounter: { + texts: {0: "#{count}", 1: "$AF4VISITOR#{plural}", equal: false}, + icon: icons.people, + width: 2, + height: 2, + padding: 0.3 + }, + playerCounter: { + texts: {0: "#{count} $AF4SPEC#{plural}", 1: "#{count} $AF4PLAYER#{plural}", equal: true}, + icon: icons.peopleGaming, + width: 2, + height: 2, + padding: 0.3 + }, + versionButton: { + texts: {0: "#{version}", 1: "$AF4VERSION", equal: false}, + icon: icons.codeBranch, + width: 1.8, + height: 1.8, + padding: 0.3 + }, + favouriteButton: { + texts: {0: "$AF4ADD TO", 1: "FAVOURITES", equal: false}, + icon: icons.karmaGlobal, + width: 1.8, + height: 1.8, + padding: 0.3 + }, + timeButton: { + texts: {0: "#{time}", 1: "$AF4TIME", equal: false}, + icon: icons.clock, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + mapsButton: { + texts: {0: "#{count}", 1: "$AF4MAPS", equal: false}, + icon: icons.numberedList, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + statsButton: { + texts: {0: "PLAYER", 1: "$AF4STATS", equal: true}, + icon: icons.stats, + width: 1.9, + height: 1.9, + padding: 0.2 }, - extendCommand: { - aliases: ['ext', 'extend'], - help: `Start a vote to extend the time on the ongoing map.`, - privilege: 0 + sectorsButton: { + texts: {0: "SECTOR", 1: "$AF4RECORDS", equal: true}, + icon: icons.clockList, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + commandListButton: { + texts: {0: "COMMAND", 1: "$AF4LIST", equal: true}, + icon: icons.infoCircle, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + voteSkip: { + texts: [ + {0: " VOTE ", 1: "TO $E22SKIP", equal: false}, + {0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true}, + {0: "$E22DISABLED", 1: "DUE TO RES", equal: true}, + {0: "MAP IS", 1: "$E22SKIPPED", equal: true}, + {0: "$E22DISABLED", 1: "DUE TO EXT", equal: true}, + ], + header: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.red}SKIP${tm.utils.palette.highlight} the ongoing map`, + icon: icons.voteSkip, + voteIcon: icons.voteSkip, + triesLimit: 3, + timeout: 30, + time: 30, + minimumRemainingTime: 30000, // msec + goal: 0.51, + width: 1.8, + height: 1.8, + padding: 0.2, + countdown: 5, + actionId: 1, + command: { + aliases: ['s', 'skip'], + help: `Start a vote to skip the ongoing map.`, + privilege: 0 + } + }, + voteReplay: { + texts: [ + {0: " VOTE ", 1: "TO $AF4RES", equal: false}, + {0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true}, + {0: "MAP IS", 1: "$AF4REPLAYED", equal: true}, + {0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true}, + {0: "VOTE TO", 1: "$AF4EXTEND", equal: true}, + {0: "LIMIT", 1: "$AF4REACHED", equal: true} + ], + timeExtension: 1000 * 60 * 5, // 5 minutes + extensionsLimit: 2, // 0 to disable limit + resHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}REPLAY${tm.utils.palette.highlight} the ongoing map`, + extendHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}EXTEND${tm.utils.palette.highlight} the time on the ongoing map`, + icon: icons.voteReplay, + voteIcon: icons.voteReplay, + replayLimit: 5, + triesLimit: 3, + timeout: 30, + time: 30, + minimumRemainingTime: 30000, //msec + goal: 0.5, + width: 1.8, + height: 1.8, + padding: 0.2, + actionId: 2, + command: { + aliases: ['r', 'res', 'replay'], + help: `Start a vote to replay the ongoing map.`, + privilege: 0 + }, + extendCommand: { + aliases: ['ext', 'extend'], + help: `Start a vote to extend the time on the ongoing map.`, + privilege: 0 + } + }, + paySkip: { + texts: [ + {0: "PAY $E22#{cost}", 1: "TO $E22SKIP", equal: false}, + {0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true}, + {0: "$E22DISABLED", 1: "DUE TO RES", equal: true}, + {0: "MAP IS", 1: "$E22SKIPPED", equal: true}, + {0: "$E22DISABLED", 1: "DUE TO EXT", equal: true}, + ], + billMessage: 'Pay to skip the ongoing map', + icon: icons.paySkip, + width: 1.8, + height: 1.8, + padding: 0.2, + cost: 500, + countdown: 15, + actionId: 3 + }, + payReplay: { + texts: [ + {0: "PAY $AF4#{cost}", 1: "TO $AF4RES", equal: false}, + {0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true}, + {0: "MAP IS", 1: "$AF4REPLAYED", equal: true}, + {0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true}, + {0: "PAY $AF4#{cost}", 1: "TO $AF4EXT", equal: false}, + {0: "$AF4EXT$z$s LIMIT", 1: "REACHED", equal: true} + ], + timeExtension: 1000 * 60 * 5, // 5 minutes + billMessage: 'Pay to replay the ongoing map or extend the timer', + icon: icons.payReplay, + width: 1.8, + height: 1.8, + padding: 0.2, + costs: [ + 100, + 200, + 300, + 400, + 500 + ], + actionId: 4 } - }, - paySkip: { - texts: [ - { 0: "PAY $E22#{cost}", 1: "TO $E22SKIP", equal: false }, - { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, - { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, - ], - billMessage: 'Pay to skip the ongoing map', - icon: icons.paySkip, - width: 1.8, - height: 1.8, - padding: 0.2, - cost: 500, - countdown: 15, - actionId: 3 - }, - payReplay: { - texts: [ - { 0: "PAY $AF4#{cost}", 1: "TO $AF4RES", equal: false }, - { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, - { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, - { 0: "PAY $AF4#{cost}", 1: "TO $AF4EXT", equal: false }, - { 0: "$AF4EXT$z$s LIMIT", 1: "REACHED", equal: true } - ], - timeExtension: 1000 * 60 * 5, // 5 minutes - billMessage: 'Pay to replay the ongoing map or extend the timer', - icon: icons.payReplay, - width: 1.8, - height: 1.8, - padding: 0.2, - costs: [ - 100, - 200, - 300, - 400, - 500 - ], - actionId: 4 - } } \ No newline at end of file From af778b2654da593b512d904cf3b225f10c432256 Mon Sep 17 00:00:00 2001 From: AroPix Date: Wed, 20 Aug 2025 18:44:14 +0200 Subject: [PATCH 23/71] Add favourite button --- plugins/ui/config/ComponentIds.js | 1 + .../race/buttons/FavouriteButton.ts | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 plugins/ui/static_components/race/buttons/FavouriteButton.ts diff --git a/plugins/ui/config/ComponentIds.js b/plugins/ui/config/ComponentIds.js index 46de49da..cf961d99 100644 --- a/plugins/ui/config/ComponentIds.js +++ b/plugins/ui/config/ComponentIds.js @@ -82,6 +82,7 @@ export default { localSectors: 520000, ultiRanking: 530000, ultiRecords: 540000, + favouriteButton: 560000, // needs 3.1 mil ids mapList: 10000000 } diff --git a/plugins/ui/static_components/race/buttons/FavouriteButton.ts b/plugins/ui/static_components/race/buttons/FavouriteButton.ts new file mode 100644 index 00000000..1525498b --- /dev/null +++ b/plugins/ui/static_components/race/buttons/FavouriteButton.ts @@ -0,0 +1,27 @@ +import type { ButtonData } from "./ButtonData.js" +import { UiButton } from "./UiButton.js" +import config from "./ButtonsWidget.config.js" +import { componentIds } from '../../../UI.js' + +const cfg = config.favouriteButton + +export class FavouriteButton extends UiButton { + + buttonData: ButtonData + + constructor() { + super() + this.buttonData = { + icon: cfg.icon, + text1: cfg.texts[0], + text2: cfg.texts[1], + iconWidth: cfg.width, + iconHeight: cfg.height, + padding: cfg.padding, + actionId: componentIds.favouriteButton, + equalTexts: cfg.texts.equal, + link: "tmtp://#addfavourite=" + tm.config.server.login + } + } + +} \ No newline at end of file From 62da46b2a8cdf581e87f46441bfbffb360a88042 Mon Sep 17 00:00:00 2001 From: AroPix Date: Wed, 20 Aug 2025 21:32:09 +0200 Subject: [PATCH 24/71] Add greet command --- .../commands/config/ChatCommands.config.js | 7 + plugins/commands/src/ChatCommands.ts | 603 ++++++++++-------- 2 files changed, 331 insertions(+), 279 deletions(-) diff --git a/plugins/commands/config/ChatCommands.config.js b/plugins/commands/config/ChatCommands.config.js index eb24988d..aef20a6d 100644 --- a/plugins/commands/config/ChatCommands.config.js +++ b/plugins/commands/config/ChatCommands.config.js @@ -10,6 +10,13 @@ export default { aliases: ['hi', 'hey', 'hello'], help: `Greet a certain someone.` }, + greet: { + text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{nickname_joiner}!`, + public: true, + privilege: 0, + aliases: ['greet'], + help: `Greet the latest joiner.` + }, bye: { text: `$g[#{nickname}$z$s$g] ${prefix}Goodbye, #{name}!`, public: true, diff --git a/plugins/commands/src/ChatCommands.ts b/plugins/commands/src/ChatCommands.ts index ec7efd61..5be7e563 100644 --- a/plugins/commands/src/ChatCommands.ts +++ b/plugins/commands/src/ChatCommands.ts @@ -1,310 +1,355 @@ import config from '../config/ChatCommands.config.js' const commands: tm.Command[] = [ - { - aliases: config.hi.aliases, - help: config.hi.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.hi.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.hi.public ? undefined : info.login, false) + { + aliases: config.hi.aliases, + help: config.hi.help, + params: [{name: 'name', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.hi.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.hi.public ? undefined : info.login, false) + }, + privilege: config.hi.privilege, + disableForMuted: true }, - privilege: config.hi.privilege, - disableForMuted: true - }, - { - aliases: config.bye.aliases, - help: config.bye.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.bye.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.bye.public ? undefined : info.login, false) + { + aliases: config.greet.aliases, + help: config.greet.help, + params: [], + callback: (info: tm.MessageInfo): void => { + let timestamp = tm.players.list[0]; + tm.players.list.slice(1).forEach(player => { + if (player.joinTimestamp > timestamp.joinTimestamp) { + timestamp = player + } + }) + tm.sendMessage(tm.utils.strVar(config.greet.text, { + nickname: info.nickname, + nickname_joiner: timestamp.nickname + }), config.greet.public ? undefined : info.login, false) + }, + privilege: config.greet.privilege, + disableForMuted: true }, - privilege: config.bye.privilege, - disableForMuted: true - }, - { - aliases: config.thx.aliases, - help: config.thx.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.thx.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.thx.public ? undefined : info.login, false) + { + aliases: config.bye.aliases, + help: config.bye.help, + params: [{name: 'name', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.bye.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.bye.public ? undefined : info.login, false) + }, + privilege: config.bye.privilege, + disableForMuted: true }, - privilege: config.thx.privilege, - disableForMuted: true - }, - { - aliases: config.gg.aliases, - help: config.gg.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.gg.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.gg.public ? undefined : info.login, false) + { + aliases: config.thx.aliases, + help: config.thx.help, + params: [{name: 'name', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.thx.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.thx.public ? undefined : info.login, false) + }, + privilege: config.thx.privilege, + disableForMuted: true }, - privilege: config.gg.privilege, - disableForMuted: true - }, - { - aliases: config.bg.aliases, - help: config.bg.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.bg.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.bg.public ? undefined : info.login, false) + { + aliases: config.gg.aliases, + help: config.gg.help, + params: [{name: 'name', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.gg.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.gg.public ? undefined : info.login, false) + }, + privilege: config.gg.privilege, + disableForMuted: true }, - privilege: config.bg.privilege, - disableForMuted: true - }, - { - aliases: config.n1.aliases, - help: config.n1.help, - params: [{ name: 'name', type: 'multiword' }], - callback: (info: tm.MessageInfo, name: string): void => { - tm.sendMessage(tm.utils.strVar(config.n1.text, { nickname: info.nickname, name: name }), config.n1.public ? undefined : info.login, false) + { + aliases: config.bg.aliases, + help: config.bg.help, + params: [{name: 'name', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.bg.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.bg.public ? undefined : info.login, false) + }, + privilege: config.bg.privilege, + disableForMuted: true }, - privilege: config.n1.privilege, - disableForMuted: true - }, - { - aliases: config.gr.aliases, - help: config.gr.help, - params: [{ name: 'name', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.gr.text, { nickname: info.nickname, name: name ?? config.defaultValue }), config.gr.public ? undefined : info.login, false) + { + aliases: config.n1.aliases, + help: config.n1.help, + params: [{name: 'name', type: 'multiword'}], + callback: (info: tm.MessageInfo, name: string): void => { + tm.sendMessage(tm.utils.strVar(config.n1.text, { + nickname: info.nickname, + name: name + }), config.n1.public ? undefined : info.login, false) + }, + privilege: config.n1.privilege, + disableForMuted: true }, - privilege: config.gr.privilege, - disableForMuted: true - }, - { - aliases: config.bgm.aliases, - help: config.bgm.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bgm.text, { nickname: info.nickname }), config.bgm.public ? undefined : info.login, false) + { + aliases: config.gr.aliases, + help: config.gr.help, + params: [{name: 'name', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.gr.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.gr.public ? undefined : info.login, false) + }, + privilege: config.gr.privilege, + disableForMuted: true }, - privilege: config.bgm.privilege, - disableForMuted: true - }, - { - aliases: config.brb.aliases, - help: config.brb.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.brb.text, { nickname: info.nickname }), config.brb.public ? undefined : info.login, false) + { + aliases: config.bgm.aliases, + help: config.bgm.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bgm.text, {nickname: info.nickname}), config.bgm.public ? undefined : info.login, false) + }, + privilege: config.bgm.privilege, + disableForMuted: true }, - privilege: config.brb.privilege, - disableForMuted: true - }, - { - aliases: config.afk.aliases, - help: config.afk.help, - callback: async (info: tm.MessageInfo): Promise => { - const res = await tm.client.call('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: info.login }, { int: 1 }] + { + aliases: config.brb.aliases, + help: config.brb.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.brb.text, {nickname: info.nickname}), config.brb.public ? undefined : info.login, false) }, - { - method: 'ForceSpectator', - params: [{ string: info.login }, { int: 0 }] - }]) - if (res instanceof Error || res[0] instanceof Error) { - tm.sendMessage(config.afk.tooManySpecs, info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.afk.text, { nickname: info.nickname }), config.afk.public ? undefined : info.login, false) - tm.client.callNoRes('SpectatorReleasePlayerSlot', [{ string: info.login }]) - } + privilege: config.brb.privilege, + disableForMuted: true }, - privilege: config.afk.privilege - }, - { - aliases: config.me.aliases, - help: config.me.help, - params: [{ name: 'thoughts', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, thoughts?: string): void => { - tm.sendMessage(tm.utils.strVar(config.me.text, { nickname: info.nickname, message: thoughts ?? '' }), config.me.public ? undefined : info.login, false) + { + aliases: config.afk.aliases, + help: config.afk.help, + callback: async (info: tm.MessageInfo): Promise => { + const res = await tm.client.call('system.multicall', + [{ + method: 'ForceSpectator', + params: [{string: info.login}, {int: 1}] + }, + { + method: 'ForceSpectator', + params: [{string: info.login}, {int: 0}] + }]) + if (res instanceof Error || res[0] instanceof Error) { + tm.sendMessage(config.afk.tooManySpecs, info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.afk.text, {nickname: info.nickname}), config.afk.public ? undefined : info.login, false) + tm.client.callNoRes('SpectatorReleasePlayerSlot', [{string: info.login}]) + } + }, + privilege: config.afk.privilege }, - privilege: config.me.privilege, - disableForMuted: true - }, - { - aliases: config.lol.aliases, - help: config.lol.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.lol.text, { nickname: info.nickname }), config.lol.public ? undefined : info.login, false) + { + aliases: config.me.aliases, + help: config.me.help, + params: [{name: 'thoughts', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, thoughts?: string): void => { + tm.sendMessage(tm.utils.strVar(config.me.text, { + nickname: info.nickname, + message: thoughts ?? '' + }), config.me.public ? undefined : info.login, false) + }, + privilege: config.me.privilege, + disableForMuted: true }, - privilege: config.lol.privilege, - disableForMuted: true - }, - { - aliases: config.lool.aliases, - help: config.lool.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.lool.text, { nickname: info.nickname }), config.lool.public ? undefined : info.login, false) + { + aliases: config.lol.aliases, + help: config.lol.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.lol.text, {nickname: info.nickname}), config.lol.public ? undefined : info.login, false) + }, + privilege: config.lol.privilege, + disableForMuted: true }, - privilege: config.lool.privilege, - disableForMuted: true - }, - { - aliases: config.loool.aliases, - help: config.loool.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.loool.text, { nickname: info.nickname }), config.loool.public ? undefined : info.login, false) + { + aliases: config.lool.aliases, + help: config.lool.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.lool.text, {nickname: info.nickname}), config.lool.public ? undefined : info.login, false) + }, + privilege: config.lool.privilege, + disableForMuted: true }, - privilege: config.loool.privilege, - disableForMuted: true - }, - { - aliases: config.time.aliases, - help: config.time.help, - callback: (info: tm.MessageInfo): void => { - const date = new Date() - tm.sendMessage(tm.utils.strVar(config.time.text, - { - time: date.toLocaleString('EU') - }), info.login) + { + aliases: config.loool.aliases, + help: config.loool.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.loool.text, {nickname: info.nickname}), config.loool.public ? undefined : info.login, false) + }, + privilege: config.loool.privilege, + disableForMuted: true }, - privilege: config.time.privilege - }, - { - aliases: config.bootme.aliases, - help: config.bootme.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bootme.text, { nickname: tm.utils.strip(info.nickname, true) }), config.bootme.public ? undefined : info.login) - tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.bootme.leaveText }]) + { + aliases: config.time.aliases, + help: config.time.help, + callback: (info: tm.MessageInfo): void => { + const date = new Date() + tm.sendMessage(tm.utils.strVar(config.time.text, + { + time: date.toLocaleString('EU') + }), info.login) + }, + privilege: config.time.privilege }, - privilege: config.bootme.privilege - }, - { - aliases: config.rq.aliases, - help: config.rq.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bootme.text, { nickname: tm.utils.strip(info.nickname, true) }), config.rq.public ? undefined : info.login) - tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.rq.leaveText }]) + { + aliases: config.bootme.aliases, + help: config.bootme.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bootme.text, {nickname: tm.utils.strip(info.nickname, true)}), config.bootme.public ? undefined : info.login) + tm.client.callNoRes('Kick', [{string: info.login}, {string: config.bootme.leaveText}]) + }, + privilege: config.bootme.privilege }, - privilege: config.rq.privilege - }, - { - aliases: config.pm.aliases, - help: config.pm.help, - params: [{ name: 'login' }, { name: 'text', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, login: string, text: string = ''): void => { - const playerInfo: tm.Player | undefined = tm.players.get(login) - if (playerInfo === undefined) { - tm.sendMessage(config.pm.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.pm.text, - { - sender: tm.utils.strip(info.nickname, false), - recipient: tm.utils.strip(playerInfo.nickname, false), - message: text - }), [info.login, playerInfo.login].join()) + { + aliases: config.rq.aliases, + help: config.rq.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bootme.text, {nickname: tm.utils.strip(info.nickname, true)}), config.rq.public ? undefined : info.login) + tm.client.callNoRes('Kick', [{string: info.login}, {string: config.rq.leaveText}]) + }, + privilege: config.rq.privilege }, - privilege: config.pm.privilege - }, - { - aliases: config.playtime.aliases, - help: config.playtime.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.playtime.text, - { time: tm.utils.getVerboseTime(Date.now() - tm.timer.mapStartTimestamp) }), - info.login) + { + aliases: config.pm.aliases, + help: config.pm.help, + params: [{name: 'login'}, {name: 'text', type: 'multiword', optional: true}], + callback: (info: tm.MessageInfo, login: string, text: string = ''): void => { + const playerInfo: tm.Player | undefined = tm.players.get(login) + if (playerInfo === undefined) { + tm.sendMessage(config.pm.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.pm.text, + { + sender: tm.utils.strip(info.nickname, false), + recipient: tm.utils.strip(playerInfo.nickname, false), + message: text + }), [info.login, playerInfo.login].join()) + }, + privilege: config.pm.privilege }, - privilege: config.playtime.privilege - }, - { - aliases: config.laston.aliases, - help: config.laston.help, - params: [{ name: 'login' }], - callback: async (info: tm.MessageInfo, login: string): Promise => { - login = login.toLowerCase() - const player = await tm.players.fetch(login) - if (player?.lastOnline === undefined) { - tm.sendMessage(tm.utils.strVar(config.laston.error, { name: login }), info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.laston.text, { - name: tm.utils.strip(player.nickname), - time: new Date(player.lastOnline).toLocaleString('EU') - }), info.login) + { + aliases: config.playtime.aliases, + help: config.playtime.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.playtime.text, + {time: tm.utils.getVerboseTime(Date.now() - tm.timer.mapStartTimestamp)}), + info.login) + }, + privilege: config.playtime.privilege }, - privilege: config.laston.privilege - }, - { - aliases: config.sessiontime.aliases, - help: config.sessiontime.help, - params: [{ name: 'login', optional: true }], - callback: async (info: tm.MessageInfo, login?: string): Promise => { - if (login === undefined || login === info.login) { - tm.sendMessage(tm.utils.strVar(config.sessiontime.selfText, { - time: tm.utils.getVerboseTime(Date.now() - info.joinTimestamp) - }), info.login) - return - } - const player = tm.players.get(login) - if (player === undefined) { - tm.sendMessage(tm.utils.strVar(config.sessiontime.error, { - name: login - }), info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.sessiontime.text, { - name: tm.utils.strip(player.nickname), - time: tm.utils.getVerboseTime(Date.now() - player.joinTimestamp) - }), info.login) + { + aliases: config.laston.aliases, + help: config.laston.help, + params: [{name: 'login'}], + callback: async (info: tm.MessageInfo, login: string): Promise => { + login = login.toLowerCase() + const player = await tm.players.fetch(login) + if (player?.lastOnline === undefined) { + tm.sendMessage(tm.utils.strVar(config.laston.error, {name: login}), info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.laston.text, { + name: tm.utils.strip(player.nickname), + time: new Date(player.lastOnline).toLocaleString('EU') + }), info.login) + }, + privilege: config.laston.privilege }, - privilege: config.sessiontime.privilege - }, - { - aliases: config.man.aliases, - help: config.man.help, - params: [{ name: 'commandName' }], - callback: (info: tm.MessageInfo, commandName: string): void => { - const command = tm.commands.list.filter(a => a.aliases.some(a => a === commandName)) - let str = '' - for (const e of command) { - if (e.help === undefined || e.privilege > info.privilege) { continue } - str += '\n' - const par = tm.utils.stringifyCommandParams(e.params) - str += tm.utils.strVar(config.man.text, { - name: commandName, - params: par.length === 0 ? '' : `(${par}) `, - help: e.help - }) - } - str = str.slice(1) - if (str.length === 0) { - tm.sendMessage(tm.utils.strVar(config.man.error, { - name: commandName - }), info.login) - return - } - tm.sendMessage(str, info.login) + { + aliases: config.sessiontime.aliases, + help: config.sessiontime.help, + params: [{name: 'login', optional: true}], + callback: async (info: tm.MessageInfo, login?: string): Promise => { + if (login === undefined || login === info.login) { + tm.sendMessage(tm.utils.strVar(config.sessiontime.selfText, { + time: tm.utils.getVerboseTime(Date.now() - info.joinTimestamp) + }), info.login) + return + } + const player = tm.players.get(login) + if (player === undefined) { + tm.sendMessage(tm.utils.strVar(config.sessiontime.error, { + name: login + }), info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.sessiontime.text, { + name: tm.utils.strip(player.nickname), + time: tm.utils.getVerboseTime(Date.now() - player.joinTimestamp) + }), info.login) + }, + privilege: config.sessiontime.privilege }, - privilege: config.man.privilege - }, - { - aliases: ['admin', 'a'], - callback: (info: tm.MessageInfo): void => { - if (info.privilege > 0) { - tm.sendMessage(config.admin.text, info.login) - } + { + aliases: config.man.aliases, + help: config.man.help, + params: [{name: 'commandName'}], + callback: (info: tm.MessageInfo, commandName: string): void => { + const command = tm.commands.list.filter(a => a.aliases.some(a => a === commandName)) + let str = '' + for (const e of command) { + if (e.help === undefined || e.privilege > info.privilege) { + continue + } + str += '\n' + const par = tm.utils.stringifyCommandParams(e.params) + str += tm.utils.strVar(config.man.text, { + name: commandName, + params: par.length === 0 ? '' : `(${par}) `, + help: e.help + }) + } + str = str.slice(1) + if (str.length === 0) { + tm.sendMessage(tm.utils.strVar(config.man.error, { + name: commandName + }), info.login) + return + } + tm.sendMessage(str, info.login) + }, + privilege: config.man.privilege }, - privilege: config.admin.privilege - }, - { - aliases: config.coppers.aliases, - help: config.coppers.help, - callback: async (info: tm.MessageInfo): Promise => { - if (!tm.config.server.isUnited) { - tm.sendMessage(config.coppers.notUnited, info.login) - return - } - const coppers: any | Error = await tm.client.call('GetServerCoppers') - if (coppers instanceof Error) { - tm.log.error(`Couldn't retrieve the coppers amount.`, coppers.message) - tm.sendMessage(config.coppers.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.coppers.text, { value: coppers }), config.coppers.public ? undefined : info.login) + { + aliases: ['admin', 'a'], + callback: (info: tm.MessageInfo): void => { + if (info.privilege > 0) { + tm.sendMessage(config.admin.text, info.login) + } + }, + privilege: config.admin.privilege }, - privilege: config.coppers.privilege - } + { + aliases: config.coppers.aliases, + help: config.coppers.help, + callback: async (info: tm.MessageInfo): Promise => { + if (!tm.config.server.isUnited) { + tm.sendMessage(config.coppers.notUnited, info.login) + return + } + const coppers: any | Error = await tm.client.call('GetServerCoppers') + if (coppers instanceof Error) { + tm.log.error(`Couldn't retrieve the coppers amount.`, coppers.message) + tm.sendMessage(config.coppers.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.coppers.text, {value: coppers}), config.coppers.public ? undefined : info.login) + }, + privilege: config.coppers.privilege + } ] tm.commands.add(...commands) \ No newline at end of file From 95c5d2fdb51d7dc8ba8bef4f42faa5fd3f630117 Mon Sep 17 00:00:00 2001 From: AroPix Date: Wed, 20 Aug 2025 21:33:53 +0200 Subject: [PATCH 25/71] Remove coloring before exclamation mark in default config --- plugins/commands/config/ChatCommands.config.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/commands/config/ChatCommands.config.js b/plugins/commands/config/ChatCommands.config.js index aef20a6d..77983fcc 100644 --- a/plugins/commands/config/ChatCommands.config.js +++ b/plugins/commands/config/ChatCommands.config.js @@ -4,56 +4,56 @@ const prefix = `$i` // Prefix all "fake" player messages with this (eg. $i, $t, export default { defaultValue: `everyone`, // This value will be used for the name if you don't specify anything in e.g. /hi hi: { - text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{name}$g!`, public: true, privilege: 0, aliases: ['hi', 'hey', 'hello'], help: `Greet a certain someone.` }, greet: { - text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{nickname_joiner}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{nickname_joiner}$g!`, public: true, privilege: 0, aliases: ['greet'], help: `Greet the latest joiner.` }, bye: { - text: `$g[#{nickname}$z$s$g] ${prefix}Goodbye, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Goodbye, #{name}$g!`, public: true, privilege: 0, aliases: ['bb', 'bye'], help: `Bid your farewell.` }, thx: { - text: `$g[#{nickname}$z$s$g] ${prefix}Thanks, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Thanks, #{name}$g!`, public: true, privilege: 0, aliases: ['ty', 'tx', 'thx', 'thanks'], help: `Express your gratitude.` }, gg: { - text: `$g[#{nickname}$z$s$g] ${prefix}Good game, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Good game, #{name}$g!`, public: true, privilege: 0, aliases: ['gg', 'goodgame'], help: `Inform others that you\'ve enjoyed the race.` }, bg: { - text: `$g[#{nickname}$z$s$g] ${prefix}Bad game, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Bad game, #{name}$g!`, public: true, privilege: 0, aliases: ['bg', 'badgame'], help: `Allow others to find out about your disenjoyment of the round.` }, n1: { - text: `$g[#{nickname}$z$s$g] ${prefix}Nice one, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Nice one, #{name}$g!`, public: true, privilege: 0, aliases: ['n1', 'nice1', 'niceone'], help: `Rain your blessings upon the few selected by thy divine ritual.` }, gr: { - text: `$g[#{nickname}$z$s$g] ${prefix}Good race, #{name}!`, + text: `$g[#{nickname}$z$s$g] ${prefix}Good race, #{name}$g!`, public: true, privilege: 0, aliases: ['gr', 'goodrace'], From b84423680a7f5eba30266c7e10f62e133571e4bd Mon Sep 17 00:00:00 2001 From: AroPix Date: Fri, 22 Aug 2025 20:11:41 +0200 Subject: [PATCH 26/71] Add config option for CP Counter to not display times --- .../static_components/race/CpCounter.component.ts | 13 +++++++++---- .../ui/static_components/race/CpCounter.config.js | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/ui/static_components/race/CpCounter.component.ts b/plugins/ui/static_components/race/CpCounter.component.ts index aa8c83e4..a221266d 100644 --- a/plugins/ui/static_components/race/CpCounter.component.ts +++ b/plugins/ui/static_components/race/CpCounter.component.ts @@ -237,6 +237,14 @@ export default class CpCounter extends StaticComponent { const spectators = tm.players.get(login)?.spectators const logins = spectators !== undefined ? Array.from(spectators) : [] logins.unshift(login) + + let times = '' + if (config.times) { + times = ` + ${cpAmount === 0 ? '' : this.constructTimeXml(login, false, config.iconBottom, + params?.isFinish, params?.current, params?.best)}` + } + return { xml: ` @@ -246,10 +254,7 @@ export default class CpCounter extends StaticComponent { ${this.header.constructXml('$' + config.colours.default + text, config.icon, config.side, { rectangleWidth, centerText })} ${counterXml} - - ${cpAmount === 0 ? '' : this.constructTimeXml(login, false, config.iconBottom, - params?.isFinish, params?.current, params?.best)} - + ${times} `, login: logins } diff --git a/plugins/ui/static_components/race/CpCounter.config.js b/plugins/ui/static_components/race/CpCounter.config.js index 617bc5ff..efcc9cf1 100644 --- a/plugins/ui/static_components/race/CpCounter.config.js +++ b/plugins/ui/static_components/race/CpCounter.config.js @@ -11,6 +11,7 @@ export default { posY: -37, margin: cfg.margin, side: true, + times: true, rectangleWidth: 7.7, icon: icons.waypoint, iconBottom: icons.clock, From bd2d9a517b904e21ab9a67b7386999ff9c1c7f09 Mon Sep 17 00:00:00 2001 From: AroPix Date: Sat, 23 Aug 2025 19:26:25 +0200 Subject: [PATCH 27/71] Add config option for the callVoteRatio --- config/Config.js | 2 ++ src/services/GameService.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/Config.js b/config/Config.js index 7a65671c..a516b420 100644 --- a/config/Config.js +++ b/config/Config.js @@ -77,6 +77,8 @@ export default { defaultTimeAttackTimeLimit: 300000, /** Minimal time value to which the dynamic timer can be set (in milliseconds) */ dynamicTimerSubtractionLimit: 30000, + /** CallVotes are skip/restart votes that can be done through the ingame UI (ESC>Advanced Options), setting it to -1 disables CallVotes entirely */ + callVoteRatio: -1, /** Relative path (/GameData/Config/) to the blacklist file */ blacklistFile: "blacklist.txt", /** Relative path (/GameData/Config/) to the guestlist file */ diff --git a/src/services/GameService.ts b/src/services/GameService.ts index 363a34a1..a62191c7 100644 --- a/src/services/GameService.ts +++ b/src/services/GameService.ts @@ -51,7 +51,7 @@ export class GameService { static async initialize(): Promise { Client.callNoRes(`system.multicall`, [ - { method: `SetCallVoteRatios`, params: [{ array: [{ struct: { Command: { string: `*` }, Ratio: { double: -1 } } }] }] }, + { method: `SetCallVoteRatios`, params: [{ array: [{ struct: { Command: { string: `*` }, Ratio: { double: config.callVoteRatio } } }] }] }, { method: `SetVehicleNetQuality`, params: [{ int: 1 }] } ] ) From 1aa434fb6520c40ee3a48ab16bf50422330fd771 Mon Sep 17 00:00:00 2001 From: AroPix Date: Fri, 22 Aug 2025 15:09:56 +0200 Subject: [PATCH 28/71] Implementing per player buttons --- .../race/buttons/ButtonData.ts | 3 +- .../race/buttons/ButtonsWidget.component.ts | 176 ++++++++++-------- 2 files changed, 96 insertions(+), 83 deletions(-) diff --git a/plugins/ui/static_components/race/buttons/ButtonData.ts b/plugins/ui/static_components/race/buttons/ButtonData.ts index 818c056c..f99c02aa 100644 --- a/plugins/ui/static_components/race/buttons/ButtonData.ts +++ b/plugins/ui/static_components/race/buttons/ButtonData.ts @@ -7,5 +7,6 @@ export interface ButtonData { padding: number, equalTexts?: boolean, actionId?: number, - link?: string + link?: string, + perPlayer?: boolean } \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts index f81c1040..4da2f2e9 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts @@ -1,102 +1,114 @@ -import { componentIds, Grid, staticButton, type GridCellFunction, StaticComponent } from '../../../UI.js' -import { VisitCounter } from './VisitCounter.js' -import { TimeButton } from './TimeButton.js' -import { PlayerCounter } from './PlayerCounter.js' -import { VersionButton } from './VersionButton.js' -import { MapsButton } from './MapsButton.js' -import { PayReplay } from './PayReplay.js' -import { PaySkip } from './PaySkip.js' -import { VoteReplay } from './VoteReplay.js' -import { VoteSkip } from './VoteSkip.js' -import { StatsButton } from './StatsButton.js' -import { CommandListButton } from './CommandListButton.js' -import { SectorsButton } from './SectorsButton.js' -import { UiButton } from './UiButton.js' -import { FavouriteButton } from "./FavouriteButton.js"; +import {componentIds, Grid, staticButton, type GridCellFunction, StaticComponent} from '../../../UI.js' +import {VisitCounter} from './VisitCounter.js' +import {TimeButton} from './TimeButton.js' +import {PlayerCounter} from './PlayerCounter.js' +import {VersionButton} from './VersionButton.js' +import {MapsButton} from './MapsButton.js' +import {PayReplay} from './PayReplay.js' +import {PaySkip} from './PaySkip.js' +import {VoteReplay} from './VoteReplay.js' +import {VoteSkip} from './VoteSkip.js' +import {StatsButton} from './StatsButton.js' +import {CommandListButton} from './CommandListButton.js' +import {SectorsButton} from './SectorsButton.js' +import {UiButton} from './UiButton.js' +import {MedalButton} from "./MedalButton.js"; +import {FavouriteButton} from "./FavouriteButton.js"; import config from './ButtonsWidget.config.js' export default class ButtonsWidget extends StaticComponent { - private readonly width: number = config.width - private readonly height: number = config.height - private readonly grid: Grid - private readonly buttons: UiButton[] = [] - private xml: string = '' + private readonly width: number = config.width + private readonly height: number = config.height + private readonly grid: Grid + private readonly buttons: UiButton[] = [] + private xml: string = '' - constructor() { - super(componentIds.buttons) - this.grid = new Grid(this.width + config.margin, this.height + config.margin, - new Array(config.columns).fill(1), new Array(config.rows).fill(1)) - const allButtons = [ - new VisitCounter(), - new TimeButton(), - new PlayerCounter(), - new VersionButton(), - new FavouriteButton(), - new MapsButton(), - new StatsButton(), - new CommandListButton(), - new SectorsButton(), - new PayReplay(this.id), - new PaySkip(this.id), - new VoteReplay(this.id), - new VoteSkip(this.id) - ] - for (const e of config.order) { - const b = allButtons.find(a => a.constructor.name === e) - if (b === undefined) { throw new Error(`Can't find button named ${e}`) } - this.buttons.push(b) + constructor() { + super(componentIds.buttons) + this.grid = new Grid(this.width + config.margin, this.height + config.margin, + new Array(config.columns).fill(1), new Array(config.rows).fill(1)) + const allButtons = [ + new VisitCounter(), + new TimeButton(), + new PlayerCounter(), + new VersionButton(), + new MapsButton(), + new StatsButton(), + new CommandListButton(), + new SectorsButton(), + new FavouriteButton(), + new PayReplay(this.id), + new PaySkip(this.id), + new VoteReplay(this.id), + new VoteSkip(this.id), + ] + for (const e of config.order) { + const b = allButtons.find(a => a.constructor.name === e) + if (b === undefined) { + throw new Error(`Can't find button named ${e}`) + } + this.buttons.push(b) + } + UiButton.onUpdate(() => { + let calls = [] + if (this.isDisplayed) { + for (const player of tm.players.list) + calls.push({ + method: 'SendDisplayManialinkPageToLogin', + params: [{string: player.login}, {string: this.constructXml(player.login)}, {int: 0}, {boolean: false}] + }) + + if (calls.length > 0) { + tm.client.call('system.multicall', calls) + } + } + }) + this.onPanelHide((player) => { // todo: this does not work properly due to button update + this.sendMultipleManialinks(this.displayToPlayer(player.login)) + }) } - UiButton.onUpdate(() => { - this.constructXml() - const xml = this.display() - if (xml !== undefined) { - tm.sendManialink(xml) - } - }) - this.onPanelHide((player) => { // todo: this does not work properly due to button update - this.sendMultipleManialinks(this.displayToPlayer(player.login)) - }) - } - getHeight(): number { - return config.height - } + getHeight(): number { + return config.height + } - display() { - if (!this.isDisplayed) { return } - this.constructXml() - return this.xml - } + display() { + } - displayToPlayer(login: string) { - if (!this.isDisplayed) { return } - if (config.hidePanel && this.hasPanelsHidden(login)) { - return this.hideToPlayer(login) + displayToPlayer(login: string) { + if (!this.isDisplayed) { return } + if (config.hidePanel && this.hasPanelsHidden(login)) { + return this.hideToPlayer(login) + } + return { xml: this.xml, login } } - return { xml: this.xml, login } - } - private constructXml(): void { - const arr: GridCellFunction[] = [] - for (const e of this.buttons) { - const data = e.buttonData - arr.push((i, j, w, h) => - staticButton(data.icon, data.text1, data.text2, w - config.margin, - h - config.margin, { - iconWidth: data.iconWidth, iconHeight: data.iconHeight, topPadding: data.padding, - equalTexts: data.equalTexts === true ? true : undefined, - actionId: data.actionId, link: data.link - })) - } - this.xml = ` + private constructXml(login: string): string { + const arr: GridCellFunction[] = [] + for (const e of this.buttons) { + const data = e.buttonData + if (e.buttonData.perPlayer) { + arr.push((i, j, w, h) => e.renderForPlayer(login, i, j, w, h)) +// + } else { + arr.push((i, j, w, h) => + staticButton(data.icon, data.text1, data.text2, w - config.margin, + h - config.margin, { + iconWidth: data.iconWidth, iconHeight: data.iconHeight, topPadding: data.padding, + equalTexts: data.equalTexts === true ? true : undefined, + actionId: data.actionId, link: data.link + })) + } + } + return ` ${this.grid.constructXml(arr)} ` - } + } } From db856bff4e8be2b2fc4bfa990e11b31a34e01580 Mon Sep 17 00:00:00 2001 From: AroPix Date: Fri, 22 Aug 2025 15:10:18 +0200 Subject: [PATCH 29/71] Add buttons showing medal of current record --- .../race/buttons/ButtonsWidget.component.ts | 1 + .../race/buttons/ButtonsWidget.config.js | 373 +++++++++--------- .../race/buttons/UiButton.ts | 4 + 3 files changed, 197 insertions(+), 181 deletions(-) diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts index 4da2f2e9..bd44caa7 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts @@ -42,6 +42,7 @@ export default class ButtonsWidget extends StaticComponent { new PaySkip(this.id), new VoteReplay(this.id), new VoteSkip(this.id), + new MedalButton() ] for (const e of config.order) { const b = allButtons.find(a => a.constructor.name === e) diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js index 2ceaef59..d1bc93fd 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js @@ -2,191 +2,202 @@ import cfg from '../../../config/RaceUi.js' import icons from '../../../config/Icons.js' export default { - height: 14.5, - width: cfg.width, - margin: cfg.margin, - rows: 3, - columns: 4, - order: [ - 'VisitCounter', - 'PlayerCounter', - 'VersionButton', - 'TimeButton', - 'MapsButton', - 'StatsButton', - 'SectorsButton', - 'CommandListButton', - 'VoteSkip', - 'VoteReplay', - 'PaySkip', - 'PayReplay' + height: 14.5, + width: cfg.width, + margin: cfg.margin, + rows: 3, + columns: 4, + order: [ + 'VisitCounter', + 'PlayerCounter', + 'VersionButton', + 'TimeButton', + 'MapsButton', + 'StatsButton', + 'SectorsButton', + 'CommandListButton', + 'VoteSkip', + 'VoteReplay', + 'PaySkip', + 'PayReplay' + ], + visitCounter: { + texts: { 0: "#{count}", 1: "$AF4VISITOR#{plural}", equal: false }, + icon: icons.people, + width: 2, + height: 2, + padding: 0.3 + }, + playerCounter: { + texts: { 0: "#{count} $AF4SPEC#{plural}", 1: "#{count} $AF4PLAYER#{plural}", equal: true }, + icon: icons.peopleGaming, + width: 2, + height: 2, + padding: 0.3 + }, + versionButton: { + texts: { 0: "#{version}", 1: "$AF4VERSION", equal: false }, + icon: icons.codeBranch, + width: 1.8, + height: 1.8, + padding: 0.3 + }, + favouriteButton: { + texts: {0: "$AF4ADD TO", 1: "FAVOURITES", equal: false}, + icon: icons.karmaGlobal, + width: 1.8, + height: 1.8, + padding: 0.3 + }, + timeButton: { + texts: { 0: "#{time}", 1: "$AF4TIME", equal: false }, + icon: icons.clock, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + mapsButton: { + texts: { 0: "#{count}", 1: "$AF4MAPS", equal: false }, + icon: icons.numberedList, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + statsButton: { + texts: { 0: "PLAYER", 1: "$AF4STATS", equal: true }, + icon: icons.stats, + width: 1.9, + height: 1.9, + padding: 0.2 + }, + sectorsButton: { + texts: { 0: "SECTOR", 1: "$AF4RECORDS", equal: true }, + icon: icons.clockList, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + commandListButton: { + texts: { 0: "COMMAND", 1: "$AF4LIST", equal: true }, + icon: icons.infoCircle, + width: 1.8, + height: 1.8, + padding: 0.2 + }, + voteSkip: { + texts: [ + { 0: " VOTE ", 1: "TO $E22SKIP", equal: false }, + { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, + { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, + { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, + { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, ], - visitCounter: { - texts: {0: "#{count}", 1: "$AF4VISITOR#{plural}", equal: false}, - icon: icons.people, - width: 2, - height: 2, - padding: 0.3 - }, - playerCounter: { - texts: {0: "#{count} $AF4SPEC#{plural}", 1: "#{count} $AF4PLAYER#{plural}", equal: true}, - icon: icons.peopleGaming, - width: 2, - height: 2, - padding: 0.3 - }, - versionButton: { - texts: {0: "#{version}", 1: "$AF4VERSION", equal: false}, - icon: icons.codeBranch, - width: 1.8, - height: 1.8, - padding: 0.3 + header: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.red}SKIP${tm.utils.palette.highlight} the ongoing map`, + icon: icons.voteSkip, + voteIcon: icons.voteSkip, + triesLimit: 3, + timeout: 30, + time: 30, + minimumRemainingTime: 30000, // msec + goal: 0.51, + width: 1.8, + height: 1.8, + padding: 0.2, + countdown: 5, + actionId: 1, + command: { + aliases: ['s', 'skip'], + help: `Start a vote to skip the ongoing map.`, + privilege: 0 + } + }, + voteReplay: { + texts: [ + { 0: " VOTE ", 1: "TO $AF4RES", equal: false }, + { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, + { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, + { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, + { 0: "VOTE TO", 1: "$AF4EXTEND", equal: true }, + { 0: "LIMIT", 1: "$AF4REACHED", equal: true } + ], + timeExtension: 1000 * 60 * 5, // 5 minutes + extensionsLimit: 2, // 0 to disable limit + resHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}REPLAY${tm.utils.palette.highlight} the ongoing map`, + extendHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}EXTEND${tm.utils.palette.highlight} the time on the ongoing map`, + icon: icons.voteReplay, + voteIcon: icons.voteReplay, + replayLimit: 5, + triesLimit: 3, + timeout: 30, + time: 30, + minimumRemainingTime: 30000, //msec + goal: 0.5, + width: 1.8, + height: 1.8, + padding: 0.2, + actionId: 2, + command: { + aliases: ['r', 'res', 'replay'], + help: `Start a vote to replay the ongoing map.`, + privilege: 0 }, - favouriteButton: { - texts: {0: "$AF4ADD TO", 1: "FAVOURITES", equal: false}, + extendCommand: { + aliases: ['ext', 'extend'], + help: `Start a vote to extend the time on the ongoing map.`, + privilege: 0 + } + }, + paySkip: { + texts: [ + { 0: "PAY $E22#{cost}", 1: "TO $E22SKIP", equal: false }, + { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, + { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, + { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, + { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, + ], + billMessage: 'Pay to skip the ongoing map', + icon: icons.paySkip, + width: 1.8, + height: 1.8, + padding: 0.2, + cost: 500, + countdown: 15, + actionId: 3 + }, + payReplay: { + texts: [ + { 0: "PAY $AF4#{cost}", 1: "TO $AF4RES", equal: false }, + { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, + { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, + { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, + { 0: "PAY $AF4#{cost}", 1: "TO $AF4EXT", equal: false }, + { 0: "$AF4EXT$z$s LIMIT", 1: "REACHED", equal: true } + ], + timeExtension: 1000 * 60 * 5, // 5 minutes + billMessage: 'Pay to replay the ongoing map or extend the timer', + icon: icons.payReplay, + width: 1.8, + height: 1.8, + padding: 0.2, + costs: [ + 100, + 200, + 300, + 400, + 500 + ], + actionId: 4 + }, + medalButton: { icon: icons.karmaGlobal, width: 1.8, height: 1.8, - padding: 0.3 - }, - timeButton: { - texts: {0: "#{time}", 1: "$AF4TIME", equal: false}, - icon: icons.clock, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - mapsButton: { - texts: {0: "#{count}", 1: "$AF4MAPS", equal: false}, - icon: icons.numberedList, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - statsButton: { - texts: {0: "PLAYER", 1: "$AF4STATS", equal: true}, - icon: icons.stats, - width: 1.9, - height: 1.9, - padding: 0.2 - }, - sectorsButton: { - texts: {0: "SECTOR", 1: "$AF4RECORDS", equal: true}, - icon: icons.clockList, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - commandListButton: { - texts: {0: "COMMAND", 1: "$AF4LIST", equal: true}, - icon: icons.infoCircle, - width: 1.8, - height: 1.8, - padding: 0.2 - }, - voteSkip: { - texts: [ - {0: " VOTE ", 1: "TO $E22SKIP", equal: false}, - {0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true}, - {0: "$E22DISABLED", 1: "DUE TO RES", equal: true}, - {0: "MAP IS", 1: "$E22SKIPPED", equal: true}, - {0: "$E22DISABLED", 1: "DUE TO EXT", equal: true}, - ], - header: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.red}SKIP${tm.utils.palette.highlight} the ongoing map`, - icon: icons.voteSkip, - voteIcon: icons.voteSkip, - triesLimit: 3, - timeout: 30, - time: 30, - minimumRemainingTime: 30000, // msec - goal: 0.51, - width: 1.8, - height: 1.8, - padding: 0.2, - countdown: 5, - actionId: 1, - command: { - aliases: ['s', 'skip'], - help: `Start a vote to skip the ongoing map.`, - privilege: 0 - } - }, - voteReplay: { - texts: [ - {0: " VOTE ", 1: "TO $AF4RES", equal: false}, - {0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true}, - {0: "MAP IS", 1: "$AF4REPLAYED", equal: true}, - {0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true}, - {0: "VOTE TO", 1: "$AF4EXTEND", equal: true}, - {0: "LIMIT", 1: "$AF4REACHED", equal: true} - ], - timeExtension: 1000 * 60 * 5, // 5 minutes - extensionsLimit: 2, // 0 to disable limit - resHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}REPLAY${tm.utils.palette.highlight} the ongoing map`, - extendHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}EXTEND${tm.utils.palette.highlight} the time on the ongoing map`, - icon: icons.voteReplay, - voteIcon: icons.voteReplay, - replayLimit: 5, - triesLimit: 3, - timeout: 30, - time: 30, - minimumRemainingTime: 30000, //msec - goal: 0.5, - width: 1.8, - height: 1.8, - padding: 0.2, - actionId: 2, - command: { - aliases: ['r', 'res', 'replay'], - help: `Start a vote to replay the ongoing map.`, - privilege: 0 - }, - extendCommand: { - aliases: ['ext', 'extend'], - help: `Start a vote to extend the time on the ongoing map.`, - privilege: 0 - } - }, - paySkip: { - texts: [ - {0: "PAY $E22#{cost}", 1: "TO $E22SKIP", equal: false}, - {0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true}, - {0: "$E22DISABLED", 1: "DUE TO RES", equal: true}, - {0: "MAP IS", 1: "$E22SKIPPED", equal: true}, - {0: "$E22DISABLED", 1: "DUE TO EXT", equal: true}, - ], - billMessage: 'Pay to skip the ongoing map', - icon: icons.paySkip, - width: 1.8, - height: 1.8, - padding: 0.2, - cost: 500, - countdown: 15, - actionId: 3 - }, - payReplay: { - texts: [ - {0: "PAY $AF4#{cost}", 1: "TO $AF4RES", equal: false}, - {0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true}, - {0: "MAP IS", 1: "$AF4REPLAYED", equal: true}, - {0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true}, - {0: "PAY $AF4#{cost}", 1: "TO $AF4EXT", equal: false}, - {0: "$AF4EXT$z$s LIMIT", 1: "REACHED", equal: true} - ], - timeExtension: 1000 * 60 * 5, // 5 minutes - billMessage: 'Pay to replay the ongoing map or extend the timer', - icon: icons.payReplay, - width: 1.8, - height: 1.8, - padding: 0.2, - costs: [ - 100, - 200, - 300, - 400, - 500 - ], - actionId: 4 + padding: 0.3, + textNoMedal: "NO MEDAL", + textBronzeMedal: "$0f0BRONZE", + textSilverMedal: "$cccSILVER", + textGoldMedal: "$fd0GOLD", + textAuthorMedal: "$0f0AUTHOR" }, - hidePanel: false + hidePanel: false, } \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/UiButton.ts b/plugins/ui/static_components/race/buttons/UiButton.ts index 122cb3d1..aaba4611 100644 --- a/plugins/ui/static_components/race/buttons/UiButton.ts +++ b/plugins/ui/static_components/race/buttons/UiButton.ts @@ -37,4 +37,8 @@ export abstract class UiButton { UiButton.skipListeners.push(callback) } + public renderForPlayer(login: string, i: number, j: number, w: number, h: number): string { + return `` + } + } \ No newline at end of file From 1d4c633bd3677e49ef2be6a0d981f413a168f63f Mon Sep 17 00:00:00 2001 From: AroPix Date: Fri, 22 Aug 2025 15:14:21 +0200 Subject: [PATCH 30/71] Remove unneeded and actually add button class (oops) --- .../race/buttons/ButtonsWidget.config.js | 1 - .../race/buttons/MedalButton.ts | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 plugins/ui/static_components/race/buttons/MedalButton.ts diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js index d1bc93fd..e7fe68d0 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js @@ -189,7 +189,6 @@ export default { actionId: 4 }, medalButton: { - icon: icons.karmaGlobal, width: 1.8, height: 1.8, padding: 0.3, diff --git a/plugins/ui/static_components/race/buttons/MedalButton.ts b/plugins/ui/static_components/race/buttons/MedalButton.ts new file mode 100644 index 00000000..1acb6559 --- /dev/null +++ b/plugins/ui/static_components/race/buttons/MedalButton.ts @@ -0,0 +1,72 @@ +import type { ButtonData } from "./ButtonData.js" +import { UiButton } from "./UiButton.js" +import config from "./ButtonsWidget.config.js" +import {horizontallyCenteredText} from '../../../UI.js' +import buttonConfig from '../../../../ui/utils/StaticButton.config.js' + +const cfg = config.medalButton + +export class MedalButton extends UiButton { + + buttonData: ButtonData + + constructor() { + super() + this.buttonData = { + icon: "", + text1: "", + text2: "", + iconWidth: 2.5, + iconHeight: cfg.height, + padding: cfg.padding, + equalTexts: false, + perPlayer: true + } + tm.addListener('LocalRecord', () => { + this.emitUpdate() + }) + + tm.addListener('BeginMap', () => { + this.emitUpdate() + }) + } + + renderForPlayer(login:string, i: number, j: number, w: number, h: number): string { + const record = tm.records.getLocal(login)?.time ?? 0 + + const map = tm.maps.current + const width = w - config.margin + const height = h - config.margin + const iconWidth = this.buttonData.iconWidth + const topPadding = this.buttonData.padding + + + type MedalInfo = { limit: number, text: string, icon: string } + + const thresholds: MedalInfo[] = [ + { limit: map.authorTime ?? Infinity, text: cfg.textAuthorMedal, icon: "MedalNadeo" }, + { limit: map.goldTime ?? Infinity, text: cfg.textGoldMedal, icon: "MedalGold" }, + { limit: map.silverTime ?? Infinity, text: cfg.textSilverMedal, icon: "MedalSilver"}, + { limit: map.bronzeTime ?? Infinity, text: cfg.textBronzeMedal, icon: "MedalBronze"}, + ] + + let medalText = cfg.textNoMedal + let iconSub = "MedalSlot" + + if (record > 0) { + const hit = thresholds.find(t => record <= t.limit) + if (hit) { medalText = hit.text; iconSub = hit.icon } + } + + const t1: string = horizontallyCenteredText(medalText, width, height, + { yOffset: 3.2, textScale: buttonConfig.textScale, padding: buttonConfig.padding }) + + + return ` + + ${t1}` + + } + +} \ No newline at end of file From 634e1a060440d436f6ee0f32890d3614d50e18a3 Mon Sep 17 00:00:00 2001 From: AroPix Date: Fri, 22 Aug 2025 19:31:18 +0200 Subject: [PATCH 31/71] Trigger update on PlayerDataUpdated --- plugins/ui/static_components/race/buttons/MedalButton.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/ui/static_components/race/buttons/MedalButton.ts b/plugins/ui/static_components/race/buttons/MedalButton.ts index 1acb6559..3c56a798 100644 --- a/plugins/ui/static_components/race/buttons/MedalButton.ts +++ b/plugins/ui/static_components/race/buttons/MedalButton.ts @@ -22,11 +22,8 @@ export class MedalButton extends UiButton { equalTexts: false, perPlayer: true } - tm.addListener('LocalRecord', () => { - this.emitUpdate() - }) - tm.addListener('BeginMap', () => { + tm.addListener(['BeginMap', 'PlayerDataUpdated', 'LocalRecord'], (): void => { this.emitUpdate() }) } From a84af0ef96a0a1beb55a2cc6b95b736b950c971e Mon Sep 17 00:00:00 2001 From: lythx Date: Sat, 23 Aug 2025 16:07:12 +0200 Subject: [PATCH 32/71] Render MedalButton per-player conditionally, add improvements and comments --- .../race/buttons/ButtonData.ts | 4 + .../race/buttons/ButtonsWidget.component.ts | 143 ++++++++++-------- .../race/buttons/ButtonsWidget.config.js | 25 +-- .../race/buttons/MedalButton.ts | 61 ++++---- .../race/buttons/UiButton.ts | 4 + 5 files changed, 132 insertions(+), 105 deletions(-) diff --git a/plugins/ui/static_components/race/buttons/ButtonData.ts b/plugins/ui/static_components/race/buttons/ButtonData.ts index f99c02aa..652fb1d8 100644 --- a/plugins/ui/static_components/race/buttons/ButtonData.ts +++ b/plugins/ui/static_components/race/buttons/ButtonData.ts @@ -8,5 +8,9 @@ export interface ButtonData { equalTexts?: boolean, actionId?: number, link?: string, + /** + * Enables per-player rendering. Buttons using per-player rendering should override the + * `UiButton.renderForPlayer()` function (see `MedalButton.ts`) + */ perPlayer?: boolean } \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts index bd44caa7..d3eb488f 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts @@ -24,58 +24,74 @@ export default class ButtonsWidget extends StaticComponent { private readonly buttons: UiButton[] = [] private xml: string = '' - constructor() { - super(componentIds.buttons) - this.grid = new Grid(this.width + config.margin, this.height + config.margin, - new Array(config.columns).fill(1), new Array(config.rows).fill(1)) - const allButtons = [ - new VisitCounter(), - new TimeButton(), - new PlayerCounter(), - new VersionButton(), - new MapsButton(), - new StatsButton(), - new CommandListButton(), - new SectorsButton(), - new FavouriteButton(), - new PayReplay(this.id), - new PaySkip(this.id), - new VoteReplay(this.id), - new VoteSkip(this.id), - new MedalButton() - ] - for (const e of config.order) { - const b = allButtons.find(a => a.constructor.name === e) - if (b === undefined) { - throw new Error(`Can't find button named ${e}`) - } - this.buttons.push(b) - } - UiButton.onUpdate(() => { - let calls = [] - if (this.isDisplayed) { - for (const player of tm.players.list) - calls.push({ - method: 'SendDisplayManialinkPageToLogin', - params: [{string: player.login}, {string: this.constructXml(player.login)}, {int: 0}, {boolean: false}] - }) + constructor() { + super(componentIds.buttons) + this.grid = new Grid(this.width + config.margin, this.height + config.margin, + new Array(config.columns).fill(1), new Array(config.rows).fill(1)) + const allButtons = [ + new VisitCounter(), + new TimeButton(), + new PlayerCounter(), + new VersionButton(), + new MapsButton(), + new StatsButton(), + new CommandListButton(), + new SectorsButton(), + new FavouriteButton(), + new PayReplay(this.id), + new PaySkip(this.id), + new VoteReplay(this.id), + new VoteSkip(this.id), + new MedalButton() + ] + for (const e of config.order) { + const b = allButtons.find(a => a.constructor.name === e) + if (b === undefined) { + throw new Error(`Can't find button named ${e}`) + } + this.buttons.push(b) + } + const perPlayerButtonExists = this.buttons.some(b => b.buttonData.perPlayer) + // Use per-player dedicated server calls only if necessary to improve performance + if (perPlayerButtonExists) { + UiButton.onUpdate(() => { + let calls = [] + if (this.isDisplayed) { + for (const player of tm.players.list) + calls.push({ + method: 'SendDisplayManialinkPageToLogin', + params: [{ string: player.login }, { string: this.constructXml(player.login) }, { int: 0 }, { boolean: false }] + }) - if (calls.length > 0) { - tm.client.call('system.multicall', calls) - } - } - }) + if (calls.length > 0) { + tm.client.call('system.multicall', calls) + } + } + }) this.onPanelHide((player) => { // todo: this does not work properly due to button update this.sendMultipleManialinks(this.displayToPlayer(player.login)) }) } - - getHeight(): number { - return config.height + else { + UiButton.onUpdate(() => { + const xml = this.display() + if (xml !== undefined) { + tm.sendManialink(xml) + } + }) } - display() { - } + + } + + getHeight(): number { + return config.height + } + + display() { + if (!this.isDisplayed) { return } + return this.constructXml() + } displayToPlayer(login: string) { @@ -87,29 +103,28 @@ export default class ButtonsWidget extends StaticComponent { } - private constructXml(login: string): string { - const arr: GridCellFunction[] = [] - for (const e of this.buttons) { - const data = e.buttonData - if (e.buttonData.perPlayer) { - arr.push((i, j, w, h) => e.renderForPlayer(login, i, j, w, h)) -// - } else { - arr.push((i, j, w, h) => - staticButton(data.icon, data.text1, data.text2, w - config.margin, - h - config.margin, { - iconWidth: data.iconWidth, iconHeight: data.iconHeight, topPadding: data.padding, - equalTexts: data.equalTexts === true ? true : undefined, - actionId: data.actionId, link: data.link - })) - } - } - return ` + private constructXml(login?: string): string { + const arr: GridCellFunction[] = [] + for (const e of this.buttons) { + const data = e.buttonData + if (e.buttonData.perPlayer) { + arr.push((i, j, w, h) => e.renderForPlayer(login ?? "", i, j, w, h)) + } else { + arr.push((i, j, w, h) => + staticButton(data.icon, data.text1, data.text2, w - config.margin, + h - config.margin, { + iconWidth: data.iconWidth, iconHeight: data.iconHeight, topPadding: data.padding, + equalTexts: data.equalTexts === true ? true : undefined, + actionId: data.actionId, link: data.link + })) + } + } + return ` ${this.grid.constructXml(arr)} ` - } + } } diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js index e7fe68d0..20eb8190 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js @@ -1,6 +1,8 @@ import cfg from '../../../config/RaceUi.js' import icons from '../../../config/Icons.js' +const palette = tm.utils.palette + export default { height: 14.5, width: cfg.width, @@ -188,15 +190,16 @@ export default { ], actionId: 4 }, - medalButton: { - width: 1.8, - height: 1.8, - padding: 0.3, - textNoMedal: "NO MEDAL", - textBronzeMedal: "$0f0BRONZE", - textSilverMedal: "$cccSILVER", - textGoldMedal: "$fd0GOLD", - textAuthorMedal: "$0f0AUTHOR" - }, - hidePanel: false, + medalButton: { + width: 3, + height: 3, + padding: 0.3, + textYOffset: 3.6, + textNoMedal: "NO MEDAL", + textBronzeMedal: `$0f0BRONZE`, + textSilverMedal: "SILVER", + textGoldMedal: `$${palette.yellow}GOLD`, + textAuthorMedal: `$${palette.green}AUTHOR` + }, + hidePanel: false, } \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/MedalButton.ts b/plugins/ui/static_components/race/buttons/MedalButton.ts index 3c56a798..aeb2684e 100644 --- a/plugins/ui/static_components/race/buttons/MedalButton.ts +++ b/plugins/ui/static_components/race/buttons/MedalButton.ts @@ -1,7 +1,7 @@ import type { ButtonData } from "./ButtonData.js" import { UiButton } from "./UiButton.js" import config from "./ButtonsWidget.config.js" -import {horizontallyCenteredText} from '../../../UI.js' +import { horizontallyCenteredText } from '../../../UI.js' import buttonConfig from '../../../../ui/utils/StaticButton.config.js' const cfg = config.medalButton @@ -12,56 +12,57 @@ export class MedalButton extends UiButton { constructor() { super() + // Texts in `ButtonData` are irrelevant because we use per-player rendering so + // button xml is constructed in `renderForPlayer` this.buttonData = { icon: "", text1: "", text2: "", - iconWidth: 2.5, + iconWidth: cfg.width, iconHeight: cfg.height, padding: cfg.padding, equalTexts: false, perPlayer: true } - tm.addListener(['BeginMap', 'PlayerDataUpdated', 'LocalRecord'], (): void => { - this.emitUpdate() - }) + tm.addListener(['BeginMap', 'PlayerDataUpdated', 'LocalRecord'], (): void => { + this.emitUpdate() + }) } - renderForPlayer(login:string, i: number, j: number, w: number, h: number): string { - const record = tm.records.getLocal(login)?.time ?? 0 + renderForPlayer(login: string, i: number, j: number, w: number, h: number): string { + const record = tm.records.getLocal(login)?.time ?? 0 - const map = tm.maps.current - const width = w - config.margin - const height = h - config.margin - const iconWidth = this.buttonData.iconWidth - const topPadding = this.buttonData.padding + const map = tm.maps.current + const width = w - config.margin + const height = h - config.margin + const topPadding = this.buttonData.padding - type MedalInfo = { limit: number, text: string, icon: string } + type MedalInfo = { limit: number, text: string, icon: string } - const thresholds: MedalInfo[] = [ - { limit: map.authorTime ?? Infinity, text: cfg.textAuthorMedal, icon: "MedalNadeo" }, - { limit: map.goldTime ?? Infinity, text: cfg.textGoldMedal, icon: "MedalGold" }, - { limit: map.silverTime ?? Infinity, text: cfg.textSilverMedal, icon: "MedalSilver"}, - { limit: map.bronzeTime ?? Infinity, text: cfg.textBronzeMedal, icon: "MedalBronze"}, - ] + const thresholds: MedalInfo[] = [ + { limit: map.authorTime ?? Infinity, text: cfg.textAuthorMedal, icon: "MedalNadeo" }, + { limit: map.goldTime ?? Infinity, text: cfg.textGoldMedal, icon: "MedalGold" }, + { limit: map.silverTime ?? Infinity, text: cfg.textSilverMedal, icon: "MedalSilver" }, + { limit: map.bronzeTime ?? Infinity, text: cfg.textBronzeMedal, icon: "MedalBronze" }, + ] - let medalText = cfg.textNoMedal - let iconSub = "MedalSlot" + let medalText = cfg.textNoMedal + let iconSub = "MedalSlot" - if (record > 0) { - const hit = thresholds.find(t => record <= t.limit) - if (hit) { medalText = hit.text; iconSub = hit.icon } - } + if (record > 0) { + const hit = thresholds.find(t => record <= t.limit) + if (hit) { medalText = hit.text; iconSub = hit.icon } + } - const t1: string = horizontallyCenteredText(medalText, width, height, - { yOffset: 3.2, textScale: buttonConfig.textScale, padding: buttonConfig.padding }) + const t1: string = horizontallyCenteredText(medalText, width, height, + { yOffset: cfg.textYOffset, textScale: buttonConfig.textScale, padding: buttonConfig.padding }) - return ` - + return ` + ${t1}` } diff --git a/plugins/ui/static_components/race/buttons/UiButton.ts b/plugins/ui/static_components/race/buttons/UiButton.ts index aaba4611..478ec497 100644 --- a/plugins/ui/static_components/race/buttons/UiButton.ts +++ b/plugins/ui/static_components/race/buttons/UiButton.ts @@ -37,6 +37,10 @@ export abstract class UiButton { UiButton.skipListeners.push(callback) } + /** + * If `ButtonData.perPlayer` is set to true this function will be used for rendering, instead + * of static rendering based on `ButtonData` (see `MedalButton.ts`) + */ public renderForPlayer(login: string, i: number, j: number, w: number, h: number): string { return `` } From 8be6267caf1150d0580560a11e84009b03cd89c2 Mon Sep 17 00:00:00 2001 From: lythx Date: Sat, 23 Aug 2025 16:12:53 +0200 Subject: [PATCH 33/71] fix bronze medal text color --- .../ui/static_components/race/buttons/ButtonsWidget.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js index 20eb8190..151a2079 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js @@ -196,7 +196,7 @@ export default { padding: 0.3, textYOffset: 3.6, textNoMedal: "NO MEDAL", - textBronzeMedal: `$0f0BRONZE`, + textBronzeMedal: `$da5BRONZE`, textSilverMedal: "SILVER", textGoldMedal: `$${palette.yellow}GOLD`, textAuthorMedal: `$${palette.green}AUTHOR` From 95409147a4a7eb4ceefe64ce4092168c5d5f4728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fel=C3=A1=C4=8Dek?= Date: Sun, 21 Sep 2025 16:08:50 +0200 Subject: [PATCH 34/71] reformat all code and introduce .editorconfig file --- .editorconfig | 372 ++++++ Update.js | 22 +- config/Config.js | 16 +- plugins/actions/Actions.ts | 122 +- plugins/actions/Config.js | 39 +- plugins/betting/Betting.ts | 32 +- plugins/betting/Config.js | 14 +- plugins/betting/ui/BetInfoWidget.component.ts | 10 +- plugins/betting/ui/BetInfoWidget.config.js | 7 +- .../betting/ui/BetPlaceWindow.component.ts | 45 +- plugins/betting/ui/BetPlaceWindow.config.js | 10 +- .../CustomBracket.config.js | 14 +- .../FinishCounter.config.js | 24 +- plugins/chat_routing_additions/UrlFixer.ts | 5 +- plugins/checkpoint_records/CheckpointDB.ts | 52 +- .../checkpoint_records/CheckpointEvents.ts | 10 +- .../checkpoint_records/CheckpointListeners.ts | 236 ++-- .../checkpoint_records/CheckpointRecords.ts | 14 +- plugins/checkpoint_records/CheckpointTypes.ts | 36 +- plugins/checkpoint_records/Config.js | 13 +- .../ui/CheckpointRecords.component.ts | 33 +- .../ui/CheckpointRecords.config.js | 61 +- .../commands/config/ChatCommands.config.js | 21 +- .../commands/config/GameCommands.config.js | 4 +- plugins/commands/config/MapCommands.config.js | 11 +- .../commands/config/RecordsCommands.config.js | 4 +- .../commands/config/ServerCommands.config.js | 8 +- plugins/commands/src/AdminCommands.ts | 402 +++--- plugins/commands/src/ChatCommands.ts | 717 +++++------ plugins/commands/src/GameCommands.ts | 549 +++++---- plugins/commands/src/JukeboxCommands.ts | 106 +- plugins/commands/src/KarmaCommands.ts | 18 +- plugins/commands/src/MapCommands.ts | 335 ++--- plugins/commands/src/PrivilegesCommands.ts | 75 +- plugins/commands/src/RaceCommands.ts | 188 +-- plugins/commands/src/RecordsCommands.ts | 20 +- plugins/commands/src/ServerCommands.ts | 816 +++++++------ plugins/dedimania/Config.js | 6 +- plugins/dedimania/Dedimania.ts | 307 ++--- plugins/dedimania/DedimaniaClient.ts | 16 +- plugins/dedimania/DedimaniaRequest.ts | 36 +- plugins/dedimania/DedimaniaResponse.ts | 5 +- plugins/dedimania/ui/DediCps.component.ts | 73 +- plugins/dedimania/ui/DediCps.config.js | 34 +- plugins/dedimania/ui/DediSectors.component.ts | 77 +- plugins/dedimania/ui/DediSectors.config.js | 34 +- plugins/donations/Config.js | 5 +- plugins/donations/Donations.ts | 46 +- plugins/freezone/Config.js | 11 +- plugins/freezone/Freezone.ts | 14 +- plugins/info_messages/Config.js | 32 +- plugins/info_messages/InfoMessages.ts | 5 +- plugins/live_checkpoints/Config.js | 4 +- .../ui/LiveCpsRanking.component.ts | 51 +- .../ui/LiveCpsRanking.config.js | 4 +- plugins/maniakarma/Countries.js | 655 +++++----- plugins/maniakarma/Maniakarma.ts | 126 +- plugins/maplist/Autojuke.ts | 29 +- plugins/maplist/Config.js | 4 +- plugins/maplist/Maplist.ts | 44 +- plugins/maplist/ui/Maplist.component.ts | 288 +++-- plugins/maplist/ui/Maplist.config.js | 23 +- plugins/messages/Config.js | 12 +- plugins/messages/Messages.ts | 298 ++--- plugins/music/Config.js | 20 +- plugins/music/Music.ts | 91 +- plugins/music/SongList.js | 12 +- plugins/music/Types.ts | 10 +- plugins/music/ui/MusicWidget.component.ts | 16 +- plugins/music/ui/MusicWidget.config.js | 6 +- plugins/music/ui/SongList.component.ts | 113 +- plugins/music/ui/SongList.config.js | 23 +- plugins/sector_records/Config.js | 13 +- plugins/sector_records/SectorDB.ts | 50 +- plugins/sector_records/SectorEvents.ts | 7 +- plugins/sector_records/SectorListeners.ts | 170 ++- plugins/sector_records/SectorRecords.ts | 15 +- plugins/sector_records/SectorTypes.ts | 33 +- .../ui/SectorRecords.component.ts | 37 +- .../sector_records/ui/SectorRecords.config.js | 61 +- plugins/server_links/Config.js | 23 +- plugins/server_links/ServerLinks.ts | 11 +- .../server_links/ui/ServerLinks.component.ts | 59 +- plugins/server_links/ui/ServerLinks.config.js | 8 +- plugins/skip_endscreen/Config.js | 5 +- plugins/skip_endscreen/SkipEndscreen.ts | 34 +- plugins/stats/Config.js | 5 +- plugins/stats/TopAverages.ts | 64 +- plugins/stats/TopDonations.ts | 55 +- plugins/stats/TopPlaytimes.ts | 56 +- plugins/stats/TopRecords.ts | 116 +- plugins/stats/TopSums.ts | 69 +- plugins/stats/TopVisits.ts | 55 +- plugins/stats/TopVotes.ts | 58 +- plugins/stats/TopWins.ts | 49 +- plugins/stats/ui/StatsUi.ts | 2 +- plugins/stats/ui/TopDonations.component.ts | 24 +- plugins/stats/ui/TopDonations.config.js | 41 +- plugins/stats/ui/TopPlaytimes.component.ts | 23 +- plugins/stats/ui/TopPlaytimes.config.js | 41 +- plugins/stats/ui/TopRanks.component.ts | 23 +- plugins/stats/ui/TopRanks.config.js | 41 +- plugins/stats/ui/TopRecords.component.ts | 23 +- plugins/stats/ui/TopRecords.config.js | 42 +- plugins/stats/ui/TopSums.component.ts | 35 +- plugins/stats/ui/TopSums.config.js | 45 +- plugins/stats/ui/TopVisits.component.ts | 23 +- plugins/stats/ui/TopVisits.config.js | 41 +- plugins/stats/ui/TopVotes.component.ts | 23 +- plugins/stats/ui/TopVotes.config.js | 41 +- plugins/stats/ui/TopWins.component.ts | 22 +- plugins/stats/ui/TopWins.config.js | 41 +- plugins/tmx/Tmx.ts | 10 +- plugins/tmx/ui/TMXWindow.component.ts | 175 +-- plugins/tmx/ui/TMXWindow.config.js | 19 +- plugins/ui/Buttons.ts | 2 +- plugins/ui/CustomUi.ts | 13 +- plugins/ui/Imports.ts | 17 +- plugins/ui/UI.ts | 157 +-- plugins/ui/config/Buttons.js | 97 +- plugins/ui/config/ComponentIds.js | 3 +- plugins/ui/config/FlagIcons.js | 184 +-- plugins/ui/config/Icons.js | 357 +++--- plugins/ui/config/Mod.js | 69 +- plugins/ui/config/PopupWindow.js | 6 +- plugins/ui/config/RaceUi.js | 128 +- plugins/ui/config/ResultUi.js | 30 +- .../dynamic_components/Changelog.component.ts | 33 +- .../ui/dynamic_components/Changelog.config.js | 13 +- .../dynamic_components/Chatlog.component.ts | 22 +- .../ui/dynamic_components/Chatlog.config.js | 17 +- .../Commandlist.component.ts | 128 +- .../dynamic_components/Commandlist.config.js | 72 +- .../PlayerStats.component.ts | 42 +- .../dynamic_components/PlayerStats.config.js | 36 +- .../ServerInfo.component.ts | 37 +- .../dynamic_components/ServerInfo.config.js | 54 +- .../TMXDetailsWindow.component.ts | 53 +- .../TMXDetailsWindow.config.js | 38 +- .../TMXSearchWindow.component.ts | 97 +- .../TMXSearchWindow.config.js | 18 +- .../WarnWindow.component.ts | 42 +- .../dynamic_components/WarnWindow.config.js | 6 +- .../admin_lists/Adminlist.component.ts | 49 +- .../admin_lists/Adminlist.config.js | 33 +- .../admin_lists/Masteradminlist.component.ts | 49 +- .../admin_lists/Masteradminlist.config.js | 33 +- .../admin_lists/Operatorlist.component.ts | 49 +- .../admin_lists/Operatorlist.config.js | 33 +- .../admin_windows/Banlist.component.ts | 27 +- .../admin_windows/Banlist.config.js | 41 +- .../admin_windows/Blacklist.component.ts | 27 +- .../admin_windows/Blacklist.config.js | 41 +- .../admin_windows/Guestlist.component.ts | 26 +- .../admin_windows/Guestlist.config.js | 41 +- .../admin_windows/Mutelist.component.ts | 30 +- .../admin_windows/Mutelist.config.js | 42 +- .../admin_windows/Playerlist.component.ts | 65 +- .../admin_windows/Playerlist.config.js | 43 +- .../checkpoint_windows/LiveCps.component.ts | 65 +- .../checkpoint_windows/LiveCps.config.js | 50 +- .../LiveSectors.component.ts | 73 +- .../checkpoint_windows/LiveSectors.config.js | 50 +- .../checkpoint_windows/LocalCps.component.ts | 67 +- .../checkpoint_windows/LocalCps.config.js | 50 +- .../LocalSectors.component.ts | 64 +- .../checkpoint_windows/LocalSectors.config.js | 50 +- .../race/AdminPanel.component.ts | 47 +- .../race/AdminPanel.config.js | 5 +- .../race/BestCps.component.ts | 65 +- .../static_components/race/BestCps.config.js | 10 +- .../race/BestFinishes.component.ts | 40 +- .../race/BestFinishes.config.js | 10 +- .../race/CpCounter.component.ts | 111 +- .../race/CpCounter.config.js | 13 +- .../race/DediRanking.component.ts | 30 +- .../race/DediRanking.config.js | 2 +- .../race/DonationPanel.component.ts | 12 +- .../race/DonationPanel.config.js | 13 +- .../race/KarmaWidget.component.ts | 36 +- .../race/KarmaWidget.config.js | 11 +- .../race/LapRanking.component.ts | 20 +- .../race/LapRanking.config.js | 4 +- .../race/LiveRanking.component.ts | 36 +- .../race/LiveRanking.config.js | 6 +- .../race/LocalRanking.component.ts | 41 +- .../race/LocalRanking.config.js | 4 +- .../race/MapWidget.component.ts | 45 +- .../race/MapWidget.config.js | 43 +- .../race/PreviousAndBest.component.ts | 5 +- .../race/PreviousAndBest.config.js | 10 +- .../race/RankWidget.component.ts | 5 +- .../race/RankWidget.config.js | 8 +- .../race/RoundScore.component.ts | 30 +- .../race/RoundScore.config.js | 4 +- .../race/RoundsPointsRanking.component.ts | 33 +- .../race/RoundsPointsRanking.config.js | 4 +- .../race/TMXRanking.component.ts | 18 +- .../race/TMXRanking.config.js | 11 +- .../race/TeamScore.component.ts | 7 +- .../race/TeamScore.config.js | 18 +- .../race/TimerWidget.component.ts | 32 +- .../race/TimerWidget.config.js | 9 +- .../race/UltiRanking.component.ts | 33 +- .../race/UltiRanking.config.js | 2 +- .../race/buttons/ButtonData.ts | 2 +- .../race/buttons/ButtonsWidget.component.ts | 114 +- .../race/buttons/ButtonsWidget.config.js | 216 ++-- .../race/buttons/MedalButton.ts | 43 +- .../race/buttons/Messages.config.js | 11 +- .../static_components/race/buttons/PaySkip.ts | 9 +- .../race/buttons/UiButton.ts | 2 +- .../race/buttons/VoteReplay.ts | 5 +- .../race/buttons/VoteSkip.ts | 9 +- .../result/AdminPanelResult.component.ts | 67 +- .../result/AdminPanelResult.config.js | 5 +- .../result/AveragesRanking.component.ts | 18 +- .../result/AveragesRanking.config.js | 10 +- .../result/BannerWidget.component.ts | 15 +- .../result/DediRankingResult.component.ts | 16 +- .../result/DediRankingResult.config.js | 2 +- .../result/DonatorsRanking.component.ts | 15 +- .../result/DonatorsRanking.config.js | 10 +- .../result/KarmaRanking.component.ts | 18 +- .../result/KarmaRanking.config.js | 8 +- .../result/KarmaWidgetResult.component.ts | 36 +- .../result/KarmaWidgetResult.config.js | 11 +- .../result/LocalRankingResult.component.ts | 17 +- .../result/LocalRankingResult.config.js | 4 +- .../result/MapWidgetResult.component.ts | 73 +- .../result/MapWidgetResult.config.js | 47 +- .../result/MostRecordsRanking.component.ts | 15 +- .../result/MostRecordsRanking.config.js | 8 +- .../result/NextMapRecords.component.ts | 18 +- .../result/NextMapRecords.config.js | 8 +- .../result/PlaytimeRanking.component.ts | 12 +- .../result/PlaytimeRanking.config.js | 10 +- .../result/RankWidgetResult.component.ts | 5 +- .../result/RoundAveragesRanking.component.ts | 29 +- .../result/RoundAveragesRanking.config.js | 8 +- .../result/TimerWidgetResult.component.ts | 9 +- .../result/VisitorsRanking.component.ts | 15 +- .../result/VisitorsRanking.config.js | 8 +- .../result/VotersRanking.component.ts | 15 +- .../result/VotersRanking.config.js | 8 +- .../result/WinnersRanking.component.ts | 15 +- .../result/WinnersRanking.config.js | 8 +- plugins/ui/test_widgets/TestWindow.ts | 61 +- plugins/ui/utils/CloseButton.config.js | 14 +- plugins/ui/utils/CloseButton.ts | 4 +- plugins/ui/utils/DynamicComponent.ts | 2 +- plugins/ui/utils/FullScreenListener.ts | 2 +- plugins/ui/utils/GetCpTypes.ts | 3 +- plugins/ui/utils/Grid.ts | 12 +- plugins/ui/utils/KeyListener.ts | 17 +- plugins/ui/utils/List.ts | 21 +- plugins/ui/utils/ManialinkListener.ts | 20 +- plugins/ui/utils/Navbar.ts | 20 +- plugins/ui/utils/Paginator.config.js | 24 +- plugins/ui/utils/Paginator.ts | 53 +- plugins/ui/utils/PopupWindow.ts | 90 +- plugins/ui/utils/RecordList.ts | 178 ++- plugins/ui/utils/RecordListRace.config.js | 14 +- plugins/ui/utils/RecordListResult.config.js | 14 +- plugins/ui/utils/StaticButton.config.js | 6 +- plugins/ui/utils/StaticButton.ts | 22 +- plugins/ui/utils/StaticComponent.ts | 173 ++- plugins/ui/utils/StaticHeader.ts | 23 +- plugins/ui/utils/StaticHeaderRace.config.js | 8 +- plugins/ui/utils/StaticHeaderResult.config.js | 34 +- plugins/ui/utils/TextUtils.config.js | 6 +- plugins/ui/utils/TextUtils.ts | 23 +- plugins/ui/utils/VoteWindow.config.js | 5 +- plugins/ui/utils/VoteWindow.ts | 58 +- plugins/ultimania/Config.js | 4 +- plugins/ultimania/Ultimania.ts | 69 +- plugins/ultimania/UltimaniaClient.ts | 6 +- plugins/ultimania/ui/UltiRecords.component.ts | 20 +- plugins/ultimania/ui/UltiRecords.config.js | 30 +- plugins/vote/Vote.ts | 175 ++- plugins/webservices/Webservices.ts | 11 +- src/Events.ts | 39 +- src/FixRankCoherence.ts | 25 +- src/Listeners.ts | 688 +++++------ src/Logger.ts | 74 +- src/Main.ts | 23 +- src/Namespace.ts | 78 +- src/ServerConfig.ts | 36 +- src/TMXFetcher.ts | 141 ++- src/Trakman.ts | 66 +- src/Utils.ts | 106 +- src/client/Client.ts | 56 +- src/client/ClientRequest.ts | 12 +- src/client/ClientResponse.ts | 31 +- src/client/ClientSocket.ts | 16 +- src/data/Colours.js | 1088 ++++++++--------- src/data/Countries.js | 647 +++++----- src/data/SpecialCharmap.js | 756 +----------- src/database/BanlistRepository.ts | 6 +- src/database/BlacklistRepository.ts | 4 +- src/database/ChatRepository.ts | 10 +- src/database/CreateQueries.ts | 6 +- src/database/DB.ts | 8 +- src/database/MapIdsRepository.ts | 22 +- src/database/MapRepository.ts | 103 +- src/database/PlayerRepository.ts | 47 +- src/database/PrivilegeRepository.ts | 16 +- src/database/RecordRepository.ts | 16 +- src/database/Repository.ts | 2 +- src/database/VoteRepository.ts | 25 +- src/services/AdministrationService.ts | 162 ++- src/services/ChatService.ts | 79 +- src/services/GameService.ts | 92 +- src/services/ManualMapLoading.ts | 24 +- src/services/MapService.ts | 219 +++- src/services/PlayerService.ts | 63 +- src/services/RecordService.ts | 211 +++- src/services/RoundsService.ts | 80 +- src/services/VoteService.ts | 94 +- 319 files changed, 11084 insertions(+), 8764 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..11937d16 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,372 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[{*.ats,*.cts,*.mts,*.ts}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_visual_guides = 120 +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = false +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = normal +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 0 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = normal +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_class_decorator_wrap = split_into_lines +ij_typescript_class_field_decorator_wrap = normal +ij_typescript_class_method_decorator_wrap = normal +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = always +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = remove +ij_typescript_enum_constants_wrap = normal +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = always +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_function_parameter_decorator_wrap = normal +ij_typescript_if_brace_force = always +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = off +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = false +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 1 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = false +ij_typescript_keep_simple_blocks_in_one_line = true +ij_typescript_keep_simple_methods_in_one_line = true +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = normal +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = normal +ij_typescript_object_literal_wrap = split_into_lines +ij_typescript_object_types_wrap = split_into_lines +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = true +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_property_prefix = +ij_typescript_reformat_c_style_comments = true +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = false +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = false +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = false +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = true +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = true +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = normal +ij_typescript_union_types_wrap = normal +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = false +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_import_type = always +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = false +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = always +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = false + +[{*.cjs,*.es6,*.js,*.mjs}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = false +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = normal +ij_javascript_assignment_wrap = normal +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 0 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = normal +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_class_decorator_wrap = split_into_lines +ij_javascript_class_field_decorator_wrap = off +ij_javascript_class_method_decorator_wrap = off +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = always +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = remove +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = always +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = true +ij_javascript_force_semicolon_style = true +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_function_parameter_decorator_wrap = off +ij_javascript_if_brace_force = always +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = off +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = false +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 1 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = false +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = true +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = normal +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = normal +ij_javascript_object_literal_wrap = split_into_lines +ij_javascript_object_types_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_property_prefix = +ij_javascript_reformat_c_style_comments = true +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = false +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = false +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = false +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = true +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = true +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = false +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_import_type = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = false +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = always +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false diff --git a/Update.js b/Update.js index 4c1d876d..7ed417d8 100644 --- a/Update.js +++ b/Update.js @@ -57,9 +57,18 @@ async function hashFile(path) { async function generateHashes() { console.log('Generating hashes for current files...') - const entries = (await fs.readdir('./src', { recursive: true, withFileTypes: true })) - .concat(await fs.readdir('./plugins', { recursive: true, withFileTypes: true })) - .concat(await fs.readdir('./config', { recursive: true, withFileTypes: true })) + const entries = (await fs.readdir('./src', { + recursive: true, + withFileTypes: true + })) + .concat(await fs.readdir('./plugins', { + recursive: true, + withFileTypes: true + })) + .concat(await fs.readdir('./config', { + recursive: true, + withFileTypes: true + })) const res = new Map( await Promise.all(entries.filter(a => a.isFile() && (a.name.endsWith('.js') || a.name.endsWith('.ts'))) @@ -132,7 +141,8 @@ async function doUpdate(fromPath, newHashes, oldHashes = null) { process.exit(1) } - let log = `Update log to version ${JSON.parse(await fs.readFile(path.join(fromPath, 'package.json'), { encoding: 'utf-8' }))['version']}\n\n` + let log = `Update log to version ${JSON.parse( + await fs.readFile(path.join(fromPath, 'package.json'), { encoding: 'utf-8' }))['version']}\n\n` if (conflicts.length > 0) { console.log('!!! Update conflicts, make sure to fix them by comparing your files to files ending in .new !!!') console.log('If you do not fix these conflicts, the controller might fail to start or crash.') @@ -154,7 +164,9 @@ async function doUpdate(fromPath, newHashes, oldHashes = null) { console.log(e) } - if (conflicts.length > 0) { process.exit(2) } + if (conflicts.length > 0) { + process.exit(2) + } if (updatePerformed) { console.log('Update successful!') } else { diff --git a/config/Config.js b/config/Config.js index a516b420..db4850c7 100644 --- a/config/Config.js +++ b/config/Config.js @@ -35,7 +35,7 @@ export default { kick: 1, forceSpectator: 1, addMap: 1, - removeMap: 1, + removeMap: 1 }, /** * Enable this for Trakman to take over handling maps. The controller will read maps from a folder @@ -51,16 +51,16 @@ export default { /** Path to the `GameData/Tracks/` directory of the server. MUST end with a slash '/'! * Default value: "../GameData/Tracks/" assuming the Trakman directory is on the same level as the server. */ - mapsDirectoryPrefix: "../GameData/Tracks/", + mapsDirectoryPrefix: '../GameData/Tracks/', /** Relative path to all the maps. Read recursively from `GameData/Tracks/`. * MUST end with a slash '/' when a directory is specified (i.e. if not left empty)! * Default value: "" */ - mapsDirectory: "", + mapsDirectory: '', /** Ignore non-stadium maps */ stadiumOnly: undefined, /** Amount of maps to load into the server. Default value: 5 */ - preloadMaps: 5, + preloadMaps: 5 }, /** * When adding a large amount of maps, it is better to add them in smaller chunks. This is the size @@ -80,11 +80,11 @@ export default { /** CallVotes are skip/restart votes that can be done through the ingame UI (ESC>Advanced Options), setting it to -1 disables CallVotes entirely */ callVoteRatio: -1, /** Relative path (/GameData/Config/) to the blacklist file */ - blacklistFile: "blacklist.txt", + blacklistFile: 'blacklist.txt', /** Relative path (/GameData/Config/) to the guestlist file */ - guestlistFile: "guestlist.txt", + guestlistFile: 'guestlist.txt', /** Relative path (/GameData/Tracks/) to the match settings file, ignored if manualMapLoading is enabled */ - matchSettingsFile: "MatchSettings/MatchSettings.txt", + matchSettingsFile: 'MatchSettings/MatchSettings.txt', /** Default message sent as the reason for administrative actions if nothing was specified by the admin */ defaultReasonMessage: 'No reason specified', /** Things that will be interpreted as true for the boolean command parameter */ @@ -94,6 +94,6 @@ export default { /** Object with options for ufuzzy, see https://github.com/leeoniya/uFuzzy#options */ searchOptions: { intraMode: 1, - alpha: "a-zа-яё" + alpha: 'a-zа-яё' } } diff --git a/plugins/actions/Actions.ts b/plugins/actions/Actions.ts index 29bd9e77..0c34dc7f 100644 --- a/plugins/actions/Actions.ts +++ b/plugins/actions/Actions.ts @@ -11,7 +11,13 @@ interface CallerInfo { const sendNoPrivilegeMessage = (info: CallerInfo): void => tm.sendMessage(config.noPermission, info.login) -let eraseObject: { id: string, admin: { login: string, nickname: string } } | undefined +let eraseObject: { + id: string, + admin: { + login: string, + nickname: string + } +} | undefined tm.addListener('BeginMap', (info): void => { if (info.isRestart) { return @@ -26,7 +32,7 @@ let toSkip = false tm.addListener('ServerStateChanged', (state): void => { if (toSkip) { - tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{boolean: true }] : undefined) + tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) toSkip = false } }) @@ -42,7 +48,10 @@ export const actions = { * @param info Player information * @param voteValue Vote value */ - addVote: (info: { login: string, nickname: string }, voteValue: -3 | -2 | -1 | 1 | 2 | 3) => { + addVote: (info: { + login: string, + nickname: string + }, voteValue: -3 | -2 | -1 | 1 | 2 | 3) => { if (voteValue === undefined || voteValue === tm.karma.current.find(a => a.login === info.login)?.vote) { return } @@ -63,13 +72,15 @@ export const actions = { sendNoPrivilegeMessage(info) return } - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.kick.reason, { reason: reason })}.` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.kick.reason, { reason: reason })}.` tm.sendMessage(tm.utils.strVar(config.kick.text, { title: info.title, adminName: tm.utils.strip(info.nickname), name: tm.utils.strip(player.nickname) }) + `${reasonString}`, config.kick.public ? undefined : info.login) - tm.client.callNoRes(`Kick`, [{ string: player.login }, { string: reason === undefined ? 'No reason specified' : reason }]) + tm.client.callNoRes(`Kick`, + [{ string: player.login }, { string: reason === undefined ? 'No reason specified' : reason }]) }, /** * Mutes a player and sends a chat message @@ -85,8 +96,10 @@ export const actions = { } const expireDate: Date | undefined = duration === undefined ? undefined : new Date(Date.now() + duration) await tm.admin.mute(player.login, info, player.nickname, reason, expireDate) - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.mute.reason, { reason: reason })}` - const durationString: string = duration === undefined ? '' : ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.mute.reason, { reason: reason })}` + const durationString: string = duration === undefined ? '' : + ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` tm.sendMessage(tm.utils.strVar(config.mute.text, { title: info.title, adminName: tm.utils.strip(info.nickname), @@ -131,16 +144,13 @@ export const actions = { sendNoPrivilegeMessage(info) return } - const res = await tm.client.call('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 1 }] - }, - { - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 0 }] - }] - ) + const res = await tm.client.call('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 1 }] + }, { + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 0 }] + }]) const name = tm.utils.strip(player.nickname) if (res instanceof Error || res[0] instanceof Error) { tm.sendMessage(tm.utils.strVar(config.forcespec.tooManySpecs, { name }), info.login) @@ -162,16 +172,13 @@ export const actions = { sendNoPrivilegeMessage(info) return } - const res = await tm.client.call('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 2 }] - }, - { - method: 'ForceSpectator', - params: [{ string: player.login }, { int: 0 }] - }] - ) + const res = await tm.client.call('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 2 }] + }, { + method: 'ForceSpectator', + params: [{ string: player.login }, { int: 0 }] + }]) const name = tm.utils.strip(player.nickname) if (res instanceof Error || res[0] instanceof Error) { tm.sendMessage(tm.utils.strVar(config.forceplay.tooManyPlayers, { name }), info.login) @@ -197,8 +204,10 @@ export const actions = { } const expireDate: Date | undefined = duration === undefined ? undefined : new Date(Date.now() + duration) await tm.admin.ban(player.ip, player.login, info, player.nickname, reason, expireDate) - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.ban.reason, { reason: reason })}` - const durationString: string = duration === undefined ? '' : ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.ban.reason, { reason: reason })}` + const durationString: string = duration === undefined ? '' : + ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` tm.sendMessage(tm.utils.strVar(config.ban.text, { title: info.title, adminName: tm.utils.strip(info.nickname), @@ -253,8 +262,10 @@ export const actions = { tm.sendMessage(tm.utils.strVar(config.blacklist.error, { login: player.login }), info.login) return } - const reasonString: string = reason === undefined ? '' : ` ${tm.utils.strVar(config.blacklist.reason, { reason: reason })}` - const durationString: string = duration === undefined ? '' : ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` + const reasonString: string = reason === undefined ? '' : + ` ${tm.utils.strVar(config.blacklist.reason, { reason: reason })}` + const durationString: string = duration === undefined ? '' : + ` for ${tm.utils.palette.highlight}${tm.utils.getVerboseTime(duration)}` tm.sendMessage(tm.utils.strVar(config.blacklist.text, { title: info.title, adminName: tm.utils.strip(info.nickname), @@ -280,7 +291,8 @@ export const actions = { return } if (result === 'Player not blacklisted') { - tm.sendMessage(tm.utils.strVar(config.unblacklist.notBlacklisted, { login: tm.utils.strip(player.nickname) }), info.login) + tm.sendMessage(tm.utils.strVar(config.unblacklist.notBlacklisted, { login: tm.utils.strip(player.nickname) }), + info.login) return } tm.sendMessage(tm.utils.strVar(config.unblacklist.text, { @@ -307,7 +319,8 @@ export const actions = { return } if (result === 'Already guest') { - tm.sendMessage(tm.utils.strVar(config.addguest.alreadyGuest, { login: tm.utils.strip(player.nickname) }), info.login) + tm.sendMessage(tm.utils.strVar(config.addguest.alreadyGuest, { login: tm.utils.strip(player.nickname) }), + info.login) return } tm.sendMessage(tm.utils.strVar(config.addguest.text, { @@ -344,14 +357,11 @@ export const actions = { }), config.rmguest.public ? undefined : info.login) }, publicAdd: async (login: string, nickname: string, title: string, mapName: string): Promise => { - const voteWindow: VoteWindow = new VoteWindow( - login, - config.publicAdd.voteGoal, - tm.utils.strVar(config.publicAdd.voteText, { mapName }), - tm.utils.strVar(config.publicAdd.voteStart, { nickname: tm.utils.strip(nickname, true), mapName }), - config.publicAdd.voteTime, - config.publicAdd.voteIcon - ) + const voteWindow: VoteWindow = new VoteWindow(login, config.publicAdd.voteGoal, + tm.utils.strVar(config.publicAdd.voteText, { mapName }), tm.utils.strVar(config.publicAdd.voteStart, { + nickname: tm.utils.strip(nickname, true), + mapName + }), config.publicAdd.voteTime, config.publicAdd.voteIcon) const result = await voteWindow.startAndGetResult(tm.players.list) if (result === undefined) { tm.sendMessage(config.publicAdd.alreadyRunning) @@ -429,7 +439,8 @@ export const actions = { await tm.admin.setPrivilege(target.login, privilege, caller) } }, - addMap: async (login: string, nickname: string, title: string, id: number | string, tmxSite?: string, fromUrl = false) => { + addMap: async (login: string, nickname: string, title: string, id: number | string, tmxSite?: string, + fromUrl = false) => { const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) let file: { @@ -449,7 +460,10 @@ export const actions = { tm.sendMessage(config.addMap.fetchError, login) return } - const obj = await tm.maps.writeFileAndAdd(file.name, file.content, { login, nickname }) + const obj = await tm.maps.writeFileAndAdd(file.name, file.content, { + login, + nickname + }) if (obj instanceof Error) { tm.log.warn(obj.message) tm.sendMessage(config.addMap.addError, login) @@ -478,7 +492,13 @@ export const actions = { return } id = tm.maps.current.id - eraseObject = { id: id, admin: { login, nickname } } + eraseObject = { + id: id, + admin: { + login, + nickname + } + } tm.sendMessage(tm.utils.strVar(config.removeMap.removeThis, { title: title, nickname: tm.utils.strip(nickname, true), @@ -496,17 +516,23 @@ export const actions = { nickname: tm.utils.strip(nickname, true), map: tm.utils.strip(map.name, true) }), config.removeMap.public ? undefined : login) - void tm.maps.remove(map.id, { login, nickname }) + void tm.maps.remove(map.id, { + login, + nickname + }) }, requeueMap: async (login: string, nickname: string, title: string): Promise => { - const status = await tm.jukebox.add(tm.maps.current.id, { login, nickname }, true) + const status = await tm.jukebox.add(tm.maps.current.id, { + login, + nickname + }, true) if (!status || status instanceof Error) { tm.sendMessage(config.requeueMap.error, login) return } tm.sendMessage(tm.utils.strVar(config.requeueMap.text, { title: title, - adminName: tm.utils.strip(nickname, true), + adminName: tm.utils.strip(nickname, true) })) }, skipMap: (): void => { @@ -517,7 +543,7 @@ export const actions = { toSkip = true break case 'race': - tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{boolean: true }] : undefined) + tm.client.callNoRes(`NextChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) break } } diff --git a/plugins/actions/Config.js b/plugins/actions/Config.js index e7c9aed3..90483671 100644 --- a/plugins/actions/Config.js +++ b/plugins/actions/Config.js @@ -27,75 +27,75 @@ export default { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has muted ${p.highlight}#{name}${p.admin}#{duration}.`, reason: `${p.admin}Reason${p.highlight}: #{reason}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.mute, + privilege: tm.config.controller.privileges.mute }, unmute: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has unmuted ${p.highlight}#{name}${p.admin}.`, error: `${p.error}Could not unmute ${p.highlight}#{login}.`, notMuted: `${p.error}#{login} is not muted.`, public: true, - privilege: tm.config.controller.privileges.mute, + privilege: tm.config.controller.privileges.mute }, forcespec: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has forced ${p.highlight}#{name} ${p.admin}into spectator mode.`, error: `${p.error}Player is not on the server.`, tooManySpecs: `${p.error}Could not forcespec ${p.highlight}#{name}${p.error} because there are too many spectators.`, public: true, - privilege: tm.config.controller.privileges.forceSpectator, + privilege: tm.config.controller.privileges.forceSpectator }, forceplay: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has forced ${p.highlight}#{name} ${p.admin}into player mode.`, error: `${p.error}Player is not on the server.`, tooManyPlayers: `${p.error}Could not forceplay ${p.highlight}#{name}${p.error} because there are too many players.`, public: true, - privilege: tm.config.controller.privileges.forceSpectator, + privilege: tm.config.controller.privileges.forceSpectator }, kickghost: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has kicked ${p.highlight}#{name}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.kick, + privilege: tm.config.controller.privileges.kick }, ban: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has banned ${p.highlight}#{name}${p.admin}#{duration}.`, error: `${p.error}Could not ban ${p.highlight}#{login}${p.error}.`, reason: `${p.admin}Reason${p.highlight}: #{reason}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.ban, + privilege: tm.config.controller.privileges.ban }, unban: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has unbanned ${p.highlight}#{name}${p.admin}.`, error: `${p.error}Could not unban ${p.highlight}#{login}${p.error}.`, notBanned: `${p.error}#{login} is not banned.`, public: true, - privilege: tm.config.controller.privileges.ban, + privilege: tm.config.controller.privileges.ban }, blacklist: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has blacklisted ${p.highlight}#{name}${p.admin}#{duration}.`, error: `${p.error}Could not blacklist ${p.highlight}#{login}${p.error}.`, reason: `${p.admin}Reason${p.highlight}: #{reason}${p.admin}.`, public: true, - privilege: tm.config.controller.privileges.blacklist, + privilege: tm.config.controller.privileges.blacklist }, unblacklist: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has removed ${p.highlight}#{name} ${p.admin}from the blacklist.`, error: `${p.error}Could not remove ${p.highlight}#{login} ${p.error}from the blacklist.`, notBlacklisted: `${p.highlight}#{login} ${p.error}is not blacklisted.`, public: true, - privilege: tm.config.controller.privileges.blacklist, + privilege: tm.config.controller.privileges.blacklist }, addguest: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has added ${p.highlight}#{name} ${p.admin}to the guestlist.`, error: `${p.error}Could not add ${p.highlight}#{login} ${p.error}to the guestlist.`, alreadyGuest: `${p.highlight}#{login} ${p.error}is already in the guestlist.`, public: true, - privilege: tm.config.controller.privileges.addGuest, + privilege: tm.config.controller.privileges.addGuest }, rmguest: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has removed ${p.highlight}#{name} ${p.admin}from the guestlist.`, error: `${p.error}Could not remove ${p.highlight}#{login} ${p.error}from the guestlist.`, notGuest: `${p.highlight}#{login} ${p.error}is not in the guestlist.`, public: true, - privilege: tm.config.controller.privileges.addGuest, + privilege: tm.config.controller.privileges.addGuest }, publicAdd: { voteGoal: 0.51, @@ -113,20 +113,17 @@ export default { setPlayerPrivilege: { unknownPlayer: `${p.error}Unknown player.`, noPrivilege: `${p.error}You cannot control privileges of a person who has equal or higher privilege than you.`, - promote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has promoted ` + - `${p.highlight}#{nickname}${p.admin} to #{rank}.`, - demote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has demoted ` + - `${p.highlight}#{nickname}${p.admin} to #{rank}.`, + promote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has promoted ` + `${p.highlight}#{nickname}${p.admin} to #{rank}.`, + demote: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has demoted ` + `${p.highlight}#{nickname}${p.admin} to #{rank}.`, rightsRemoved: `${p.admin}#{title} ${p.highlight}#{adminNickname}${p.admin} has removed privileges of ${p.highlight}#{nickname}${p.admin}.`, - alreadyIs: `${p.highlight}#{nickname}${p.error} is already #{rank}.`, + alreadyIs: `${p.highlight}#{nickname}${p.error} is already #{rank}.` }, addMap: { fetchError: `${p.error}Failed to fetch the map file from TMX.`, addError: `${p.error}Failed to add the map.`, - alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + - `it will be ${p.highlight}queued ${p.admin}instead.`, + alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + `it will be ${p.highlight}queued ${p.admin}instead.`, added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued ${p.highlight}#{map}${p.admin} from TMX.`, - public: true, + public: true }, removeMap: { text: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed map ${p.highlight}#{map} ${p.admin}from the server.`, @@ -134,11 +131,11 @@ export default { alreadyRemoved: `${p.error}This map is already getting removed.`, error: `${p.error}Error while removing the map.`, onlyMap: `${p.error}There are no more maps in the server playlist.`, - public: true, + public: true }, requeueMap: { text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has requeued the ongoing map.`, error: `${p.error}This map has already been requeued.`, - public: true, + public: true } } diff --git a/plugins/betting/Betting.ts b/plugins/betting/Betting.ts index b6367f79..161ddd86 100644 --- a/plugins/betting/Betting.ts +++ b/plugins/betting/Betting.ts @@ -15,11 +15,10 @@ const returnCoppers = (login: string) => { if (prize === undefined) { throw new Error(`Prize undefined while returning coppers in betting plugin`) } - void tm.utils.payCoppers(login, tm.utils.getCoppersAfterTax(prize), tm.utils.strVar(config.copperReturnMessage, - { - amount: prize, - serverName: tm.utils.strip(tm.config.server.name, false) - })) + void tm.utils.payCoppers(login, tm.utils.getCoppersAfterTax(prize), tm.utils.strVar(config.copperReturnMessage, { + amount: prize, + serverName: tm.utils.strip(tm.config.server.name, false) + })) } const onTimeRunOut = (wasInterrupted = false) => { @@ -65,7 +64,7 @@ const acceptBet = async (player: tm.Player) => { if (paymentStatus === true) { betLogins.push(player.login) tm.sendMessage(tm.utils.strVar(config.messages.accept, { - name: tm.utils.strip(player.nickname), + name: tm.utils.strip(player.nickname) })) betPlaceWindow.hideToPlayer(player.login) } @@ -78,8 +77,9 @@ betPlaceWindow.onBetAccept = acceptBet if (config.isEnabled) { tm.addListener('Startup', () => { const msg = isActive ? config.messages.startupEnabled : config.messages.startupDisabled - for (const e of tm.players.list.filter(a => a.privilege >= config.activatePrivilege)) + for (const e of tm.players.list.filter(a => a.privilege >= config.activatePrivilege)) { tm.sendMessage(msg, e.login) + } }) tm.addListener('ServerStateChanged', (state) => { @@ -130,11 +130,10 @@ if (config.isEnabled) { return } tm.utils.payCoppers(bestRecord.login, tm.utils.getCoppersAfterTax(prize * betLogins.length), - tm.utils.strVar(config.winMessage, - { - amount: prize * betLogins.length, - serverName: tm.utils.strip(tm.config.server.name, false) - })) + tm.utils.strVar(config.winMessage, { + amount: prize * betLogins.length, + serverName: tm.utils.strip(tm.config.server.name, false) + })) tm.sendMessage(tm.utils.strVar(config.messages.win, { name: tm.utils.strip(bestRecord.nickname), prize: prize * betLogins.length @@ -178,7 +177,11 @@ if (config.isEnabled) { tm.commands.add({ aliases: config.bet.aliases, help: config.bet.help, - params: [{ name: 'prize', type: 'int', optional: true }], + params: [{ + name: 'prize', + type: 'int', + optional: true + }], callback(info, newPrize?: number) { if (!isOpen) { tm.sendMessage(config.messages.closed, info.login) @@ -189,8 +192,7 @@ if (config.isEnabled) { acceptBet(info) } else if (newPrize !== undefined) { if (newPrize < config.minimumAmount) { - tm.sendMessage(tm.utils.strVar(config.messages.amountTooLow, - { minimum: config.minimumAmount }), info.login) + tm.sendMessage(tm.utils.strVar(config.messages.amountTooLow, { minimum: config.minimumAmount }), info.login) return } startBet(info, newPrize) diff --git a/plugins/betting/Config.js b/plugins/betting/Config.js index 70f12c81..85abe775 100644 --- a/plugins/betting/Config.js +++ b/plugins/betting/Config.js @@ -2,8 +2,7 @@ const p = tm.utils.palette export default { // If false plugin cant be activated - isEnabled: false, - // This can be changed using an ingame command + isEnabled: false, // This can be changed using an ingame command isActive: false, betTimeSeconds: 30, minimumAmount: 50, @@ -14,10 +13,8 @@ export default { start: `${p.highlight}#{name} ${p.donation}has started a bet with ${p.highlight}#{prize} ${p.donation}coppers.`, begin: `${p.donation}Bets are open. Use ${p.highlight}/bet ${p.donation}to place a bet.`, accept: `${p.highlight}#{name} ${p.donation}has accepted the bet.`, - startupEnabled: `${p.donation}Betting plugin is ${p.highlight}enabled${p.donation}.` - + ` Use ${p.highlight}//dbet ${p.donation}if you want to disable it.`, - startupDisabled: `${p.donation}Betting plugin is ${p.highlight}disabled${p.donation}.` - + ` Use ${p.highlight}//ebet ${p.donation}if you want to enable it.`, + startupEnabled: `${p.donation}Betting plugin is ${p.highlight}enabled${p.donation}.` + ` Use ${p.highlight}//dbet ${p.donation}if you want to disable it.`, + startupDisabled: `${p.donation}Betting plugin is ${p.highlight}disabled${p.donation}.` + ` Use ${p.highlight}//ebet ${p.donation}if you want to enable it.`, amountTooLow: `${p.error}Amount of bet coppers too low. Minimum amount is ${p.highlight}#{minimum}${p.error}.`, closed: `${p.error}Bets are closed.` }, @@ -32,8 +29,7 @@ export default { public: true, privilege: 0, prizeNeeded: `${p.error}You need to specify the bet prize.`, - noPrizeNeeded: `${p.error}Specified bet prize was ignored because somebody ` + - `else has already placed a bet. Prize is ${p.highlight}#{prize}${p.error}.`, + noPrizeNeeded: `${p.error}Specified bet prize was ignored because somebody ` + `else has already placed a bet. Prize is ${p.highlight}#{prize}${p.error}.` }, activate: { aliases: ['ebet', 'enablebets'], @@ -49,4 +45,4 @@ export default { success: `${p.donation}#{title} ${p.highlight}#{name} ${p.donation}has ${p.highlight}disabled ${p.donation}the betting plugin.`, alreadyNotActive: `${p.error}Betting plugin is already disabled.` } -} \ No newline at end of file +} diff --git a/plugins/betting/ui/BetInfoWidget.component.ts b/plugins/betting/ui/BetInfoWidget.component.ts index ebd6eddb..66ec83e5 100644 --- a/plugins/betting/ui/BetInfoWidget.component.ts +++ b/plugins/betting/ui/BetInfoWidget.component.ts @@ -1,6 +1,4 @@ -import { - DynamicComponent, StaticHeader, componentIds, centeredText, components -} from '../../ui/UI.js' +import { centeredText, componentIds, components, DynamicComponent, StaticHeader } from '../../ui/UI.js' import config from './BetInfoWidget.config.js' export default class BetInfoWidget extends DynamicComponent { @@ -13,8 +11,7 @@ export default class BetInfoWidget extends DynamicComponent { super(componentIds.betInfoWidget) this.updatePosYAndHeight() this.header = new StaticHeader('race', { - rectangleWidth: StaticHeader.racePreset.rectangleWidth - - (config.prizeWidth + config.margin) + rectangleWidth: StaticHeader.racePreset.rectangleWidth - (config.prizeWidth + config.margin) }) tm.addListener('EndMap', () => this.hide()) } @@ -30,8 +27,7 @@ export default class BetInfoWidget extends DynamicComponent { } const side = config.side === true ? 'right' : 'left' const data = components.staticHeights[tm.getGameMode()] - this.posY = config.topBorder - data[side].reduce((acc, cur) => - acc += cur.getHeight() + config.marginBig, 0) + this.posY = config.topBorder - data[side].reduce((acc, cur) => acc += cur.getHeight() + config.marginBig, 0) } displayToPlayer(login: string): void { diff --git a/plugins/betting/ui/BetInfoWidget.config.js b/plugins/betting/ui/BetInfoWidget.config.js index 11edfebb..ac5c15df 100644 --- a/plugins/betting/ui/BetInfoWidget.config.js +++ b/plugins/betting/ui/BetInfoWidget.config.js @@ -1,9 +1,8 @@ -import { icons, raceConfig } from "../../ui/UI.js" +import { icons, raceConfig } from '../../ui/UI.js' export default { posX: raceConfig.rightPosition, - side: true, - // If true overrides posY prop and places the widget as last component + side: true, // If true overrides posY prop and places the widget as last component placeAsLastComponent: true, posY: 0, icon: icons.cash, @@ -14,4 +13,4 @@ export default { prizeColour: tm.utils.palette.green, topBorder: raceConfig.topBorder, // used for Y positioning marginBig: raceConfig.marginBig -} \ No newline at end of file +} diff --git a/plugins/betting/ui/BetPlaceWindow.component.ts b/plugins/betting/ui/BetPlaceWindow.component.ts index 3ca6a372..8811c292 100644 --- a/plugins/betting/ui/BetPlaceWindow.component.ts +++ b/plugins/betting/ui/BetPlaceWindow.component.ts @@ -1,7 +1,4 @@ -import { - DynamicComponent, StaticHeader, Grid, type GridCellFunction, - centeredText, icons, addManialinkListener, componentIds, components -} from '../../ui/UI.js' +import { addManialinkListener, centeredText, componentIds, components, DynamicComponent, Grid, type GridCellFunction, icons, StaticHeader } from '../../ui/UI.js' import config from './BetPlaceWindow.config.js' export default class BetPlaceWindow extends DynamicComponent { @@ -17,15 +14,13 @@ export default class BetPlaceWindow extends DynamicComponent { constructor() { super(componentIds.betPlaceWindow) - this.headerRectW = config.width - (StaticHeader.racePreset.squareWidth + - config.timerWidth + StaticHeader.racePreset.margin * 2) + this.headerRectW = config.width - (StaticHeader.racePreset.squareWidth + config.timerWidth + StaticHeader.racePreset.margin * 2) this.header = new StaticHeader('race', { rectangleWidth: this.headerRectW }) this.updatePosYAndHeight() - addManialinkListener(this.id + config.actionIdOffset, - config.options.length, (info, offset) => { - const amount = config.options[offset] - this.onBetStart(info, amount) - }) + addManialinkListener(this.id + config.actionIdOffset, config.options.length, (info, offset) => { + const amount = config.options[offset] + this.onBetStart(info, amount) + }) addManialinkListener(this.id + config.actionIdOffset + config.options.length, async (info) => this.onBetAccept(info)) } @@ -38,16 +33,19 @@ export default class BetPlaceWindow extends DynamicComponent { const side = config.relativePos.side === true ? 'right' : 'left' const data = components.staticHeights[tm.getGameMode()] this.height = config.staticPos.height ?? data[side][config.relativePos.widgetNumber]?.getHeight() ?? 0 - this.posY = config.staticPos.posY ?? - (config.topBorder - data[side].slice(0, config.relativePos.widgetNumber) - .reduce((acc, cur) => acc += cur.getHeight() + config.marginBig, 0)) + this.posY = config.staticPos.posY ?? (config.topBorder - data[side].slice(0, config.relativePos.widgetNumber) + .reduce((acc, cur) => acc += cur.getHeight() + config.marginBig, 0)) } - displayToPlayer(login: string, params: { seconds: number, placedBet: boolean }): void { - const grid = new Grid(config.width + config.margin * 2, - this.height + config.margin - StaticHeader.raceHeight, [1, 1, 1], [1, 1], { - background: config.background, margin: config.margin - }) + displayToPlayer(login: string, params: { + seconds: number, + placedBet: boolean + }): void { + const grid = new Grid(config.width + config.margin * 2, this.height + config.margin - StaticHeader.raceHeight, + [1, 1, 1], [1, 1], { + background: config.background, + margin: config.margin + }) const cell: GridCellFunction = (i, j, w, h) => { return ` ${centeredText(config.options[j + (i * 3)].toString(), w, h)}` @@ -59,7 +57,10 @@ export default class BetPlaceWindow extends DynamicComponent { bgcolor="${config.background}" action="${this.id + config.options.length + config.actionIdOffset}"/> ${centeredText(`Bet accepted`, config.width, h / 2, config.betAcceptedText)} ${centeredText(`Prize: $${tm.utils.palette.green}${(this.prize ?? 0) * this.betLogins.length}C`, config.width, - h / 2, { yOffset: h / 2 + config.prizeText.yOffset, textScale: config.prizeText.textScale })}` + h / 2, { + yOffset: h / 2 + config.prizeText.yOffset, + textScale: config.prizeText.textScale + })}` } else if (this.prize === undefined) { const cells: GridCellFunction[] = [] for (let i = 0; i < config.options.length; i++) { cells.push(cell) } @@ -81,8 +82,8 @@ export default class BetPlaceWindow extends DynamicComponent { - ${centeredText('$' + countdownColour + params.seconds.toString(), - topRightW, StaticHeader.raceHeight, config.countdownText)} + ${centeredText('$' + countdownColour + params.seconds.toString(), topRightW, StaticHeader.raceHeight, + config.countdownText)} ${content} diff --git a/plugins/betting/ui/BetPlaceWindow.config.js b/plugins/betting/ui/BetPlaceWindow.config.js index d6753696..dae1bb12 100644 --- a/plugins/betting/ui/BetPlaceWindow.config.js +++ b/plugins/betting/ui/BetPlaceWindow.config.js @@ -1,17 +1,15 @@ -import { raceConfig, StaticHeader } from "../../ui/UI.js" +import { raceConfig, StaticHeader } from '../../ui/UI.js' const p = tm.utils.palette const width = 10 export default { posX: raceConfig.rightPosition - (width + raceConfig.marginBig), - headerText: `Bet`, - // To position the widget next to side ui widgets set these properties and keep staticPos props null + headerText: `Bet`, // To position the widget next to side ui widgets set these properties and keep staticPos props null relativePos: { side: true, widgetNumber: 3 - }, - // To set fixed position set these props to numbers + }, // To set fixed position set these props to numbers staticPos: { posY: null, height: null @@ -44,4 +42,4 @@ export default { }, topBorder: raceConfig.topBorder, // used for Y positioning marginBig: raceConfig.marginBig -} \ No newline at end of file +} diff --git a/plugins/chat_routing_additions/CustomBracket.config.js b/plugins/chat_routing_additions/CustomBracket.config.js index ede22af7..4752f864 100644 --- a/plugins/chat_routing_additions/CustomBracket.config.js +++ b/plugins/chat_routing_additions/CustomBracket.config.js @@ -2,10 +2,14 @@ const p = tm.utils.palette export default { isEnabled: true, - brackets: [ - { logins: ["login1"], left: `$${p.purple}[$g`, right: `$z$s$${p.purple}]$g ` }, - { logins: ["login1", "login2", "login3"], left: `$${p.red}[$g`, right: `$z$s$${p.red}]$g ` } - ], - // If there is another function modifying chat nickname with higher importance it will overwrite this one + brackets: [{ + logins: ['login1'], + left: `$${p.purple}[$g`, + right: `$z$s$${p.purple}]$g ` + }, { + logins: ['login1', 'login2', 'login3'], + left: `$${p.red}[$g`, + right: `$z$s$${p.red}]$g ` + }], // If there is another function modifying chat nickname with higher importance it will overwrite this one importance: 1 } diff --git a/plugins/chat_routing_additions/FinishCounter.config.js b/plugins/chat_routing_additions/FinishCounter.config.js index edc7b8c6..0ff7c653 100644 --- a/plugins/chat_routing_additions/FinishCounter.config.js +++ b/plugins/chat_routing_additions/FinishCounter.config.js @@ -2,11 +2,21 @@ const p = tm.utils.palette export default { isEnabled: false, - colours: [ - { amount: 0, colour: '$fff' }, - { amount: 10, colour: '$' + p.green }, - { amount: 30, colour: '$' + p.purple }, - { amount: 100, colour: '$' + p.yellow }, - { amount: 200, colour: ['$03A', '$319', '$519', '$709', '$81A'] } // Gradient + colours: [{ + amount: 0, + colour: '$fff' + }, { + amount: 10, + colour: '$' + p.green + }, { + amount: 30, + colour: '$' + p.purple + }, { + amount: 100, + colour: '$' + p.yellow + }, { + amount: 200, + colour: ['$03A', '$319', '$519', '$709', '$81A'] + } // Gradient ] -} \ No newline at end of file +} diff --git a/plugins/chat_routing_additions/UrlFixer.ts b/plugins/chat_routing_additions/UrlFixer.ts index a14eb063..b59430a4 100644 --- a/plugins/chat_routing_additions/UrlFixer.ts +++ b/plugins/chat_routing_additions/UrlFixer.ts @@ -5,9 +5,8 @@ if (config.isEnabled) { if (info.text.match(config.matchRegex)) { const [text, fixedText]: string[][] = [info.text.split(` `), [``]] for (const t of text) { - t.match(config.matchRegex) - ? fixedText.push(`$L${tm.utils.fixProtocol(tm.utils.strip(t))}$L`) - : fixedText.push(t) + t.match(config.matchRegex) ? fixedText.push(`$L${tm.utils.fixProtocol(tm.utils.strip(t))}$L`) : + fixedText.push(t) } return fixedText.join(` `).slice(1) } diff --git a/plugins/checkpoint_records/CheckpointDB.ts b/plugins/checkpoint_records/CheckpointDB.ts index fd37284c..e86a982c 100644 --- a/plugins/checkpoint_records/CheckpointDB.ts +++ b/plugins/checkpoint_records/CheckpointDB.ts @@ -13,8 +13,7 @@ const createQueries = [`CREATE TABLE IF NOT EXISTS best_checkpoint_records( CONSTRAINT fk_map_id FOREIGN KEY(map_id) REFERENCES map_ids(id) -);`, -`CREATE TABLE IF NOT EXISTS checkpoint_records( +);`, `CREATE TABLE IF NOT EXISTS checkpoint_records( map_id INT4 NOT NULL, player_id INT4 NOT NULL, checkpoints INT4[] NOT NULL, @@ -42,20 +41,30 @@ for (const e of createQueries) { export const allCpsDB = { - async get(mapDBId: number, ...playerLogins: string[]): - Promise<{ checkpoints: (number | undefined)[], login: string, nickname: string }[] | Error> { + async get(mapDBId: number, ...playerLogins: string[]): Promise<{ + checkpoints: (number | undefined)[], + login: string, + nickname: string + }[] | Error> { if (playerLogins.length === 0) { return [] } const playerDBIds = await tm.db.getPlayerId(playerLogins) const query = `SELECT checkpoints, login, nickname FROM checkpoint_records JOIN players ON players.id=checkpoint_records.player_id WHERE map_id=$1 AND (${playerDBIds.map((_: any, i: number) => `player_id=$${i + 2} OR `).join('').slice(0, -3)});` - const res: { checkpoints: number[], login: string, nickname: string }[] | Error = - (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) + const res: { + checkpoints: number[], + login: string, + nickname: string + }[] | Error = (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) if (res instanceof Error) { tm.log.error(`Error when fetching checkpoints records of players ${playerDBIds} on map ${mapDBId}`, res.message) return [] } - return res.map(a => ({ login: a.login, nickname: a.nickname, checkpoints: a.checkpoints.map(b => b === -1 ? undefined : b) })) + return res.map(a => ({ + login: a.login, + nickname: a.nickname, + checkpoints: a.checkpoints.map(b => b === -1 ? undefined : b) + })) }, async add(mapId: number, login: string, checkpoints: number[]): Promise { @@ -76,11 +85,21 @@ export const allCpsDB = { export const bestCpsDB = { - async get(mapId: string | number): Promise<{ checkpoint: number, date: Date, login: string, nickname: string }[] | Error> { + async get(mapId: string | number): Promise<{ + checkpoint: number, + date: Date, + login: string, + nickname: string + }[] | Error> { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) if (mapDBId === undefined) { return [] } - const res: { checkpoint: number, date: Date, index: number, login: string, nickname: string }[] | Error = - (await queryDB(`SELECT checkpoint, index, date, login, nickname FROM best_checkpoint_records + const res: { + checkpoint: number, + date: Date, + index: number, + login: string, + nickname: string + }[] | Error = (await queryDB(`SELECT checkpoint, index, date, login, nickname FROM best_checkpoint_records JOIN players ON players.id=best_checkpoint_records.player_id WHERE map_id=$1;`, mapId)) if (res instanceof Error) { @@ -89,12 +108,18 @@ export const bestCpsDB = { } const ret: Omit[] = [] for (const e of res) { - ret[e.index] = { checkpoint: e.checkpoint, date: e.date, login: e.login, nickname: e.nickname } + ret[e.index] = { + checkpoint: e.checkpoint, + date: e.date, + login: e.login, + nickname: e.nickname + } } return ret }, - async add(mapId: string | number, playerId: string | number, index: number, checkpoint: number, date: Date): Promise { + async add(mapId: string | number, playerId: string | number, index: number, checkpoint: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } @@ -102,7 +127,8 @@ export const bestCpsDB = { await queryDB(query, mapDBId, playerDBId, index, checkpoint, date) }, - async update(mapId: string | number, playerId: string | number, index: number, checkpoint: number, date: Date): Promise { + async update(mapId: string | number, playerId: string | number, index: number, checkpoint: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } diff --git a/plugins/checkpoint_records/CheckpointEvents.ts b/plugins/checkpoint_records/CheckpointEvents.ts index 9a5c6f55..b79e7ae2 100644 --- a/plugins/checkpoint_records/CheckpointEvents.ts +++ b/plugins/checkpoint_records/CheckpointEvents.ts @@ -12,8 +12,9 @@ const nicknameUpdateListeners: Function[] = [] * @param event Event name * @param callback Callback function to execute */ -const addListener = (event: T, callback: CheckpointEventFunctions[T]): void => { - switch (event) { +const addListener = (event: T, + callback: CheckpointEventFunctions[T]): void => { + switch(event) { case 'BestCheckpoint': bestCheckpointListeners.push(callback as any) return @@ -34,9 +35,10 @@ const addListener = (event: T, callbac } } -const emitEvent = async (event: T, ...params: Parameters) => { +const emitEvent = async (event: T, + ...params: Parameters) => { let listeners: any - switch (event) { + switch(event) { case 'BestCheckpoint': listeners = bestCheckpointListeners break diff --git a/plugins/checkpoint_records/CheckpointListeners.ts b/plugins/checkpoint_records/CheckpointListeners.ts index 087c00cf..45713bae 100644 --- a/plugins/checkpoint_records/CheckpointListeners.ts +++ b/plugins/checkpoint_records/CheckpointListeners.ts @@ -1,5 +1,5 @@ import type { BestCheckpoints, PlayerCheckpoints } from './CheckpointTypes.js' -import { bestCpsDB, allCpsDB } from './CheckpointDB.js' +import { allCpsDB, bestCpsDB } from './CheckpointDB.js' import { emitEvent } from './CheckpointEvents.js' import config from './Config.js' @@ -19,7 +19,9 @@ const onMapStart = async (): Promise => { await tm.log.fatal(`Failed to fetch best checkpoints for map ${tm.maps.current.id}`, res.message) return } - if (res === undefined) return + if (res === undefined) { + return + } currentBestCps = res const playerCps = await allCpsDB.get(currentMapDBId, ...tm.players.list.map(a => a.login)) if (playerCps instanceof Error) { @@ -42,7 +44,10 @@ if (config.isEnabled) { }, true) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string; nickname: string; }[] = [] + const changedObjects: { + login: string; + nickname: string; + }[] = [] for (const e of currentBestCps) { if (e === undefined) { continue } const newNickname = info.find(a => a.login === e.login)?.nickname @@ -73,14 +78,26 @@ if (config.isEnabled) { const playerCheckpoints = currentPlayerCps.find(a => a.login === info.player.login) const isStunts = tm.getGameMode() === 'Stunts' if (playerCheckpoints === undefined) { - currentPlayerCps.push({ login: info.player.login, nickname: info.player.nickname, checkpoints: [info.time] }) + currentPlayerCps.push({ + login: info.player.login, + nickname: info.player.nickname, + checkpoints: [info.time] + }) void allCpsDB.add(currentMapDBId, info.player.login, [info.time]) - emitEvent('PlayerCheckpoint', { login: info.player.login, nickname: info.player.nickname, index: info.index }) - } else if (((playerCheckpoints.checkpoints[info.index] ?? Infinity) > info.time && !isStunts) || - ((playerCheckpoints.checkpoints[info.index] ?? -1) < info.time && isStunts)) { + emitEvent('PlayerCheckpoint', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) + } else if (((playerCheckpoints.checkpoints[info.index] ?? Infinity) > info.time && !isStunts) || ((playerCheckpoints.checkpoints[info.index] ?? -1) < info.time && isStunts)) { playerCheckpoints.checkpoints[info.index] = info.time - void allCpsDB.update(currentMapDBId, info.player.login, playerCheckpoints.checkpoints.map(a => a === undefined ? -1 : a)) - emitEvent('PlayerCheckpoint', { login: info.player.login, nickname: info.player.nickname, index: info.index }) + void allCpsDB.update(currentMapDBId, info.player.login, + playerCheckpoints.checkpoints.map(a => a === undefined ? -1 : a)) + emitEvent('PlayerCheckpoint', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) } const cp = currentBestCps[info.index]?.checkpoint if (cp === undefined || (cp > info.time && !isStunts) || (cp < info.time && isStunts)) { @@ -90,109 +107,160 @@ if (config.isEnabled) { checkpoint: info.time, date: date } - cp === undefined ? void bestCpsDB.add(currentMapDBId, info.player.login, info.index, info.time, date) - : void bestCpsDB.update(currentMapDBId, info.player.login, info.index, info.time, date) - emitEvent('BestCheckpoint', { login: info.player.login, nickname: info.player.nickname, index: info.index, date }) + cp === undefined ? void bestCpsDB.add(currentMapDBId, info.player.login, info.index, info.time, date) : + void bestCpsDB.update(currentMapDBId, info.player.login, info.index, info.time, date) + emitEvent('BestCheckpoint', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index, + date + }) } }) tm.addListener('PlayerJoin', async (info) => { const playerCps = await allCpsDB.get(currentMapDBId, info.login) if (playerCps instanceof Error) { - await tm.log.fatal(`Failed to fetch player ${info.login} checkpoint records for map ${tm.maps.current.id}`, playerCps.message) + await tm.log.fatal(`Failed to fetch player ${info.login} checkpoint records for map ${tm.maps.current.id}`, + playerCps.message) return } currentPlayerCps.push(...playerCps) }) - tm.commands.add( - { - aliases: config.commands.deletemycp.aliases, - help: config.commands.deletemycp.help, - params: [{ name: 'cpIndex', type: 'int', optional: true }], - callback(info, cpIndex?: number) { - const cps = currentPlayerCps.find(a => a.login === info.login) - if (cps === undefined) { - tm.sendMessage(config.noCpRecords, info.login) + tm.commands.add({ + aliases: config.commands.deletemycp.aliases, + help: config.commands.deletemycp.help, + params: [{ + name: 'cpIndex', + type: 'int', + optional: true + }], + callback(info, cpIndex?: number) { + const cps = currentPlayerCps.find(a => a.login === info.login) + if (cps === undefined) { + tm.sendMessage(config.noCpRecords, info.login) + return + } + if (cpIndex === undefined) { + const arr: number[] = cps.checkpoints.filter(a => a !== undefined) as any + cps.checkpoints.length = 0 + tm.sendMessage(config.allPlayerCpsRemoved, info.login) + emitEvent('DeletePlayerCheckpoint', { + ...info, + deletedCheckpoints: arr.map((a, i) => ({ + time: a, + index: i + })) + }) + void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) + } else { + if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { + tm.sendMessage(config.outOfRange, info.login) return } - if (cpIndex === undefined) { - const arr: number[] = cps.checkpoints.filter(a => a !== undefined) as any - cps.checkpoints.length = 0 - tm.sendMessage(config.allPlayerCpsRemoved, info.login) - emitEvent('DeletePlayerCheckpoint', { ...info, deletedCheckpoints: arr.map((a, i) => ({ time: a, index: i })) }) - void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) - } else { - if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = cps.checkpoints[cpIndex - 1] - cps.checkpoints[cpIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.playerCpRemoved, { index: tm.utils.getOrdinalSuffix(cpIndex) }), info.login) - if (deleted !== undefined) { - emitEvent('DeletePlayerCheckpoint', { ...info, deletedCheckpoints: [{ time: deleted, index: cpIndex }] }) - } - void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) + const deleted = cps.checkpoints[cpIndex - 1] + cps.checkpoints[cpIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.playerCpRemoved, { index: tm.utils.getOrdinalSuffix(cpIndex) }), + info.login) + if (deleted !== undefined) { + emitEvent('DeletePlayerCheckpoint', { + ...info, + deletedCheckpoints: [{ + time: deleted, + index: cpIndex + }] + }) } - }, - privilege: config.commands.deletemycp.privilege + void allCpsDB.update(currentMapDBId, info.login, cps.checkpoints.map(a => a === undefined ? -1 : a)) + } }, - { - aliases: config.commands.deletecp.aliases, - help: config.commands.deletecp.help, - params: [{ name: 'cpIndex', type: 'int', optional: true }], - callback(info, cpIndex?: number) { - if (cpIndex === undefined) { - const arr: { - login: string, - nickname: string, - checkpoint: number, - date: Date - }[] = currentBestCps.filter(a => a !== undefined) as any - currentBestCps.length = 0 - tm.sendMessage(tm.utils.strVar(config.allBestCpsRemoved, - { title: info.title, nickname: tm.utils.strip(info.nickname, true) })) - emitEvent('DeleteBestCheckpoint', arr.map((a, i) => ({ ...a, index: i }))) - void bestCpsDB.delete(currentMapDBId) - } else { - if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { - tm.sendMessage(config.outOfRange, info.login) - return - } - const deleted = currentBestCps[cpIndex - 1] - currentBestCps[cpIndex - 1] = undefined - tm.sendMessage(tm.utils.strVar(config.bestCpRemoved, { - title: info.title, - nickname: tm.utils.strip(info.nickname, true), - index: tm.utils.getOrdinalSuffix(cpIndex) - }), config.commands.deletecp.public ? undefined : info.login) - if (deleted !== undefined) { - emitEvent('DeleteBestCheckpoint', [{ ...deleted, index: cpIndex }]) - } - void bestCpsDB.delete(currentMapDBId, cpIndex - 1) + privilege: config.commands.deletemycp.privilege + }, { + aliases: config.commands.deletecp.aliases, + help: config.commands.deletecp.help, + params: [{ + name: 'cpIndex', + type: 'int', + optional: true + }], + callback(info, cpIndex?: number) { + if (cpIndex === undefined) { + const arr: { + login: string, + nickname: string, + checkpoint: number, + date: Date + }[] = currentBestCps.filter(a => a !== undefined) as any + currentBestCps.length = 0 + tm.sendMessage(tm.utils.strVar(config.allBestCpsRemoved, { + title: info.title, + nickname: tm.utils.strip(info.nickname, true) + })) + emitEvent('DeleteBestCheckpoint', arr.map((a, i) => ({ + ...a, + index: i + }))) + void bestCpsDB.delete(currentMapDBId) + } else { + if (cpIndex < 1 || cpIndex > tm.maps.current.checkpointsAmount) { + tm.sendMessage(config.outOfRange, info.login) + return } - }, - privilege: config.commands.deletecp.privilege - } - ) + const deleted = currentBestCps[cpIndex - 1] + currentBestCps[cpIndex - 1] = undefined + tm.sendMessage(tm.utils.strVar(config.bestCpRemoved, { + title: info.title, + nickname: tm.utils.strip(info.nickname, true), + index: tm.utils.getOrdinalSuffix(cpIndex) + }), config.commands.deletecp.public ? undefined : info.login) + if (deleted !== undefined) { + emitEvent('DeleteBestCheckpoint', [{ + ...deleted, + index: cpIndex + }]) + } + void bestCpsDB.delete(currentMapDBId, cpIndex - 1) + } + }, + privilege: config.commands.deletecp.privilege + }) } -const getMapCheckpoints = (): ({ login: string, nickname: string, checkpoint: number, date: Date } | null)[] => { - const arr: ({ login: string, nickname: string, checkpoint: number, date: Date } | null)[] = new Array(tm.maps.current.checkpointsAmount - 1).fill(null) +const getMapCheckpoints = (): ({ + login: string, + nickname: string, + checkpoint: number, + date: Date +} | null)[] => { + const arr: ({ + login: string, + nickname: string, + checkpoint: number, + date: Date + } | null)[] = new Array(tm.maps.current.checkpointsAmount - 1).fill(null) for (const [i, e] of currentBestCps.entries()) { arr[i] = e ?? null } return arr } -const getPlayerCheckpoints = (): ({ login: string, nickname: string, checkpoints: (number | null)[] })[] => { - const arr: ({ login: string, nickname: string, checkpoints: (number | null)[] })[] = [] +const getPlayerCheckpoints = (): ({ + login: string, + nickname: string, + checkpoints: (number | null)[] +})[] => { + const arr: ({ + login: string, + nickname: string, + checkpoints: (number | null)[] + })[] = [] for (const [i, e] of tm.players.list.entries()) { arr[i] = { login: e.login, nickname: e.nickname, - checkpoints: new Array(tm.maps.current.checkpointsAmount - 1).fill(null).map((a, i) => currentPlayerCps.find(a => a.login === e.login)?.checkpoints[i] ?? null) + checkpoints: new Array(tm.maps.current.checkpointsAmount - 1).fill(null) + .map((a, i) => currentPlayerCps.find(a => a.login === e.login)?.checkpoints[i] ?? null) } } return arr diff --git a/plugins/checkpoint_records/CheckpointRecords.ts b/plugins/checkpoint_records/CheckpointRecords.ts index b39d0f56..fd8bbd03 100644 --- a/plugins/checkpoint_records/CheckpointRecords.ts +++ b/plugins/checkpoint_records/CheckpointRecords.ts @@ -14,15 +14,23 @@ export const checkpointRecords = { /** * @returns an array of current map best checkpoint records or null if checkpoint record doesn't exist */ - get mapCheckpoints(): ({ login: string, nickname: string, checkpoint: number, date: Date } | null)[] { + get mapCheckpoints(): ({ + login: string, + nickname: string, + checkpoint: number, + date: Date + } | null)[] { return getMapCheckpoints() }, - /** * @returns an array of current map online players and their checkpoint records */ - get playerCheckpoints(): ({ login: string, nickname: string, checkpoints: (number | null)[] })[] { + get playerCheckpoints(): ({ + login: string, + nickname: string, + checkpoints: (number | null)[] + })[] { return getPlayerCheckpoints() }, diff --git a/plugins/checkpoint_records/CheckpointTypes.ts b/plugins/checkpoint_records/CheckpointTypes.ts index c13e19d2..1cb1942c 100644 --- a/plugins/checkpoint_records/CheckpointTypes.ts +++ b/plugins/checkpoint_records/CheckpointTypes.ts @@ -12,13 +12,35 @@ export interface PlayerCheckpoints { } export interface CheckpointEventFunctions { - 'BestCheckpoint': ((bestCheckpoint: Readonly<{ login: string, nickname: string, index: number, date: Date }>) => void) - 'CheckpointsFetch': ((bestCheckpoints: Readonly, playerCheckpoints: readonly Readonly[]) => void) - 'DeleteBestCheckpoint': ((deletedCheckpoints: readonly Readonly<{ index: number, login: string, checkpoint: number, date: Date }>[]) => void) + 'BestCheckpoint': ((bestCheckpoint: Readonly<{ + login: string, + nickname: string, + index: number, + date: Date + }>) => void) + 'CheckpointsFetch': ((bestCheckpoints: Readonly, + playerCheckpoints: readonly Readonly[]) => void) + 'DeleteBestCheckpoint': ((deletedCheckpoints: readonly Readonly<{ + index: number, + login: string, + checkpoint: number, + date: Date + }>[]) => void) 'DeletePlayerCheckpoint': ((player: Readonly<{ - login: string, nickname: string, - deletedCheckpoints: readonly Readonly<{ index: number, time: number }>[] + login: string, + nickname: string, + deletedCheckpoints: readonly Readonly<{ + index: number, + time: number + }>[] + }>) => void) + 'PlayerCheckpoint': ((playerCheckpoint: Readonly<{ + login: string, + nickname: string, + index: number }>) => void) - 'PlayerCheckpoint': ((playerCheckpoint: Readonly<{ login: string, nickname: string, index: number }>) => void) - 'NicknameUpdated': ((players: Readonly[]>) => void) + 'NicknameUpdated': ((players: Readonly[]>) => void) } \ No newline at end of file diff --git a/plugins/checkpoint_records/Config.js b/plugins/checkpoint_records/Config.js index 918bf298..a314c159 100644 --- a/plugins/checkpoint_records/Config.js +++ b/plugins/checkpoint_records/Config.js @@ -1,18 +1,15 @@ -import { palette as p } from "../../config/PrefixesAndPalette.js" +import { palette as p } from '../../config/PrefixesAndPalette.js' export default { - isEnabled: true, - // Using DB client makes the plugin a bit faster due to high amount of database queries + isEnabled: true, // Using DB client makes the plugin a bit faster due to high amount of database queries // Program can run only run limited amount of clients, the process will hang otherwise useDBClient: true, noCpRecords: `${p.error}You have no checkpoint records on the ongoing map.`, allPlayerCpsRemoved: `${p.servermsg}Your checkpoints on the ongoing map were removed.`, outOfRange: `${p.error}Checkpoint index needs to be > 0 and <= to the ongoing map's checkpoint count.`, playerCpRemoved: `${p.servermsg}Your ${p.highlight}#{index}${p.servermsg} checkpoint was removed.`, - allBestCpsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` - + `${p.highlight}all checkpoint records${p.admin} on the ongoing map.`, - bestCpRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` - + `${p.highlight}#{index}${p.admin} checkpoint record on the ongoing map.`, + allBestCpsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` + `${p.highlight}all checkpoint records${p.admin} on the ongoing map.`, + bestCpRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` + `${p.highlight}#{index}${p.admin} checkpoint record on the ongoing map.`, commands: { deletecp: { aliases: ['delcp', 'deletecp'], @@ -26,4 +23,4 @@ export default { privilege: 0 } } -} \ No newline at end of file +} diff --git a/plugins/checkpoint_records/ui/CheckpointRecords.component.ts b/plugins/checkpoint_records/ui/CheckpointRecords.component.ts index ece77f8e..13110a5b 100644 --- a/plugins/checkpoint_records/ui/CheckpointRecords.component.ts +++ b/plugins/checkpoint_records/ui/CheckpointRecords.component.ts @@ -3,7 +3,7 @@ * @since 0.3 */ -import { closeButton, componentIds, Grid, centeredText, Paginator, type GridCellFunction, type GridCellObject, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from '../../ui/UI.js' import { checkpointRecords } from '../../checkpoint_records/CheckpointRecords.js' import config from './CheckpointRecords.config.js' @@ -18,9 +18,12 @@ class CheckpointRecords extends PopupWindow { } constructor() { - super(componentIds.checkpointRecords, config.icon, config.title, (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) - this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, new Array(config.entries + 1).fill(1), config.grid) - this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, Math.ceil(tm.maps.current.checkpointsAmount / config.entries)) + super(componentIds.checkpointRecords, config.icon, config.title, + (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) + this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, + new Array(config.entries + 1).fill(1), config.grid) + this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, + Math.ceil(tm.maps.current.checkpointsAmount / config.entries)) this.paginator.onPageChange = (login: string, page: number) => { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } @@ -61,15 +64,13 @@ class CheckpointRecords extends PopupWindow { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } - protected async constructContent(login: string, params: { page: number }): Promise { - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Index ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - (i, j, w, h) => centeredText(' Date ', w, h), - (i, j, w, h) => centeredText(' Best ', w, h), - (i, j, w, h) => centeredText(' Personal ', w, h) - ] + protected async constructContent(login: string, params: { + page: number + }): Promise { + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Index ', w, h), + (i, j, w, h) => centeredText(' Nickname ', w, h), (i, j, w, h) => centeredText(' Login ', w, h), + (i, j, w, h) => centeredText(' Date ', w, h), (i, j, w, h) => centeredText(' Best ', w, h), + (i, j, w, h) => centeredText(' Personal ', w, h)] const cps = checkpointRecords.mapCheckpoints const cpIndex = config.entries * (params.page - 1) const personalCps = checkpointRecords.playerCheckpoints @@ -90,8 +91,7 @@ class CheckpointRecords extends PopupWindow { const dateCell: GridCellFunction = (i, j, w, h) => { const cp = cps[i + cpIndex - 1] - return centeredText((cp === undefined || cp === null) ? '' : - tm.utils.formatDate(cp.date, true), w, h) + return centeredText((cp === undefined || cp === null) ? '' : tm.utils.formatDate(cp.date, true), w, h) } const bestSectorCell: GridCellFunction = (i, j, w, h) => { @@ -121,7 +121,8 @@ class CheckpointRecords extends PopupWindow { } else if (difference === 0) { differenceString = `($${this.diffColours.equal + tm.utils.getTimeString(difference)}$FFF)` } else { - differenceString = `($${this.diffColours.worse}${worseSign}${tm.utils.getTimeString(Math.abs(difference))}$FFF)` + differenceString = `($${this.diffColours.worse}${worseSign}${tm.utils.getTimeString( + Math.abs(difference))}$FFF)` } } return centeredText(differenceString + ' ' + tm.utils.getTimeString(cp), w, h) diff --git a/plugins/checkpoint_records/ui/CheckpointRecords.config.js b/plugins/checkpoint_records/ui/CheckpointRecords.config.js index 1cd0cd39..5f199a55 100644 --- a/plugins/checkpoint_records/ui/CheckpointRecords.config.js +++ b/plugins/checkpoint_records/ui/CheckpointRecords.config.js @@ -1,34 +1,43 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: " Checkpoint Records ", + title: ' Checkpoint Records ', icon: icons.clock, entries: 14, - columnProportions: [ - 0.7, - 2, - 2, - 2, - 1, - 1.5 - ], - navbar: [ - { name: 'Sector Records', actionId: ids.sectorRecords }, - { name: 'Dedi Checkpoints', actionId: ids.dediCps }, - { name: 'Dedi Sectors', actionId: ids.dediSectors }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], - stuntsNavbar: [ - { name: 'Sector Records', actionId: ids.sectorRecords }, - { name: 'Ulti Records', actionId: ids.ultiRecords }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], + columnProportions: [0.7, 2, 2, 2, 1, 1.5], + navbar: [{ + name: 'Sector Records', + actionId: ids.sectorRecords + }, { + name: 'Dedi Checkpoints', + actionId: ids.dediCps + }, { + name: 'Dedi Sectors', + actionId: ids.dediSectors + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], + stuntsNavbar: [{ + name: 'Sector Records', + actionId: ids.sectorRecords + }, { + name: 'Ulti Records', + actionId: ids.ultiRecords + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -38,4 +47,4 @@ export default { }, noTimeText: '--:--.-', stuntsNoTimeText: '--' -} \ No newline at end of file +} diff --git a/plugins/commands/config/ChatCommands.config.js b/plugins/commands/config/ChatCommands.config.js index 77983fcc..d630a79f 100644 --- a/plugins/commands/config/ChatCommands.config.js +++ b/plugins/commands/config/ChatCommands.config.js @@ -11,11 +11,11 @@ export default { help: `Greet a certain someone.` }, greet: { - text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{nickname_joiner}$g!`, - public: true, - privilege: 0, - aliases: ['greet'], - help: `Greet the latest joiner.` + text: `$g[#{nickname}$z$s$g] ${prefix}Hello, #{nickname_joiner}$g!`, + public: true, + privilege: 0, + aliases: ['greet'], + help: `Greet the latest joiner.` }, bye: { text: `$g[#{nickname}$z$s$g] ${prefix}Goodbye, #{name}$g!`, @@ -145,16 +145,14 @@ export default { help: `Find out about the current map playtime.` }, laston: { - text: `${p.servermsg}Player ${p.highlight}#{name} ${p.servermsg}was last` + - ` active on${p.highlight} #{time}${p.servermsg}.`, + text: `${p.servermsg}Player ${p.highlight}#{name} ${p.servermsg}was last` + ` active on${p.highlight} #{time}${p.servermsg}.`, error: `${p.error}Unknown player ${p.highlight}#{name}${p.error}.`, privilege: 0, aliases: ['lo', 'laston'], help: `Enlighten yourself with the last visit date of the specifed specimen.` }, sessiontime: { - text: `${p.servermsg}Current session time of ${p.highlight}#{name}${p.servermsg}` + - ` is${p.highlight} #{time}${p.servermsg}.`, + text: `${p.servermsg}Current session time of ${p.highlight}#{name}${p.servermsg}` + ` is${p.highlight} #{time}${p.servermsg}.`, selfText: `${p.servermsg}Your current session time is ${p.highlight}#{time}${p.servermsg}.`, error: `${p.error}Player ${p.highlight}#{name}${p.error} is not online.`, privilege: 0, @@ -172,8 +170,7 @@ export default { admin: { text: `${p.error}Use //[command] for admin commands.`, privilege: 0, - aliases: ['admin', 'a'], - // help: `` // not needed here + aliases: ['admin', 'a'] // help: `` // not needed here }, coppers: { text: `${p.admin}Current server coppers amount is ${p.highlight}#{value}C${p.admin}.`, @@ -184,4 +181,4 @@ export default { aliases: ['ccs', 'coppers', 'checkcoppers'], help: `Check the amount of coppers the server account currently has.` } -} \ No newline at end of file +} diff --git a/plugins/commands/config/GameCommands.config.js b/plugins/commands/config/GameCommands.config.js index 514548a9..fd378d93 100644 --- a/plugins/commands/config/GameCommands.config.js +++ b/plugins/commands/config/GameCommands.config.js @@ -115,5 +115,5 @@ export default { privilege: 3, aliases: ['drp', 'disablerespawn'], help: `Set whether checkpoint respawning is enabled.` - }, -} \ No newline at end of file + } +} diff --git a/plugins/commands/config/MapCommands.config.js b/plugins/commands/config/MapCommands.config.js index 9f0c2cb1..0414ce38 100644 --- a/plugins/commands/config/MapCommands.config.js +++ b/plugins/commands/config/MapCommands.config.js @@ -17,8 +17,7 @@ export default { addrandom: { fetchError: `${p.error}Failed to fetch random map from TMX.`, addError: `${p.error}Failed to add the map.`, - added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued` + - ` ${p.highlight}#{map}${p.admin} from TMX random.`, + added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued` + ` ${p.highlight}#{map}${p.admin} from TMX random.`, public: true, privilege: tm.config.controller.privileges.addMap, aliases: ['ar', 'addr', 'addrandom'], @@ -32,8 +31,7 @@ export default { addfromurl: { fetchError: `${p.error}Failed to fetch the map file.`, addError: `${p.error}Failed to add the map.`, - alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + - `it will be ${p.highlight}queued ${p.admin}instead.`, + alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + `it will be ${p.highlight}queued ${p.admin}instead.`, added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued ${p.highlight}#{map}${p.admin} from url.`, public: true, privilege: tm.config.controller.privileges.addMap, @@ -50,12 +48,11 @@ export default { notAvailable: `${p.error}Public ${p.highlight}/add ${p.error} is not available on this server.`, fetchError: `${p.error}Failed to fetch the map file from TMX.`, addError: `${p.error}Failed to add the map.`, - alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + - `it will be ${p.highlight}queued ${p.admin}instead.`, + alreadyAdded: `${p.admin}Map ${p.highlight}#{map}${p.admin} added by ${p.highlight}#{nickname}${p.admin} is already on the server, ` + `it will be ${p.highlight}queued ${p.admin}instead.`, added: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has added and queued ${p.highlight}#{map}${p.admin} from TMX.`, public: true, privilege: 0, aliases: ['add', 'am', 'addmap'], help: `Add a map from TMX.` } -} \ No newline at end of file +} diff --git a/plugins/commands/config/RecordsCommands.config.js b/plugins/commands/config/RecordsCommands.config.js index 898dc629..9edd628a 100644 --- a/plugins/commands/config/RecordsCommands.config.js +++ b/plugins/commands/config/RecordsCommands.config.js @@ -16,5 +16,5 @@ export default { privilege: 3, aliases: ['pr', 'prunerecs', 'prunerecords'], help: `Remove all records on the ongoing map.` - }, -} \ No newline at end of file + } +} diff --git a/plugins/commands/config/ServerCommands.config.js b/plugins/commands/config/ServerCommands.config.js index 9cc0f9be..15d17452 100644 --- a/plugins/commands/config/ServerCommands.config.js +++ b/plugins/commands/config/ServerCommands.config.js @@ -58,10 +58,8 @@ export default { help: `Set the referee mode.` }, pay: { - selfText: `${p.admin}You withdrew ${p.highlight}#{coppers}` + - ` ${p.admin}coppers from the server.`, - text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}paid ${p.highlight}#{coppers}` + - ` ${p.admin}coppers to ${p.highlight}#{target}${p.admin}.`, + selfText: `${p.admin}You withdrew ${p.highlight}#{coppers}` + ` ${p.admin}coppers from the server.`, + text: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}paid ${p.highlight}#{coppers}` + ` ${p.admin}coppers to ${p.highlight}#{target}${p.admin}.`, defaultMessage: `You received #{coppers} coppers from #{server}$z$s.`, error: `${p.error}Failed to pay coppers to ${p.highlight}#{login}${p.error}.`, public: false, @@ -179,4 +177,4 @@ export default { aliases: ['call'], help: `Execute a dedicated server method. Params need to be specified in a valid json format or in "" for strings.` } -} \ No newline at end of file +} diff --git a/plugins/commands/src/AdminCommands.ts b/plugins/commands/src/AdminCommands.ts index 6a12b75a..6c610f7b 100644 --- a/plugins/commands/src/AdminCommands.ts +++ b/plugins/commands/src/AdminCommands.ts @@ -1,187 +1,225 @@ import config from '../config/AdminCommands.config.js' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.kick.aliases, - help: config.kick.help, - params: [{ name: 'login', type: 'player' }, { name: 'reason', type: 'multiword', optional: true }], - callback: actions.kick, - privilege: config.kick.privilege - }, - { - aliases: config.mute.aliases, - help: config.mute.help, - params: [{ name: 'login', type: 'offlinePlayer' }, { name: 'duration', type: 'time', optional: true }, { - name: 'reason', - type: 'multiword', - optional: true - }], - callback: actions.mute, - privilege: config.mute.privilege - }, - { - aliases: config.unmute.aliases, - help: config.unmute.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.unmute, - privilege: config.unmute.privilege - }, - { - aliases: config.forcespec.aliases, - help: config.forcespec.help, - params: [{ name: 'login', type: 'player' }], - callback: actions.forceSpectator, - privilege: config.forcespec.privilege - }, - { - aliases: config.forceplay.aliases, - help: config.forceplay.help, - params: [{ name: 'login', type: 'player' }], - callback: actions.forcePlay, - privilege: config.forceplay.privilege - }, - { - aliases: config.kickghost.aliases, - help: config.kickghost.help, - params: [{ name: 'login' }], - callback: (info: tm.MessageInfo, login: string): void => { - tm.sendMessage(tm.utils.strVar(config.kickghost.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), - name: login - }), config.kickghost.public ? undefined : info.login) - tm.client.callNoRes(`Kick`, [{ string: login }]) - }, - privilege: config.kickghost.privilege - }, - { - aliases: config.ban.aliases, - help: config.ban.help, - params: [{ name: 'login', type: 'player' }, { name: 'duration', type: 'time', optional: true }, { - name: 'reason', - type: 'multiword', - optional: true - }], - callback: actions.ban, - privilege: config.ban.privilege - }, - { - aliases: config.unban.aliases, - help: config.unban.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.unban, - privilege: config.unban.privilege - }, - { - aliases: config.blacklist.aliases, - help: config.blacklist.help, - params: [{ name: 'login', type: 'offlinePlayer' }, { name: 'duration', type: 'time', optional: true }, { - name: 'reason', - type: 'multiword', - optional: true - }], - callback: actions.blacklist, - privilege: config.blacklist.privilege - }, - { - aliases: config.unblacklist.aliases, - help: config.unblacklist.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.unblacklist, - privilege: config.unblacklist.privilege - }, - { - aliases: config.addguest.aliases, - help: config.addguest.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.addGuest, - privilege: config.addguest.privilege - }, - { - aliases: config.rmguest.aliases, - help: config.rmguest.help, - params: [{ name: 'login', type: 'offlinePlayer' }], - callback: actions.removeGuest, - privilege: config.rmguest.privilege - }, - { - aliases: config.loadmatchsettings.aliases, - help: config.loadmatchsettings.help, - params: [{ name: 'fileName', type: 'multiword', }], - callback: async (info: tm.MessageInfo, fileName: string) => { - const res = await tm.client.call(`LoadMatchSettings`, [{ string: fileName }]) - if (res instanceof Error) { - tm.sendMessage(config.loadmatchsettings.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.loadmatchsettings.text, - { - title: info.title, - adminName: tm.utils.strip(info.nickname), - file: tm.utils.strip(fileName) - } - ), config.loadmatchsettings.public ? undefined : info.login) - }, - privilege: config.loadmatchsettings.privilege - }, - { - aliases: config.savematchsettings.aliases, - help: config.savematchsettings.help, - params: [{ name: 'fileName', type: 'multiword', optional: true }], - callback: async (info: tm.MessageInfo, fileName?: string) => { - fileName = fileName || tm.config.controller.matchSettingsFile - const res = await tm.client.call(`SaveMatchSettings`, [{ string: fileName }]) - if (res instanceof Error) { - tm.sendMessage(config.savematchsettings.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.savematchsettings.text, - { - title: info.title, - adminName: tm.utils.strip(info.nickname), - file: tm.utils.strip(fileName) - } - ), config.savematchsettings.public ? undefined : info.login) - }, - privilege: config.savematchsettings.privilege - }, - { - aliases: config.updatemaps.aliases, - help: config.updatemaps.help, - callback: async (info: tm.MessageInfo) => { - await tm.maps.updateMaps() - tm.sendMessage(tm.utils.strVar(config.updatemaps.text, - { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.updatemaps.public ? undefined : info.login) - }, - privilege: config.updatemaps.privilege - }, - { - aliases: config.recalculateranks.aliases, - help: config.recalculateranks.help, - callback: async (info: tm.MessageInfo) => { - await tm.records.recalculateRanks() - tm.sendMessage(tm.utils.strVar(config.recalculateranks.text, { - title: info.title, adminName: tm.utils.strip(info.nickname) - }), config.recalculateranks.public ? undefined : info.login) - }, - privilege: config.recalculateranks.privilege - }, - { - aliases: config.servermessage.aliases, - help: config.servermessage.help, - params: [{ name: 'text', type: 'multiword', optional: true }], - callback: async (info: tm.MessageInfo, text: string = ''): Promise => { - tm.sendMessage(tm.utils.strVar(config.servermessage.message, { - server: tm.config.server.name, // No strip - message: text - }), config.servermessage.public ? undefined : info.login, false) - }, - privilege: config.servermessage.privilege - } -] +const commands: tm.Command[] = [{ + aliases: config.kick.aliases, + help: config.kick.help, + params: [{ + name: 'login', + type: 'player' + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.kick, + privilege: config.kick.privilege +}, { + aliases: config.mute.aliases, + help: config.mute.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }, { + name: 'duration', + type: 'time', + optional: true + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.mute, + privilege: config.mute.privilege +}, { + aliases: config.unmute.aliases, + help: config.unmute.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.unmute, + privilege: config.unmute.privilege +}, { + aliases: config.forcespec.aliases, + help: config.forcespec.help, + params: [{ + name: 'login', + type: 'player' + }], + callback: actions.forceSpectator, + privilege: config.forcespec.privilege +}, { + aliases: config.forceplay.aliases, + help: config.forceplay.help, + params: [{ + name: 'login', + type: 'player' + }], + callback: actions.forcePlay, + privilege: config.forceplay.privilege +}, { + aliases: config.kickghost.aliases, + help: config.kickghost.help, + params: [{ name: 'login' }], + callback: (info: tm.MessageInfo, login: string): void => { + tm.sendMessage(tm.utils.strVar(config.kickghost.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + name: login + }), config.kickghost.public ? undefined : info.login) + tm.client.callNoRes(`Kick`, [{ string: login }]) + }, + privilege: config.kickghost.privilege +}, { + aliases: config.ban.aliases, + help: config.ban.help, + params: [{ + name: 'login', + type: 'player' + }, { + name: 'duration', + type: 'time', + optional: true + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.ban, + privilege: config.ban.privilege +}, { + aliases: config.unban.aliases, + help: config.unban.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.unban, + privilege: config.unban.privilege +}, { + aliases: config.blacklist.aliases, + help: config.blacklist.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }, { + name: 'duration', + type: 'time', + optional: true + }, { + name: 'reason', + type: 'multiword', + optional: true + }], + callback: actions.blacklist, + privilege: config.blacklist.privilege +}, { + aliases: config.unblacklist.aliases, + help: config.unblacklist.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.unblacklist, + privilege: config.unblacklist.privilege +}, { + aliases: config.addguest.aliases, + help: config.addguest.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.addGuest, + privilege: config.addguest.privilege +}, { + aliases: config.rmguest.aliases, + help: config.rmguest.help, + params: [{ + name: 'login', + type: 'offlinePlayer' + }], + callback: actions.removeGuest, + privilege: config.rmguest.privilege +}, { + aliases: config.loadmatchsettings.aliases, + help: config.loadmatchsettings.help, + params: [{ + name: 'fileName', + type: 'multiword' + }], + callback: async (info: tm.MessageInfo, fileName: string) => { + const res = await tm.client.call(`LoadMatchSettings`, [{ string: fileName }]) + if (res instanceof Error) { + tm.sendMessage(config.loadmatchsettings.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.loadmatchsettings.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + file: tm.utils.strip(fileName) + }), config.loadmatchsettings.public ? undefined : info.login) + }, + privilege: config.loadmatchsettings.privilege +}, { + aliases: config.savematchsettings.aliases, + help: config.savematchsettings.help, + params: [{ + name: 'fileName', + type: 'multiword', + optional: true + }], + callback: async (info: tm.MessageInfo, fileName?: string) => { + fileName = fileName || tm.config.controller.matchSettingsFile + const res = await tm.client.call(`SaveMatchSettings`, [{ string: fileName }]) + if (res instanceof Error) { + tm.sendMessage(config.savematchsettings.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.savematchsettings.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + file: tm.utils.strip(fileName) + }), config.savematchsettings.public ? undefined : info.login) + }, + privilege: config.savematchsettings.privilege +}, { + aliases: config.updatemaps.aliases, + help: config.updatemaps.help, + callback: async (info: tm.MessageInfo) => { + await tm.maps.updateMaps() + tm.sendMessage(tm.utils.strVar(config.updatemaps.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.updatemaps.public ? undefined : info.login) + }, + privilege: config.updatemaps.privilege +}, { + aliases: config.recalculateranks.aliases, + help: config.recalculateranks.help, + callback: async (info: tm.MessageInfo) => { + await tm.records.recalculateRanks() + tm.sendMessage(tm.utils.strVar(config.recalculateranks.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.recalculateranks.public ? undefined : info.login) + }, + privilege: config.recalculateranks.privilege +}, { + aliases: config.servermessage.aliases, + help: config.servermessage.help, + params: [{ + name: 'text', + type: 'multiword', + optional: true + }], + callback: async (info: tm.MessageInfo, text: string = ''): Promise => { + tm.sendMessage(tm.utils.strVar(config.servermessage.message, { + server: tm.config.server.name, // No strip + message: text + }), config.servermessage.public ? undefined : info.login, false) + }, + privilege: config.servermessage.privilege +}] tm.commands.add(...commands) diff --git a/plugins/commands/src/ChatCommands.ts b/plugins/commands/src/ChatCommands.ts index 5be7e563..57a65aac 100644 --- a/plugins/commands/src/ChatCommands.ts +++ b/plugins/commands/src/ChatCommands.ts @@ -1,355 +1,372 @@ import config from '../config/ChatCommands.config.js' -const commands: tm.Command[] = [ - { - aliases: config.hi.aliases, - help: config.hi.help, - params: [{name: 'name', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.hi.text, { - nickname: info.nickname, - name: name ?? config.defaultValue - }), config.hi.public ? undefined : info.login, false) - }, - privilege: config.hi.privilege, - disableForMuted: true - }, - { - aliases: config.greet.aliases, - help: config.greet.help, - params: [], - callback: (info: tm.MessageInfo): void => { - let timestamp = tm.players.list[0]; - tm.players.list.slice(1).forEach(player => { - if (player.joinTimestamp > timestamp.joinTimestamp) { - timestamp = player - } - }) - tm.sendMessage(tm.utils.strVar(config.greet.text, { - nickname: info.nickname, - nickname_joiner: timestamp.nickname - }), config.greet.public ? undefined : info.login, false) - }, - privilege: config.greet.privilege, - disableForMuted: true - }, - { - aliases: config.bye.aliases, - help: config.bye.help, - params: [{name: 'name', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.bye.text, { - nickname: info.nickname, - name: name ?? config.defaultValue - }), config.bye.public ? undefined : info.login, false) - }, - privilege: config.bye.privilege, - disableForMuted: true - }, - { - aliases: config.thx.aliases, - help: config.thx.help, - params: [{name: 'name', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.thx.text, { - nickname: info.nickname, - name: name ?? config.defaultValue - }), config.thx.public ? undefined : info.login, false) - }, - privilege: config.thx.privilege, - disableForMuted: true - }, - { - aliases: config.gg.aliases, - help: config.gg.help, - params: [{name: 'name', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.gg.text, { - nickname: info.nickname, - name: name ?? config.defaultValue - }), config.gg.public ? undefined : info.login, false) - }, - privilege: config.gg.privilege, - disableForMuted: true - }, - { - aliases: config.bg.aliases, - help: config.bg.help, - params: [{name: 'name', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.bg.text, { - nickname: info.nickname, - name: name ?? config.defaultValue - }), config.bg.public ? undefined : info.login, false) - }, - privilege: config.bg.privilege, - disableForMuted: true - }, - { - aliases: config.n1.aliases, - help: config.n1.help, - params: [{name: 'name', type: 'multiword'}], - callback: (info: tm.MessageInfo, name: string): void => { - tm.sendMessage(tm.utils.strVar(config.n1.text, { - nickname: info.nickname, - name: name - }), config.n1.public ? undefined : info.login, false) - }, - privilege: config.n1.privilege, - disableForMuted: true - }, - { - aliases: config.gr.aliases, - help: config.gr.help, - params: [{name: 'name', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, name?: string): void => { - tm.sendMessage(tm.utils.strVar(config.gr.text, { - nickname: info.nickname, - name: name ?? config.defaultValue - }), config.gr.public ? undefined : info.login, false) - }, - privilege: config.gr.privilege, - disableForMuted: true - }, - { - aliases: config.bgm.aliases, - help: config.bgm.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bgm.text, {nickname: info.nickname}), config.bgm.public ? undefined : info.login, false) - }, - privilege: config.bgm.privilege, - disableForMuted: true - }, - { - aliases: config.brb.aliases, - help: config.brb.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.brb.text, {nickname: info.nickname}), config.brb.public ? undefined : info.login, false) - }, - privilege: config.brb.privilege, - disableForMuted: true - }, - { - aliases: config.afk.aliases, - help: config.afk.help, - callback: async (info: tm.MessageInfo): Promise => { - const res = await tm.client.call('system.multicall', - [{ - method: 'ForceSpectator', - params: [{string: info.login}, {int: 1}] - }, - { - method: 'ForceSpectator', - params: [{string: info.login}, {int: 0}] - }]) - if (res instanceof Error || res[0] instanceof Error) { - tm.sendMessage(config.afk.tooManySpecs, info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.afk.text, {nickname: info.nickname}), config.afk.public ? undefined : info.login, false) - tm.client.callNoRes('SpectatorReleasePlayerSlot', [{string: info.login}]) - } - }, - privilege: config.afk.privilege - }, - { - aliases: config.me.aliases, - help: config.me.help, - params: [{name: 'thoughts', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, thoughts?: string): void => { - tm.sendMessage(tm.utils.strVar(config.me.text, { - nickname: info.nickname, - message: thoughts ?? '' - }), config.me.public ? undefined : info.login, false) - }, - privilege: config.me.privilege, - disableForMuted: true - }, - { - aliases: config.lol.aliases, - help: config.lol.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.lol.text, {nickname: info.nickname}), config.lol.public ? undefined : info.login, false) - }, - privilege: config.lol.privilege, - disableForMuted: true - }, - { - aliases: config.lool.aliases, - help: config.lool.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.lool.text, {nickname: info.nickname}), config.lool.public ? undefined : info.login, false) - }, - privilege: config.lool.privilege, - disableForMuted: true - }, - { - aliases: config.loool.aliases, - help: config.loool.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.loool.text, {nickname: info.nickname}), config.loool.public ? undefined : info.login, false) - }, - privilege: config.loool.privilege, - disableForMuted: true - }, - { - aliases: config.time.aliases, - help: config.time.help, - callback: (info: tm.MessageInfo): void => { - const date = new Date() - tm.sendMessage(tm.utils.strVar(config.time.text, - { - time: date.toLocaleString('EU') - }), info.login) - }, - privilege: config.time.privilege - }, - { - aliases: config.bootme.aliases, - help: config.bootme.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bootme.text, {nickname: tm.utils.strip(info.nickname, true)}), config.bootme.public ? undefined : info.login) - tm.client.callNoRes('Kick', [{string: info.login}, {string: config.bootme.leaveText}]) - }, - privilege: config.bootme.privilege - }, - { - aliases: config.rq.aliases, - help: config.rq.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.bootme.text, {nickname: tm.utils.strip(info.nickname, true)}), config.rq.public ? undefined : info.login) - tm.client.callNoRes('Kick', [{string: info.login}, {string: config.rq.leaveText}]) - }, - privilege: config.rq.privilege - }, - { - aliases: config.pm.aliases, - help: config.pm.help, - params: [{name: 'login'}, {name: 'text', type: 'multiword', optional: true}], - callback: (info: tm.MessageInfo, login: string, text: string = ''): void => { - const playerInfo: tm.Player | undefined = tm.players.get(login) - if (playerInfo === undefined) { - tm.sendMessage(config.pm.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.pm.text, - { - sender: tm.utils.strip(info.nickname, false), - recipient: tm.utils.strip(playerInfo.nickname, false), - message: text - }), [info.login, playerInfo.login].join()) - }, - privilege: config.pm.privilege - }, - { - aliases: config.playtime.aliases, - help: config.playtime.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.playtime.text, - {time: tm.utils.getVerboseTime(Date.now() - tm.timer.mapStartTimestamp)}), - info.login) - }, - privilege: config.playtime.privilege - }, - { - aliases: config.laston.aliases, - help: config.laston.help, - params: [{name: 'login'}], - callback: async (info: tm.MessageInfo, login: string): Promise => { - login = login.toLowerCase() - const player = await tm.players.fetch(login) - if (player?.lastOnline === undefined) { - tm.sendMessage(tm.utils.strVar(config.laston.error, {name: login}), info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.laston.text, { - name: tm.utils.strip(player.nickname), - time: new Date(player.lastOnline).toLocaleString('EU') - }), info.login) - }, - privilege: config.laston.privilege - }, - { - aliases: config.sessiontime.aliases, - help: config.sessiontime.help, - params: [{name: 'login', optional: true}], - callback: async (info: tm.MessageInfo, login?: string): Promise => { - if (login === undefined || login === info.login) { - tm.sendMessage(tm.utils.strVar(config.sessiontime.selfText, { - time: tm.utils.getVerboseTime(Date.now() - info.joinTimestamp) - }), info.login) - return - } - const player = tm.players.get(login) - if (player === undefined) { - tm.sendMessage(tm.utils.strVar(config.sessiontime.error, { - name: login - }), info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.sessiontime.text, { - name: tm.utils.strip(player.nickname), - time: tm.utils.getVerboseTime(Date.now() - player.joinTimestamp) - }), info.login) - }, - privilege: config.sessiontime.privilege - }, - { - aliases: config.man.aliases, - help: config.man.help, - params: [{name: 'commandName'}], - callback: (info: tm.MessageInfo, commandName: string): void => { - const command = tm.commands.list.filter(a => a.aliases.some(a => a === commandName)) - let str = '' - for (const e of command) { - if (e.help === undefined || e.privilege > info.privilege) { - continue - } - str += '\n' - const par = tm.utils.stringifyCommandParams(e.params) - str += tm.utils.strVar(config.man.text, { - name: commandName, - params: par.length === 0 ? '' : `(${par}) `, - help: e.help - }) - } - str = str.slice(1) - if (str.length === 0) { - tm.sendMessage(tm.utils.strVar(config.man.error, { - name: commandName - }), info.login) - return - } - tm.sendMessage(str, info.login) - }, - privilege: config.man.privilege - }, - { - aliases: ['admin', 'a'], - callback: (info: tm.MessageInfo): void => { - if (info.privilege > 0) { - tm.sendMessage(config.admin.text, info.login) - } - }, - privilege: config.admin.privilege - }, - { - aliases: config.coppers.aliases, - help: config.coppers.help, - callback: async (info: tm.MessageInfo): Promise => { - if (!tm.config.server.isUnited) { - tm.sendMessage(config.coppers.notUnited, info.login) - return - } - const coppers: any | Error = await tm.client.call('GetServerCoppers') - if (coppers instanceof Error) { - tm.log.error(`Couldn't retrieve the coppers amount.`, coppers.message) - tm.sendMessage(config.coppers.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.coppers.text, {value: coppers}), config.coppers.public ? undefined : info.login) - }, - privilege: config.coppers.privilege +const commands: tm.Command[] = [{ + aliases: config.hi.aliases, + help: config.hi.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.hi.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.hi.public ? undefined : info.login, false) + }, + privilege: config.hi.privilege, + disableForMuted: true +}, { + aliases: config.greet.aliases, + help: config.greet.help, + params: [], + callback: (info: tm.MessageInfo): void => { + let timestamp = tm.players.list[0]; + tm.players.list.slice(1).forEach(player => { + if (player.joinTimestamp > timestamp.joinTimestamp) { + timestamp = player + } + }) + tm.sendMessage(tm.utils.strVar(config.greet.text, { + nickname: info.nickname, + nickname_joiner: timestamp.nickname + }), config.greet.public ? undefined : info.login, false) + }, + privilege: config.greet.privilege, + disableForMuted: true +}, { + aliases: config.bye.aliases, + help: config.bye.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.bye.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.bye.public ? undefined : info.login, false) + }, + privilege: config.bye.privilege, + disableForMuted: true +}, { + aliases: config.thx.aliases, + help: config.thx.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.thx.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.thx.public ? undefined : info.login, false) + }, + privilege: config.thx.privilege, + disableForMuted: true +}, { + aliases: config.gg.aliases, + help: config.gg.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.gg.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.gg.public ? undefined : info.login, false) + }, + privilege: config.gg.privilege, + disableForMuted: true +}, { + aliases: config.bg.aliases, + help: config.bg.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.bg.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.bg.public ? undefined : info.login, false) + }, + privilege: config.bg.privilege, + disableForMuted: true +}, { + aliases: config.n1.aliases, + help: config.n1.help, + params: [{ + name: 'name', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, name: string): void => { + tm.sendMessage(tm.utils.strVar(config.n1.text, { + nickname: info.nickname, + name: name + }), config.n1.public ? undefined : info.login, false) + }, + privilege: config.n1.privilege, + disableForMuted: true +}, { + aliases: config.gr.aliases, + help: config.gr.help, + params: [{ + name: 'name', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, name?: string): void => { + tm.sendMessage(tm.utils.strVar(config.gr.text, { + nickname: info.nickname, + name: name ?? config.defaultValue + }), config.gr.public ? undefined : info.login, false) + }, + privilege: config.gr.privilege, + disableForMuted: true +}, { + aliases: config.bgm.aliases, + help: config.bgm.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bgm.text, { nickname: info.nickname }), + config.bgm.public ? undefined : info.login, false) + }, + privilege: config.bgm.privilege, + disableForMuted: true +}, { + aliases: config.brb.aliases, + help: config.brb.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.brb.text, { nickname: info.nickname }), + config.brb.public ? undefined : info.login, false) + }, + privilege: config.brb.privilege, + disableForMuted: true +}, { + aliases: config.afk.aliases, + help: config.afk.help, + callback: async (info: tm.MessageInfo): Promise => { + const res = await tm.client.call('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: info.login }, { int: 1 }] + }, { + method: 'ForceSpectator', + params: [{ string: info.login }, { int: 0 }] + }]) + if (res instanceof Error || res[0] instanceof Error) { + tm.sendMessage(config.afk.tooManySpecs, info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.afk.text, { nickname: info.nickname }), + config.afk.public ? undefined : info.login, false) + tm.client.callNoRes('SpectatorReleasePlayerSlot', [{ string: info.login }]) } -] + }, + privilege: config.afk.privilege +}, { + aliases: config.me.aliases, + help: config.me.help, + params: [{ + name: 'thoughts', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, thoughts?: string): void => { + tm.sendMessage(tm.utils.strVar(config.me.text, { + nickname: info.nickname, + message: thoughts ?? '' + }), config.me.public ? undefined : info.login, false) + }, + privilege: config.me.privilege, + disableForMuted: true +}, { + aliases: config.lol.aliases, + help: config.lol.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.lol.text, { nickname: info.nickname }), + config.lol.public ? undefined : info.login, false) + }, + privilege: config.lol.privilege, + disableForMuted: true +}, { + aliases: config.lool.aliases, + help: config.lool.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.lool.text, { nickname: info.nickname }), + config.lool.public ? undefined : info.login, false) + }, + privilege: config.lool.privilege, + disableForMuted: true +}, { + aliases: config.loool.aliases, + help: config.loool.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.loool.text, { nickname: info.nickname }), + config.loool.public ? undefined : info.login, false) + }, + privilege: config.loool.privilege, + disableForMuted: true +}, { + aliases: config.time.aliases, + help: config.time.help, + callback: (info: tm.MessageInfo): void => { + const date = new Date() + tm.sendMessage(tm.utils.strVar(config.time.text, { + time: date.toLocaleString('EU') + }), info.login) + }, + privilege: config.time.privilege +}, { + aliases: config.bootme.aliases, + help: config.bootme.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bootme.text, { nickname: tm.utils.strip(info.nickname, true) }), + config.bootme.public ? undefined : info.login) + tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.bootme.leaveText }]) + }, + privilege: config.bootme.privilege +}, { + aliases: config.rq.aliases, + help: config.rq.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.bootme.text, { nickname: tm.utils.strip(info.nickname, true) }), + config.rq.public ? undefined : info.login) + tm.client.callNoRes('Kick', [{ string: info.login }, { string: config.rq.leaveText }]) + }, + privilege: config.rq.privilege +}, { + aliases: config.pm.aliases, + help: config.pm.help, + params: [{ name: 'login' }, { + name: 'text', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, login: string, text: string = ''): void => { + const playerInfo: tm.Player | undefined = tm.players.get(login) + if (playerInfo === undefined) { + tm.sendMessage(config.pm.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.pm.text, { + sender: tm.utils.strip(info.nickname, false), + recipient: tm.utils.strip(playerInfo.nickname, false), + message: text + }), [info.login, playerInfo.login].join()) + }, + privilege: config.pm.privilege +}, { + aliases: config.playtime.aliases, + help: config.playtime.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage( + tm.utils.strVar(config.playtime.text, { time: tm.utils.getVerboseTime(Date.now() - tm.timer.mapStartTimestamp) }), + info.login) + }, + privilege: config.playtime.privilege +}, { + aliases: config.laston.aliases, + help: config.laston.help, + params: [{ name: 'login' }], + callback: async (info: tm.MessageInfo, login: string): Promise => { + login = login.toLowerCase() + const player = await tm.players.fetch(login) + if (player?.lastOnline === undefined) { + tm.sendMessage(tm.utils.strVar(config.laston.error, { name: login }), info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.laston.text, { + name: tm.utils.strip(player.nickname), + time: new Date(player.lastOnline).toLocaleString('EU') + }), info.login) + }, + privilege: config.laston.privilege +}, { + aliases: config.sessiontime.aliases, + help: config.sessiontime.help, + params: [{ + name: 'login', + optional: true + }], + callback: async (info: tm.MessageInfo, login?: string): Promise => { + if (login === undefined || login === info.login) { + tm.sendMessage(tm.utils.strVar(config.sessiontime.selfText, { + time: tm.utils.getVerboseTime(Date.now() - info.joinTimestamp) + }), info.login) + return + } + const player = tm.players.get(login) + if (player === undefined) { + tm.sendMessage(tm.utils.strVar(config.sessiontime.error, { + name: login + }), info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.sessiontime.text, { + name: tm.utils.strip(player.nickname), + time: tm.utils.getVerboseTime(Date.now() - player.joinTimestamp) + }), info.login) + }, + privilege: config.sessiontime.privilege +}, { + aliases: config.man.aliases, + help: config.man.help, + params: [{ name: 'commandName' }], + callback: (info: tm.MessageInfo, commandName: string): void => { + const command = tm.commands.list.filter(a => a.aliases.some(a => a === commandName)) + let str = '' + for (const e of command) { + if (e.help === undefined || e.privilege > info.privilege) { + continue + } + str += '\n' + const par = tm.utils.stringifyCommandParams(e.params) + str += tm.utils.strVar(config.man.text, { + name: commandName, + params: par.length === 0 ? '' : `(${par}) `, + help: e.help + }) + } + str = str.slice(1) + if (str.length === 0) { + tm.sendMessage(tm.utils.strVar(config.man.error, { + name: commandName + }), info.login) + return + } + tm.sendMessage(str, info.login) + }, + privilege: config.man.privilege +}, { + aliases: ['admin', 'a'], + callback: (info: tm.MessageInfo): void => { + if (info.privilege > 0) { + tm.sendMessage(config.admin.text, info.login) + } + }, + privilege: config.admin.privilege +}, { + aliases: config.coppers.aliases, + help: config.coppers.help, + callback: async (info: tm.MessageInfo): Promise => { + if (!tm.config.server.isUnited) { + tm.sendMessage(config.coppers.notUnited, info.login) + return + } + const coppers: any | Error = await tm.client.call('GetServerCoppers') + if (coppers instanceof Error) { + tm.log.error(`Couldn't retrieve the coppers amount.`, coppers.message) + tm.sendMessage(config.coppers.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.coppers.text, { value: coppers }), + config.coppers.public ? undefined : info.login) + }, + privilege: config.coppers.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/GameCommands.ts b/plugins/commands/src/GameCommands.ts index 70b5be3f..b46957ee 100644 --- a/plugins/commands/src/GameCommands.ts +++ b/plugins/commands/src/GameCommands.ts @@ -1,266 +1,321 @@ import config from '../config/GameCommands.config.js' -const commands: tm.Command[] = [ - { - aliases: config.setgamemode.aliases, - help: config.setgamemode.help, - params: [{ - name: 'mode', validValues: ['round', 'ta', 'team', 'lap', 'stunt', 'cup', - 'rounds', 'timeattack', 'teams', 'laps', 'stunts'] - }], - callback: (info: tm.MessageInfo, mode: string): void => { - let modeInt = 1 - switch (mode.toLowerCase()) { - case 'rounds': case 'round': modeInt = 0 - break - case 'timeattack': case 'ta': modeInt = 1 - break - case 'teams': case 'team': modeInt = 2 - break - case 'laps': case 'lap': modeInt = 3 - break - case 'stunts': case 'stunt': modeInt = 4 - break - case 'cup': modeInt = 5 - } - tm.sendMessage(tm.utils.strVar(config.setgamemode.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), mode: mode.toUpperCase() - }), - config.setgamemode.public ? undefined : info.login) - tm.client.callNoRes(`SetGameMode`, [{ int: modeInt }]) - }, - privilege: config.setgamemode.privilege +const commands: tm.Command[] = [{ + aliases: config.setgamemode.aliases, + help: config.setgamemode.help, + params: [{ + name: 'mode', + validValues: ['round', 'ta', 'team', 'lap', 'stunt', 'cup', 'rounds', 'timeattack', 'teams', 'laps', 'stunts'] + }], + callback: (info: tm.MessageInfo, mode: string): void => { + let modeInt = 1 + switch(mode.toLowerCase()) { + case 'rounds': + case 'round': + modeInt = 0 + break + case 'timeattack': + case 'ta': + modeInt = 1 + break + case 'teams': + case 'team': + modeInt = 2 + break + case 'laps': + case 'lap': + modeInt = 3 + break + case 'stunts': + case 'stunt': + modeInt = 4 + break + case 'cup': + modeInt = 5 + } + tm.sendMessage(tm.utils.strVar(config.setgamemode.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + mode: mode.toUpperCase() + }), config.setgamemode.public ? undefined : info.login) + tm.client.callNoRes(`SetGameMode`, [{ int: modeInt }]) }, - { - aliases: config.settimelimit.aliases, - help: config.settimelimit.help, - params: [{ name: 'time', type: 'time' }], - callback: (info: tm.MessageInfo, time: number): void => { - tm.sendMessage(tm.utils.strVar(config.settimelimit.text, - { - title: info.title, adminName: tm.utils.strip(info.nickname), - time: tm.utils.getVerboseTime(time) - }), - config.settimelimit.public ? undefined : info.login) - tm.timer.setTimeLimit(time) - //tm.client.callNoRes(`SetTimeAttackLimit`, [{ int: time }]) - }, - privilege: config.settimelimit.privilege + privilege: config.setgamemode.privilege +}, { + aliases: config.settimelimit.aliases, + help: config.settimelimit.help, + params: [{ + name: 'time', + type: 'time' + }], + callback: (info: tm.MessageInfo, time: number): void => { + tm.sendMessage(tm.utils.strVar(config.settimelimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + time: tm.utils.getVerboseTime(time) + }), config.settimelimit.public ? undefined : info.login) + tm.timer.setTimeLimit(time) + //tm.client.callNoRes(`SetTimeAttackLimit`, [{ int: time }]) }, - { - aliases: config.setchattime.aliases, - help: config.setchattime.help, - params: [{ name: 'time', type: 'time' }], - callback: (info: tm.MessageInfo, time: number): void => { - tm.sendMessage(tm.utils.strVar(config.setchattime.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), time: tm.utils.getVerboseTime(time) - }), config.setchattime.public ? undefined : info.login) - tm.client.callNoRes(`SetChatTime`, [{ int: time }]) - }, - privilege: config.setchattime.privilege + privilege: config.settimelimit.privilege +}, { + aliases: config.setchattime.aliases, + help: config.setchattime.help, + params: [{ + name: 'time', + type: 'time' + }], + callback: (info: tm.MessageInfo, time: number): void => { + tm.sendMessage(tm.utils.strVar(config.setchattime.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + time: tm.utils.getVerboseTime(time) + }), config.setchattime.public ? undefined : info.login) + tm.client.callNoRes(`SetChatTime`, [{ int: time }]) }, - { - aliases: config.setwarmup.aliases, - help: config.setwarmup.help, - params: [{ name: 'enabled', type: 'boolean' }], - callback: (info: tm.MessageInfo, enabled: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.setwarmup.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), state: enabled ? 'enabled' : 'disabled' - }), - config.setwarmup.public ? undefined : info.login) - tm.client.callNoRes(`SetWarmUp`, [{ boolean: enabled }]) - }, - privilege: config.setwarmup.privilege + privilege: config.setchattime.privilege +}, { + aliases: config.setwarmup.aliases, + help: config.setwarmup.help, + params: [{ + name: 'enabled', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, enabled: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.setwarmup.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + state: enabled ? 'enabled' : 'disabled' + }), config.setwarmup.public ? undefined : info.login) + tm.client.callNoRes(`SetWarmUp`, [{ boolean: enabled }]) }, - { - aliases: config.setlapsamount.aliases, - help: config.setlapsamount.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setlapsamount.insufficientLaps, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setlapsamount.text, { + privilege: config.setwarmup.privilege +}, { + aliases: config.setlapsamount.aliases, + help: config.setlapsamount.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setlapsamount.insufficientLaps, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setlapsamount.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setlapsamount.public ? undefined : info.login) + tm.client.callNoRes(`SetNbLaps`, [{ int: amount }]) + }, + privilege: config.setlapsamount.privilege +}, { + aliases: config.setroundslapsamount.aliases, + help: config.setroundslapsamount.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setroundslapsamount.insufficientLaps, info.login) + return + } + if (amount === 0) { + tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.resetText, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setroundslapsamount.public ? undefined : info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.text, { title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setlapsamount.public ? undefined : info.login) - tm.client.callNoRes(`SetNbLaps`, [{ int: amount }]) - }, - privilege: config.setlapsamount.privilege + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setroundslapsamount.public ? undefined : info.login) + } + tm.client.callNoRes(`SetRoundForcedLaps`, [{ int: amount }]) }, - { - aliases: config.setroundslapsamount.aliases, - help: config.setroundslapsamount.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setroundslapsamount.insufficientLaps, info.login) - return - } - if (amount === 0) { - tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.resetText, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setroundslapsamount.public ? undefined : info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.setroundslapsamount.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setroundslapsamount.public ? undefined : info.login) - } - tm.client.callNoRes(`SetRoundForcedLaps`, [{ int: amount }]) - }, - privilege: config.setroundslapsamount.privilege + privilege: config.setroundslapsamount.privilege +}, { + aliases: config.setroundspointlimit.aliases, + help: config.setroundspointlimit.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setroundspointlimit.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setroundspointlimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setroundspointlimit.public ? undefined : info.login) + tm.client.callNoRes(`SetRoundPointsLimit`, [{ int: amount }]) }, - { - aliases: config.setroundspointlimit.aliases, - help: config.setroundspointlimit.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setroundspointlimit.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setroundspointlimit.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setroundspointlimit.public ? undefined : info.login) - tm.client.callNoRes(`SetRoundPointsLimit`, [{ int: amount }]) - }, - privilege: config.setroundspointlimit.privilege + privilege: config.setroundspointlimit.privilege +}, { + aliases: config.setteamspointlimit.aliases, + help: config.setteamspointlimit.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setteamspointlimit.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setteamspointlimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setteamspointlimit.public ? undefined : info.login) + tm.client.callNoRes(`SetTeamPointsLimit`, [{ int: amount }]) }, - { - aliases: config.setteamspointlimit.aliases, - help: config.setteamspointlimit.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setteamspointlimit.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setteamspointlimit.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setteamspointlimit.public ? undefined : info.login) - tm.client.callNoRes(`SetTeamPointsLimit`, [{ int: amount }]) - }, - privilege: config.setteamspointlimit.privilege + privilege: config.setteamspointlimit.privilege +}, { + aliases: config.setteamsmaxpoints.aliases, + help: config.setteamsmaxpoints.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setteamsmaxpoints.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setteamsmaxpoints.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setteamsmaxpoints.public ? undefined : info.login) + tm.client.callNoRes(`SetTeamMaxPoints`, [{ int: amount }]) }, - { - aliases: config.setteamsmaxpoints.aliases, - help: config.setteamsmaxpoints.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setteamsmaxpoints.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setteamsmaxpoints.text, - { title: info.title, adminName: tm.utils.strip(info.nickname), amount: amount }), - config.setteamsmaxpoints.public ? undefined : info.login) - tm.client.callNoRes(`SetTeamMaxPoints`, [{ int: amount }]) - }, - privilege: config.setteamsmaxpoints.privilege + privilege: config.setteamsmaxpoints.privilege +}, { + aliases: config.setcuppointlimit.aliases, + help: config.setcuppointlimit.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setcuppointlimit.insufficientPoints, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcuppointlimit.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcuppointlimit.public ? undefined : info.login) + tm.client.callNoRes(`SetCupPointsLimit`, [{ int: amount }]) }, - { - aliases: config.setcuppointlimit.aliases, - help: config.setcuppointlimit.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setcuppointlimit.insufficientPoints, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcuppointlimit.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setcuppointlimit.public ? undefined : info.login) - tm.client.callNoRes(`SetCupPointsLimit`, [{ int: amount }]) - }, - privilege: config.setcuppointlimit.privilege + privilege: config.setcuppointlimit.privilege +}, { + aliases: config.setcuproundspermap.aliases, + help: config.setcuproundspermap.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setcuproundspermap.insufficientRounds, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcuproundspermap.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcuproundspermap.public ? undefined : info.login) + tm.client.callNoRes(`SetCupRoundsPerChallenge`, [{ int: amount }]) }, - { - aliases: config.setcuproundspermap.aliases, - help: config.setcuproundspermap.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setcuproundspermap.insufficientRounds, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcuproundspermap.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setcuproundspermap.public ? undefined : info.login) - tm.client.callNoRes(`SetCupRoundsPerChallenge`, [{ int: amount }]) - }, - privilege: config.setcuproundspermap.privilege + privilege: config.setcuproundspermap.privilege +}, { + aliases: config.setcupwarmuprounds.aliases, + help: config.setcupwarmuprounds.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount < 0) { + tm.sendMessage(config.setcupwarmuprounds.insufficientRounds, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcupwarmuprounds.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcupwarmuprounds.public ? undefined : info.login) + tm.client.callNoRes(`SetCupWarmUpDuration`, [{ int: amount }]) }, - { - aliases: config.setcupwarmuprounds.aliases, - help: config.setcupwarmuprounds.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount < 0) { - tm.sendMessage(config.setcupwarmuprounds.insufficientRounds, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcupwarmuprounds.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), config.setcupwarmuprounds.public ? undefined : info.login) - tm.client.callNoRes(`SetCupWarmUpDuration`, [{ int: amount }]) - }, - privilege: config.setcupwarmuprounds.privilege + privilege: config.setcupwarmuprounds.privilege +}, { + aliases: config.setcupwinnersamount.aliases, + help: config.setcupwinnersamount.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + if (amount <= 0) { + tm.sendMessage(config.setcupwinnersamount.insufficientWinners, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setcupwinnersamount.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + amount: amount + }), config.setcupwinnersamount.public ? undefined : info.login) + tm.client.callNoRes(`SetCupNbWinners`, [{ int: amount }]) }, - { - aliases: config.setcupwinnersamount.aliases, - help: config.setcupwinnersamount.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - if (amount <= 0) { - tm.sendMessage(config.setcupwinnersamount.insufficientWinners, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setcupwinnersamount.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), amount: amount - }), - config.setcupwinnersamount.public ? undefined : info.login) - tm.client.callNoRes(`SetCupNbWinners`, [{ int: amount }]) - }, - privilege: config.setcupwinnersamount.privilege + privilege: config.setcupwinnersamount.privilege +}, { + aliases: config.forceshowopp.aliases, + help: config.forceshowopp.help, + params: [{ + name: 'status', + type: 'boolean' + }, { + name: 'amount', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, status: boolean, amount?: number): void => { + let n: number + if (!status) { n = 0 } else if (amount !== undefined) { n = amount } else { n = 1 } + tm.sendMessage(tm.utils.strVar(config.forceshowopp.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'enabled' : 'disabled' + }), config.forceshowopp.public ? undefined : info.login) + tm.client.callNoRes(`SetForceShowAllOpponents`, [{ int: n }]) }, - { - aliases: config.forceshowopp.aliases, - help: config.forceshowopp.help, - params: [{ name: 'status', type: 'boolean' }, { name: 'amount', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, status: boolean, amount?: number): void => { - let n: number - if (!status) { n = 0 } else if (amount !== undefined) { n = amount } else { n = 1 } - tm.sendMessage(tm.utils.strVar(config.forceshowopp.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'enabled' : 'disabled' }), config.forceshowopp.public ? undefined : info.login) - tm.client.callNoRes(`SetForceShowAllOpponents`, [{ int: n }]) - }, - privilege: config.forceshowopp.privilege + privilege: config.forceshowopp.privilege +}, { + aliases: config.disablerespawn.aliases, + help: config.disablerespawn.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.disablerespawn.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'disabled' : 'enabled' + }), config.disablerespawn.public ? undefined : info.login) + tm.client.callNoRes(`SetDisableRespawn`, [{ boolean: status }]) }, - { - aliases: config.disablerespawn.aliases, - help: config.disablerespawn.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.disablerespawn.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'disabled' : 'enabled' }), config.disablerespawn.public ? undefined : info.login) - tm.client.callNoRes(`SetDisableRespawn`, [{ boolean: status }]) - }, - privilege: config.disablerespawn.privilege - } -] + privilege: config.disablerespawn.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/JukeboxCommands.ts b/plugins/commands/src/JukeboxCommands.ts index 0ab7f2db..35899399 100644 --- a/plugins/commands/src/JukeboxCommands.ts +++ b/plugins/commands/src/JukeboxCommands.ts @@ -1,56 +1,66 @@ import config from '../config/JukeboxCommands.config.js' -const commands: tm.Command[] = [ - { - aliases: config.dropjukebox.aliases, - help: config.dropjukebox.help, - params: [{ name: 'index', type: 'int' }], - callback: (info: tm.MessageInfo, index: number): void => { - const map: tm.Map | undefined = tm.jukebox.juked[index - 1]?.map - if (map === undefined) { - tm.sendMessage(config.dropjukebox.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.dropjukebox.text, { title: info.title, adminName: tm.utils.strip(info.nickname), name: tm.utils.strip(map.name) }), config.dropjukebox.public ? undefined : info.login) - tm.jukebox.remove(map.id, info) - }, - privilege: config.dropjukebox.privilege +const commands: tm.Command[] = [{ + aliases: config.dropjukebox.aliases, + help: config.dropjukebox.help, + params: [{ + name: 'index', + type: 'int' + }], + callback: (info: tm.MessageInfo, index: number): void => { + const map: tm.Map | undefined = tm.jukebox.juked[index - 1]?.map + if (map === undefined) { + tm.sendMessage(config.dropjukebox.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.dropjukebox.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + name: tm.utils.strip(map.name) + }), config.dropjukebox.public ? undefined : info.login) + tm.jukebox.remove(map.id, info) }, - { - aliases: config.clearjukebox.aliases, - help: config.clearjukebox.help, - callback: (info: tm.MessageInfo): void => { - if (tm.jukebox.juked.length === 0) { - tm.sendMessage(config.clearjukebox.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.clearjukebox.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.clearjukebox.public ? undefined : info.login) - for (const map of tm.jukebox.juked) { - tm.jukebox.remove(map.map.id, info) - } - }, - privilege: config.clearjukebox.privilege + privilege: config.dropjukebox.privilege +}, { + aliases: config.clearjukebox.aliases, + help: config.clearjukebox.help, + callback: (info: tm.MessageInfo): void => { + if (tm.jukebox.juked.length === 0) { + tm.sendMessage(config.clearjukebox.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.clearjukebox.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.clearjukebox.public ? undefined : info.login) + for (const map of tm.jukebox.juked) { + tm.jukebox.remove(map.map.id, info) + } }, - { - aliases: config.shuffle.aliases, - help: config.shuffle.help, - callback: async (info: tm.MessageInfo): Promise => { - tm.sendMessage(tm.utils.strVar(config.shuffle.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.shuffle.public ? undefined : info.login) - tm.jukebox.shuffle(info) - }, - privilege: config.shuffle.privilege + privilege: config.clearjukebox.privilege +}, { + aliases: config.shuffle.aliases, + help: config.shuffle.help, + callback: async (info: tm.MessageInfo): Promise => { + tm.sendMessage(tm.utils.strVar(config.shuffle.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.shuffle.public ? undefined : info.login) + tm.jukebox.shuffle(info) }, - { - aliases: config.clearhistory.aliases, - help: config.clearhistory.help, - callback: async (info: tm.MessageInfo): Promise => { - tm.sendMessage(tm.utils.strVar(config.clearhistory.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.clearhistory.public ? undefined : info.login) - tm.jukebox.clearHistory(info) - }, - privilege: config.clearhistory.privilege - + privilege: config.shuffle.privilege +}, { + aliases: config.clearhistory.aliases, + help: config.clearhistory.help, + callback: async (info: tm.MessageInfo): Promise => { + tm.sendMessage(tm.utils.strVar(config.clearhistory.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.clearhistory.public ? undefined : info.login) + tm.jukebox.clearHistory(info) + }, + privilege: config.clearhistory.privilege - } -] +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/KarmaCommands.ts b/plugins/commands/src/KarmaCommands.ts index 762400f9..c436dbac 100644 --- a/plugins/commands/src/KarmaCommands.ts +++ b/plugins/commands/src/KarmaCommands.ts @@ -19,13 +19,11 @@ tm.addListener('PlayerChat', (info): void => { processVote(info.text as keyof typeof options, info) }) -tm.commands.add( - { - aliases: config.aliases, - help: config.help, - callback: async (info): Promise => { - processVote(info.aliasUsed as keyof typeof options, info) - }, - privilege: 0, - } -) +tm.commands.add({ + aliases: config.aliases, + help: config.help, + callback: async (info): Promise => { + processVote(info.aliasUsed as keyof typeof options, info) + }, + privilege: 0 +}) diff --git a/plugins/commands/src/MapCommands.ts b/plugins/commands/src/MapCommands.ts index 263c5e74..16a8a04e 100644 --- a/plugins/commands/src/MapCommands.ts +++ b/plugins/commands/src/MapCommands.ts @@ -2,175 +2,194 @@ import config from '../config/MapCommands.config.js' import fetch from 'node-fetch' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.add.aliases, - help: config.add.help, - params: [{ name: 'id', type: 'int' }, { name: 'tmxSite', optional: true }], - callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { - await actions.addMap(info.login, info.nickname, info.title, id, tmxSite) - }, - privilege: config.add.privilege +const commands: tm.Command[] = [{ + aliases: config.add.aliases, + help: config.add.help, + params: [{ + name: 'id', + type: 'int' + }, { + name: 'tmxSite', + optional: true + }], + callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { + await actions.addMap(info.login, info.nickname, info.title, id, tmxSite) }, - { - aliases: config.addlocal.aliases, - help: config.addlocal.help, - params: [{ name: 'filename' }], - callback: async (info: tm.MessageInfo, filename: string): Promise => { - const map: tm.Map | Error = await tm.maps.add(filename, info) - if (map instanceof Error) { - tm.sendMessage(config.addlocal.addError, info.login) + privilege: config.add.privilege +}, { + aliases: config.addlocal.aliases, + help: config.addlocal.help, + params: [{ name: 'filename' }], + callback: async (info: tm.MessageInfo, filename: string): Promise => { + const map: tm.Map | Error = await tm.maps.add(filename, info) + if (map instanceof Error) { + tm.sendMessage(config.addlocal.addError, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.addlocal.added, { + title: info.title, + map: tm.utils.strip(map.name, true), + nickname: tm.utils.strip(info.nickname, true) + }), config.addlocal.public ? undefined : info.login) + }, + privilege: config.addlocal.privilege +}, { + aliases: config.addrandom.aliases, + help: config.addrandom.help, + params: [{ + name: 'tmxSite', + optional: true + }], + callback: async (info: tm.MessageInfo, tmxSite?: string): Promise => { + let obj: { + map?: tm.Map; + wasAlreadyAdded: boolean; + } | Error + let iteration = 0 + do { + const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] + const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) + const file: { + name: string, + content: Buffer + } | Error = await tm.tmx.fetchRandomMapFile(site).catch((err: Error) => err) + if (file instanceof Error) { + tm.sendMessage(config.addrandom.fetchError, info.login) return } - tm.sendMessage(tm.utils.strVar(config.addlocal.added, { + obj = await tm.maps.writeFileAndAdd(file.name, file.content, info, { cancelIfAlreadyAdded: true }) + iteration++ + } while (!(obj instanceof Error) && obj.wasAlreadyAdded && iteration < 10) + if (obj instanceof Error) { + tm.log.warn(obj.message) + tm.sendMessage(config.addrandom.addError, info.login) + return + } else if (obj.map === undefined) { + tm.sendMessage(config.addrandom.addError, info.login) + return + } else { + tm.sendMessage(tm.utils.strVar(config.addrandom.added, { title: info.title, - map: tm.utils.strip(map.name, true), + map: tm.utils.strip(obj.map.name, true), nickname: tm.utils.strip(info.nickname, true) - }), config.addlocal.public ? undefined : info.login) - }, - privilege: config.addlocal.privilege + }), config.addrandom.public ? undefined : info.login) + } }, - { - aliases: config.addrandom.aliases, - help: config.addrandom.help, - params: [{ name: 'tmxSite', optional: true }], - callback: async (info: tm.MessageInfo, tmxSite?: string): Promise => { - let obj: { map?: tm.Map; wasAlreadyAdded: boolean; } | Error - let iteration = 0 - do { - const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] - const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) - const file: { name: string, content: Buffer } | Error = - await tm.tmx.fetchRandomMapFile(site).catch((err: Error) => err) - if (file instanceof Error) { - tm.sendMessage(config.addrandom.fetchError, info.login) - return - } - obj = await tm.maps.writeFileAndAdd(file.name, file.content, info, { cancelIfAlreadyAdded: true }) - iteration++ - } while (!(obj instanceof Error) && obj.wasAlreadyAdded && iteration < 10) - if (obj instanceof Error) { - tm.log.warn(obj.message) - tm.sendMessage(config.addrandom.addError, info.login) - return - } else if (obj.map === undefined) { - tm.sendMessage(config.addrandom.addError, info.login) - return - } else { - tm.sendMessage(tm.utils.strVar(config.addrandom.added, { - title: info.title, - map: tm.utils.strip(obj.map.name, true), - nickname: tm.utils.strip(info.nickname, true) - }), config.addrandom.public ? undefined : info.login) - } - }, - privilege: config.addrandom.privilege + privilege: config.addrandom.privilege +}, { + aliases: config.remove.aliases, + help: config.remove.help, + callback: async (info): Promise => { + await actions.removeMap(info.login, info.nickname, info.title) }, - { - aliases: config.remove.aliases, - help: config.remove.help, - callback: async (info): Promise => { - await actions.removeMap(info.login, info.nickname, info.title) - }, - privilege: config.remove.privilege + privilege: config.remove.privilege +}, { + aliases: config.addfromurl.aliases, + help: config.addfromurl.help, + params: [{ name: 'url' }, { + name: 'filename', + optional: true + }], + callback: async (info: tm.MessageInfo, url: string, filename?: string): Promise => { + const file = await fetch(url).catch((err: Error) => err) + if (file instanceof Error || !file.ok) { + tm.sendMessage(config.addfromurl.fetchError, info.login) + return + } + const arrayBuffer = await file.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + const obj = await tm.maps.writeFileAndAdd(filename ?? url, buffer, info) + if (obj instanceof Error) { + tm.log.warn(obj.message) + tm.sendMessage(config.addfromurl.addError, info.login) + return + } else if (obj.wasAlreadyAdded) { + tm.sendMessage(tm.utils.strVar(config.addfromurl.alreadyAdded, { + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.addfromurl.public ? undefined : info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.addfromurl.added, { + title: info.title, + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.addfromurl.public ? undefined : info.login) + } }, - { - aliases: config.addfromurl.aliases, - help: config.addfromurl.help, - params: [{ name: 'url' }, { name: 'filename', optional: true }], - callback: async (info: tm.MessageInfo, url: string, filename?: string): Promise => { - const file = await fetch(url).catch((err: Error) => err) - if (file instanceof Error || !file.ok) { - tm.sendMessage(config.addfromurl.fetchError, info.login) - return - } - const arrayBuffer = await file.arrayBuffer() - const buffer = Buffer.from(arrayBuffer) - const obj = await tm.maps.writeFileAndAdd(filename ?? url, buffer, info) - if (obj instanceof Error) { - tm.log.warn(obj.message) - tm.sendMessage(config.addfromurl.addError, info.login) - return - } else if (obj.wasAlreadyAdded) { - tm.sendMessage(tm.utils.strVar(config.addfromurl.alreadyAdded, { - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.addfromurl.public ? undefined : info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.addfromurl.added, { - title: info.title, - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.addfromurl.public ? undefined : info.login) - } - }, - privilege: config.addfromurl.privilege + privilege: config.addfromurl.privilege +}, { + aliases: config.addallfromdb.aliases, + help: config.addallfromdb.help, + callback: async (info: tm.MessageInfo): Promise => { + const res: any[] | Error = await tm.db.query('SELECT * FROM maps;') + if (res instanceof Error) { + tm.sendMessage(config.addallfromdb.error, info.login) + return + } + for (const map of res) { + if (tm.maps.list.some(a => a.id === map.id)) { continue } + tm.maps.add(map.filename) + } }, - { - aliases: config.addallfromdb.aliases, - help: config.addallfromdb.help, - callback: async (info: tm.MessageInfo): Promise => { - const res: any[] | Error = await tm.db.query('SELECT * FROM maps;') - if (res instanceof Error) { - tm.sendMessage(config.addallfromdb.error, info.login) - return - } - for (const map of res) { - if (tm.maps.list.some(a => a.id === map.id)) { continue } - tm.maps.add(map.filename) + privilege: config.addallfromdb.privilege +}, { + aliases: config.publicadd.aliases, + help: config.publicadd.help, + params: [{ + name: 'id', + type: 'int' + }, { + name: 'tmxSite', + optional: true + }], + callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { + if (!tm.config.controller.allowPublicAdd) { + tm.sendMessage(config.publicadd.notAvailable, info.login) + return + } + const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] + const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) + let file: { + name: string, + content: Buffer + } | Error = await tm.tmx.fetchMapFile(id, site).catch((err: Error) => err) + if (file instanceof Error) { + const remainingSites: tm.TMXSite[] = tmxSites.filter(a => a !== tmxSite) + for (const e of remainingSites) { + file = await tm.tmx.fetchMapFile(id, e).catch((err: Error) => err) + if (!(file instanceof Error)) { break } } - }, - privilege: config.addallfromdb.privilege - }, - { - aliases: config.publicadd.aliases, - help: config.publicadd.help, - params: [{ name: 'id', type: 'int' }, { name: 'tmxSite', optional: true }], - callback: async (info: tm.MessageInfo, id: number, tmxSite?: string): Promise => { - if (!tm.config.controller.allowPublicAdd) { - tm.sendMessage(config.publicadd.notAvailable, info.login) - return - } - const tmxSites: tm.TMXSite[] = ['TMNF', 'TMN', 'TMO', 'TMS', 'TMU'] - const site: tm.TMXSite | undefined = tmxSites.find(a => a === tmxSite?.toUpperCase()) - let file: { name: string, content: Buffer } | Error = await tm.tmx.fetchMapFile(id, site).catch((err: Error) => err) - if (file instanceof Error) { - const remainingSites: tm.TMXSite[] = tmxSites.filter(a => a !== tmxSite) - for (const e of remainingSites) { - file = await tm.tmx.fetchMapFile(id, e).catch((err: Error) => err) - if (!(file instanceof Error)) { break } - } - } - if (file instanceof Error) { - tm.sendMessage(config.publicadd.fetchError, info.login) + } + if (file instanceof Error) { + tm.sendMessage(config.publicadd.fetchError, info.login) + return + } + if (tm.config.controller.voteOnPublicAdd) { + const voteResult: boolean = await actions.publicAdd(info.login, info.nickname, info.title, + file.name.slice(0, -14)) + if (!voteResult) { return } - if (tm.config.controller.voteOnPublicAdd) { - const voteResult: boolean = await actions.publicAdd(info.login, info.nickname, info.title, file.name.slice(0, -14)) - if (!voteResult) { - return - } - } - const obj = await tm.maps.writeFileAndAdd(file.name, file.content, info) - if (obj instanceof Error) { - tm.log.warn(obj.message) - tm.sendMessage(config.publicadd.addError, info.login) - return - } else if (obj.wasAlreadyAdded) { - tm.sendMessage(tm.utils.strVar(config.publicadd.alreadyAdded, { - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.publicadd.public ? undefined : info.login) - } else { - tm.sendMessage(tm.utils.strVar(config.publicadd.added, { - title: info.title, - map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), - nickname: tm.utils.strip(info.nickname, true) - }), config.publicadd.public ? undefined : info.login) - } - }, - privilege: config.publicadd.privilege - } -] + } + const obj = await tm.maps.writeFileAndAdd(file.name, file.content, info) + if (obj instanceof Error) { + tm.log.warn(obj.message) + tm.sendMessage(config.publicadd.addError, info.login) + return + } else if (obj.wasAlreadyAdded) { + tm.sendMessage(tm.utils.strVar(config.publicadd.alreadyAdded, { + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.publicadd.public ? undefined : info.login) + } else { + tm.sendMessage(tm.utils.strVar(config.publicadd.added, { + title: info.title, + map: tm.utils.strip(tm.utils.decodeURI(obj.map.name), true), + nickname: tm.utils.strip(info.nickname, true) + }), config.publicadd.public ? undefined : info.login) + } + }, + privilege: config.publicadd.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/PrivilegesCommands.ts b/plugins/commands/src/PrivilegesCommands.ts index a18a0960..b19f6ea5 100644 --- a/plugins/commands/src/PrivilegesCommands.ts +++ b/plugins/commands/src/PrivilegesCommands.ts @@ -1,43 +1,50 @@ import config from '../config/PrivilegesCommands.config.js' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.masteradmin.aliases, - help: config.masteradmin.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 3) - }, - privilege: config.masteradmin.privilege +const commands: tm.Command[] = [{ + aliases: config.masteradmin.aliases, + help: config.masteradmin.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 3) }, - { - aliases: config.admin.aliases, - help: config.admin.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 2) - }, - privilege: 3 + privilege: config.masteradmin.privilege +}, { + aliases: config.admin.aliases, + help: config.admin.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 2) }, - { - aliases: config.operator.aliases, - help: config.operator.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 1) - }, - privilege: 2 + privilege: 3 +}, { + aliases: config.operator.aliases, + help: config.operator.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 1) }, - { - aliases: config.user.aliases, - help: config.user.help, - params: [{ name: 'player', type: 'offlinePlayer' }], - callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { - await actions.setPlayerPrivilege(player.login, info, 0) - }, - privilege: 2 + privilege: 2 +}, { + aliases: config.user.aliases, + help: config.user.help, + params: [{ + name: 'player', + type: 'offlinePlayer' + }], + callback: async (info: tm.MessageInfo, player: tm.OfflinePlayer): Promise => { + await actions.setPlayerPrivilege(player.login, info, 0) }, -] + privilege: 2 +}] tm.commands.add(...commands) diff --git a/plugins/commands/src/RaceCommands.ts b/plugins/commands/src/RaceCommands.ts index 095b91c5..9447d567 100644 --- a/plugins/commands/src/RaceCommands.ts +++ b/plugins/commands/src/RaceCommands.ts @@ -1,104 +1,104 @@ import config from '../config/RaceCommands.config.js' import { actions } from '../../actions/Actions.js' -const commands: tm.Command[] = [ - { - aliases: config.skip.aliases, - help: config.skip.help, - callback: (info: tm.MessageInfo): void => { - if (tm.getState() === 'result') { return } - tm.sendMessage(tm.utils.strVar(config.skip.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.skip.public ? undefined : info.login) - actions.skipMap() - }, - privilege: config.skip.privilege +const commands: tm.Command[] = [{ + aliases: config.skip.aliases, + help: config.skip.help, + callback: (info: tm.MessageInfo): void => { + if (tm.getState() === 'result') { return } + tm.sendMessage(tm.utils.strVar(config.skip.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.skip.public ? undefined : info.login) + actions.skipMap() }, - { - aliases: config.res.aliases, - help: config.res.help, - callback: (info: tm.MessageInfo): void => { - if (tm.getState() === 'result') { return } - tm.sendMessage(tm.utils.strVar(config.res.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.res.public ? undefined : info.login) - tm.client.callNoRes(`RestartChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) - }, - privilege: config.res.privilege + privilege: config.skip.privilege +}, { + aliases: config.res.aliases, + help: config.res.help, + callback: (info: tm.MessageInfo): void => { + if (tm.getState() === 'result') { return } + tm.sendMessage(tm.utils.strVar(config.res.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.res.public ? undefined : info.login) + tm.client.callNoRes(`RestartChallenge`, tm.getGameMode() === 'Cup' ? [{ boolean: true }] : undefined) }, - { - aliases: config.prev.aliases, - help: config.prev.help, - callback: async (info: tm.MessageInfo): Promise => { - if (tm.jukebox.history[0] === undefined) { - tm.sendMessage(config.prev.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.prev.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - }), config.prev.public ? undefined : info.login) - await tm.jukebox.add(tm.jukebox.history[0].id, info) - }, - privilege: config.prev.privilege + privilege: config.res.privilege +}, { + aliases: config.prev.aliases, + help: config.prev.help, + callback: async (info: tm.MessageInfo): Promise => { + if (tm.jukebox.history[0] === undefined) { + tm.sendMessage(config.prev.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.prev.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.prev.public ? undefined : info.login) + await tm.jukebox.add(tm.jukebox.history[0].id, info) }, - { - aliases: config.replay.aliases, - help: config.replay.help, - callback: (info: tm.MessageInfo): void => { - actions.requeueMap(info.login, info.nickname, info.title) - }, - privilege: config.replay.privilege + privilege: config.prev.privilege +}, { + aliases: config.replay.aliases, + help: config.replay.help, + callback: (info: tm.MessageInfo): void => { + actions.requeueMap(info.login, info.nickname, info.title) }, - { - aliases: config.endround.aliases, - help: config.endround.help, - callback: (info: tm.MessageInfo): void => { - if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts - tm.sendMessage(config.endround.error, info.login) - return - } - tm.client.callNoRes('ForceEndRound') - tm.sendMessage(tm.utils.strVar(config.endround.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.endround.public ? undefined : info.login) - }, - privilege: config.endround.privilege + privilege: config.replay.privilege +}, { + aliases: config.endround.aliases, + help: config.endround.help, + callback: (info: tm.MessageInfo): void => { + if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts + tm.sendMessage(config.endround.error, info.login) + return + } + tm.client.callNoRes('ForceEndRound') + tm.sendMessage(tm.utils.strVar(config.endround.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.endround.public ? undefined : info.login) }, - { - aliases: config.forceteam.aliases, - help: config.forceteam.help, - params: [{ name: 'player' }, { name: 'team', validValues: ['blue', 'red'] }], - callback: async (info: tm.MessageInfo, player: string, team: string): Promise => { - if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts - tm.sendMessage(config.forceteam.notRounds, info.login) - return - } - const playerInfo: tm.Player | undefined = tm.players.get(player) - if (playerInfo === undefined) { - tm.sendMessage(config.forceteam.playerOffline, info.login) - return - } - let teamInt: number = 0 - let teamColour: string = '' - switch (team.toLowerCase()) { - case 'blue': - teamInt = 0 - teamColour = `${tm.utils.colours.blue}` - break - case 'red': - teamInt = 1 - teamColour = `${tm.utils.colours.red}` - } - tm.sendMessage(tm.utils.strVar(config.forceteam.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), name: tm.utils.strip(playerInfo.nickname), - team: (teamColour + team.toUpperCase()) - }), config.forceteam.public ? undefined : info.login) - tm.client.callNoRes(`ForcePlayerTeam`, [{ string: player }, { int: teamInt }]) - }, - privilege: config.forceteam.privilege - } -] + privilege: config.endround.privilege +}, { + aliases: config.forceteam.aliases, + help: config.forceteam.help, + params: [{ name: 'player' }, { + name: 'team', + validValues: ['blue', 'red'] + }], + callback: async (info: tm.MessageInfo, player: string, team: string): Promise => { + if (tm.config.game.gameMode === 1 || tm.config.game.gameMode === 4) { // TimeAttack & Stunts + tm.sendMessage(config.forceteam.notRounds, info.login) + return + } + const playerInfo: tm.Player | undefined = tm.players.get(player) + if (playerInfo === undefined) { + tm.sendMessage(config.forceteam.playerOffline, info.login) + return + } + let teamInt: number = 0 + let teamColour: string = '' + switch(team.toLowerCase()) { + case 'blue': + teamInt = 0 + teamColour = `${tm.utils.colours.blue}` + break + case 'red': + teamInt = 1 + teamColour = `${tm.utils.colours.red}` + } + tm.sendMessage(tm.utils.strVar(config.forceteam.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + name: tm.utils.strip(playerInfo.nickname), + team: (teamColour + team.toUpperCase()) + }), config.forceteam.public ? undefined : info.login) + tm.client.callNoRes(`ForcePlayerTeam`, [{ string: player }, { int: teamInt }]) + }, + privilege: config.forceteam.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/commands/src/RecordsCommands.ts b/plugins/commands/src/RecordsCommands.ts index 83f385f3..f44f3a53 100644 --- a/plugins/commands/src/RecordsCommands.ts +++ b/plugins/commands/src/RecordsCommands.ts @@ -21,17 +21,25 @@ const commands: tm.Command[] = [{ return } } - tm.sendMessage(tm.utils.strVar(config.delrec.text, { title: info.title, adminName: tm.utils.strip(info.nickname), nickname: tm.utils.strip(playerRecord.nickname) }), config.delrec.public ? undefined : info.login) - tm.records.remove(playerRecord, tm.maps.current.id, info, tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) + tm.sendMessage(tm.utils.strVar(config.delrec.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + nickname: tm.utils.strip(playerRecord.nickname) + }), config.delrec.public ? undefined : info.login) + tm.records.remove(playerRecord, tm.maps.current.id, info, + tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) }, privilege: config.delrec.privilege -}, -{ +}, { aliases: config.prunerecs.aliases, help: config.prunerecs.help, callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.prunerecs.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.prunerecs.public ? undefined : info.login) - tm.records.removeAll(tm.maps.current.id, info, tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) + tm.sendMessage(tm.utils.strVar(config.prunerecs.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.prunerecs.public ? undefined : info.login) + tm.records.removeAll(tm.maps.current.id, info, + tm.maps.current.isInLapsMode ? tm.maps.current.lapsAmount : undefined) }, privilege: config.prunerecs.privilege }] diff --git a/plugins/commands/src/ServerCommands.ts b/plugins/commands/src/ServerCommands.ts index 71342225..f29e7d47 100644 --- a/plugins/commands/src/ServerCommands.ts +++ b/plugins/commands/src/ServerCommands.ts @@ -32,393 +32,489 @@ const resumeTimer = (info: tm.Player) => { })) } -const commands: tm.Command[] = [ - { - aliases: config.timelimit.aliases, - help: config.timelimit.help, - params: [{ name: 'action' }], - callback: (info, actionStr: string): void => { - if (tm.getState() !== 'race') { - tm.sendMessage(config.timelimit.notRaceMode, info.login) - return - } - if (!tm.timer.isDynamic) { - tm.sendMessage(config.timelimit.notDynamic, info.login) - return - } - if (actionStr === 'pause' || actionStr === 'stop') { - pauseTimer(info) - return - } - if (actionStr === 'resume' || actionStr === 'unpause' || actionStr === 'start') { - resumeTimer(info) - return - } - let action: 'set' | 'add' | 'subtract' - let time: number | Error - if (actionStr.startsWith('+')) { - action = 'add' - time = tm.utils.parseTimeString(actionStr.slice(1)) - } else if (actionStr.startsWith('-')) { - action = 'subtract' - time = tm.utils.parseTimeString(actionStr.slice(1)) - } else { - action = 'set' - time = tm.utils.parseTimeString(actionStr) - } - if (time instanceof Error) { - tm.sendMessage(config.timelimit.invalidParam, info.login) - return - } - let wasSet = true - if (action === 'set') { - wasSet = tm.timer.setTime(time) - } else if (action === 'add') { - tm.timer.addTime(time) // can always add time - } else if (action === 'subtract') { - wasSet = tm.timer.subtractTime(time) - } - if (wasSet) { - tm.sendMessage(tm.utils.strVar(config.timelimit.set, { - title: info.title, - adminName: tm.utils.strip(info.nickname), - time: tm.utils.getVerboseTime(tm.timer.remainingRaceTime) - })) - } else { - tm.sendMessage(config.timelimit.tooLow, info.login) - } - }, - privilege: config.timelimit.privilege - }, - { - aliases: config.pauseTimer.aliases, - help: config.pauseTimer.help, - callback: (info): void => pauseTimer(info), - privilege: config.pauseTimer.privilege - }, - { - aliases: config.resumeTimer.aliases, - help: config.resumeTimer.help, - callback: (info): void => resumeTimer(info), - privilege: config.resumeTimer.privilege - }, - { - aliases: config.enabledynamictimer.aliases, - help: config.enabledynamictimer.help, - callback: (info): void => { - if (tm.timer.isDynamicOnNextRound) { - tm.sendMessage(config.enabledynamictimer.alreadyEnabled, info.login) - return - } - tm.timer.enableDynamic() - tm.sendMessage(tm.utils.strVar(config.enabledynamictimer.text, { +const commands: tm.Command[] = [{ + aliases: config.timelimit.aliases, + help: config.timelimit.help, + params: [{ name: 'action' }], + callback: (info, actionStr: string): void => { + if (tm.getState() !== 'race') { + tm.sendMessage(config.timelimit.notRaceMode, info.login) + return + } + if (!tm.timer.isDynamic) { + tm.sendMessage(config.timelimit.notDynamic, info.login) + return + } + if (actionStr === 'pause' || actionStr === 'stop') { + pauseTimer(info) + return + } + if (actionStr === 'resume' || actionStr === 'unpause' || actionStr === 'start') { + resumeTimer(info) + return + } + let action: 'set' | 'add' | 'subtract' + let time: number | Error + if (actionStr.startsWith('+')) { + action = 'add' + time = tm.utils.parseTimeString(actionStr.slice(1)) + } else if (actionStr.startsWith('-')) { + action = 'subtract' + time = tm.utils.parseTimeString(actionStr.slice(1)) + } else { + action = 'set' + time = tm.utils.parseTimeString(actionStr) + } + if (time instanceof Error) { + tm.sendMessage(config.timelimit.invalidParam, info.login) + return + } + let wasSet = true + if (action === 'set') { + wasSet = tm.timer.setTime(time) + } else if (action === 'add') { + tm.timer.addTime(time) // can always add time + } else if (action === 'subtract') { + wasSet = tm.timer.subtractTime(time) + } + if (wasSet) { + tm.sendMessage(tm.utils.strVar(config.timelimit.set, { title: info.title, - adminName: tm.utils.strip(info.nickname) + adminName: tm.utils.strip(info.nickname), + time: tm.utils.getVerboseTime(tm.timer.remainingRaceTime) })) - }, - privilege: config.enabledynamictimer.privilege + } else { + tm.sendMessage(config.timelimit.tooLow, info.login) + } }, - { - aliases: config.disabledynamictimer.aliases, - help: config.disabledynamictimer.help, - callback: (info): void => { - if (!tm.timer.isDynamicOnNextRound) { - tm.sendMessage(config.disabledynamictimer.alreadyDisabled, info.login) - return - } - tm.timer.disableDynamic() - tm.sendMessage(tm.utils.strVar(config.disabledynamictimer.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname) - })) - }, - privilege: config.disabledynamictimer.privilege + privilege: config.timelimit.privilege +}, { + aliases: config.pauseTimer.aliases, + help: config.pauseTimer.help, + callback: (info): void => pauseTimer(info), + privilege: config.pauseTimer.privilege +}, { + aliases: config.resumeTimer.aliases, + help: config.resumeTimer.help, + callback: (info): void => resumeTimer(info), + privilege: config.resumeTimer.privilege +}, { + aliases: config.enabledynamictimer.aliases, + help: config.enabledynamictimer.help, + callback: (info): void => { + if (tm.timer.isDynamicOnNextRound) { + tm.sendMessage(config.enabledynamictimer.alreadyEnabled, info.login) + return + } + tm.timer.enableDynamic() + tm.sendMessage(tm.utils.strVar(config.enabledynamictimer.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + })) }, - { - aliases: config.setrefpwd.aliases, - help: config.setrefpwd.help, - params: [{ name: 'password', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, password?: string): void => { - const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field - if (password !== undefined && !regex.test(password)) { - tm.sendMessage(config.setrefpwd.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setrefpwd.text, { title: info.title, adminName: tm.utils.strip(info.nickname), password: password !== undefined ? password : 'none (disabled)' }), config.setrefpwd.public ? undefined : info.login) - tm.client.callNoRes(`SetRefereePassword`, [{ string: password === undefined ? '' : password }]) - }, - privilege: config.setrefpwd.privilege + privilege: config.enabledynamictimer.privilege +}, { + aliases: config.disabledynamictimer.aliases, + help: config.disabledynamictimer.help, + callback: (info): void => { + if (!tm.timer.isDynamicOnNextRound) { + tm.sendMessage(config.disabledynamictimer.alreadyDisabled, info.login) + return + } + tm.timer.disableDynamic() + tm.sendMessage(tm.utils.strVar(config.disabledynamictimer.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + })) }, - { - aliases: config.setrefmode.aliases, - help: config.setrefmode.help, - params: [{ name: 'mode', type: 'boolean' }], - callback: (info: tm.MessageInfo, mode: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.setrefmode.text, { title: info.title, adminName: tm.utils.strip(info.nickname), mode: mode ? 'ALL' : 'TOP3' }), config.setrefmode.public ? undefined : info.login) - tm.client.call(`SetRefereeMode`, [{ int: mode ? 1 : 0 }]) - }, - privilege: config.setrefmode.privilege + privilege: config.disabledynamictimer.privilege +}, { + aliases: config.setrefpwd.aliases, + help: config.setrefpwd.help, + params: [{ + name: 'password', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, password?: string): void => { + const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field + if (password !== undefined && !regex.test(password)) { + tm.sendMessage(config.setrefpwd.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setrefpwd.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + password: password !== undefined ? password : 'none (disabled)' + }), config.setrefpwd.public ? undefined : info.login) + tm.client.callNoRes(`SetRefereePassword`, [{ string: password === undefined ? '' : password }]) }, - { - aliases: config.pay.aliases, - help: config.pay.help, - params: [{ name: 'amount', type: 'int' }, { name: 'login', optional: true }, { name: 'message', optional: true }], - callback: async (info: tm.MessageInfo, amount: number, login?: string, message?: string): Promise => { - const status = await tm.utils.payCoppers(login ?? info.login, amount, - message ?? tm.utils.strVar(config.pay.defaultMessage, { coppers: amount, server: tm.config.server.name })) - if (status instanceof Error) { - tm.sendMessage(tm.utils.strVar(config.pay.error, { login: login ?? info.login }), info.login) + privilege: config.setrefpwd.privilege +}, { + aliases: config.setrefmode.aliases, + help: config.setrefmode.help, + params: [{ + name: 'mode', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, mode: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.setrefmode.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + mode: mode ? 'ALL' : 'TOP3' + }), config.setrefmode.public ? undefined : info.login) + tm.client.call(`SetRefereeMode`, [{ int: mode ? 1 : 0 }]) + }, + privilege: config.setrefmode.privilege +}, { + aliases: config.pay.aliases, + help: config.pay.help, + params: [{ + name: 'amount', + type: 'int' + }, { + name: 'login', + optional: true + }, { + name: 'message', + optional: true + }], + callback: async (info: tm.MessageInfo, amount: number, login?: string, message?: string): Promise => { + const status = await tm.utils.payCoppers(login ?? info.login, amount, + message ?? tm.utils.strVar(config.pay.defaultMessage, { + coppers: amount, + server: tm.config.server.name + })) + if (status instanceof Error) { + tm.sendMessage(tm.utils.strVar(config.pay.error, { login: login ?? info.login }), info.login) + } else { + if (login === undefined) { + tm.sendMessage(tm.utils.strVar(config.pay.selfText, { + coppers: amount + }), info.login) } else { - if (login === undefined) { - tm.sendMessage(tm.utils.strVar(config.pay.selfText, { - coppers: amount, - }), info.login) - } else { - const player: tm.OfflinePlayer | undefined = tm.players.get(login) ?? await tm.players.fetch(login) - tm.sendMessage(tm.utils.strVar(config.pay.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname, true), - coppers: amount, - target: tm.utils.strip(player?.nickname ?? login, true) - }), config.pay.public ? undefined : info.login) - } + const player: tm.OfflinePlayer | undefined = tm.players.get(login) ?? await tm.players.fetch(login) + tm.sendMessage(tm.utils.strVar(config.pay.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname, true), + coppers: amount, + target: tm.utils.strip(player?.nickname ?? login, true) + }), config.pay.public ? undefined : info.login) } - }, - privilege: config.pay.privilege + } }, - { - aliases: config.setservername.aliases, - help: config.setservername.help, - params: [{ name: 'name', type: 'multiword' }], - callback: (info: tm.MessageInfo, name: string): void => { - tm.sendMessage(tm.utils.strVar(config.setservername.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: name.length === 0 ? `the server login` : name }), config.setservername.public ? undefined : info.login) - tm.client.callNoRes(`SetServerName`, [{ string: name }]) - }, - privilege: config.setservername.privilege + privilege: config.pay.privilege +}, { + aliases: config.setservername.aliases, + help: config.setservername.help, + params: [{ + name: 'name', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, name: string): void => { + tm.sendMessage(tm.utils.strVar(config.setservername.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: name.length === 0 ? `the server login` : name + }), config.setservername.public ? undefined : info.login) + tm.client.callNoRes(`SetServerName`, [{ string: name }]) }, - { - aliases: config.setcomment.aliases, - help: config.setcomment.help, - params: [{ name: 'comment', type: 'multiword' }], - callback: (info: tm.MessageInfo, comment: string): void => { - tm.sendMessage(tm.utils.strVar(config.setcomment.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: comment.length === 0 ? `absolutely nothing` : comment }), config.setcomment.public ? undefined : info.login) - tm.client.callNoRes(`SetServerComment`, [{ string: comment }]) - }, - privilege: config.setcomment.privilege + privilege: config.setservername.privilege +}, { + aliases: config.setcomment.aliases, + help: config.setcomment.help, + params: [{ + name: 'comment', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, comment: string): void => { + tm.sendMessage(tm.utils.strVar(config.setcomment.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: comment.length === 0 ? `absolutely nothing` : comment + }), config.setcomment.public ? undefined : info.login) + tm.client.callNoRes(`SetServerComment`, [{ string: comment }]) }, - { - aliases: config.setpassword.aliases, - help: config.setpassword.help, - params: [{ name: 'password', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, password?: string): void => { - const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field - if (password !== undefined && !regex.test(password)) { - tm.sendMessage(config.setpassword.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setpassword.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: password !== undefined ? password : 'none (disabled)' }), config.setpassword.public ? undefined : info.login) - tm.client.callNoRes(`SetServerPassword`, [{ string: password === undefined ? '' : password }]) - }, - privilege: config.setpassword.privilege + privilege: config.setcomment.privilege +}, { + aliases: config.setpassword.aliases, + help: config.setpassword.help, + params: [{ + name: 'password', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, password?: string): void => { + const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field + if (password !== undefined && !regex.test(password)) { + tm.sendMessage(config.setpassword.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setpassword.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: password !== undefined ? password : 'none (disabled)' + }), config.setpassword.public ? undefined : info.login) + tm.client.callNoRes(`SetServerPassword`, [{ string: password === undefined ? '' : password }]) }, - { - aliases: config.setspecpassword.aliases, - help: config.setspecpassword.help, - params: [{ name: 'password', type: 'multiword', optional: true }], - callback: (info: tm.MessageInfo, password?: string): void => { - const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field - if (password !== undefined && !regex.test(password)) { - tm.sendMessage(config.setspecpassword.error, info.login) - return - } - tm.sendMessage(tm.utils.strVar(config.setspecpassword.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: password !== undefined ? password : 'none (disabled)' }), config.setspecpassword.public ? undefined : info.login) - tm.client.callNoRes(`SetServerPasswordForSpectator`, [{ string: password === undefined ? '' : password }]) - }, - privilege: config.setspecpassword.privilege + privilege: config.setpassword.privilege +}, { + aliases: config.setspecpassword.aliases, + help: config.setspecpassword.help, + params: [{ + name: 'password', + type: 'multiword', + optional: true + }], + callback: (info: tm.MessageInfo, password?: string): void => { + const regex: RegExp = /[\p{ASCII}]+/u // Passwords outside of ASCII range cannot be entered in the field + if (password !== undefined && !regex.test(password)) { + tm.sendMessage(config.setspecpassword.error, info.login) + return + } + tm.sendMessage(tm.utils.strVar(config.setspecpassword.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: password !== undefined ? password : 'none (disabled)' + }), config.setspecpassword.public ? undefined : info.login) + tm.client.callNoRes(`SetServerPasswordForSpectator`, [{ string: password === undefined ? '' : password }]) }, - { - aliases: config.setmaxplayers.aliases, - help: config.setmaxplayers.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - tm.sendMessage(tm.utils.strVar(config.setmaxplayers.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: amount }), config.setmaxplayers.public ? undefined : info.login) - tm.client.callNoRes(`SetMaxPlayers`, [{ int: amount }]) - }, - privilege: config.setmaxplayers.privilege + privilege: config.setspecpassword.privilege +}, { + aliases: config.setmaxplayers.aliases, + help: config.setmaxplayers.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + tm.sendMessage(tm.utils.strVar(config.setmaxplayers.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: amount + }), config.setmaxplayers.public ? undefined : info.login) + tm.client.callNoRes(`SetMaxPlayers`, [{ int: amount }]) }, - { - aliases: config.setmaxspecs.aliases, - help: config.setmaxspecs.help, - params: [{ name: 'amount', type: 'int' }], - callback: (info: tm.MessageInfo, amount: number): void => { - tm.sendMessage(tm.utils.strVar(config.setmaxspecs.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: amount }), config.setmaxspecs.public ? undefined : info.login) - tm.client.callNoRes(`SetMaxSpectators`, [{ int: amount }]) - }, - privilege: config.setmaxspecs.privilege + privilege: config.setmaxplayers.privilege +}, { + aliases: config.setmaxspecs.aliases, + help: config.setmaxspecs.help, + params: [{ + name: 'amount', + type: 'int' + }], + callback: (info: tm.MessageInfo, amount: number): void => { + tm.sendMessage(tm.utils.strVar(config.setmaxspecs.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: amount + }), config.setmaxspecs.public ? undefined : info.login) + tm.client.callNoRes(`SetMaxSpectators`, [{ int: amount }]) }, - { - aliases: config.sendnotice.aliases, - help: config.sendnotice.help, - params: [{ name: 'time', type: 'time' }, { name: 'notice', type: 'multiword' }], - callback: (info: tm.MessageInfo, time: number, notice: string): void => { - const s: string[] = notice.split(' ').filter(a => a !== '') - const player = tm.players.get(s[s.length - 1]) - let loginAvatar: string = '' - if (player !== undefined) { - notice = s.slice(0, -1).join(' ') - loginAvatar = player.login - } - tm.sendMessage(tm.utils.strVar(config.sendnotice.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), value: notice - }), config.sendnotice.public ? undefined : info.login) - tm.client.callNoRes(`SendNotice`, [{ string: notice }, { string: loginAvatar }, { int: time }]) - }, - privilege: config.sendnotice.privilege + privilege: config.setmaxspecs.privilege +}, { + aliases: config.sendnotice.aliases, + help: config.sendnotice.help, + params: [{ + name: 'time', + type: 'time' + }, { + name: 'notice', + type: 'multiword' + }], + callback: (info: tm.MessageInfo, time: number, notice: string): void => { + const s: string[] = notice.split(' ').filter(a => a !== '') + const player = tm.players.get(s[s.length - 1]) + let loginAvatar: string = '' + if (player !== undefined) { + notice = s.slice(0, -1).join(' ') + loginAvatar = player.login + } + tm.sendMessage(tm.utils.strVar(config.sendnotice.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: notice + }), config.sendnotice.public ? undefined : info.login) + tm.client.callNoRes(`SendNotice`, [{ string: notice }, { string: loginAvatar }, { int: time }]) }, - { - aliases: config.allowmapdownload.aliases, - help: config.allowmapdownload.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.allowmapdownload.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'allowed' : 'disallowed' }), config.allowmapdownload.public ? undefined : info.login) - tm.client.callNoRes(`AllowChallengeDownload`, [{ boolean: status }]) - }, - privilege: config.allowmapdownload.privilege + privilege: config.sendnotice.privilege +}, { + aliases: config.allowmapdownload.aliases, + help: config.allowmapdownload.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.allowmapdownload.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'allowed' : 'disallowed' + }), config.allowmapdownload.public ? undefined : info.login) + tm.client.callNoRes(`AllowChallengeDownload`, [{ boolean: status }]) }, - { - aliases: config.sethideserver.aliases, - help: config.sethideserver.help, - params: [{ name: 'value', validValues: ['hidden', 'visible', 'notmnf'] }], - callback: (info: tm.MessageInfo, value: string): void => { - let status: string = '' - let hideInt: number = 0 - switch (value) { - case 'visible': - status = config.sethideserver.status.visible - hideInt = 0 - break - case 'hidden': - status = config.sethideserver.status.hidden - hideInt = 1 - break - case 'notmnf': - status = config.sethideserver.status.noTmnf - hideInt = 2 - } - tm.sendMessage(tm.utils.strVar(config.sethideserver.text, { - title: info.title, - adminName: tm.utils.strip(info.nickname), status: status - }), config.sethideserver.public ? undefined : info.login) - tm.client.callNoRes(`SetHideServer`, [{ int: hideInt }]) - }, - privilege: config.sethideserver.privilege + privilege: config.allowmapdownload.privilege +}, { + aliases: config.sethideserver.aliases, + help: config.sethideserver.help, + params: [{ + name: 'value', + validValues: ['hidden', 'visible', 'notmnf'] + }], + callback: (info: tm.MessageInfo, value: string): void => { + let status: string = '' + let hideInt: number = 0 + switch(value) { + case 'visible': + status = config.sethideserver.status.visible + hideInt = 0 + break + case 'hidden': + status = config.sethideserver.status.hidden + hideInt = 1 + break + case 'notmnf': + status = config.sethideserver.status.noTmnf + hideInt = 2 + } + tm.sendMessage(tm.utils.strVar(config.sethideserver.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + status: status + }), config.sethideserver.public ? undefined : info.login) + tm.client.callNoRes(`SetHideServer`, [{ int: hideInt }]) }, - { - aliases: config.autosavereplays.aliases, - help: config.autosavereplays.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.autosavereplays.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'enabled' : 'disabled' }), config.autosavereplays.public ? undefined : info.login) - tm.client.callNoRes(`AutoSaveReplays`, [{ boolean: status }]) - }, - privilege: config.autosavereplays.privilege + privilege: config.sethideserver.privilege +}, { + aliases: config.autosavereplays.aliases, + help: config.autosavereplays.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.autosavereplays.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'enabled' : 'disabled' + }), config.autosavereplays.public ? undefined : info.login) + tm.client.callNoRes(`AutoSaveReplays`, [{ boolean: status }]) }, - { - aliases: config.autosavevalreplays.aliases, - help: config.autosavevalreplays.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.autosavevalreplays.text, { title: info.title, adminName: tm.utils.strip(info.nickname), value: status ? 'enabled' : 'disabled' }), config.autosavevalreplays.public ? undefined : info.login) - tm.client.callNoRes(`AutoSaveValidationReplays`, [{ boolean: status }]) - }, - privilege: config.autosavevalreplays.privilege + privilege: config.autosavereplays.privilege +}, { + aliases: config.autosavevalreplays.aliases, + help: config.autosavevalreplays.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.autosavevalreplays.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + value: status ? 'enabled' : 'disabled' + }), config.autosavevalreplays.public ? undefined : info.login) + tm.client.callNoRes(`AutoSaveValidationReplays`, [{ boolean: status }]) }, - { - aliases: config.killcontroller.aliases, - help: config.killcontroller.help, - callback: (info: tm.MessageInfo): never => { - tm.sendMessage(tm.utils.strVar(config.killcontroller.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.killcontroller.public ? undefined : info.login) - process.exit(0) - }, - privilege: config.killcontroller.privilege + privilege: config.autosavevalreplays.privilege +}, { + aliases: config.killcontroller.aliases, + help: config.killcontroller.help, + callback: (info: tm.MessageInfo): never => { + tm.sendMessage(tm.utils.strVar(config.killcontroller.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.killcontroller.public ? undefined : info.login) + process.exit(0) }, - { - aliases: config.setbuddynotif.aliases, - help: config.setbuddynotif.help, - params: [{ name: 'status', type: 'boolean' }], - callback: (info: tm.MessageInfo, status: boolean): void => { - tm.sendMessage(tm.utils.strVar(config.setbuddynotif.text, { title: info.title, adminName: tm.utils.strip(info.nickname), status: status ? 'enabled' : 'disabled' }), config.setbuddynotif.public ? undefined : info.login) - tm.client.callNoRes(`SetBuddyNotification`, [{ string: '' }, { boolean: status }]) - }, - privilege: config.setbuddynotif.privilege + privilege: config.killcontroller.privilege +}, { + aliases: config.setbuddynotif.aliases, + help: config.setbuddynotif.help, + params: [{ + name: 'status', + type: 'boolean' + }], + callback: (info: tm.MessageInfo, status: boolean): void => { + tm.sendMessage(tm.utils.strVar(config.setbuddynotif.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname), + status: status ? 'enabled' : 'disabled' + }), config.setbuddynotif.public ? undefined : info.login) + tm.client.callNoRes(`SetBuddyNotification`, [{ string: '' }, { boolean: status }]) }, - { - aliases: config.shutdown.aliases, - help: config.shutdown.help, - callback: (info: tm.MessageInfo): void => { - tm.sendMessage(tm.utils.strVar(config.shutdown.text, { title: info.title, adminName: tm.utils.strip(info.nickname) }), config.shutdown.public ? undefined : info.login) - tm.client.callNoRes(`StopServer`) - }, - privilege: config.shutdown.privilege + privilege: config.setbuddynotif.privilege +}, { + aliases: config.shutdown.aliases, + help: config.shutdown.help, + callback: (info: tm.MessageInfo): void => { + tm.sendMessage(tm.utils.strVar(config.shutdown.text, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.shutdown.public ? undefined : info.login) + tm.client.callNoRes(`StopServer`) }, - { - aliases: config.call.aliases, - help: config.call.help, - params: [{ name: 'method' }, { name: 'params', type: 'multiword', optional: true }], - callback: async (info: tm.MessageInfo, method: string, params?: string): Promise => { - if (params === undefined) { params = '' } - const paramsArr = params.split(' ').map(a => a.trim()).filter(a => a !== '') - let quot: [number, number] | undefined - for (let i = 0; i < paramsArr.length; i++) { - for (let j = 0; j < paramsArr[i].length; j++) { - if (paramsArr[i][j] === '"' && paramsArr[i][j - 1] !== "\\") { - if (quot === undefined) { - quot = [i, j] - } else { - const str = paramsArr.slice(quot[0], i + 1).join('') - const endIndex = str.length + j + 1 - paramsArr[i].length - const sliced = str.slice(quot[1], endIndex) - paramsArr.splice(quot[0], (i + 1) - quot[0], sliced) - quot = undefined - break - } + privilege: config.shutdown.privilege +}, { + aliases: config.call.aliases, + help: config.call.help, + params: [{ name: 'method' }, { + name: 'params', + type: 'multiword', + optional: true + }], + callback: async (info: tm.MessageInfo, method: string, params?: string): Promise => { + if (params === undefined) { params = '' } + const paramsArr = params.split(' ').map(a => a.trim()).filter(a => a !== '') + let quot: [number, number] | undefined + for (let i = 0; i < paramsArr.length; i++) { + for (let j = 0; j < paramsArr[i].length; j++) { + if (paramsArr[i][j] === '"' && paramsArr[i][j - 1] !== "\\") { + if (quot === undefined) { + quot = [i, j] + } else { + const str = paramsArr.slice(quot[0], i + 1).join('') + const endIndex = str.length + j + 1 - paramsArr[i].length + const sliced = str.slice(quot[1], endIndex) + paramsArr.splice(quot[0], (i + 1) - quot[0], sliced) + quot = undefined + break } } } - const parsedParams: tm.CallParams[] = [] - for (const e of paramsArr) { - if (e[0] === '"' && e[e.length - 1] === '"') { - parsedParams.push({ string: e.slice(1, -1) }) - continue - } - try { - const parsed = JSON.parse(e) - if (Number.isInteger(parsed)) { - parsedParams.push({ int: parsed }) - } else if (typeof parsed === 'number') { - parsedParams.push({ double: parsed }) - } else if (typeof parsed === 'boolean') { - parsedParams.push({ boolean: parsed }) - } else if (Array.isArray(parsed)) { - parsedParams.push({ array: parsed }) - } else if (typeof parsed === 'object') { - parsedParams.push({ struct: parsed }) - } - } catch (err: any) { - tm.sendMessage(`${tm.utils.palette.error}Parse error: ${err.message}`, info.login) - return - } + } + const parsedParams: tm.CallParams[] = [] + for (const e of paramsArr) { + if (e[0] === '"' && e[e.length - 1] === '"') { + parsedParams.push({ string: e.slice(1, -1) }) + continue } - const response = await tm.client.call(method, parsedParams) - if (response instanceof Error) { - tm.sendMessage(`${tm.utils.palette.error}Error: ${response.message}`, info.login) - } else { - tm.sendMessage(JSON.stringify(response), info.login) + try { + const parsed = JSON.parse(e) + if (Number.isInteger(parsed)) { + parsedParams.push({ int: parsed }) + } else if (typeof parsed === 'number') { + parsedParams.push({ double: parsed }) + } else if (typeof parsed === 'boolean') { + parsedParams.push({ boolean: parsed }) + } else if (Array.isArray(parsed)) { + parsedParams.push({ array: parsed }) + } else if (typeof parsed === 'object') { + parsedParams.push({ struct: parsed }) + } + } catch(err: any) { + tm.sendMessage(`${tm.utils.palette.error}Parse error: ${err.message}`, info.login) + return } - }, - privilege: config.call.privilege - } -] + } + const response = await tm.client.call(method, parsedParams) + if (response instanceof Error) { + tm.sendMessage(`${tm.utils.palette.error}Error: ${response.message}`, info.login) + } else { + tm.sendMessage(JSON.stringify(response), info.login) + } + }, + privilege: config.call.privilege +}] tm.commands.add(...commands) \ No newline at end of file diff --git a/plugins/dedimania/Config.js b/plugins/dedimania/Config.js index 34600f58..5ee37d23 100644 --- a/plugins/dedimania/Config.js +++ b/plugins/dedimania/Config.js @@ -1,8 +1,7 @@ const p = tm.utils.palette export default { - isEnabled: true, - // Maximum amount of fetched records + isEnabled: true, // Maximum amount of fetched records // (Dedimania normally stores 30, there are exceptions such as Nadeo maps with 50-80 records) dediCount: 30, syncName: true, // If true, sets player nicknames in the database to those fetched from Dedimania @@ -10,6 +9,5 @@ export default { host: 'dedimania.net', reconnectTimeout: 120, // Timeout for Dedimania reconnection attempts, in seconds updateInterval: 240, // Interval for Dedimania server update, in seconds (recommended to be kept between 2-5 minutes) - modifiedLapsMessage: `${p.dedimessage}Dedimania records will be sent in ${p.highlight}Time Attack` + - ` ${p.dedimessage}instead of ${p.highlight}Rounds ${p.dedimessage}mode due to modified lap amount.` + modifiedLapsMessage: `${p.dedimessage}Dedimania records will be sent in ${p.highlight}Time Attack` + ` ${p.dedimessage}instead of ${p.highlight}Rounds ${p.dedimessage}mode due to modified lap amount.` } diff --git a/plugins/dedimania/Dedimania.ts b/plugins/dedimania/Dedimania.ts index 27f01f57..f1225351 100644 --- a/plugins/dedimania/Dedimania.ts +++ b/plugins/dedimania/Dedimania.ts @@ -66,7 +66,8 @@ const initialize = async (): Promise => { if (uploadLaps && tm.getGameMode() !== 'Laps') { tm.sendMessage(config.modifiedLapsMessage) } - await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), current.author) + await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), + current.author) tm.log.trace('Connected to Dedimania') } @@ -93,7 +94,8 @@ const reinitialize = async (): Promise => { if (uploadLaps && tm.getGameMode() !== 'Laps') { tm.sendMessage(config.modifiedLapsMessage) } - await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), current.author) + await getRecords(current.id, current.name, (tm.utils.environmentToNadeoEnvironment(current.environment) as string), + current.author) } const getRecords = async (id: string, name: string, environment: string, author: string): Promise => { @@ -120,80 +122,65 @@ const getRecords = async (id: string, name: string, environment: string, author: const nextIds: string[] = tm.jukebox.queue.slice(0, 5).map(a => a.id) const players = tm.players.list const rawDedis: any[] | Error = await client.call('dedimania.CurrentChallenge', - [ - { string: id }, - { string: name }, - { string: environment }, - { string: author }, - { string: 'TMF' }, - { int: recordModeEnum[leaderboard] }, - { - struct: { - SrvName: { string: cfg.name }, - Comment: { string: cfg.comment }, - Private: { boolean: cfg.password !== '' }, - SrvIP: { string: '127.0.0.1' }, // Can actually get the real server IP via cfg.ipAddress - SrvPort: { string: '5000' }, - XmlRpcPort: { string: '5000' }, - NumPlayers: { int: players.filter(a => !a.isSpectator).length }, - MaxPlayers: { int: cfg.currentMaxPlayers }, - NumSpecs: { int: players.filter(a => a.isSpectator).length }, - MaxSpecs: { int: cfg.currentMaxPlayers }, - LadderMode: { int: cfg.currentLadderMode }, - NextFiveUID: { string: nextIds.join('/') } - } - }, - { int: config.dediCount }, - { array: getPlayersArray() } - ]) + [{ string: id }, { string: name }, { string: environment }, { string: author }, { string: 'TMF' }, + { int: recordModeEnum[leaderboard] }, { + struct: { + SrvName: { string: cfg.name }, + Comment: { string: cfg.comment }, + Private: { boolean: cfg.password !== '' }, + SrvIP: { string: '127.0.0.1' }, // Can actually get the real server IP via cfg.ipAddress + SrvPort: { string: '5000' }, + XmlRpcPort: { string: '5000' }, + NumPlayers: { int: players.filter(a => !a.isSpectator).length }, + MaxPlayers: { int: cfg.currentMaxPlayers }, + NumSpecs: { int: players.filter(a => a.isSpectator).length }, + MaxSpecs: { int: cfg.currentMaxPlayers }, + LadderMode: { int: cfg.currentLadderMode }, + NextFiveUID: { string: nextIds.join('/') } + } + }, { int: config.dediCount }, { array: getPlayersArray() }]) if (rawDedis instanceof Error) { - tm.log.error(`Failed to fetch dedimania records for map ${tm.utils.strip(name)} (${id}), received error:`, rawDedis.message) + tm.log.error(`Failed to fetch dedimania records for map ${tm.utils.strip(name)} (${id}), received error:`, + rawDedis.message) return - } - else if (rawDedis?.[0]?.Records === undefined) { + } else if (rawDedis?.[0]?.Records === undefined) { tm.log.error(`Failed to fetch dedimania records for map ${tm.utils.strip(name)} (${id}), received empty response`) return } - currentDedis = rawDedis[0].Records.map((a: any): DediRecord => - ({ - login: a.Login, nickname: a.NickName, time: a.Best, - checkpoints: a.Checks.slice(0, a.Checks.length - 1), leaderboard, - isLapRecord: uploadLaps - })) + currentDedis = rawDedis[0].Records.map((a: any): DediRecord => ({ + login: a.Login, + nickname: a.NickName, + time: a.Best, + checkpoints: a.Checks.slice(0, a.Checks.length - 1), + leaderboard, + isLapRecord: uploadLaps + })) if (config.syncName && currentDedis.length > 0) { void tm.updatePlayerInfo(...currentDedis) } emitFetchEvent(currentDedis) } -const sendRecords = async (mapId: string, name: string, environment: string, author: string, checkpointsAmount: number): Promise => { +const sendRecords = async (mapId: string, name: string, environment: string, author: string, + checkpointsAmount: number): Promise => { if (!client.connected || newDedis.length === 0) { return } const recordsArray: any = [] for (const d of newDedis) { - recordsArray.push( - { - struct: { - Login: { string: d.login }, - Best: { int: d.time }, - Checks: { string: [...d.checkpoints, d.time].join(',') } - } + recordsArray.push({ + struct: { + Login: { string: d.login }, + Best: { int: d.time }, + Checks: { string: [...d.checkpoints, d.time].join(',') } } - ) + }) } const status: any[] | Error = await client.call('dedimania.ChallengeRaceTimes', - [ - { string: mapId }, - { string: name }, - { string: environment }, - { string: author }, - { string: 'TMF' }, - { int: recordModeEnum[leaderboard] }, - { int: checkpointsAmount }, - { int: config.dediCount }, - { array: recordsArray } - ] - ) - if (status instanceof Error) { tm.log.error(`Failed to send dedimania records for map ${tm.utils.strip(name)} (${mapId})`, status.message) } + [{ string: mapId }, { string: name }, { string: environment }, { string: author }, { string: 'TMF' }, + { int: recordModeEnum[leaderboard] }, { int: checkpointsAmount }, { int: config.dediCount }, + { array: recordsArray }]) + if (status instanceof Error) { + tm.log.error(`Failed to send dedimania records for map ${tm.utils.strip(name)} (${mapId})`, status.message) + } } const addRecord = (player: Omit, @@ -203,21 +190,30 @@ const addRecord = (player: Omit a.time <= time).length + 1 if (position > config.dediCount || time > (pb ?? Infinity)) { return } if (pb === undefined) { - const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, undefined, position, undefined) - currentDedis.splice(position - 1, 0, - { - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps - }) + const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, undefined, position, + undefined) + currentDedis.splice(position - 1, 0, { + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps + }) newDedis.push({ - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps }) tm.log.info(getLogString(undefined, position, undefined, time, player)) emitRecordEvent(dediRecordInfo) } else if (time === pb) { const previousPosition: number = currentDedis.findIndex(a => a.login === player.login) + 1 - const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, time, previousPosition, previousPosition) + const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, time, previousPosition, + previousPosition) tm.log.info(getLogString(previousPosition, previousPosition, time, time, player)) emitRecordEvent(dediRecordInfo) } else if (time < pb) { @@ -227,17 +223,25 @@ const addRecord = (player: Omit a.login === player.login) + 1) + const dediRecordInfo: NewDediRecord = constructRecordObject(player, checkpoints, time, previousTime, position, + currentDedis.findIndex(a => a.login === player.login) + 1) currentDedis = currentDedis.filter(a => a.login !== player.login) - currentDedis.splice(position - 1, 0, - { - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps - }) + currentDedis.splice(position - 1, 0, { + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps + }) newDedis = newDedis.filter(a => a.login !== player.login) newDedis.push({ - login: player.login, time: time, nickname: player.nickname, - checkpoints: [...checkpoints], leaderboard, isLapRecord: uploadLaps + login: player.login, + time: time, + nickname: player.nickname, + checkpoints: [...checkpoints], + leaderboard, + isLapRecord: uploadLaps }) tm.log.info(getLogString(previousIndex + 1, position, previousTime, time, player)) emitRecordEvent(dediRecordInfo) @@ -251,28 +255,22 @@ const updateServerPlayers = (): void => { const nextIds: string[] = tm.jukebox.queue.slice(0, 5).map(a => a.id) const players = tm.players.list const status: any[] | Error = await client.call('dedimania.UpdateServerPlayers', - [ - { string: 'TMF' }, - { int: tm.config.game.gameMode }, - { - struct: { - SrvName: { string: cfg.name }, - Comment: { string: cfg.comment }, - Private: { boolean: cfg.password !== '' }, - SrvIP: { string: '127.0.0.1' }, - SrvPort: { string: '5000' }, - XmlRpcPort: { string: '5000' }, - NumPlayers: { int: players.filter(a => !a.isSpectator).length }, - MaxPlayers: { int: cfg.currentMaxPlayers }, - NumSpecs: { int: players.filter(a => a.isSpectator).length }, - MaxSpecs: { int: cfg.currentMaxPlayers }, - LadderMode: { int: cfg.currentLadderMode }, - NextFiveUID: { string: nextIds.join('/') } - } - }, - { array: getPlayersArray() } - ] - ) + [{ string: 'TMF' }, { int: tm.config.game.gameMode }, { + struct: { + SrvName: { string: cfg.name }, + Comment: { string: cfg.comment }, + Private: { boolean: cfg.password !== '' }, + SrvIP: { string: '127.0.0.1' }, + SrvPort: { string: '5000' }, + XmlRpcPort: { string: '5000' }, + NumPlayers: { int: players.filter(a => !a.isSpectator).length }, + MaxPlayers: { int: cfg.currentMaxPlayers }, + NumSpecs: { int: players.filter(a => a.isSpectator).length }, + MaxSpecs: { int: cfg.currentMaxPlayers }, + LadderMode: { int: cfg.currentLadderMode }, + NextFiveUID: { string: nextIds.join('/') } + } + }, { array: getPlayersArray() }]) if (status instanceof Error) { tm.log.error('Failed to update dedimania status', status.message) } }, config.updateInterval * 1000) } @@ -281,85 +279,100 @@ const updateServerPlayers = (): void => { * Updates the player information and server player list on the dedimania website * @param player Player object */ -const playerJoin = async (player: - { login: string, nickname: string, region: string, isSpectator: boolean, ladderRank: number }): Promise => { +const playerJoin = async (player: { + login: string, + nickname: string, + region: string, + isSpectator: boolean, + ladderRank: number +}): Promise => { if (!client.connected) { return } const status: any[] | Error = await client.call('dedimania.PlayerArrive', - [ - { string: 'TMF' }, - { string: player.login }, - { string: player.nickname }, - { string: tm.utils.countryToCode(player.region.split('|')[0]) }, - { string: '' }, // TEAMNAME - { int: player.ladderRank }, - { boolean: player.isSpectator }, - { boolean: false } // OFFICIAL MODE ALWAYS FALSE - ] - ) - if (status instanceof Error) { tm.log.error(`Failed to update dedimania player information for ${tm.utils.strip(player.nickname)} (${player.login})`, status.message) } + [{ string: 'TMF' }, { string: player.login }, { string: player.nickname }, + { string: tm.utils.countryToCode(player.region.split('|')[0]) }, { string: '' }, // TEAMNAME + { int: player.ladderRank }, { boolean: player.isSpectator }, { boolean: false } // OFFICIAL MODE ALWAYS FALSE + ]) + if (status instanceof Error) { + tm.log.error( + `Failed to update dedimania player information for ${tm.utils.strip(player.nickname)} (${player.login})`, + status.message) + } } /** * Updates the server player list on the dedimania website * @param player Player object */ -const playerLeave = async (player: { login: string, nickname: string }): Promise => { +const playerLeave = async (player: { + login: string, + nickname: string +}): Promise => { if (!client.connected) { return } const status: any[] | Error = await client.call('dedimania.PlayerLeave', - [ - { string: 'TMF' }, - { string: player.login } - ]) - if (status instanceof Error) { tm.log.error(`Failed to update player information for ${tm.utils.strip(player.nickname)} (${player.login})`, status.message) } + [{ string: 'TMF' }, { string: player.login }]) + if (status instanceof Error) { + tm.log.error(`Failed to update player information for ${tm.utils.strip(player.nickname)} (${player.login})`, + status.message) + } } const getPlayersArray = (): any[] => { const players: tm.Player[] = tm.players.list const arr: any[] = [] for (const player of players) { - arr.push( - [ - { - struct: { - Login: { string: player.login }, - Nation: { string: player.countryCode }, - TeamName: { string: '' }, - TeamId: { int: -1 }, - IsSpec: { boolean: player.isSpectator }, - Ranking: { int: player.ladderRank }, - IsOff: { boolean: false } // OFFICIAL MODE ALWAYS FALSE - } - } - ] - ) + arr.push([{ + struct: { + Login: { string: player.login }, + Nation: { string: player.countryCode }, + TeamName: { string: '' }, + TeamId: { int: -1 }, + IsSpec: { boolean: player.isSpectator }, + Ranking: { int: player.ladderRank }, + IsOff: { boolean: false } // OFFICIAL MODE ALWAYS FALSE + } + }]) } return arr } const constructRecordObject = (player: Omit, - checkpoints: number[], time: number, previousTime: number | undefined, position: number, previousPosition: number | undefined): NewDediRecord => { + checkpoints: number[], time: number, previousTime: number | undefined, position: number, + previousPosition: number | undefined): NewDediRecord => { return { ...player, time, checkpoints, position, - previous: (previousTime && previousPosition) ? { time: previousTime, position: previousPosition } : undefined, + previous: (previousTime && previousPosition) ? { + time: previousTime, + position: previousPosition + } : undefined, leaderboard, isLapRecord: uploadLaps } } -const getLogString = (previousPosition: number | undefined, position: number, - previousTime: number | undefined, time: number, player: { login: string, nickname: string }): string[] => { - const rs = tm.utils.getRankingString({ position, time }, (previousPosition && previousTime) ? - { time: previousTime, position: previousPosition } : undefined) - return [`${tm.utils.strip(player.nickname)} (${player.login}) has ${rs.status} the ${tm.utils.getOrdinalSuffix(position)} dedimania record. Time: ${tm.utils.getTimeString(time)}${rs.difference !== undefined ? ` (-${rs.difference})` : ``}`] +const getLogString = (previousPosition: number | undefined, position: number, previousTime: number | undefined, + time: number, player: { + login: string, + nickname: string + }): string[] => { + const rs = tm.utils.getRankingString({ + position, + time + }, (previousPosition && previousTime) ? { + time: previousTime, + position: previousPosition + } : undefined) + return [`${tm.utils.strip(player.nickname)} (${player.login}) has ${rs.status} the ${tm.utils.getOrdinalSuffix( + position)} dedimania record. Time: ${tm.utils.getTimeString(time)}${rs.difference !== undefined ? + ` (-${rs.difference})` : ``}`] } if (config.isEnabled) { tm.addListener('Startup', (): void => { - if(tm.getGameMode() !== 'Stunts') { + if (tm.getGameMode() !== 'Stunts') { tm.log.trace('Connecting to Dedimania...') void initialize() } @@ -371,7 +384,7 @@ if (config.isEnabled) { if (prevLb === 'Disabled' && leaderboard !== 'Disabled') { void initialize() return - } + } if (prevLb !== 'Disabled' && leaderboard === 'Disabled') { currentDedis.length = 0 newDedis.length = 0 @@ -381,12 +394,14 @@ if (config.isEnabled) { if (uploadLaps && tm.getGameMode() !== 'Laps') { tm.sendMessage(config.modifiedLapsMessage) } - void getRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), info.author) + void getRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), + info.author) }, true) tm.addListener('EndMap', (info): void => { const cpAmount = uploadLaps ? info.checkpointsPerLap : info.checkpointsAmount - void sendRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), info.author, cpAmount) + void sendRecords(info.id, info.name, (tm.utils.environmentToNadeoEnvironment(info.environment) as string), + info.author, cpAmount) }) tm.addListener('PlayerJoin', (info): void => { @@ -542,7 +557,7 @@ export const dedimania = { return client.connected }, - /** + /** * Whether the plugin is uploading lap times instead of total race time on the current map. * True if the server is in Laps mode or if it's in Cup/Rounds/Teams mode with modified laps amount. */ @@ -551,7 +566,7 @@ export const dedimania = { }, /** - * Dedimania leaderboard ('Rounds' or 'TimeAttack') from which the current records were + * Dedimania leaderboard ('Rounds' or 'TimeAttack') from which the current records were * fetched and to which the new records will be uploaded. * The plugin fetches and uploads records to the Rounds Mode leaderboard in Cup/Rounds/Teams mode * if the laps amount is not modified, otherwise to the TimeAttack leaderboard. diff --git a/plugins/dedimania/DedimaniaClient.ts b/plugins/dedimania/DedimaniaClient.ts index 50f8e12a..d461033a 100644 --- a/plugins/dedimania/DedimaniaClient.ts +++ b/plugins/dedimania/DedimaniaClient.ts @@ -12,7 +12,10 @@ export class DedimaniaClient { private _connected: boolean = false private password: string | undefined - async connect(host: string, port: number): Promise { + async connect(host: string, port: number): Promise { if (process.env.DEDIMANIA_PASSWORD === undefined) { return { isAuthenticationError: true, @@ -27,7 +30,10 @@ export class DedimaniaClient { const packmask: string | Error = await tm.client.call('GetServerPackMask') as any if (packmask instanceof Error) { tm.log.error('Failed to fetch server packmask', packmask.message) - return { error: packmask, isAuthenticationError: false } + return { + error: packmask, + isAuthenticationError: false + } } const request: DedimaniaRequest = new DedimaniaRequest('dedimania.Authenticate', [{ struct: { @@ -50,7 +56,8 @@ export class DedimaniaClient { this.receivingResponse = false if (this.response.isError !== null) { resolve({ - error: new Error(`Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`), + error: new Error( + `Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`), isAuthenticationError: false }) } else { @@ -109,7 +116,8 @@ export class DedimaniaClient { const poll = (): void => { if (this.response.status === 'completed') { if (this.response.isError === true) { - resolve(new Error(`Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`)) + resolve(new Error( + `Dedimania server responded with an error ${this.response.errorString} Code: ${this.response.errorCode}`)) } else { resolve(this.response.json) } diff --git a/plugins/dedimania/DedimaniaRequest.ts b/plugins/dedimania/DedimaniaRequest.ts index a6d83862..125504e0 100644 --- a/plugins/dedimania/DedimaniaRequest.ts +++ b/plugins/dedimania/DedimaniaRequest.ts @@ -5,21 +5,14 @@ export class DedimaniaRequest { private sessionKey?: string private xmlBuffer: Buffer private _buffer: Buffer = Buffer.from( - 'POST /Dedimania HTTP/1.1\n' + - 'Host: dedimania.net\n' + - 'User-Agent: XMLaccess\n' + - 'Cache-Control: no-cache\n' + - 'Accept-Encoding: gzip\n' + - 'Content-Encoding: gzip\n' + - 'Content-type: text/xml; charset=UTF-8\n' - ) + 'POST /Dedimania HTTP/1.1\n' + 'Host: dedimania.net\n' + 'User-Agent: XMLaccess\n' + 'Cache-Control: no-cache\n' + 'Accept-Encoding: gzip\n' + 'Content-Encoding: gzip\n' + 'Content-type: text/xml; charset=UTF-8\n') /** - * Prepares XML string for a dedimania request. - * @param method dedimania method - * @param params parameters - * @param sessionKey dedimania session key - */ + * Prepares XML string for a dedimania request. + * @param method dedimania method + * @param params parameters + * @param sessionKey dedimania session key + */ constructor(method: string, params: tm.CallParams[], sessionKey?: string) { let xml: string = `${method}` for (const param of params) { @@ -32,17 +25,12 @@ export class DedimaniaRequest { this.sessionKey = sessionKey const gzip: Buffer = zlib.gzipSync(this.xmlBuffer) if (this.sessionKey === undefined) { - this._buffer = Buffer.concat([ - this._buffer, - Buffer.from(`Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\n\r\n`), - gzip - ]) + this._buffer = Buffer.concat([this._buffer, Buffer.from( + `Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\n\r\n`), gzip]) } else { - this._buffer = Buffer.concat([ - this._buffer, - Buffer.from(`Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\nCookie: PHPSESSID=${this.sessionKey}\r\n\r\n`), - gzip - ]) + this._buffer = Buffer.concat([this._buffer, Buffer.from( + `Content-length: ${gzip.length}\r\nKeep-Alive: timeout=600, max=2000\r\nConnection: Keep-Alive\r\nCookie: PHPSESSID=${this.sessionKey}\r\n\r\n`), + gzip]) } } @@ -53,7 +41,7 @@ export class DedimaniaRequest { if (value === undefined) { return new Error(`Received undefined while creating dedimania XML request, expected ${type}.`) } - switch (type) { + switch(type) { case 'boolean': return `${value === true ? '1' : '0'}` case 'int': diff --git a/plugins/dedimania/DedimaniaResponse.ts b/plugins/dedimania/DedimaniaResponse.ts index 36da4443..410be00b 100644 --- a/plugins/dedimania/DedimaniaResponse.ts +++ b/plugins/dedimania/DedimaniaResponse.ts @@ -98,10 +98,11 @@ export class DedimaniaResponse { const changeType: any = (value: any, type: string): any => { const arr: any[] = [] const obj: any = {} - switch (type) { + switch(type) { case 'boolean': return value === '1' - case 'int': case 'i4': + case 'int': + case 'i4': return parseInt(value) case 'double': return parseFloat(value) diff --git a/plugins/dedimania/ui/DediCps.component.ts b/plugins/dedimania/ui/DediCps.component.ts index 97687a55..da2373a1 100644 --- a/plugins/dedimania/ui/DediCps.component.ts +++ b/plugins/dedimania/ui/DediCps.component.ts @@ -4,7 +4,7 @@ */ import { dedimania } from "../../dedimania/Dedimania.js" -import { componentIds, Paginator, Grid, centeredText, closeButton, getCpTypes, type GridCellFunction, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, getCpTypes, Grid, type GridCellFunction, Paginator, PopupWindow } from '../../ui/UI.js' import config from './DediCps.config.js' export default class DediCps extends PopupWindow { @@ -24,8 +24,10 @@ export default class DediCps extends PopupWindow { constructor() { super(componentIds.dediCps, config.icon, config.title, config.navbar) const records = dedimania.records - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(records.length / this.entries)) - this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, true) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(records.length / this.entries)) + this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, + true) this.paginator.onPageChange = (login: string): void => { this.getPagesAndOpen(login) } @@ -54,7 +56,10 @@ export default class DediCps extends PopupWindow { this.getPagesAndOpen(info.login) } - protected constructContent(login: string, params: { page: number, cpPage: number }): string { + protected constructContent(login: string, params: { + page: number, + cpPage: number + }): string { const records = dedimania.records const [cpIndex, cpsToDisplay] = this.getCpIndexAndAmount(params.cpPage) const playerIndex: number = (params.page - 1) * this.entries - 1 @@ -94,41 +99,44 @@ export default class DediCps extends PopupWindow { let grid: Grid let headers: GridCellFunction[] = [] if (params.cpPage === 1) { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText((j - this.startCellsOnFirstPage).toString(), w, h)), - (i, j, w, h) => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), - ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i, j, w, h) => centeredText(' Nickname ', w, h), + (i, j, w, h) => centeredText(' Login ', w, h), ...new Array(cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => centeredText( + (j - this.startCellsOnFirstPage).toString(), w, h)), (i, j, w, h) => centeredText(' Finish ', w, h), + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), + ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } else { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i: number, j: number, w: number, h: number): string => centeredText(' Nickname ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText((j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), + ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText( + (j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), (i: number, j: number, w: number, h: number): string => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnNextPages - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), - ...new Array(this.cpsOnNextPages + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), + ...new Array(this.cpsOnNextPages + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } const arr = [...headers] for (let i = 0; i < entriesToDisplay; i++) { if (params.cpPage === 1) { - arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) } else { - arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) } } return grid.constructXml(arr) } - protected constructFooter(login: string, params: { page: number, cpPage: number }): string { + protected constructFooter(login: string, params: { + page: number, + cpPage: number + }): string { const w = (this.cpPaginator.buttonW + this.cpPaginator.margin) * this.cpPaginator.buttonCount + config.cpPaginatorMargin return `${closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin)} ${this.paginator.constructXml(params.page)} @@ -138,8 +146,8 @@ export default class DediCps extends PopupWindow { } private getCpIndexAndAmount(cpPage: number): [number, number] { - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap - 1 : tm.maps.current.checkpointsAmount - 1 + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap - 1 : + tm.maps.current.checkpointsAmount - 1 let cpsToDisplay: number = Math.min(cpAmount, this.cpsOnFirstPage) let cpIndex = 0 if (cpPage > 1) { @@ -156,7 +164,10 @@ export default class DediCps extends PopupWindow { const page = this.paginator.getPageByLogin(login) const cpPage = this.cpPaginator.getPageByLogin(login) const pageCount: number = this.paginator.pageCount - this.displayToPlayer(login, { page, cpPage }, `${page}/${Math.max(1, pageCount)}`) + this.displayToPlayer(login, { + page, + cpPage + }, `${page}/${Math.max(1, pageCount)}`) } private reRender(): void { @@ -168,8 +179,8 @@ export default class DediCps extends PopupWindow { private calculateCpPages(): number { let cpPages = 1 - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap - 1 : tm.maps.current.checkpointsAmount - 1 + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap - 1 : + tm.maps.current.checkpointsAmount - 1 for (let i = 1; i < cpAmount; i++) { if (cpPages === 1 && i >= this.cpsOnFirstPage) { cpPages++ diff --git a/plugins/dedimania/ui/DediCps.config.js b/plugins/dedimania/ui/DediCps.config.js index 3b43b57c..a1d2ffe0 100644 --- a/plugins/dedimania/ui/DediCps.config.js +++ b/plugins/dedimania/ui/DediCps.config.js @@ -1,35 +1,45 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { entries: 15, cpsOnFirstPage: 10, icon: icons.chartDedi, - title: "Dedi Checkpoints", + title: 'Dedi Checkpoints', selfColour: `${p.green}F`, cpColours: { best: `${p.green}F`, worst: `${p.red}F`, equal: `${p.yellow}F` }, - navbar: [ - { name: 'Dedi Sectors', actionId: ids.dediSectors }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - { name: 'Live Checkpoints', actionId: ids.liveCps }, - { name: 'Live Sectors', actionId: ids.liveSectors } - ], + navbar: [{ + name: 'Dedi Sectors', + actionId: ids.dediSectors + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }, { + name: 'Live Checkpoints', + actionId: ids.liveCps + }, { + name: 'Live Sectors', + actionId: ids.liveSectors + }], cpPaginatorMargin: 0.25, startCellWidth: 2, indexCellWidth: 1, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, command: { aliases: ['dcp', 'dedicptms', 'dedirecs'], help: `Display current map dedimania checkpoints.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/dedimania/ui/DediSectors.component.ts b/plugins/dedimania/ui/DediSectors.component.ts index 44b68710..d116925c 100644 --- a/plugins/dedimania/ui/DediSectors.component.ts +++ b/plugins/dedimania/ui/DediSectors.component.ts @@ -4,7 +4,7 @@ */ import { dedimania, type DediRecord } from '../../dedimania/Dedimania.js' -import { componentIds, Paginator, Grid, centeredText, closeButton, getCpTypes, type GridCellFunction, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, getCpTypes, Grid, type GridCellFunction, Paginator, PopupWindow } from '../../ui/UI.js' import config from './DediSectors.config.js' export default class DediSectors extends PopupWindow { @@ -24,8 +24,10 @@ export default class DediSectors extends PopupWindow { constructor() { super(componentIds.dediSectors, config.icon, config.title, config.navbar) const records = dedimania.records - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(records.length / this.entries)) - this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, true) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(records.length / this.entries)) + this.cpPaginator = new Paginator(this.openId + 10, this.windowWidth, this.footerHeight, this.calculateCpPages(), 1, + true) this.paginator.onPageChange = (login: string): void => { this.getPagesAndOpen(login) } @@ -54,10 +56,16 @@ export default class DediSectors extends PopupWindow { this.getPagesAndOpen(info.login) } - protected constructContent(login: string, params: { page: number, cpPage: number }): string { + protected constructContent(login: string, params: { + page: number, + cpPage: number + }): string { const records: DediRecord[] = [] for (const e of dedimania.records) { - records.push({ ...e, checkpoints: [...e.checkpoints, e.time].map((a, i, arr) => i === 0 ? a : a - arr[i - 1]) }) + records.push({ + ...e, + checkpoints: [...e.checkpoints, e.time].map((a, i, arr) => i === 0 ? a : a - arr[i - 1]) + }) } const [cpIndex, cpsToDisplay] = this.getCpIndexAndAmount(params.cpPage) const playerIndex: number = (params.page - 1) * this.entries - 1 @@ -97,42 +105,44 @@ export default class DediSectors extends PopupWindow { let grid: Grid let headers: GridCellFunction[] = [] if (params.cpPage === 1) { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText((j - this.startCellsOnFirstPage).toString(), w, h)), - (i, j, w, h) => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), - ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i, j, w, h) => centeredText(' Nickname ', w, h), + (i, j, w, h) => centeredText(' Login ', w, h), ...new Array(cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => centeredText( + (j - this.startCellsOnFirstPage).toString(), w, h)), (i, j, w, h) => centeredText(' Finish ', w, h), + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnFirstPage).fill(this.startCellWidth), + ...new Array(this.cpsOnFirstPage + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } else { - headers = [ - (i, j, w, h) => centeredText(' Lp. ', w, h), + headers = [(i, j, w, h) => centeredText(' Lp. ', w, h), (i: number, j: number, w: number, h: number): string => centeredText(' Nickname ', w, h), - ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => - centeredText((j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), + ...new Array(cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => centeredText( + (j + cpIndex - (this.startCellsOnNextPages)).toString(), w, h)), (i: number, j: number, w: number, h: number): string => centeredText(' Finish ', w, h), - ...new Array(this.cpsOnNextPages - cpsToDisplay).fill((i: number, j: number, w: number, h: number): string => '') - ] - grid = new Grid(this.contentWidth, this.contentHeight, [this.indexCellWidth, - ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), ...new Array(this.cpsOnNextPages + 1).fill(1)], - new Array(this.entries + 1).fill(1), config.grid) + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill( + (i: number, j: number, w: number, h: number): string => '')] + grid = new Grid(this.contentWidth, this.contentHeight, + [this.indexCellWidth, ...new Array(this.startCellsOnNextPages).fill(this.startCellWidth), + ...new Array(this.cpsOnNextPages + 1).fill(1)], new Array(this.entries + 1).fill(1), config.grid) } const arr = [...headers] for (let i = 0; i < entriesToDisplay; i++) { if (params.cpPage === 1) { - arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, loginCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnFirstPage - cpsToDisplay).fill(emptyCell)) } else { - arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) + arr.push(indexCell, nickNameCell, ...new Array(cpsToDisplay).fill(cell), finishCell, + ...new Array(this.cpsOnNextPages - cpsToDisplay).fill(emptyCell)) } } return grid.constructXml(arr) } - protected constructFooter(login: string, params: { page: number, cpPage: number }): string { + protected constructFooter(login: string, params: { + page: number, + cpPage: number + }): string { const w = (this.cpPaginator.buttonW + this.cpPaginator.margin) * this.cpPaginator.buttonCount + config.cpPaginatorMargin return `${closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin)} ${this.paginator.constructXml(params.page)} @@ -142,8 +152,7 @@ export default class DediSectors extends PopupWindow { } private getCpIndexAndAmount(cpPage: number): [number, number] { - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount let cpsToDisplay: number = Math.min(cpAmount, this.cpsOnFirstPage) let cpIndex = 0 if (cpPage > 1) { @@ -160,7 +169,10 @@ export default class DediSectors extends PopupWindow { const page = this.paginator.getPageByLogin(login) const cpPage = this.cpPaginator.getPageByLogin(login) const pageCount: number = this.paginator.pageCount - this.displayToPlayer(login, { page, cpPage }, `${page}/${Math.max(1, pageCount)}`) + this.displayToPlayer(login, { + page, + cpPage + }, `${page}/${Math.max(1, pageCount)}`) } private reRender(): void { @@ -172,8 +184,7 @@ export default class DediSectors extends PopupWindow { private calculateCpPages(): number { let cpPages = 1 - const cpAmount = dedimania.isUploadingLaps ? - tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount + const cpAmount = dedimania.isUploadingLaps ? tm.maps.current.checkpointsPerLap : tm.maps.current.checkpointsAmount for (let i = 1; i < cpAmount; i++) { if (cpPages === 1 && i >= this.cpsOnFirstPage) { cpPages++ diff --git a/plugins/dedimania/ui/DediSectors.config.js b/plugins/dedimania/ui/DediSectors.config.js index 9199269b..22841bed 100644 --- a/plugins/dedimania/ui/DediSectors.config.js +++ b/plugins/dedimania/ui/DediSectors.config.js @@ -1,35 +1,45 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { entries: 15, cpsOnFirstPage: 10, icon: icons.chartDedi, - title: "Dedi Sectors", + title: 'Dedi Sectors', selfColour: `${p.green}F`, cpColours: { best: `${p.green}F`, worst: `${p.red}F`, equal: `${p.yellow}F` }, - navbar: [ - { name: 'Dedi Checkpoints', actionId: ids.dediCps }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - { name: 'Live Checkpoints', actionId: ids.liveCps }, - { name: 'Live Sectors', actionId: ids.liveSectors } - ], + navbar: [{ + name: 'Dedi Checkpoints', + actionId: ids.dediCps + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }, { + name: 'Live Checkpoints', + actionId: ids.liveCps + }, { + name: 'Live Sectors', + actionId: ids.liveSectors + }], cpPaginatorMargin: 0.25, startCellWidth: 2, indexCellWidth: 1, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, command: { aliases: ['dsc', 'dedisectms'], help: `Display current map dedimania sectors.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/donations/Config.js b/plugins/donations/Config.js index a5b9454c..0cf4449f 100644 --- a/plugins/donations/Config.js +++ b/plugins/donations/Config.js @@ -2,8 +2,7 @@ const p = tm.utils.palette export default { paymentFail: `${p.error}Failed to process payment.`, - paymentSuccess: `${p.highlight}#{nickname}${p.donation} ` - + `donated ${p.highlight}#{amount}C${p.donation} to the server.`, + paymentSuccess: `${p.highlight}#{nickname}${p.donation} ` + `donated ${p.highlight}#{amount}C${p.donation} to the server.`, minimalAmount: 50, amountTooLow: `${p.error}Amount of coppers too low. ${p.highlight}50${p.error} is the minimum.`, command: { @@ -11,4 +10,4 @@ export default { help: 'Donate coppers to the server.', privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/donations/Donations.ts b/plugins/donations/Donations.ts index b9fd676b..7236564f 100644 --- a/plugins/donations/Donations.ts +++ b/plugins/donations/Donations.ts @@ -10,7 +10,9 @@ interface DonationInfo { }[] } -const listeners: ((info: DonationInfo & { readonly amount: number }) => void)[] = [] +const listeners: ((info: DonationInfo & { + readonly amount: number +}) => void)[] = [] const nicknameChangeListeners: ((info: DonationInfo[]) => void)[] = [] let onlineDonators: DonationInfo[] = [] await tm.db.query(`CREATE TABLE IF NOT EXISTS donations( @@ -75,9 +77,13 @@ async function getFromDB(logins: string | string[]): Promise acc += cur.amount, 0), - history: res.map(a => ({ amount: a.amount, date: a.date })) + history: res.map(a => ({ + amount: a.amount, + date: a.date + })) } } const ids = await tm.db.getPlayerId(logins) @@ -95,9 +101,13 @@ async function getFromDB(logins: string | string[]): Promise a.login === login) if (arr.length === 0) { continue } ret.push({ - login, nickname: arr[0]?.nickname, + login, + nickname: arr[0]?.nickname, sum: arr.reduce((acc, cur) => acc += cur.amount, 0), - history: arr.map(a => ({ amount: a.amount, date: a.date })) + history: arr.map(a => ({ + amount: a.amount, + date: a.date + })) }) } return ret @@ -115,7 +125,10 @@ const addToDB = async (login: string, amount: number, date: Date): Promise tm.commands.add({ aliases: ['donate'], help: 'Donate coppers to the server.', - params: [{ name: 'amount', type: 'int' }], + params: [{ + name: 'amount', + type: 'int' + }], callback(info, amount: number) { donate(info.login, info.nickname, amount) }, @@ -124,7 +137,7 @@ tm.commands.add({ /** * Donate coppers to server - * @param payerLogin Login of the player + * @param payerLogin Login of the player * @param payerNickname Nickname of the player * @param amount Amount of coppers to donate * @returns True if successful, false if player refuses payment, Error if dedicated server call fails @@ -151,17 +164,26 @@ const donate = async (payerLogin: string, payerNickname: string, amount: number) if (info === undefined) { if (isOnline) { info = { - login: payerLogin, nickname: payerNickname, sum: 0, history: [] + login: payerLogin, + nickname: payerNickname, + sum: 0, + history: [] } onlineDonators.push(info) } else { return true } } - info.history.push({ amount, date }) + info.history.push({ + amount, + date + }) info.sum += amount for (const e of listeners) { - e({ ...info, amount }) + e({ + ...info, + amount + }) } return true } @@ -205,7 +227,9 @@ export const donations = { * Add a callback function to execute on donation * @param callback Function to execute on event. It takes donation object as a parameter */ - onDonation(callback: (info: DonationInfo & { readonly amount: number }) => void) { + onDonation(callback: (info: DonationInfo & { + readonly amount: number + }) => void) { listeners.push(callback) }, diff --git a/plugins/freezone/Config.js b/plugins/freezone/Config.js index 9a2030e4..b118b315 100644 --- a/plugins/freezone/Config.js +++ b/plugins/freezone/Config.js @@ -1,9 +1,6 @@ export default { - isEnabled: false, - // Manialive hash, static value that never changes - manialiveHash: '6f116833b419fe7cb9c912fdaefb774845f60e79', - // Last Manialive (for TMF at least) version release - manialiveVersion: 239, - // Freezone Manialive URL (aka Trackmania Webservices) + isEnabled: false, // Manialive hash, static value that never changes + manialiveHash: '6f116833b419fe7cb9c912fdaefb774845f60e79', // Last Manialive (for TMF at least) version release + manialiveVersion: 239, // Freezone Manialive URL (aka Trackmania Webservices) manialiveUrl: 'ws.trackmania.com' -} \ No newline at end of file +} diff --git a/plugins/freezone/Freezone.ts b/plugins/freezone/Freezone.ts index 52fa8292..dfc52736 100644 --- a/plugins/freezone/Freezone.ts +++ b/plugins/freezone/Freezone.ts @@ -11,14 +11,11 @@ const sendLive = async (): Promise => { const cfg: tm.ServerInfo = tm.config.server // Data object in any because TS coping language const data = { - serverLogin: cfg.login, - // Remove colours here so that we won't have to later + serverLogin: cfg.login, // Remove colours here so that we won't have to later serverName: tm.utils.strip(cfg.name, true), serverVersion: [cfg.game, cfg.version, cfg.build].join(), - manialiveVersion: config.manialiveVersion, - // Check if > 40 and set to 40 in that case - maxPlayers: cfg.currentMaxPlayers > 40 ? 40 : cfg.currentMaxPlayers, - // Public is 1, private is 0 + manialiveVersion: config.manialiveVersion, // Check if > 40 and set to 40 in that case + maxPlayers: cfg.currentMaxPlayers > 40 ? 40 : cfg.currentMaxPlayers, // Public is 1, private is 0 visibility: cfg.password.length === 0 ? 1 : 0, classHash: config.manialiveHash } @@ -34,7 +31,7 @@ const sendLive = async (): Promise => { path: `/freezone/live/`, method: 'POST', headers: { - 'Authorization': auth, + 'Authorization': auth } } return new Promise((resolve, reject): void => { @@ -79,8 +76,7 @@ if (config.isEnabled) { tm.addListener('Startup', async (): Promise => { tm.log.trace('Connecting to ManiaLive...') const status: true | Error = await initialize() - if (status instanceof Error) { tm.log.error(status.message) } - else { tm.log.trace('Connected to ManiaLive') } + if (status instanceof Error) { tm.log.error(status.message) } else { tm.log.trace('Connected to ManiaLive') } }) } diff --git a/plugins/info_messages/Config.js b/plugins/info_messages/Config.js index 41f2868d..86da6f10 100644 --- a/plugins/info_messages/Config.js +++ b/plugins/info_messages/Config.js @@ -1,17 +1,12 @@ const p = tm.utils.palette export default { - isEnabled: true, - // All the info messages will be prefixed with this text - messagePrefix: `${p.record}[${p.highlight}INFO${p.record}]`, - // Default formatting for the info messages + isEnabled: true, // All the info messages will be prefixed with this text + messagePrefix: `${p.record}[${p.highlight}INFO${p.record}]`, // Default formatting for the info messages // That is put inbetween the prefix and the message - defaultFormatting: `$z$s `, - // Controls whether the default chat prefix is shown - chatPrefixEnabled: true, - // Messages will be chosen randomly from this array - messages: [ - `${p.highlight}This server is running ${p.rank}$L[${tm.config.controller.repo}]Trakman v${tm.config.controller.version}$L${p.highlight}.`, + defaultFormatting: `$z$s `, // Controls whether the default chat prefix is shown + chatPrefixEnabled: true, // Messages will be chosen randomly from this array + messages: [`${p.highlight}This server is running ${p.rank}$L[${tm.config.controller.repo}]Trakman v${tm.config.controller.version}$L${p.highlight}.`, `${p.highlight}Bugreports and suggestions are welcome on the ${p.rank}$L[${tm.config.controller.repo}/issues]Trakman bugtracker$L${p.highlight}.`, `${p.highlight}Use ${p.rank}/help ${p.highlight}to get you started with the chat commands.`, `${p.highlight}Make sure to ${p.rank}star our repo ${p.highlight}$L[${tm.config.controller.repo}]on GitHub$L if you like the controller.`, @@ -26,17 +21,10 @@ export default { `${p.highlight}Use ${p.rank}/pm ${p.highlight}to send private messages to other players.`, `${p.highlight}Use ${p.rank}/playtime ${p.highlight}to see for how long the current map has been played.`, `${p.highlight}Use ${p.rank}/autojuke ${p.highlight}to automatically queue a map. See ${p.rank}/man aj ${p.highlight}for options.`, - `${p.highlight}Use ${p.rank}/man ${p.highlight}with any command to see how to utilise it.`, - ], - // List of events to send a random message on, independently of the interval + `${p.highlight}Use ${p.rank}/man ${p.highlight}with any command to see how to utilise it.`], // List of events to send a random message on, independently of the interval // For the list of events, peek the wiki // Alternatively, see the Events interface in /src/Namespace.ts/ - events: [ - `EndMap`, - `adfadsfsdf`, - ], - // Whether to send a random message on a specific interval - sendOnInterval: false, - // Message sending interval - messageInterval: 120, // Seconds -} \ No newline at end of file + events: [`EndMap`, `adfadsfsdf`], // Whether to send a random message on a specific interval + sendOnInterval: false, // Message sending interval + messageInterval: 120 // Seconds +} diff --git a/plugins/info_messages/InfoMessages.ts b/plugins/info_messages/InfoMessages.ts index 1f68b9cc..e71b55dd 100644 --- a/plugins/info_messages/InfoMessages.ts +++ b/plugins/info_messages/InfoMessages.ts @@ -13,9 +13,8 @@ const getRandomMessage = (): string => { } const sendInfoMessage = (): void => { - tm.sendMessage(config.messagePrefix - + `${config.defaultFormatting}` - + getRandomMessage(), undefined, config.chatPrefixEnabled) + tm.sendMessage(config.messagePrefix + `${config.defaultFormatting}` + getRandomMessage(), undefined, + config.chatPrefixEnabled) } if (config.isEnabled) { diff --git a/plugins/live_checkpoints/Config.js b/plugins/live_checkpoints/Config.js index 6a25e758..26c37336 100644 --- a/plugins/live_checkpoints/Config.js +++ b/plugins/live_checkpoints/Config.js @@ -1,3 +1,3 @@ export default { - isEnabled: false, -} \ No newline at end of file + isEnabled: false +} diff --git a/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts b/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts index c497ccdf..2fd807e5 100644 --- a/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts +++ b/plugins/live_checkpoints/ui/LiveCpsRanking.component.ts @@ -1,4 +1,4 @@ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../ui/UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../ui/UI.js' import config from './LiveCpsRanking.config.js' interface PlayerCheckpointData { @@ -107,19 +107,19 @@ export default class LiveCpsRanking extends StaticComponent { return this.hideToPlayer(login) } const sorted = Array.from(this.playerData.values()) - .sort((a, b) => { - if (a.finished !== b.finished) { // finishers always first - return a.finished ? -1 : 1 - } - if (a.finished && b.finished) { // 2 finishers sorted by time - return (a.finishTime ?? 0) - (b.finishTime ?? 0) - } - if (a.currentCp !== b.currentCp) { // others sorted by cp index - return b.currentCp - a.currentCp - } - return a.time - b.time // otherwise just use the time - }) - .slice(0, this.getEntries()) + .sort((a, b) => { + if (a.finished !== b.finished) { // finishers always first + return a.finished ? -1 : 1 + } + if (a.finished && b.finished) { // 2 finishers sorted by time + return (a.finishTime ?? 0) - (b.finishTime ?? 0) + } + if (a.currentCp !== b.currentCp) { // others sorted by cp index + return b.currentCp - a.currentCp + } + return a.time - b.time // otherwise just use the time + }) + .slice(0, this.getEntries()) // infinity is used for finished runs, recordlist will display "F" as the index in this case // otherwise, the actual cp index is used instead of the default 1, 2, 3.. const indices = sorted.map(a => a.finished ? Infinity : a.currentCp >= 0 ? a.currentCp : -1) @@ -134,7 +134,11 @@ export default class LiveCpsRanking extends StaticComponent { this.title = config.title // actual cp times could be added but i dont think recordlist can handle the refresh in that case content = this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, records - .map(a => ({ name: a.nickname, time: a.time, login: a.login })), undefined, indices) + .map(a => ({ + name: a.nickname, + time: a.time, + login: a.login + })), undefined, indices) return { xml: ` @@ -149,7 +153,13 @@ export default class LiveCpsRanking extends StaticComponent { } } - private initPlayer({ login, nickname }: { login: string, nickname: string }): void { + private initPlayer({ + login, + nickname + }: { + login: string, + nickname: string + }): void { // using login instead of playerId as that can be overwritten with >=250 players this.playerData.set(login, { login: login, @@ -166,9 +176,12 @@ export default class LiveCpsRanking extends StaticComponent { let dontParseTime = false let noRecordEntryText: string | undefined this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, - { dontParseTime, noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { + dontParseTime, + noRecordEntryText + }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/live_checkpoints/ui/LiveCpsRanking.config.js b/plugins/live_checkpoints/ui/LiveCpsRanking.config.js index 5a3902fe..1e5cbf42 100644 --- a/plugins/live_checkpoints/ui/LiveCpsRanking.config.js +++ b/plugins/live_checkpoints/ui/LiveCpsRanking.config.js @@ -5,7 +5,7 @@ export default { entries: 6, entryHeight: 2.15, width: cfg.width, - title: "Live Checkpoints", + title: 'Live Checkpoints', icon: icons.bestClock, margin: cfg.margin, topCount: 1, @@ -13,4 +13,4 @@ export default { cpsCollectedColour: tm.utils.palette.green, hidePanel: true, maxRecordsAmount: 3000 // If more records than this get driven in one round the click listener will break -} \ No newline at end of file +} diff --git a/plugins/maniakarma/Countries.js b/plugins/maniakarma/Countries.js index 7eb080c7..84eb2664 100644 --- a/plugins/maniakarma/Countries.js +++ b/plugins/maniakarma/Countries.js @@ -1,374 +1,281 @@ -export default [ - { - "code": "DZA", - "name": "Algeria" - }, - { - "code": "AGO", - "name": "Angola" - }, - { - "code": "ARG", - "name": "Argentina" - }, - { - "code": "AUS", - "name": "Australia" - }, - { - "code": "AUT", - "name": "Austria" - }, - { - "code": "BGD", - "name": "Bangladesh" - }, - { - "code": "BEL", - "name": "Belgium" - }, - { - "code": "BIH", - "name": "Bosnia&Herzegovina" - }, - { - "code": "BLR", - "name": "Belarus" - }, - { - "code": "BOL", - "name": "Bolivia" - }, - { - "code": "BRA", - "name": "Brazil" - }, - { - "code": "BHR", - "name": "Bahrain" - }, - { - "code": "BGR", - "name": "Bulgaria" - }, - { - "code": "CAN", - "name": "Canada" - }, - { - "code": "COG", - "name": "Congo" - }, - { - "code": "CHL", - "name": "Chile" - }, - { - "code": "CHN", - "name": "China" - }, - { - "code": "CIV", - "name": "Ivory Coast" - }, - { - "code": "CMR", - "name": "Cameroon" - }, - { - "code": "COL", - "name": "Colombia" - }, - { - "code": "HRV", - "name": "Croatia" - }, - { - "code": "CYP", - "name": "Cyprus" - }, - { - "code": "CZE", - "name": "Czech republic" - }, - { - "code": "DNK", - "name": "Denmark" - }, - { - "code": "ECU", - "name": "Ecuador" - }, - { - "code": "EGY", - "name": "Egypt" - }, - { - "code": "ESP", - "name": "Spain" - }, - { - "code": "EST", - "name": "Estonia" - }, - { - "code": "FIN", - "name": "Finland" - }, - { - "code": "FRA", - "name": "France" - }, - { - "code": "GBR", - "name": "United Kingdom" - }, - { - "code": "DEU", - "name": "Germany" - }, - { - "code": "GRC", - "name": "Greece" - }, - { - "code": "GTM", - "name": "Guatemala" - }, - { - "code": "HUN", - "name": "Hungary" - }, - { - "code": "IDN", - "name": "Indonesia" - }, - { - "code": "IND", - "name": "India" - }, - { - "code": "IRN", - "name": "Iran" - }, - { - "code": "IRL", - "name": "Ireland" - }, - { - "code": "IRQ", - "name": "Iraq" - }, - { - "code": "ISR", - "name": "Israel" - }, - { - "code": "ITA", - "name": "Italy" - }, - { - "code": "JPN", - "name": "Japan" - }, - { - "code": "KAZ", - "name": "Kazakhstan" - }, - { - "code": "KOR", - "name": "Korea" - }, - { - "code": "SAU", - "name": "Saudi Arabia" - }, - { - "code": "KWT", - "name": "Kuwait" - }, - { - "code": "LAO", - "name": "Laos" - }, - { - "code": "LVA", - "name": "Latvia" - }, - { - "code": "LBY", - "name": "Libya" - }, - { - "code": "LBR", - "name": "Lebanon" - }, - { - "code": "LTU", - "name": "Lithuania" - }, - { - "code": "LUX", - "name": "Luxembourg" - }, - { - "code": "MAR", - "name": "Morocco" - }, - { - "code": "MYS", - "name": "Malaysia" - }, - { - "code": "MEX", - "name": "Mexico" - }, - { - "code": "MNG", - "name": "Mongolia" - }, - { - "code": "MKD", - "name": "Macedonia" - }, - { - "code": "NLD", - "name": "Netherlands" - }, - { - "code": "NGA", - "name": "Nigeria" - }, - { - "code": "NOR", - "name": "Norway" - }, - { - "code": "NZL", - "name": "New Zealand" - }, - { - "code": "OMN", - "name": "Oman" - }, - { - // YOU MIGHT BE WONDERING WHY IS OTHER COUNTRIES SUDDENLY POLAND? - // WELL, IT TURNS OUT THERE'S NO WAY TO SEND OTHER COUNTRIES - // TO THE MANIAKARMA API, THEREFORE WE DEFAULT TO POLAND - // DIRECT ALL YOUR QUESTIONS TO THE GENIUS BEHIND THE API! - "code": "POL", - "name": "Other Countries" - }, - { - "code": "PAK", - "name": "Pakistan" - }, - { - "code": "PAN", - "name": "Panama" - }, - { - "code": "PER", - "name": "Peru" - }, - { - "code": "PHL", - "name": "Philippines" - }, - { - "code": "POL", - "name": "Poland" - }, - { - "code": "PRT", - "name": "Portugal" - }, - { - "code": "QAT", - "name": "Qatar" - }, - { - "code": "ROU", - "name": "Romania" - }, - { - "code": "ZAF", - "name": "South Africa" - }, - { - "code": "RUS", - "name": "Russia" - }, - { - "code": "SEN", - "name": "Senegal" - }, - { - "code": "SGP", - "name": "Singapore" - }, - { - "code": "SVN", - "name": "Slovenia" - }, - { - "code": "SRB", - "name": "Serbia" - }, - { - "code": "CHE", - "name": "Switzerland" - }, - { - "code": "SVK", - "name": "Slovakia" - }, - { - "code": "SWE", - "name": "Sweden" - }, - { - "code": "SYR", - "name": "Syria" - }, - { - "code": "THA", - "name": "Thailand" - }, - { - "code": "TUN", - "name": "Tunisia" - }, - { - "code": "TUR", - "name": "Turkey" - }, - { - "code": "ARE", - "name": "United Arab Emirates" - }, - { - "code": "UKR", - "name": "Ukraine" - }, - { - "code": "URY", - "name": "Uruguay" - }, - { - "code": "USA", - "name": "United States of America" - }, - { - "code": "UZB", - "name": "Uzbekistan" - }, - { - "code": "VEN", - "name": "Venezuela" - }, - { - "code": "VNM", - "name": "Vietnam" - } -] \ No newline at end of file +export default [{ + 'code': 'DZA', + 'name': 'Algeria' +}, { + 'code': 'AGO', + 'name': 'Angola' +}, { + 'code': 'ARG', + 'name': 'Argentina' +}, { + 'code': 'AUS', + 'name': 'Australia' +}, { + 'code': 'AUT', + 'name': 'Austria' +}, { + 'code': 'BGD', + 'name': 'Bangladesh' +}, { + 'code': 'BEL', + 'name': 'Belgium' +}, { + 'code': 'BIH', + 'name': 'Bosnia&Herzegovina' +}, { + 'code': 'BLR', + 'name': 'Belarus' +}, { + 'code': 'BOL', + 'name': 'Bolivia' +}, { + 'code': 'BRA', + 'name': 'Brazil' +}, { + 'code': 'BHR', + 'name': 'Bahrain' +}, { + 'code': 'BGR', + 'name': 'Bulgaria' +}, { + 'code': 'CAN', + 'name': 'Canada' +}, { + 'code': 'COG', + 'name': 'Congo' +}, { + 'code': 'CHL', + 'name': 'Chile' +}, { + 'code': 'CHN', + 'name': 'China' +}, { + 'code': 'CIV', + 'name': 'Ivory Coast' +}, { + 'code': 'CMR', + 'name': 'Cameroon' +}, { + 'code': 'COL', + 'name': 'Colombia' +}, { + 'code': 'HRV', + 'name': 'Croatia' +}, { + 'code': 'CYP', + 'name': 'Cyprus' +}, { + 'code': 'CZE', + 'name': 'Czech republic' +}, { + 'code': 'DNK', + 'name': 'Denmark' +}, { + 'code': 'ECU', + 'name': 'Ecuador' +}, { + 'code': 'EGY', + 'name': 'Egypt' +}, { + 'code': 'ESP', + 'name': 'Spain' +}, { + 'code': 'EST', + 'name': 'Estonia' +}, { + 'code': 'FIN', + 'name': 'Finland' +}, { + 'code': 'FRA', + 'name': 'France' +}, { + 'code': 'GBR', + 'name': 'United Kingdom' +}, { + 'code': 'DEU', + 'name': 'Germany' +}, { + 'code': 'GRC', + 'name': 'Greece' +}, { + 'code': 'GTM', + 'name': 'Guatemala' +}, { + 'code': 'HUN', + 'name': 'Hungary' +}, { + 'code': 'IDN', + 'name': 'Indonesia' +}, { + 'code': 'IND', + 'name': 'India' +}, { + 'code': 'IRN', + 'name': 'Iran' +}, { + 'code': 'IRL', + 'name': 'Ireland' +}, { + 'code': 'IRQ', + 'name': 'Iraq' +}, { + 'code': 'ISR', + 'name': 'Israel' +}, { + 'code': 'ITA', + 'name': 'Italy' +}, { + 'code': 'JPN', + 'name': 'Japan' +}, { + 'code': 'KAZ', + 'name': 'Kazakhstan' +}, { + 'code': 'KOR', + 'name': 'Korea' +}, { + 'code': 'SAU', + 'name': 'Saudi Arabia' +}, { + 'code': 'KWT', + 'name': 'Kuwait' +}, { + 'code': 'LAO', + 'name': 'Laos' +}, { + 'code': 'LVA', + 'name': 'Latvia' +}, { + 'code': 'LBY', + 'name': 'Libya' +}, { + 'code': 'LBR', + 'name': 'Lebanon' +}, { + 'code': 'LTU', + 'name': 'Lithuania' +}, { + 'code': 'LUX', + 'name': 'Luxembourg' +}, { + 'code': 'MAR', + 'name': 'Morocco' +}, { + 'code': 'MYS', + 'name': 'Malaysia' +}, { + 'code': 'MEX', + 'name': 'Mexico' +}, { + 'code': 'MNG', + 'name': 'Mongolia' +}, { + 'code': 'MKD', + 'name': 'Macedonia' +}, { + 'code': 'NLD', + 'name': 'Netherlands' +}, { + 'code': 'NGA', + 'name': 'Nigeria' +}, { + 'code': 'NOR', + 'name': 'Norway' +}, { + 'code': 'NZL', + 'name': 'New Zealand' +}, { + 'code': 'OMN', + 'name': 'Oman' +}, { + // YOU MIGHT BE WONDERING WHY IS OTHER COUNTRIES SUDDENLY POLAND? + // WELL, IT TURNS OUT THERE'S NO WAY TO SEND OTHER COUNTRIES + // TO THE MANIAKARMA API, THEREFORE WE DEFAULT TO POLAND + // DIRECT ALL YOUR QUESTIONS TO THE GENIUS BEHIND THE API! + 'code': 'POL', + 'name': 'Other Countries' +}, { + 'code': 'PAK', + 'name': 'Pakistan' +}, { + 'code': 'PAN', + 'name': 'Panama' +}, { + 'code': 'PER', + 'name': 'Peru' +}, { + 'code': 'PHL', + 'name': 'Philippines' +}, { + 'code': 'POL', + 'name': 'Poland' +}, { + 'code': 'PRT', + 'name': 'Portugal' +}, { + 'code': 'QAT', + 'name': 'Qatar' +}, { + 'code': 'ROU', + 'name': 'Romania' +}, { + 'code': 'ZAF', + 'name': 'South Africa' +}, { + 'code': 'RUS', + 'name': 'Russia' +}, { + 'code': 'SEN', + 'name': 'Senegal' +}, { + 'code': 'SGP', + 'name': 'Singapore' +}, { + 'code': 'SVN', + 'name': 'Slovenia' +}, { + 'code': 'SRB', + 'name': 'Serbia' +}, { + 'code': 'CHE', + 'name': 'Switzerland' +}, { + 'code': 'SVK', + 'name': 'Slovakia' +}, { + 'code': 'SWE', + 'name': 'Sweden' +}, { + 'code': 'SYR', + 'name': 'Syria' +}, { + 'code': 'THA', + 'name': 'Thailand' +}, { + 'code': 'TUN', + 'name': 'Tunisia' +}, { + 'code': 'TUR', + 'name': 'Turkey' +}, { + 'code': 'ARE', + 'name': 'United Arab Emirates' +}, { + 'code': 'UKR', + 'name': 'Ukraine' +}, { + 'code': 'URY', + 'name': 'Uruguay' +}, { + 'code': 'USA', + 'name': 'United States of America' +}, { + 'code': 'UZB', + 'name': 'Uzbekistan' +}, { + 'code': 'VEN', + 'name': 'Venezuela' +}, { + 'code': 'VNM', + 'name': 'Vietnam' +}] diff --git a/plugins/maniakarma/Maniakarma.ts b/plugins/maniakarma/Maniakarma.ts index c2b65da5..ed814a06 100644 --- a/plugins/maniakarma/Maniakarma.ts +++ b/plugins/maniakarma/Maniakarma.ts @@ -8,12 +8,23 @@ let authCode: string let isConnected = false let apiUrl: string let mapKarmaValue = 0 -let mapKarma: MKMapVotes = { fantastic: 0, beautiful: 0, good: 0, bad: 0, poor: 0, waste: 0 } +let mapKarma: MKMapVotes = { + fantastic: 0, + beautiful: 0, + good: 0, + bad: 0, + poor: 0, + waste: 0 +} const playerVotes: MKVote[] = [] const newVotes: MKVote[] = [] let lastMap: Readonly -const mapFetchListeners: ((info: { votes: MKVote[], ratio: number, karma: MKMapVotes }) => void)[] = [] +const mapFetchListeners: ((info: { + votes: MKVote[], + ratio: number, + karma: MKMapVotes +}) => void)[] = [] const voteListeners: ((votes: MKVote[]) => void)[] = [] const playerFetchListeners: ((vote: MKVote) => void)[] = [] const defaultKarmaObject: Readonly = { @@ -26,7 +37,13 @@ const defaultKarmaObject: Readonly = { } const emitMapFetch = (votes: MKVote[], ratio: number, karma: MKMapVotes): void => { - for (const e of mapFetchListeners) { e({ votes, ratio, karma }) } + for (const e of mapFetchListeners) { + e({ + votes, + ratio, + karma + }) + } } const emitVote = (...votes: MKVote[]): void => { @@ -116,8 +133,7 @@ const fetchVotes = async (...logins: string[]): Promise => { } const json: any = getJson(await res.text()) if (checkResError(json)) { - const err = new Error(`Failed to fetch maniakarma votes, received` + - ` response:\n${JSON.stringify(json, null, 2)}`) + const err = new Error(`Failed to fetch maniakarma votes, received` + ` response:\n${JSON.stringify(json, null, 2)}`) tm.log.error(err.message) return err } @@ -132,7 +148,11 @@ const fetchVotes = async (...logins: string[]): Promise => { const arr: [-3, -2, -1, 1, 2, 3] = [-3, -2, -1, 1, 2, 3] const v = arr.find(a => a === vote) if (v !== undefined) { - ret.push({ mapId: tm.maps.current.id, vote: v, login }) + ret.push({ + mapId: tm.maps.current.id, + vote: v, + login + }) storePlayerVotes(login, v) } } @@ -163,13 +183,13 @@ const sendVotes = async (newVotes: MKVote[]): Promise => { return } if (!res.ok) { - tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}.`, - `Code: ${res.status} Text: ${res.status}`) + tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}.`, `Code: ${res.status} Text: ${res.status}`) return } const json = getJson(await res.text()) if (checkResError(json)) { - tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}, received response:`, JSON.stringify(json, null, 2)) + tm.log.error(`Failed to send maniakarma votes for map ${lastMap.id}, received response:`, + JSON.stringify(json, null, 2)) } } @@ -206,9 +226,14 @@ const getJson = (data: string): any => { } function addVote(mapId: string, login: string, vote: -3 | -2 | -1 | 1 | 2 | 3): void -function addVote(mapId: string, votes: readonly { login: string, vote: -3 | -2 | -1 | 1 | 2 | 3 }[]): void -function addVote(mapId: string, arg: string | readonly { login: string, vote: -3 | -2 | -1 | 1 | 2 | 3 }[] - , vote?: -3 | -2 | -1 | 1 | 2 | 3): void { +function addVote(mapId: string, votes: readonly { + login: string, + vote: -3 | -2 | -1 | 1 | 2 | 3 +}[]): void +function addVote(mapId: string, arg: string | readonly { + login: string, + vote: -3 | -2 | -1 | 1 | 2 | 3 +}[], vote?: -3 | -2 | -1 | 1 | 2 | 3): void { const voteNames: string[] = ['waste', 'poor', 'bad', 'good', 'beautiful', 'fantastic'] if (Array.isArray(arg)) { const updated: MKVote[] = [] @@ -217,21 +242,37 @@ function addVote(mapId: string, arg: string | readonly { login: string, vote: -3 if (v?.login === e.login && v?.vote === e.vote) { continue } mapKarma[voteNames[e.vote > 0 ? e.vote + 2 : e.vote + 3] as keyof typeof mapKarma]++ if (v === undefined) { - const obj = { mapId, login: e.login, vote: e.vote } + const obj = { + mapId, + login: e.login, + vote: e.vote + } playerVotes.push(obj) updated.push(obj) - } - else { + } else { mapKarma[voteNames[v.vote > 0 ? v.vote + 2 : v.vote + 3] as keyof typeof mapKarma]-- v.vote = e.vote updated.push(v) } const newVote: MKVote | undefined = newVotes.find(a => a.login === e.login) - if (newVote === undefined) { newVotes.push({ mapId, login: e.login, vote: e.vote }) } - else { newVote.vote = e.vote } - const voteValues = { waste: 0, poor: 20, bad: 40, good: 60, beautiful: 80, fantastic: 100 } + if (newVote === undefined) { + newVotes.push({ + mapId, + login: e.login, + vote: e.vote + }) + } else { newVote.vote = e.vote } + const voteValues = { + waste: 0, + poor: 20, + bad: 40, + good: 60, + beautiful: 80, + fantastic: 100 + } const count = Object.values(mapKarma).reduce((acc, cur) => acc + cur, 0) - mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]).reduce((acc, cur): number => acc + cur, 0) / count + mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]) + .reduce((acc, cur): number => acc + cur, 0) / count } emitVote(...updated) return @@ -241,18 +282,40 @@ function addVote(mapId: string, arg: string | readonly { login: string, vote: -3 mapKarma[voteNames[vote > 0 ? vote + 2 : vote + 3] as keyof typeof mapKarma]++ const v: MKVote | undefined = playerVotes.find(a => a.login === login) if (v?.login === login && v.vote === vote) { return } - if (v === undefined) { playerVotes.push({ mapId, login, vote }) } - else { + if (v === undefined) { + playerVotes.push({ + mapId, + login, + vote + }) + } else { mapKarma[voteNames[v.vote > 0 ? v.vote + 2 : v.vote + 3] as keyof typeof mapKarma]-- v.vote = vote } const newVote: MKVote | undefined = newVotes.find(a => a.login === login) - if (newVote === undefined) { newVotes.push({ mapId, login, vote }) } - else { newVote.vote = vote } - const voteValues = { waste: 0, poor: 20, bad: 40, good: 60, beautiful: 80, fantastic: 100 } + if (newVote === undefined) { + newVotes.push({ + mapId, + login, + vote + }) + } else { newVote.vote = vote } + const voteValues = { + waste: 0, + poor: 20, + bad: 40, + good: 60, + beautiful: 80, + fantastic: 100 + } const count = Object.values(mapKarma).reduce((acc, cur) => acc + cur, 0) - mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]).reduce((acc, cur): number => acc + cur, 0) / count - emitVote({ mapId, login, vote }) + mapKarmaValue = Object.entries(mapKarma).map(a => (voteValues as any)[a[0]] * a[1]) + .reduce((acc, cur): number => acc + cur, 0) / count + emitVote({ + mapId, + login, + vote + }) } const fixCoherence = async (): Promise => { @@ -262,7 +325,10 @@ const fixCoherence = async (): Promise => { const v: tm.Vote | undefined = localVotes.find(a => a.login === e.login && a.vote === e.vote) if (v === undefined) { const nickname: string | undefined = tm.players.get(e.login)?.nickname - tm.karma.add({ login: e.login, nickname: nickname ?? e.login }, e.vote) + tm.karma.add({ + login: e.login, + nickname: nickname ?? e.login + }, e.vote) } } addVote(tm.maps.current.id, localVotes) @@ -369,7 +435,11 @@ export const maniakarma = { * Adds a callback function to execute when maniakarma votes for new map get fetched * @param callback Function to execute on event. It takes object of votes array and map vote ratio */ - onMapFetch(callback: ((data: { votes: MKVote[], ratio: number, karma: MKMapVotes }) => void)) { + onMapFetch(callback: ((data: { + votes: MKVote[], + ratio: number, + karma: MKMapVotes + }) => void)) { mapFetchListeners.push(callback) }, diff --git a/plugins/maplist/Autojuke.ts b/plugins/maplist/Autojuke.ts index eef573fd..7cd070b9 100644 --- a/plugins/maplist/Autojuke.ts +++ b/plugins/maplist/Autojuke.ts @@ -7,11 +7,13 @@ import { maplist } from './Maplist.js' import config from './Config.js' -const chooseAndAddMap = (maps: tm.Map[], info: { nickname: string, login: string }, errorMsg: string): void => { - const eligibleMaps: tm.Map[] = maps.filter(a => - !tm.jukebox.juked.some(b => b.map.id === a.id) && - !tm.jukebox.history.some(b => b.id === a.id) && - tm.maps.current.id !== a.id) +const chooseAndAddMap = (maps: tm.Map[], info: { + nickname: string, + login: string +}, errorMsg: string): void => { + const eligibleMaps: tm.Map[] = maps.filter( + a => !tm.jukebox.juked.some(b => b.map.id === a.id) && !tm.jukebox.history.some( + b => b.id === a.id) && tm.maps.current.id !== a.id) if (eligibleMaps.length === 0) { tm.sendMessage(errorMsg, info.login) return @@ -24,14 +26,18 @@ const chooseAndAddMap = (maps: tm.Map[], info: { nickname: string, login: string })) } -const autojuke = async (info: { privilege: number, login: string, nickname: string }, - option?: string): Promise => { +const autojuke = async (info: { + privilege: number, + login: string, + nickname: string +}, option?: string): Promise => { if (info.privilege <= 0 && tm.jukebox.juked.some(a => a.callerLogin === info.login)) { tm.sendMessage(config.noPermission, info.login) return } - switch (option) { - case 'nofinish': case 'nofin': { + switch(option) { + case 'nofinish': + case 'nofin': { const mapsNoRec: Readonly[] = await maplist.filterNoFinish(info.login) chooseAndAddMap(mapsNoRec, info, config.noFinishError) break @@ -55,7 +61,10 @@ const autojuke = async (info: { privilege: number, login: string, nickname: stri tm.commands.add({ aliases: config.autojuke.aliases, help: config.autojuke.help, - params: [{ name: 'option', optional: true }], + params: [{ + name: 'option', + optional: true + }], callback: autojuke, privilege: config.autojuke.privilege }) diff --git a/plugins/maplist/Config.js b/plugins/maplist/Config.js index 75b82d8e..cb40e4c6 100644 --- a/plugins/maplist/Config.js +++ b/plugins/maplist/Config.js @@ -12,5 +12,5 @@ export default { aliases: ['aj', 'autojuke'], help: `Juke a random map. Options: nofinish(nofin), norank, noauthor`, privilege: 0 - }, -} \ No newline at end of file + } +} diff --git a/plugins/maplist/Maplist.ts b/plugins/maplist/Maplist.ts index 6c9b1694..5a10ac28 100644 --- a/plugins/maplist/Maplist.ts +++ b/plugins/maplist/Maplist.ts @@ -12,7 +12,8 @@ let oldestSort: tm.Map[] = [] const jukebox: tm.Map[] = [] let cache: { type: 'best' | 'worst' | 'name' | 'author' | 'nofin' | 'norank' | 'noauthor' | 'newest' | 'oldest', - query: string, list: tm.Map[] + query: string, + list: tm.Map[] }[] = [] const updateListeners: ((action: 'add' | 'remove', addedOrRemovedMap: Readonly) => void)[] = [] const jukeboxUpdateListeners: ((jukebox: readonly Readonly[]) => void)[] = [] @@ -169,7 +170,10 @@ export const maplist = { let list: tm.Map[] | undefined = cache.find(a => a.query === login && a.type === sort)?.list if (list === undefined) { const order = sort === 'best' ? 1 : -1 - const ranks: { mapId: string, rank: number }[] = (await tm.records.getRank(login, tm.maps.list.map(a => a.id))) + const ranks: { + mapId: string, + rank: number + }[] = (await tm.records.getRank(login, tm.maps.list.map(a => a.id))) .sort((a, b): number => (a.rank - b.rank) * order) const rankMap = new Map(ranks.map((e, i) => [e.mapId, i])) list = Array(rankMap.size) @@ -183,7 +187,11 @@ export const maplist = { } // in case it doesn't work just uncomment this, it's not much overhead but its overhead nonetheless // list.filter(a => a) // delete empty slots from the list - cache.unshift({ query: login, list, type: sort }) + cache.unshift({ + query: login, + list, + type: sort + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -200,7 +208,11 @@ export const maplist = { if (list === undefined) { indices = tm.utils.matchString(query, authorSort.map(a => a.name)) list = indices.map(a => authorSort[a]) - cache.unshift({ query, list, type: 'name' }) + cache.unshift({ + query, + list, + type: 'name' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -217,7 +229,11 @@ export const maplist = { if (list === undefined) { indices = tm.utils.matchString(query, authorSort.map(a => a.author)) list = indices.map(a => authorSort[a]) - cache.unshift({ query, list, type: 'author' }) + cache.unshift({ + query, + list, + type: 'author' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -249,7 +265,11 @@ export const maplist = { if (list === undefined) { const mapsWithRec: string[] = (await tm.records.fetchByLogin(login)).map(a => a.map) list = authorSort.filter(a => !mapsWithRec.includes(a.id)) - cache.unshift({ query: login, list, type: 'nofin' }) + cache.unshift({ + query: login, + list, + type: 'nofin' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -267,7 +287,11 @@ export const maplist = { .filter(a => authorSort.find(b => b.id === a.map)?.authorTime ?? Infinity < a.time) .map(a => a.map) list = authorSort.filter(a => !mapsWithAuthor.includes(a.id)) - cache.unshift({ query: login, list, type: 'norank' }) + cache.unshift({ + query: login, + list, + type: 'norank' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list @@ -284,7 +308,11 @@ export const maplist = { const ranks = (await (tm.records.getRank(login, authorSort.map(a => a.id)))) .filter(a => a.rank <= tm.records.maxLocalsAmount) list = authorSort.filter(a => !ranks.some(b => a.id === b.mapId)) - cache.unshift({ query: login, list, type: 'noauthor' }) + cache.unshift({ + query: login, + list, + type: 'noauthor' + }) cache.length = Math.min(config.cacheSize, cache.length) } return list diff --git a/plugins/maplist/ui/Maplist.component.ts b/plugins/maplist/ui/Maplist.component.ts index 94da2efd..2d687eb3 100644 --- a/plugins/maplist/ui/Maplist.component.ts +++ b/plugins/maplist/ui/Maplist.component.ts @@ -3,22 +3,32 @@ * @since 0.1 */ -import { centeredText, closeButton, Grid, componentIds, leftAlignedText, addManialinkListener, PopupWindow, Paginator } from '../../ui/UI.js' +import { addManialinkListener, centeredText, closeButton, componentIds, Grid, leftAlignedText, Paginator, PopupWindow } from '../../ui/UI.js' import { actions } from '../../actions/Actions.js' import { maplist } from '../Maplist.js' import config from './Maplist.config.js' -export default class MapList extends PopupWindow<{ page: number, paginator: Paginator, list?: readonly tm.Map[] }> { +export default class MapList extends PopupWindow<{ + page: number, + paginator: Paginator, + list?: readonly tm.Map[] +}> { private readonly paginator: Paginator private readonly mapAddId: number = 1000 private readonly maxMapCount = 1_000_000 private readonly grid: Grid private readonly mapActionIds: string[] = [] - private readonly playerQueries: { paginator: Paginator, list: readonly tm.Map[], login: string, query?: string }[] = [] + private readonly playerQueries: { + paginator: Paginator, + list: readonly tm.Map[], + login: string, + query?: string + }[] = [] private readonly paginatorIdOffset: number = this.mapAddId + this.maxMapCount private readonly mapDeleteId: number = this.mapAddId + this.maxMapCount * 2 - private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : process.env.SERVER_PACKMASK !== "nations" + private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : + process.env.SERVER_PACKMASK !== "nations" private nextPaginatorId = 0 constructor() { @@ -29,7 +39,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi let pageCount: number = Math.ceil(tm.maps.count / (config.rows * config.columns)) if (pageCount === 0) { pageCount++ } this.paginator.setPageCount(pageCount) - this.displayToPlayer(login, { page, paginator: this.paginator }, `${page}/${pageCount}`) + this.displayToPlayer(login, { + page, + paginator: this.paginator + }, `${page}/${pageCount}`) } this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.columns).fill(1), new Array(config.rows).fill(1), config.grid) @@ -58,7 +71,11 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi tm.commands.add({ aliases: config.commands.list.aliases, help: config.commands.list.help, - params: [{ name: 'query', optional: true, type: 'multiword' }], + params: [{ + name: 'query', + optional: true, + type: 'multiword' + }], callback: (info: tm.MessageInfo, query?: string): void => { if (query === undefined) { tm.openManialink(this.openId, info.login) @@ -84,7 +101,8 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi } if (query === 'jb') { query = 'jukebox' } const option: string = query.split(' ').filter(a => a !== '')[0] - const arr = ['jukebox', 'name', 'karma', 'short', 'long', 'best', 'worst', 'worstkarma', 'oldest', 'newest'] as const + const arr = ['jukebox', 'name', 'karma', 'short', 'long', 'best', 'worst', 'worstkarma', 'oldest', + 'newest'] as const const o = arr.find(a => a === option) if (o !== undefined) { void this.openWithOption(info.login, o, page) @@ -103,42 +121,50 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi } }, privilege: config.commands.list.privilege - }, - { - aliases: config.commands.best.aliases, - help: config.commands.best.help, - params: [{ name: 'page', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, page?: number): void => { - this.openWithOption(info.login, 'best', page ?? 1) - }, - privilege: config.commands.best.privilege + }, { + aliases: config.commands.best.aliases, + help: config.commands.best.help, + params: [{ + name: 'page', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, page?: number): void => { + this.openWithOption(info.login, 'best', page ?? 1) }, - { - aliases: config.commands.worst.aliases, - help: config.commands.worst.help, - params: [{ name: 'page', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, page?: number): void => { - this.openWithOption(info.login, 'worst', page ?? 1) - }, - privilege: config.commands.worst.privilege + privilege: config.commands.best.privilege + }, { + aliases: config.commands.worst.aliases, + help: config.commands.worst.help, + params: [{ + name: 'page', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, page?: number): void => { + this.openWithOption(info.login, 'worst', page ?? 1) }, - { - aliases: config.commands.jukebox.aliases, - help: config.commands.jukebox.help, - params: [{ name: 'page', type: 'int', optional: true }], - callback: (info: tm.MessageInfo, page?: number): void => { - this.openWithOption(info.login, 'jukebox', page ?? 1) - }, - privilege: config.commands.jukebox.privilege - } - ) + privilege: config.commands.worst.privilege + }, { + aliases: config.commands.jukebox.aliases, + help: config.commands.jukebox.help, + params: [{ + name: 'page', + type: 'int', + optional: true + }], + callback: (info: tm.MessageInfo, page?: number): void => { + this.openWithOption(info.login, 'jukebox', page ?? 1) + }, + privilege: config.commands.jukebox.privilege + }) maplist.onListUpdate((): void => this.reRender()) maplist.onJukeboxUpdate((): void => this.reRender()) } - async openWithOption(login: string, option: 'jukebox' | 'name' | 'karma' - | 'short' | 'long' | 'best' | 'worst' | 'worstkarma' | 'nofinish' - | 'norank' | 'noauthor' | 'oldest' | 'newest', page: number): Promise { + async openWithOption(login: string, + option: 'jukebox' | 'name' | 'karma' | 'short' | 'long' | 'best' | 'worst' | 'worstkarma' | 'nofinish' | 'norank' | 'noauthor' | 'oldest' | 'newest', + page: number): Promise { let list: readonly Readonly[] if (option === 'best' || option === 'worst') { list = await maplist.getByPosition(login, option) @@ -150,16 +176,24 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi const paginator = this.getPaginator(login, list, option) page = Math.max(1, Math.min(Math.ceil(list.length / (config.rows * config.columns)), page)) paginator.setPageForLogin(login, page) - this.displayToPlayer(login, { page, paginator, list }, `${page}/${paginator.pageCount}`, - undefined, config.optionTitles[option as keyof typeof config.optionTitles]) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${paginator.pageCount}`, undefined, config.optionTitles[option as keyof typeof config.optionTitles]) } async openWithQuery(login: string, query: string, page: number, searchByAuthor?: true): Promise { - const list: Readonly[] = searchByAuthor === true ? await maplist.searchByAuthor(query) : await maplist.searchByName(query) + const list: Readonly[] = searchByAuthor === true ? await maplist.searchByAuthor(query) : + await maplist.searchByName(query) const paginator = this.getPaginator(login, list, query) page = Math.max(1, Math.min(Math.ceil(list.length / (config.rows * config.columns)), page)) paginator.setPageForLogin(login, page) - this.displayToPlayer(login, { page, paginator, list }, `${page}/${paginator.pageCount}`) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${paginator.pageCount}`) } openOnPage(login: string, page: number): void { @@ -167,7 +201,11 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi const paginator = this.paginator page = Math.min(Math.ceil(list.length / (config.rows * config.columns)), page) paginator.setPageForLogin(login, page) - this.displayToPlayer(login, { page, paginator, list }, `${page}/${paginator.pageCount}`) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${paginator.pageCount}`) } private getPaginator(login: string, list: readonly tm.Map[], option: string): Paginator { @@ -178,19 +216,28 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi playerQuery.list = list paginator = playerQuery.paginator paginator.setPageForLogin(login, 1) - paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, - { page, paginator, list }, `${page}/${pageCount}`, undefined, - config.optionTitles[option as keyof typeof config.optionTitles]) + paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${pageCount}`, undefined, config.optionTitles[option as keyof typeof config.optionTitles]) paginator.setPageCount(pageCount) } else { - paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, - this.windowWidth, this.footerHeight, pageCount) + paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, this.windowWidth, + this.footerHeight, pageCount) this.nextPaginatorId += 10 this.nextPaginatorId = this.nextPaginatorId % 3000 - this.playerQueries.push({ paginator, login, list, query: option }) - paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, - { page, paginator, list }, `${page}/${pageCount}`, undefined, - config.optionTitles[option as keyof typeof config.optionTitles]) + this.playerQueries.push({ + paginator, + login, + list, + query: option + }) + paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${pageCount}`, undefined, config.optionTitles[option as keyof typeof config.optionTitles]) } return paginator } @@ -211,10 +258,18 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi let pageCount: number = Math.ceil(list.length / (config.rows * config.columns)) if (pageCount === 0) { pageCount++ } if (page === undefined) { - this.displayToPlayer(login, { page: 1, paginator, list }, `1/${pageCount}`, undefined, query) + this.displayToPlayer(login, { + page: 1, + paginator, + list + }, `1/${pageCount}`, undefined, query) return } - this.displayToPlayer(login, { page, paginator, list }, `${page}/${pageCount}`, undefined, query) + this.displayToPlayer(login, { + page, + paginator, + list + }, `${page}/${pageCount}`, undefined, query) } } @@ -223,7 +278,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi let pageCount: number = Math.ceil(tm.maps.count / (config.rows * config.columns)) if (pageCount === 0) { pageCount++ } if (page === undefined || page > pageCount) { - this.displayToPlayer(info.login, { page: 1, paginator: this.paginator }, `1/${pageCount}`) + this.displayToPlayer(info.login, { + page: 1, + paginator: this.paginator + }, `1/${pageCount}`) return } this.paginator.setPageCount(pageCount) @@ -232,7 +290,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi this.playerQueries[index].paginator.destroy() this.playerQueries.splice(index, 1) } - this.displayToPlayer(info.login, { page, paginator: this.paginator }, `${page}/${pageCount}`) + this.displayToPlayer(info.login, { + page, + paginator: this.paginator + }, `${page}/${pageCount}`) } protected onClose(info: tm.ManialinkClickInfo): void { @@ -244,17 +305,23 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi this.hideToPlayer(info.login) } - protected async constructContent(login: string, params?: { page: number, list?: readonly tm.Map[] }): Promise { + protected async constructContent(login: string, params?: { + page: number, + list?: readonly tm.Map[] + }): Promise { const maps: readonly Readonly[] = params?.list ?? maplist.get() const startIndex: number = (config.rows * config.columns) * ((params?.page ?? 1) - 1) - const recordIndexStrings: string[] = await this.getRecordIndexStrings(login, ...maps.slice(startIndex, - (config.rows * config.columns) + startIndex).map(a => a.id)) + const recordIndexStrings: string[] = await this.getRecordIndexStrings(login, + ...maps.slice(startIndex, (config.rows * config.columns) + startIndex).map(a => a.id)) const mapsToDisplay: number = Math.min(maps.length - startIndex, config.rows * config.columns) const cell = (i: number, j: number, w: number, h: number): string => { const gridIndex: number = (i * config.columns) + j const recordIndexString: string = recordIndexStrings[gridIndex] const index: number = startIndex + gridIndex - const { actionId, deleteId } = this.getActionAndDeleteId(maps[index].id) + const { + actionId, + deleteId + } = this.getActionAndDeleteId(maps[index].id) const header: string = this.getHeader(login, index, maps[index].id, actionId, deleteId, w, h) const rowH: number = (h - this.margin) / 4 const width: number = (w - this.margin * 3) - config.iconWidth @@ -279,7 +346,8 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi sizen="${config.iconWidth - this.margin * 2} ${rowH - this.margin * 3}" image="${config.icons[2]}"/> - ${leftAlignedText(tm.utils.safeString(maps[index].author), width, rowH - this.margin, { textScale: config.textScale })} + ${leftAlignedText(tm.utils.safeString(maps[index].author), width, rowH - this.margin, + { textScale: config.textScale })} @@ -288,8 +356,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[3]}"/> - ${centeredText(tm.utils.getTimeString(maps[index].authorTime), config.timeWidth, rowH - this.margin, - { textScale: config.textScale, padding: config.padding })} + ${centeredText(tm.utils.getTimeString(maps[index].authorTime), config.timeWidth, rowH - this.margin, { + textScale: config.textScale, + padding: config.padding + })} @@ -298,7 +368,10 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[4]}"/> - ${centeredText(` ${recordIndexString} `, config.positionWidth, rowH - this.margin, { textScale: config.textScale, padding: config.padding })} + ${centeredText(` ${recordIndexString} `, config.positionWidth, rowH - this.margin, { + textScale: config.textScale, + padding: config.padding + })} @@ -307,8 +380,13 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[this.displayEnvironment ? 8 : 5]}"/> - ${centeredText(this.displayEnvironment ? (maps[index].environment === 'Stadium' ? 'Stad' : maps[index].environment) : maps[index].voteRatio === -1 ? config.defaultText : maps[index].voteRatio.toFixed(0), karmaW, - rowH - this.margin, { textScale: config.textScale, padding: config.padding })} + ${centeredText( + this.displayEnvironment ? (maps[index].environment === 'Stadium' ? 'Stad' : maps[index].environment) : + maps[index].voteRatio === -1 ? config.defaultText : maps[index].voteRatio.toFixed(0), karmaW, + rowH - this.margin, { + textScale: config.textScale, + padding: config.padding + })} ` @@ -316,8 +394,11 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi return this.grid.constructXml(new Array(mapsToDisplay).fill(cell)) } - protected constructFooter(login: string, params?: { paginator: Paginator }): string { - return closeButton(this.closeId, this.windowWidth, this.footerHeight) + (params?.paginator ?? this.paginator).constructXml(login) + protected constructFooter(login: string, params?: { + paginator: Paginator + }): string { + return closeButton(this.closeId, this.windowWidth, + this.footerHeight) + (params?.paginator ?? this.paginator).constructXml(login) } private handleMapClick(mapId: string, login: string, nickname: string, privilege: number): boolean { @@ -328,19 +409,29 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi return false } if (tm.jukebox.juked.some(a => a.map.id === mapId)) { - tm.jukebox.remove(mapId, { login, nickname }) - tm.sendMessage(tm.utils.strVar(config.messages.remove, - { player: tm.utils.strip(nickname, true), map: tm.utils.strip(tm.utils.decodeURI(map.name), true) }), config.public ? undefined : login) - } - else { - if ((privilege < config.multijukePrivilege && tm.jukebox.juked.some(a => a.callerLogin === login)) - || (config.multijukeMaxMaps - 1 < tm.jukebox.juked.filter(a => a.callerLogin === login).length && privilege < config.unlimitedJukePrivilege)) { + tm.jukebox.remove(mapId, { + login, + nickname + }) + tm.sendMessage(tm.utils.strVar(config.messages.remove, { + player: tm.utils.strip(nickname, true), + map: tm.utils.strip(tm.utils.decodeURI(map.name), true) + }), config.public ? undefined : login) + } else { + if ((privilege < config.multijukePrivilege && tm.jukebox.juked.some( + a => a.callerLogin === login)) || (config.multijukeMaxMaps - 1 < tm.jukebox.juked.filter( + a => a.callerLogin === login).length && privilege < config.unlimitedJukePrivilege)) { tm.sendMessage(config.messages.noPermission, login) return false } - tm.jukebox.add(mapId, { login, nickname }) - tm.sendMessage(tm.utils.strVar(config.messages.add, - { player: tm.utils.strip(nickname, true), map: tm.utils.strip(tm.utils.decodeURI(map.name), true) }), config.public ? undefined : login) + tm.jukebox.add(mapId, { + login, + nickname + }) + tm.sendMessage(tm.utils.strVar(config.messages.add, { + player: tm.utils.strip(nickname, true), + map: tm.utils.strip(tm.utils.decodeURI(map.name), true) + }), config.public ? undefined : login) } return true } @@ -350,37 +441,41 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi const ret: string[] = [] for (let i = 0; i < mapIds.length; i++) { const r = ranks.find(a => a.mapId === mapIds[i]) - if (r === undefined) { ret.push(config.texts.noRank) } - else { ret.push(tm.utils.getOrdinalSuffix(r.rank)) } + if (r === undefined) { ret.push(config.texts.noRank) } else { ret.push(tm.utils.getOrdinalSuffix(r.rank)) } } return ret } - private getActionAndDeleteId(mapId: string): { actionId: number, deleteId: number } { + private getActionAndDeleteId(mapId: string): { + actionId: number, + deleteId: number + } { let actionId = -1 let deleteId = -1 const mapActionId: number = this.mapActionIds.indexOf(mapId) if (mapActionId !== -1) { actionId = mapActionId + this.openId + this.mapAddId deleteId = mapActionId + this.openId + this.mapDeleteId - } - else { + } else { this.mapActionIds.push(mapId) actionId = this.mapActionIds.length + this.openId + this.mapAddId - 1 deleteId = this.mapActionIds.length + this.openId + this.mapDeleteId - 1 } - return { actionId, deleteId } + return { + actionId, + deleteId + } } - private getHeader(login: string, mapIndex: number, mapId: string, actionId: number, deleteId: number, w: number, h: number): string { + private getHeader(login: string, mapIndex: number, mapId: string, actionId: number, deleteId: number, w: number, + h: number): string { const height: number = h - this.margin const index: number = tm.jukebox.juked.findIndex(a => a.map.id === mapId) const prevIndex: number = [tm.maps.current, ...tm.jukebox.history].findIndex(a => a.id === mapId) const player = tm.players.get(login) if (player === undefined) { return '' } let overlay: string | undefined - if (player?.privilege < 1 - && (prevIndex !== -1 || (tm.jukebox.juked[index]?.callerLogin !== undefined && tm.jukebox.juked[index].callerLogin !== login))) { + if (player?.privilege < 1 && (prevIndex !== -1 || (tm.jukebox.juked[index]?.callerLogin !== undefined && tm.jukebox.juked[index].callerLogin !== login))) { overlay = ` ` } @@ -393,14 +488,23 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi - ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, width - (this.margin * 2 + config.queueWidth) + config.queueNumberWidth, height / 4 - this.margin, { textScale: config.textScale })} + ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, + width - (this.margin * 2 + config.queueWidth) + config.queueNumberWidth, height / 4 - this.margin, + { textScale: config.textScale })} - ${centeredText(`$${config.colour}${config.texts.queued}`, config.queueWidth, height / 4 - this.margin, { padding: config.padding, textScale: config.textScale })} + ${centeredText(`$${config.colour}${config.texts.queued}`, config.queueWidth, height / 4 - this.margin, { + padding: config.padding, + textScale: config.textScale + })} - ${centeredText(`$${config.colour}${tm.utils.getOrdinalSuffix(index + 1)}`, config.queueNumberWidth, height / 4 - this.margin, { padding: config.padding, textScale: config.textScale })} + ${centeredText(`$${config.colour}${tm.utils.getOrdinalSuffix(index + 1)}`, config.queueNumberWidth, + height / 4 - this.margin, { + padding: config.padding, + textScale: config.textScale + })} ` } @@ -408,8 +512,7 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi if (deletePrivilege) { width = (w - this.margin * 4) - config.iconWidth * 2 } - const deleteButton = deletePrivilege ? - ` + const deleteButton = deletePrivilege ? ` @@ -422,7 +525,8 @@ export default class MapList extends PopupWindow<{ page: number, paginator: Pagi image="${config.icons[0]}"/> - ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, width, height / 4 - this.margin, { textScale: config.textScale })} + ${leftAlignedText(`${config.texts.map}${mapIndex + 1}`, width, height / 4 - this.margin, + { textScale: config.textScale })} ${deleteButton}` } diff --git a/plugins/maplist/ui/Maplist.config.js b/plugins/maplist/ui/Maplist.config.js index 11fed3c7..5f4fff53 100644 --- a/plugins/maplist/ui/Maplist.config.js +++ b/plugins/maplist/ui/Maplist.config.js @@ -1,8 +1,9 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { - title: "Map List", + title: 'Map List', icon: icons.mapList, textScale: 1, padding: 0.1, @@ -19,7 +20,7 @@ export default { texts: { map: 'Map #', queued: 'Queued', - noRank: "--." + noRank: '--.' }, rows: 5, columns: 5, @@ -27,11 +28,11 @@ export default { background: 'FFFA', margin: 0.15 }, - navbar: [ - { name: 'Map Info', actionId: ids.TMXWindow } - ], - icons: [ - icons.ongoingMap, // Map header icon + navbar: [{ + name: 'Map Info', + actionId: ids.TMXWindow + }], + icons: [icons.ongoingMap, // Map header icon icons.tag, // Map name icon icons.personBuilder, // Author name icon icons.clockAuthor, // Author time icon @@ -47,8 +48,8 @@ export default { queueNumberWidth: 2.9, timeWidth: 5, positionWidth: 3.1, - iconBackground: "222C", - contentBackground: "555C", + iconBackground: '222C', + contentBackground: '555C', plusImage: icons.addMap, minusImage: icons.removeMap, blankImage: icons.blank, @@ -83,4 +84,4 @@ export default { privilege: 0 } } -} \ No newline at end of file +} diff --git a/plugins/messages/Config.js b/plugins/messages/Config.js index b504afe1..520d4ce7 100644 --- a/plugins/messages/Config.js +++ b/plugins/messages/Config.js @@ -5,7 +5,7 @@ export default { specialWin: 50, startup: { message: `${p.highlight}$L[${tm.config.controller.repo}]Trakman v#{version}$L${p.servermsg} startup sequence successful.`, - public: true, + public: true }, changelog: { message: `${p.error}You can see the recent changes with the ${p.highlight}/changes ${p.error}command.`, @@ -56,17 +56,17 @@ export default { public: true }, recordDifference: { - message: ` $n${p.record}(${p.rank}#{position} ${p.highlight}#{time}${p.record})`, + message: ` $n${p.record}(${p.rank}#{position} ${p.highlight}#{time}${p.record})` }, dediDifference: { - message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}-#{time}${p.dedirecord})`, + message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}-#{time}${p.dedirecord})` }, dediRecord: { message: `${p.highlight}#{nickname}${p.dedirecord} has #{status} the ${p.rank}#{position}${p.dedirecord} dedimania record. Time${p.highlight}: #{time}#{difference}`, public: true }, ultiDifference: { - message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}+#{score}${p.dedirecord})`, + message: ` $n${p.dedirecord}(${p.rank}#{position} ${p.highlight}+#{score}${p.dedirecord})` }, ultiRecord: { message: `${p.highlight}#{nickname}${p.dedirecord} has #{status} the ${p.rank}#{position}${p.dedirecord} ultimania record. Score${p.highlight}: #{score}#{difference}`, @@ -79,5 +79,5 @@ export default { jukeSkipped: { message: `${p.vote}Map ${p.highlight}#{map} ${p.vote}will be dropped from the queue, as ${p.highlight}#{nickname} ${p.vote}has left the server.`, public: true - }, -} \ No newline at end of file + } +} diff --git a/plugins/messages/Messages.ts b/plugins/messages/Messages.ts index 08ff002f..4e96abcc 100644 --- a/plugins/messages/Messages.ts +++ b/plugins/messages/Messages.ts @@ -8,54 +8,18 @@ import c from './Config.js' * @since 0.1 */ -const events: tm.Listener[] = [ - { - event: 'Startup', - callback: (): void => { - if (c.startup.public) { tm.sendMessage(tm.utils.strVar(c.startup.message, { version: tm.config.controller.version })) } - if (c.changelog.public) { tm.sendMessage(c.changelog.message) } +const events: tm.Listener[] = [{ + event: 'Startup', + callback: (): void => { + if (c.startup.public) { + tm.sendMessage(tm.utils.strVar(c.startup.message, { version: tm.config.controller.version })) } - }, - { - event: ['BeginMap', 'Startup'], - callback: async (): Promise => { - for (const player of tm.players.list) { - const index: number = tm.records.local.findIndex(a => a.login === player.login) - if (index === -1 && c.noPb.public) { - tm.sendMessage(c.noPb.message, player.login) - } else { - if (c.pb.public) { - const rec: tm.LocalRecord = tm.records.local[index] - tm.sendMessage(tm.utils.strVar(c.pb.message, { - time: tm.utils.getTimeString(rec.time), - rank: tm.utils.getOrdinalSuffix(index + 1) - }), player.login) - } - } - const playerRank: number | undefined = player.rank - if (playerRank === undefined && c.noRank.public) { - tm.sendMessage(c.noRank.message, player.login) - } else { - if (c.rank.public) { - tm.sendMessage(tm.utils.strVar(c.rank.message, { - rank: tm.utils.getOrdinalSuffix(playerRank ?? 0), - total: tm.players.totalCount - }), player.login) - } - } - } - } - }, - { - event: 'PlayerJoin', - callback: async (player: tm.JoinInfo): Promise => { - if (c.welcome.public) { - tm.sendMessage(tm.utils.strVar(c.welcome.message, { - name: tm.utils.strip(tm.config.server.name), - version: tm.config.controller.version - }), player.login) - } - if (c.changelog.public) { tm.sendMessage(c.changelog.message, player.login) } + if (c.changelog.public) { tm.sendMessage(c.changelog.message) } + } +}, { + event: ['BeginMap', 'Startup'], + callback: async (): Promise => { + for (const player of tm.players.list) { const index: number = tm.records.local.findIndex(a => a.login === player.login) if (index === -1 && c.noPb.public) { tm.sendMessage(c.noPb.message, player.login) @@ -79,114 +43,161 @@ const events: tm.Listener[] = [ }), player.login) } } - if (c.join.public) { - tm.sendMessage(tm.utils.strVar(c.join.message, { - title: player.title, - nickname: tm.utils.strip(player.nickname, true), - country: player.country, - visits: player.visits - })) + } + } +}, { + event: 'PlayerJoin', + callback: async (player: tm.JoinInfo): Promise => { + if (c.welcome.public) { + tm.sendMessage(tm.utils.strVar(c.welcome.message, { + name: tm.utils.strip(tm.config.server.name), + version: tm.config.controller.version + }), player.login) + } + if (c.changelog.public) { tm.sendMessage(c.changelog.message, player.login) } + const index: number = tm.records.local.findIndex(a => a.login === player.login) + if (index === -1 && c.noPb.public) { + tm.sendMessage(c.noPb.message, player.login) + } else { + if (c.pb.public) { + const rec: tm.LocalRecord = tm.records.local[index] + tm.sendMessage(tm.utils.strVar(c.pb.message, { + time: tm.utils.getTimeString(rec.time), + rank: tm.utils.getOrdinalSuffix(index + 1) + }), player.login) + } + } + const playerRank: number | undefined = player.rank + if (playerRank === undefined && c.noRank.public) { + tm.sendMessage(c.noRank.message, player.login) + } else { + if (c.rank.public) { + tm.sendMessage(tm.utils.strVar(c.rank.message, { + rank: tm.utils.getOrdinalSuffix(playerRank ?? 0), + total: tm.players.totalCount + }), player.login) } } - }, - { - event: 'EndMap', - callback: async (info: tm.EndMapInfo): Promise => { - if (info.winnerLogin !== undefined && info.winnerWins !== undefined) { - const player: tm.OfflinePlayer | undefined = tm.players.get(info.winnerLogin) ?? await tm.players.fetch(info.winnerLogin) - if (info.winnerWins % c.specialWin === 0 && c.winPublic.public) { - tm.sendMessage(tm.utils.strVar(c.winPublic.message, { - nickname: tm.utils.strip(player?.nickname ?? '', true), + if (c.join.public) { + tm.sendMessage(tm.utils.strVar(c.join.message, { + title: player.title, + nickname: tm.utils.strip(player.nickname, true), + country: player.country, + visits: player.visits + })) + } + } +}, { + event: 'EndMap', + callback: async (info: tm.EndMapInfo): Promise => { + if (info.winnerLogin !== undefined && info.winnerWins !== undefined) { + const player: tm.OfflinePlayer | undefined = tm.players.get(info.winnerLogin) ?? await tm.players.fetch( + info.winnerLogin) + if (info.winnerWins % c.specialWin === 0 && c.winPublic.public) { + tm.sendMessage(tm.utils.strVar(c.winPublic.message, { + nickname: tm.utils.strip(player?.nickname ?? '', true), + wins: tm.utils.getOrdinalSuffix(info.winnerWins) + }), undefined) + } else { + if (c.win.public) { + tm.sendMessage(tm.utils.strVar(c.win.message, { wins: tm.utils.getOrdinalSuffix(info.winnerWins) - }), undefined) - } else { - if (c.win.public) { - tm.sendMessage(tm.utils.strVar(c.win.message, { - wins: tm.utils.getOrdinalSuffix(info.winnerWins) - }), info.winnerLogin) - } + }), info.winnerLogin) } } - if (info.droppedMap !== undefined && c.jukeSkipped.public) { - tm.sendMessage(tm.utils.strVar(c.jukeSkipped.message, { - map: tm.utils.strip(info.droppedMap.map.name, true), - nickname: tm.utils.strip((await tm.players.fetch(info.droppedMap.callerLogin))?.nickname ?? '') - })) - } - if (tm.jukebox.juked[0]?.callerLogin !== undefined && c.nextJuke.public) { - const player: tm.OfflinePlayer | undefined = tm.players.get(tm.jukebox.juked[0].callerLogin) ?? await tm.players.fetch(tm.jukebox.juked[0].callerLogin) - tm.sendMessage(tm.utils.strVar(c.nextJuke.message, { - map: tm.utils.strip(tm.utils.decodeURI(tm.jukebox.juked[0].map.name), true), - nickname: tm.utils.strip(player?.nickname ?? '', true) - })) - } } - }, - { - event: 'PlayerLeave', - callback: (player: tm.LeaveInfo): void => { - if (c.leave.public) { - tm.sendMessage(tm.utils.strVar(c.leave.message, { - nickname: tm.utils.strip(player.nickname, true), - time: tm.utils.getVerboseTime(player.sessionTime) - })) - } + if (info.droppedMap !== undefined && c.jukeSkipped.public) { + tm.sendMessage(tm.utils.strVar(c.jukeSkipped.message, { + map: tm.utils.strip(info.droppedMap.map.name, true), + nickname: tm.utils.strip((await tm.players.fetch(info.droppedMap.callerLogin))?.nickname ?? '') + })) } - }, - { - event: 'LocalRecord', - callback: (info: tm.RecordInfo): void => { - if (c.record.public) { - const isStunts = tm.getGameMode() === 'Stunts' - const diffSign = isStunts ? '+' : '-' - let prevObj: undefined | { time: number, position: number } = info.previous - if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { - prevObj = undefined - } - const rs = tm.utils.getRankingString({ time: info.time, position: info.position }, prevObj) - tm.sendMessage(tm.utils.strVar(c.record.message, { - nickname: tm.utils.strip(info.nickname, true), - status: rs.status, - position: tm.utils.getOrdinalSuffix(info.position), - time: tm.utils.getTimeString(info.time), - type: isStunts ? 'Score' : 'Time', - difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { - position: info.previous?.position, - time: diffSign + rs.difference - }) : '' - })) + if (tm.jukebox.juked[0]?.callerLogin !== undefined && c.nextJuke.public) { + const player: tm.OfflinePlayer | undefined = tm.players.get( + tm.jukebox.juked[0].callerLogin) ?? await tm.players.fetch(tm.jukebox.juked[0].callerLogin) + tm.sendMessage(tm.utils.strVar(c.nextJuke.message, { + map: tm.utils.strip(tm.utils.decodeURI(tm.jukebox.juked[0].map.name), true), + nickname: tm.utils.strip(player?.nickname ?? '', true) + })) + } + } +}, { + event: 'PlayerLeave', + callback: (player: tm.LeaveInfo): void => { + if (c.leave.public) { + tm.sendMessage(tm.utils.strVar(c.leave.message, { + nickname: tm.utils.strip(player.nickname, true), + time: tm.utils.getVerboseTime(player.sessionTime) + })) + } + } +}, { + event: 'LocalRecord', + callback: (info: tm.RecordInfo): void => { + if (c.record.public) { + const isStunts = tm.getGameMode() === 'Stunts' + const diffSign = isStunts ? '+' : '-' + let prevObj: undefined | { + time: number, + position: number + } = info.previous + if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { + prevObj = undefined } + const rs = tm.utils.getRankingString({ + time: info.time, + position: info.position + }, prevObj) + tm.sendMessage(tm.utils.strVar(c.record.message, { + nickname: tm.utils.strip(info.nickname, true), + status: rs.status, + position: tm.utils.getOrdinalSuffix(info.position), + time: tm.utils.getTimeString(info.time), + type: isStunts ? 'Score' : 'Time', + difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { + position: info.previous?.position, + time: diffSign + rs.difference + }) : '' + })) } - }, - { - event: 'LapRecord', - callback: (info: tm.LapRecordInfo): void => { - if (c.lapRecord.public) { - let prevObj: undefined | { time: number, position: number } = info.previous - if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { - prevObj = undefined - } - const rs = tm.utils.getRankingString({ time: info.time, position: info.position }, prevObj) - tm.sendMessage(tm.utils.strVar(c.lapRecord.message, { - nickname: tm.utils.strip(info.nickname, true), - status: rs.status, - position: tm.utils.getOrdinalSuffix(info.position), - time: tm.utils.getTimeString(info.time), - difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { - position: info.previous?.position, - time: rs.difference - }) : '' - })) + } +}, { + event: 'LapRecord', + callback: (info: tm.LapRecordInfo): void => { + if (c.lapRecord.public) { + let prevObj: undefined | { + time: number, + position: number + } = info.previous + if (info.previous !== undefined && info.previous.position > tm.records.maxLocalsAmount) { + prevObj = undefined } + const rs = tm.utils.getRankingString({ + time: info.time, + position: info.position + }, prevObj) + tm.sendMessage(tm.utils.strVar(c.lapRecord.message, { + nickname: tm.utils.strip(info.nickname, true), + status: rs.status, + position: tm.utils.getOrdinalSuffix(info.position), + time: tm.utils.getTimeString(info.time), + difference: rs.difference !== undefined ? tm.utils.strVar(c.recordDifference.message, { + position: info.previous?.position, + time: rs.difference + }) : '' + })) } } -] +}] for (const event of events) { tm.addListener(event.event, event.callback, true) } dedimania.onRecord((record) => { if (c.dediRecord.public) { - const rs = tm.utils.getRankingString({ position: record.position, time: record.time }, record.previous) + const rs = tm.utils.getRankingString({ + position: record.position, + time: record.time + }, record.previous) tm.sendMessage(tm.utils.strVar(c.dediRecord.message, { nickname: tm.utils.strip(record.nickname, true), status: rs.status, @@ -202,9 +213,14 @@ dedimania.onRecord((record) => { ultimania.onRecord((record) => { if (c.ultiRecord.public) { - const prev = record.previous === undefined ? undefined : - { time: record.previous.score, position: record.previous.position } - const rs = tm.utils.getRankingString({ position: record.position, time: record.score }, prev) + const prev = record.previous === undefined ? undefined : { + time: record.previous.score, + position: record.previous.position + } + const rs = tm.utils.getRankingString({ + position: record.position, + time: record.score + }, prev) tm.sendMessage(tm.utils.strVar(c.ultiRecord.message, { nickname: tm.utils.strip(record.nickname, true), status: rs.status, diff --git a/plugins/music/Config.js b/plugins/music/Config.js index e927cdc3..06b7eb69 100644 --- a/plugins/music/Config.js +++ b/plugins/music/Config.js @@ -3,28 +3,22 @@ const p = tm.utils.palette export default { isEnabled: false, songListPath: './plugins/music/SongList.js', // Needed for updates on song add/remove - overrideMapMusic: true, - // If song is in history it can't be requeued without force queue privileges - historySize: 3, - // Privilege required to queue recently queued songs, queue more than one song and shuffle songs + overrideMapMusic: true, // If song is in history it can't be requeued without force queue privileges + historySize: 3, // Privilege required to queue recently queued songs, queue more than one song and shuffle songs forceQueuePrivilege: 1, messages: { - addToQueue: `${p.highlight}#{nickname} ${p.vote}has added ${p.highlight}#{song}${p.vote}` + - ` by ${p.highlight}#{author}${p.vote} to the song queue.`, + addToQueue: `${p.highlight}#{nickname} ${p.vote}has added ${p.highlight}#{song}${p.vote}` + ` by ${p.highlight}#{author}${p.vote} to the song queue.`, cantAddMoreThanOne: `${p.error}You can't add more than one song to the queue.`, addToQueueError: `${p.error}Error while adding the song to the queue.`, removeFromQueue: `${p.highlight}#{nickname} ${p.vote}has removed ${p.highlight}#{song}${p.vote} from the song queue.`, removeFromQueueError: `${p.error}Error while removing the song from the queue.`, - add: `${p.highlight}#{nickname} ${p.vote}has added and queued song ${p.highlight}#{song}${p.vote}` + - ` by ${p.highlight}#{author}${p.vote}.`, + add: `${p.highlight}#{nickname} ${p.vote}has added and queued song ${p.highlight}#{song}${p.vote}` + ` by ${p.highlight}#{author}${p.vote}.`, addNameError: `${p.error}Song named ${p.highlight}#{name} ${p.error}already exists.`, - addFileExtensionError: `${p.error}Song url file extension needs to be ` + - `${p.highlight}.ogg ${p.error}or ${p.highlight}.mux${p.error}, ${p.highlight}#{url} ${p.error}is invalid.`, + addFileExtensionError: `${p.error}Song url file extension needs to be ` + `${p.highlight}.ogg ${p.error}or ${p.highlight}.mux${p.error}, ${p.highlight}#{url} ${p.error}is invalid.`, remove: `${p.highlight}#{nickname} ${p.vote}has deleted song ${p.highlight}#{song}${p.vote}.`, removeError: `${p.error}Song named ${p.highlight}#{name} ${p.error}doesn't exist.`, shuffle: `${p.highlight}#{nickname} ${p.vote}has shuffled the song queue.` - }, - // Whether add and remove from queue messages are public + }, // Whether add and remove from queue messages are public publicQueue: true, addCommand: { aliases: ['addsong'], @@ -48,4 +42,4 @@ export default { aliases: ['songs', 'songlist'], help: 'Open song list.' } -} \ No newline at end of file +} diff --git a/plugins/music/Music.ts b/plugins/music/Music.ts index dc872de5..5f85ec31 100644 --- a/plugins/music/Music.ts +++ b/plugins/music/Music.ts @@ -1,6 +1,6 @@ import config from './Config.js' import songList from './SongList.js' -import type { Song, SongAddedCallback, SongRemovedCallback, QueueChangedCallback, Caller } from './Types.js' +import type { Caller, QueueChangedCallback, Song, SongAddedCallback, SongRemovedCallback } from './Types.js' import SongList from './ui/SongList.component.js' import MusicWidget from './ui/MusicWidget.component.js' import fs from 'fs/promises' @@ -24,7 +24,7 @@ export const music = { /** * Add a callback function to execute on a song queue change - * @param callback Function to execute on event. + * @param callback Function to execute on event. * It takes song queue and optional change information object as parameters. */ onQueueChanged(callback: QueueChangedCallback) { @@ -33,7 +33,7 @@ export const music = { /** * Add a callback function to execute when a new song gets added to the song list - * @param callback Function to execute on event. + * @param callback Function to execute on event. * It takes song object and caller object as parameters. (Caller is the player who added the song) */ onSongAdded(callback: SongAddedCallback) { @@ -42,7 +42,7 @@ export const music = { /** * Add a callback function to execute when a song gets removed from the song list - * @param callback Function to execute on event. + * @param callback Function to execute on event. * It takes song object and caller object as parameters. (Caller is the player who removed the song) */ onSongRemoved(callback: SongRemovedCallback) { @@ -65,13 +65,19 @@ export const music = { return 'name taken' } url = tm.utils.fixProtocol(url) - const song: Song = { name, author, url, isJuked: false } + const song: Song = { + name, + author, + url, + isJuked: false + } queue.push(song) emitEvent(songAddCallbacks, song, caller) const status = addToQueue(name, false, caller) if (typeof status !== 'string') { emitEvent(queueChangeCallbacks, queue, { - song, action: 'added' + song, + action: 'added' }) } void updateSongsConfigFile('add', song) @@ -93,7 +99,8 @@ export const music = { listUi.updateSongs(current, queue) emitEvent(songRemoveCallbacks, song, caller) emitEvent(queueChangeCallbacks, queue, { - song, action: 'removed' + song, + action: 'removed' }) void updateSongsConfigFile('remove', song) return true @@ -105,8 +112,8 @@ export const music = { * @param caller Caller player object * @returns Song object if it got added, error message if unsuccessful */ - addSongToQueue(songName: string, caller?: Caller): - Readonly | 'already queued' | 'not in songlist' | 'no privilege' { + addSongToQueue(songName: string, + caller?: Caller): Readonly | 'already queued' | 'not in songlist' | 'no privilege' { return addToQueue(songName, true, caller) }, @@ -140,7 +147,10 @@ if (config.isEnabled) { tm.log.warn(`Song name duplicates present in SongList, to fix them edit SongList.js file or use ingame commands.`) updateSongsConfigFile() } - queue.push(...songs.map(a => ({ ...a, isJuked: false }))) + queue.push(...songs.map(a => ({ + ...a, + isJuked: false + }))) listUi.updateSongs(current, queue) const msg = config.messages listUi.onSongJuked = (song, info) => { @@ -203,7 +213,8 @@ if (config.isEnabled) { if (status === true) { tm.sendMessage(tm.utils.strVar(msg.add, { nickname: tm.utils.strip(info.nickname), - song: name, author + song: name, + author }), add.public ? undefined : info.login) } else if (status === 'name taken') { tm.sendMessage(tm.utils.strVar(msg.addNameError, { name }), info.login) @@ -237,7 +248,11 @@ if (config.isEnabled) { tm.commands.add({ aliases: ol.aliases, help: ol.help, - params: [{ name: 'query', type: 'multiword', optional: true }], + params: [{ + name: 'query', + type: 'multiword', + optional: true + }], callback: (info, query?: string) => { if (query === undefined || query.trim().length === 0) { listUi.open(info) @@ -257,7 +272,7 @@ if (config.isEnabled) { callback: (info) => { shuffleQueue(info) tm.sendMessage(tm.utils.strVar(msg.shuffle, { - nickname: tm.utils.strip(info.nickname), + nickname: tm.utils.strip(info.nickname) }), sh.public ? undefined : info.login) }, privilege: sh.privilege @@ -271,8 +286,8 @@ function emitEvent any)[]>(eventCallbacks: T, ...pa } } -function addToQueue(songName: string, emitEvents: boolean, caller?: Caller): - Song | "already queued" | "not in songlist" | "no privilege" { +function addToQueue(songName: string, emitEvents: boolean, + caller?: Caller): Song | "already queued" | "not in songlist" | "no privilege" { const songIndex = queue.findIndex(a => a.name === songName) if (songIndex === -1) { return 'not in songlist' @@ -298,24 +313,25 @@ function addToQueue(songName: string, emitEvents: boolean, caller?: Caller): } if (emitEvents) { emitEvent(queueChangeCallbacks, queue, { - song, action: 'addedToQueue' + song, + action: 'addedToQueue' }) } return song } /** - * Removes a song from the song queue - * @param songName Song name - * @param caller Caller player object - * @returns Song object if it got removed, error message if unsuccessful - */ + * Removes a song from the song queue + * @param songName Song name + * @param caller Caller player object + * @returns Song object if it got removed, error message if unsuccessful + */ function removeFromQueue(name: string, caller?: Caller): Readonly | 'not queued' { if (!queue.filter(a => a.isJuked === true).some(a => a.name === name)) { return 'not queued' } const index: number = queue.findIndex(a => a.name === name) if (caller !== undefined) { - tm.log.trace(`${tm.utils.strip(caller.nickname)} (${caller.login}) removed song ` + - `${tm.utils.strip(queue[index].name)} by ${queue[index].author} from the queue`) + tm.log.trace(`${tm.utils.strip(caller.nickname)} (${caller.login}) removed song ` + `${tm.utils.strip( + queue[index].name)} by ${queue[index].author} from the queue`) } else { tm.log.trace(`Song ${tm.utils.strip(queue[index].name)} by ${queue[index].author} has been removed from the queue`) } @@ -325,7 +341,8 @@ function removeFromQueue(name: string, caller?: Caller): Readonly | 'not q queue.push(song) listUi.updateSongs(current, queue) emitEvent(queueChangeCallbacks, queue, { - song, action: 'removedFromQueue' + song, + action: 'removedFromQueue' }) return song } @@ -335,9 +352,16 @@ function shuffleQueue(caller?: Caller): boolean { return false } queue = queue - .map(a => ({ song: { ...a, isJuked: false, caller: undefined }, rand: Math.random() })) - .sort((a, b): number => a.rand - b.rand) - .map(a => a.song) + .map(a => ({ + song: { + ...a, + isJuked: false, + caller: undefined + }, + rand: Math.random() + })) + .sort((a, b): number => a.rand - b.rand) + .map(a => a.song) listUi.updateSongs(current, queue) emitEvent(queueChangeCallbacks, queue) if (caller !== undefined) { @@ -350,10 +374,7 @@ function shuffleQueue(caller?: Caller): boolean { function updateNextSong(url: string) { if (url === undefined) { return } - tm.client.callNoRes('SetForcedMusic', [ - { boolean: config.overrideMapMusic }, - { string: url } - ]) + tm.client.callNoRes('SetForcedMusic', [{ boolean: config.overrideMapMusic }, { string: url }]) } function fixDuplicateNames(): boolean { @@ -377,13 +398,17 @@ function fixDuplicateNames(): boolean { async function updateSongsConfigFile(action?: 'add' | 'remove', song?: Song): Promise { if (action === 'add' && song !== undefined) { - songs.push({ name: song.name, author: song.author, url: song.url }) + songs.push({ + name: song.name, + author: song.author, + url: song.url + }) } else if (action === 'remove' && song !== undefined) { songs = songs.filter(a => a.name !== song.name) } try { await fs.writeFile(config.songListPath, 'export default ' + JSON.stringify(songs, null, 2)) - } catch (err: any) { + } catch(err: any) { tm.log.error(`Cannot update song list in config. ${err?.message}`) } } diff --git a/plugins/music/SongList.js b/plugins/music/SongList.js index 0e1534e9..bdb50988 100644 --- a/plugins/music/SongList.js +++ b/plugins/music/SongList.js @@ -1,7 +1,5 @@ -export default [ - { - "name": "Transcendental Etudes No. 2", - "author": "Franz Liszt", - "url": "https://cdn.discordapp.com/attachments/618467373053247489/1093636918933213274/yt1s.com-Liszt-TE-2-Fusees-ANIMATED.ogg" - }, -] \ No newline at end of file +export default [{ + 'name': 'Transcendental Etudes No. 2', + 'author': 'Franz Liszt', + 'url': 'https://cdn.discordapp.com/attachments/618467373053247489/1093636918933213274/yt1s.com-Liszt-TE-2-Fusees-ANIMATED.ogg' +}] diff --git a/plugins/music/Types.ts b/plugins/music/Types.ts index a40ae003..7c853e91 100644 --- a/plugins/music/Types.ts +++ b/plugins/music/Types.ts @@ -3,6 +3,7 @@ interface Caller { readonly nickname: string readonly privilege: number } + interface Song { name: string author: string @@ -11,11 +12,10 @@ interface Song { caller?: Caller } -type QueueChangedCallback = - ((queue: Song[], change?: { - song: Song, - action: 'added' | 'removed' | 'addedToQueue' | 'removedFromQueue' - }) => void | Promise) +type QueueChangedCallback = ((queue: Song[], change?: { + song: Song, + action: 'added' | 'removed' | 'addedToQueue' | 'removedFromQueue' +}) => void | Promise) type SongAddedCallback = ((song: Song, caller?: Readonly) => void | Promise) type SongRemovedCallback = ((song: Song, caller?: Readonly) => void | Promise) diff --git a/plugins/music/ui/MusicWidget.component.ts b/plugins/music/ui/MusicWidget.component.ts index 26646a6f..3bc6fb68 100644 --- a/plugins/music/ui/MusicWidget.component.ts +++ b/plugins/music/ui/MusicWidget.component.ts @@ -1,4 +1,4 @@ -import { componentIds, StaticHeader, StaticComponent, leftAlignedText } from '../../ui/UI.js' +import { componentIds, leftAlignedText, StaticComponent, StaticHeader } from '../../ui/UI.js' import type { Song } from '../Types.js' import config from './MusicWidget.config.js' @@ -23,7 +23,7 @@ export default class MusicWidget extends StaticComponent { setCurrentSong(song: Song | undefined) { this.song = song const xml = this.display() - if(xml !== undefined) { + if (xml !== undefined) { tm.sendManialink(xml) } } @@ -39,7 +39,10 @@ export default class MusicWidget extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private updateXML(): void { @@ -73,8 +76,11 @@ export default class MusicWidget extends StaticComponent { private text(text: string, w: number, h: number, xOffset: number = 0, background?: string): string { xOffset = xOffset ?? 0 return ` - ${leftAlignedText(text, w, h, - { textScale: config.textScale, padding: config.textPadding, xOffset, })}` + ${leftAlignedText(text, w, h, { + textScale: config.textScale, + padding: config.textPadding, + xOffset + })}` } } diff --git a/plugins/music/ui/MusicWidget.config.js b/plugins/music/ui/MusicWidget.config.js index eba4d96e..69d5950f 100644 --- a/plugins/music/ui/MusicWidget.config.js +++ b/plugins/music/ui/MusicWidget.config.js @@ -7,8 +7,8 @@ export default { margin: cfg.margin, width: cfg.width, background: cfg.background, - noDataText: "--", - iconBackground: "000a", + noDataText: '--', + iconBackground: '000a', icons: { name: icons.disk, author: icons.personMusic @@ -19,4 +19,4 @@ export default { textPadding: 0.15, iconWidth: 1.7, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/music/ui/SongList.component.ts b/plugins/music/ui/SongList.component.ts index 679f9e0b..f03dd504 100644 --- a/plugins/music/ui/SongList.component.ts +++ b/plugins/music/ui/SongList.component.ts @@ -1,4 +1,4 @@ -import { closeButton, componentIds, Grid, centeredText, type GridCellFunction, Paginator, PopupWindow, addManialinkListener } from '../../ui/UI.js' +import { addManialinkListener, centeredText, closeButton, componentIds, Grid, type GridCellFunction, Paginator, PopupWindow } from '../../ui/UI.js' import pluginConfig from '../Config.js' import type { Song } from '../Types.js' import config from './SongList.config.js' @@ -9,7 +9,9 @@ interface DisplayParams { page: number isSearch: boolean paginator?: Paginator, - list?: (Song & { index: number })[] + list?: (Song & { + index: number + })[] query?: string target?: SearchTarget } @@ -17,11 +19,18 @@ interface DisplayParams { export default class SongList extends PopupWindow { private currentSong: Song | undefined - private songs: (Song & { index: number })[] = [] + private songs: (Song & { + index: number + })[] = [] private previousSongs: Song[] = [] private readonly queries: { - paginator: Paginator, list: (Song & { index: number })[], - login: string, query: string, target: SearchTarget + paginator: Paginator, + list: (Song & { + index: number + })[], + login: string, + query: string, + target: SearchTarget }[] = [] readonly grid: Grid readonly paginator: Paginator @@ -39,8 +48,10 @@ export default class SongList extends PopupWindow { new Array(config.entries).fill(1), config.grid) this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, 1) this.paginator.onPageChange = (login, page, info) => { - this.displayToPlayer(login, { page, isSearch: false }, - `${page}/${this.paginator.pageCount}`, info.privilege) + this.displayToPlayer(login, { + page, + isSearch: false + }, `${page}/${this.paginator.pageCount}`, info.privilege) } addManialinkListener(this.openId + this.addActionIdOffset, this.songListSizeLimit, (info, offset) => { const song = this.songs[offset] @@ -57,7 +68,10 @@ export default class SongList extends PopupWindow { updateSongs(currentSong: Song | undefined, queue: Song[]) { this.currentSong = currentSong queue = currentSong === undefined ? [...queue] : [...queue, currentSong] - this.songs = queue.map((a, i) => ({ ...a, index: i + 1 })).sort((a, b) => a.name > b.name ? 1 : -1) + this.songs = queue.map((a, i) => ({ + ...a, + index: i + 1 + })).sort((a, b) => a.name > b.name ? 1 : -1) this.paginator.setPageCount(Math.ceil(queue.length / (config.entries - 1))) this.reRender() } @@ -70,12 +84,19 @@ export default class SongList extends PopupWindow { openWithQuery(player: tm.Player, query: string, searchTarget: SearchTarget = 'name') { const list = this.getSearchResult(query, searchTarget) const paginator = this.getPaginator(player.login, list, player.privilege, query, searchTarget) - this.displayToPlayer(player.login, { page: 1, isSearch: true, paginator, list, query, target: searchTarget }, - `1/${paginator.pageCount}`, player.privilege) + this.displayToPlayer(player.login, { + page: 1, + isSearch: true, + paginator, + list, + query, + target: searchTarget + }, `1/${paginator.pageCount}`, player.privilege) } - private getPaginator(login: string, list: (Song & { index: number })[], - privilege: number, query: string, target: SearchTarget): Paginator { + private getPaginator(login: string, list: (Song & { + index: number + })[], privilege: number, query: string, target: SearchTarget): Paginator { const pageCount: number = Math.ceil(list.length / (config.entries - 1)) const playerQuery = this.queries.find(a => a.login === login) let paginator: Paginator // @@ -85,21 +106,36 @@ export default class SongList extends PopupWindow { paginator.setPageForLogin(login, 1) paginator.setPageCount(pageCount) } else { - paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, - this.windowWidth, this.footerHeight, pageCount) + paginator = new Paginator(this.openId + this.paginatorIdOffset + this.nextPaginatorId, this.windowWidth, + this.footerHeight, pageCount) this.nextPaginatorId += 10 this.nextPaginatorId = (this.nextPaginatorId % this.paginatorIdLimit) + this.paginatorIdOffset - this.queries.push({ paginator, login, list, query, target }) + this.queries.push({ + paginator, + login, + list, + query, + target + }) } - paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, - { page, isSearch: true, paginator, list }, `${page}/${pageCount}`, privilege) + paginator.onPageChange = (login: string, page: number): Promise => this.displayToPlayer(login, { + page, + isSearch: true, + paginator, + list + }, `${page}/${pageCount}`, privilege) return paginator } - protected onOpen(info: { login: string, privilege: number }): void { + protected onOpen(info: { + login: string, + privilege: number + }): void { const page = this.paginator.getPageByLogin(info.login) - this.displayToPlayer(info.login, { page, isSearch: false }, - `${page}/${this.paginator.pageCount}`, info.privilege) + this.displayToPlayer(info.login, { + page, + isSearch: false + }, `${page}/${this.paginator.pageCount}`, info.privilege) } private reRender(): void { @@ -110,8 +146,8 @@ export default class SongList extends PopupWindow { if (player.params.query !== undefined) { player.params.list = this.getSearchResult(player.params.query, player.params.target ?? 'name') } - this.displayToPlayer(player.login, player.params, - `${page}/${paginator.pageCount}`, tm.players.get(player.login)?.privilege ?? 0) + this.displayToPlayer(player.login, player.params, `${page}/${paginator.pageCount}`, + tm.players.get(player.login)?.privilege ?? 0) } } @@ -124,23 +160,17 @@ export default class SongList extends PopupWindow { const page = params?.page ?? 1 const list = params?.list ?? this.songs const index = (page - 1) * (config.entries - 1) - 1 - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Index ', w, h), - (i, j, w, h) => centeredText(' Name ', w, h), - (i, j, w, h) => centeredText(' Author ', w, h), - (i, j, w, h) => centeredText('Queue position', w, h), - (i, j, w, h) => centeredText(' Queued by ', w, h), - (i, j, w, h) => centeredText(' Queue ', w, h), - ] + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Index ', w, h), + (i, j, w, h) => centeredText(' Name ', w, h), (i, j, w, h) => centeredText(' Author ', w, h), + (i, j, w, h) => centeredText('Queue position', w, h), (i, j, w, h) => centeredText(' Queued by ', w, h), + (i, j, w, h) => centeredText(' Queue ', w, h)] const indexCell: GridCellFunction = (i, j, w, h) => { const cover = '' return centeredText((i + index + 1).toString(), w, h) + cover } - const nameCell: GridCellFunction = (i, j, w, h) => - centeredText(list[index + i].name, w, h) - const authorCell: GridCellFunction = (i, j, w, h) => - centeredText(list[index + i].author, w, h) + const nameCell: GridCellFunction = (i, j, w, h) => centeredText(list[index + i].name, w, h) + const authorCell: GridCellFunction = (i, j, w, h) => centeredText(list[index + i].author, w, h) const queueIndex: GridCellFunction = (i, j, w, h) => { const song = list[index + i] if (this.currentSong?.name === song.name) { @@ -148,8 +178,8 @@ export default class SongList extends PopupWindow { } return centeredText(song.index.toString(), w, h) } - const queuedByCell: GridCellFunction = (i, j, w, h) => - centeredText(list[index + i].caller?.nickname ?? config.defaultText, w, h) + const queuedByCell: GridCellFunction = (i, j, w, h) => centeredText( + list[index + i].caller?.nickname ?? config.defaultText, w, h) const addToQueueCell: GridCellFunction = (i, j, w, h) => { const song = list[index + i] let actionId = this.openId + this.addActionIdOffset + index + i @@ -186,15 +216,14 @@ export default class SongList extends PopupWindow { private checkQueuePrivilege(login: string, privilege: number, song: Song): boolean { if (this.currentSong?.name === song.name) { return false } if (song.caller?.login === login) { return true } - return !((privilege < pluginConfig.forceQueuePrivilege && - (this.previousSongs.some(a => a.name === song.name) || - this.songs.some(a => a.caller?.login === login))) || - (song.isJuked && privilege < pluginConfig.forceQueuePrivilege)) + return !((privilege < pluginConfig.forceQueuePrivilege && (this.previousSongs.some( + a => a.name === song.name) || this.songs.some( + a => a.caller?.login === login))) || (song.isJuked && privilege < pluginConfig.forceQueuePrivilege)) } protected constructFooter(login: string, params?: DisplayParams): string { - return closeButton(this.closeId, this.windowWidth, this.footerHeight) + - (params?.paginator ?? this.paginator).constructXml(login) + return closeButton(this.closeId, this.windowWidth, + this.footerHeight) + (params?.paginator ?? this.paginator).constructXml(login) } } diff --git a/plugins/music/ui/SongList.config.js b/plugins/music/ui/SongList.config.js index b0912452..1e959ae1 100644 --- a/plugins/music/ui/SongList.config.js +++ b/plugins/music/ui/SongList.config.js @@ -1,8 +1,9 @@ import { icons } from '../../ui/UI.js' + const p = tm.utils.palette export default { - title: " Song List ", + title: ' Song List ', icon: icons.musicList, iconWidth: 2, iconHeight: 2, @@ -14,19 +15,11 @@ export default { overlayColour: '7777', entries: 15, defaultText: '--', - columnProportions: [ - 1, - 3, - 3, - 1.7, - 3, - 1 - ], - navbar: [ - ], + columnProportions: [1, 3, 3, 1.7, 3, 1], + navbar: [], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" - }, -} \ No newline at end of file + headerBackground: '333C' + } +} diff --git a/plugins/sector_records/Config.js b/plugins/sector_records/Config.js index 116900d6..5c884fc3 100644 --- a/plugins/sector_records/Config.js +++ b/plugins/sector_records/Config.js @@ -1,18 +1,15 @@ -import { palette as p } from "../../config/PrefixesAndPalette.js" +import { palette as p } from '../../config/PrefixesAndPalette.js' export default { - isEnabled: true, - // Using DB client makes the plugin a bit faster due to high amount of database queries + isEnabled: true, // Using DB client makes the plugin a bit faster due to high amount of database queries // If too many plugins are using DB clients the process might hang useDBClient: true, noSectorRecords: `${p.error}You have no sector records on the ongoing map.`, allPlayerSectorsRemoved: `${p.servermsg}Your sectors on the ongoing map were removed.`, outOfRange: `${p.error}Sector index needs to be > 0 and <= to the ongoing map's sector count.`, playerSectorRemoved: `${p.servermsg}Your ${p.highlight}#{index}${p.servermsg} sector was removed.`, - allBestSectorsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` - + `${p.highlight}all sector records${p.admin} on the ongoing map.`, - bestSectorRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` - + `${p.highlight}#{index}${p.admin} sector record on the ongoing map.`, + allBestSectorsRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed ` + `${p.highlight}all sector records${p.admin} on the ongoing map.`, + bestSectorRemoved: `${p.admin}#{title} ${p.highlight}#{nickname}${p.admin} has removed the ` + `${p.highlight}#{index}${p.admin} sector record on the ongoing map.`, commands: { deletesector: { aliases: ['delsec', 'deletesector'], @@ -26,4 +23,4 @@ export default { privilege: 0 } } -} \ No newline at end of file +} diff --git a/plugins/sector_records/SectorDB.ts b/plugins/sector_records/SectorDB.ts index 2138eca6..2f17f1c9 100644 --- a/plugins/sector_records/SectorDB.ts +++ b/plugins/sector_records/SectorDB.ts @@ -13,8 +13,7 @@ const createQueries = [`CREATE TABLE IF NOT EXISTS best_sector_records( CONSTRAINT fk_map_id FOREIGN KEY(map_id) REFERENCES map_ids(id) -);`, -`CREATE TABLE IF NOT EXISTS sector_records( +);`, `CREATE TABLE IF NOT EXISTS sector_records( map_id INT4 NOT NULL, player_id INT4 NOT NULL, sectors INT4[] NOT NULL, @@ -41,18 +40,30 @@ for (const e of createQueries) { export const allSecsDB = { - async get(mapDBId: number, ...playerLogins: string[]): Promise<{ sectors: (number | undefined)[], login: string, nickname: string }[] | Error> { + async get(mapDBId: number, ...playerLogins: string[]): Promise<{ + sectors: (number | undefined)[], + login: string, + nickname: string + }[] | Error> { if (playerLogins.length === 0) { return [] } const playerDBIds = await tm.db.getPlayerId(playerLogins) const query = `SELECT sectors, login, nickname FROM sector_records JOIN players ON players.id=sector_records.player_id WHERE map_id=$1 AND (${playerDBIds.map((_: any, i: number) => `player_id=$${i + 2} OR `).join('').slice(0, -3)});` - const res: { sectors: number[], login: string, nickname: string }[] | Error = (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) + const res: { + sectors: number[], + login: string, + nickname: string + }[] | Error = (await queryDB(query, mapDBId, ...playerDBIds.map(a => a.id))) if (res instanceof Error) { tm.log.error(`Error when fetching sector records of players ${playerDBIds} on map ${mapDBId}`, res.message) return [] } - return res.map(a => ({ login: a.login, sectors: a.sectors.map(b => b === -1 ? undefined : b), nickname: a.nickname })) + return res.map(a => ({ + login: a.login, + sectors: a.sectors.map(b => b === -1 ? undefined : b), + nickname: a.nickname + })) }, async add(mapId: number, login: string, sectors: number[]): Promise { @@ -73,11 +84,21 @@ export const allSecsDB = { export const bestSecsDB = { - async get(mapId: string | number): Promise<{ sector: number, date: Date, login: string, nickname: string }[] | Error> { + async get(mapId: string | number): Promise<{ + sector: number, + date: Date, + login: string, + nickname: string + }[] | Error> { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) if (mapDBId === undefined) { return [] } - const res: { sector: number, date: Date, index: number, login: string, nickname: string }[] | Error = - (await queryDB(`SELECT sector, index, date, login, nickname FROM best_sector_records + const res: { + sector: number, + date: Date, + index: number, + login: string, + nickname: string + }[] | Error = (await queryDB(`SELECT sector, index, date, login, nickname FROM best_sector_records JOIN players ON players.id=best_sector_records.player_id WHERE map_id=$1;`, mapId)) if (res instanceof Error) { @@ -86,12 +107,18 @@ export const bestSecsDB = { } const ret: Omit[] = [] for (const e of res) { - ret[e.index] = { sector: e.sector, date: e.date, login: e.login, nickname: e.nickname } + ret[e.index] = { + sector: e.sector, + date: e.date, + login: e.login, + nickname: e.nickname + } } return ret }, - async add(mapId: string | number, playerId: string | number, index: number, sector: number, date: Date): Promise { + async add(mapId: string | number, playerId: string | number, index: number, sector: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } @@ -99,7 +126,8 @@ export const bestSecsDB = { await queryDB(query, mapDBId, playerDBId, index, sector, date) }, - async update(mapId: string | number, playerId: string | number, index: number, sector: number, date: Date): Promise { + async update(mapId: string | number, playerId: string | number, index: number, sector: number, + date: Date): Promise { const mapDBId = typeof mapId === 'number' ? mapId : await tm.db.getMapId(mapId) const playerDBId = typeof playerId === 'number' ? playerId : await tm.db.getPlayerId(playerId) if (mapDBId === undefined || playerDBId === undefined) { return } diff --git a/plugins/sector_records/SectorEvents.ts b/plugins/sector_records/SectorEvents.ts index 00a64d13..0fb6e7e4 100644 --- a/plugins/sector_records/SectorEvents.ts +++ b/plugins/sector_records/SectorEvents.ts @@ -13,7 +13,7 @@ const nicknameUpdateListeners: Function[] = [] * @param callback Callback function to execute */ const addListener = (event: T, callback: SectorEventFunctions[T]): void => { - switch (event) { + switch(event) { case 'BestSector': bestSectorListeners.push(callback as any) return @@ -34,9 +34,10 @@ const addListener = (event: T, callback: S } } -const emitEvent = async (event: T, ...params: Parameters) => { +const emitEvent = async (event: T, + ...params: Parameters) => { let listeners: any - switch (event) { + switch(event) { case 'BestSector': listeners = bestSectorListeners break diff --git a/plugins/sector_records/SectorListeners.ts b/plugins/sector_records/SectorListeners.ts index 460ff446..01fc3472 100644 --- a/plugins/sector_records/SectorListeners.ts +++ b/plugins/sector_records/SectorListeners.ts @@ -53,7 +53,10 @@ if (config.isEnabled) { } return } - const changedObjects: { login: string; nickname: string; }[] = [] + const changedObjects: { + login: string; + nickname: string; + }[] = [] for (const e of currentBestSecs) { if (e === undefined) { continue } const newNickname = info.find(a => a.login === e.login)?.nickname @@ -91,22 +94,42 @@ if (config.isEnabled) { const time = info.time - (info.player.currentCheckpoints[info.index - 1]?.time ?? 0) const isStunts = tm.getGameMode() === 'Stunts' if (playerSectors === undefined) { - currentPlayerSecs.push({ login: info.player.login, sectors: [time], nickname: info.player.nickname }) + currentPlayerSecs.push({ + login: info.player.login, + sectors: [time], + nickname: info.player.nickname + }) void allSecsDB.add(currentMapDBId, info.player.login, [time]) - emitEvent('PlayerSector', { login: info.player.login, nickname: info.player.nickname, index: info.index }) + emitEvent('PlayerSector', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) } else if (((playerSectors.sectors[info.index] ?? Infinity) > time && !isStunts) || ((playerSectors.sectors[info.index] ?? -1) < time && isStunts)) { playerSectors.sectors[info.index] = time void allSecsDB.update(currentMapDBId, info.player.login, playerSectors.sectors.map(a => a === undefined ? -1 : a)) - emitEvent('PlayerSector', { login: info.player.login, nickname: info.player.nickname, index: info.index }) + emitEvent('PlayerSector', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index + }) } const sector = currentBestSecs[info.index]?.sector if (sector === undefined || (sector > time && !isStunts) || (sector < time && isStunts)) { currentBestSecs[info.index] = { - login: info.player.login, nickname: info.player.nickname, sector: time, date: date + login: info.player.login, + nickname: info.player.nickname, + sector: time, + date: date } - sector === undefined ? void bestSecsDB.add(currentMapDBId, info.player.login, info.index, time, - date) : void bestSecsDB.update(currentMapDBId, info.player.login, info.index, time, date) - emitEvent('BestSector', { login: info.player.login, nickname: info.player.nickname, index: info.index, date }) + sector === undefined ? void bestSecsDB.add(currentMapDBId, info.player.login, info.index, time, date) : + void bestSecsDB.update(currentMapDBId, info.player.login, info.index, time, date) + emitEvent('BestSector', { + login: info.player.login, + nickname: info.player.nickname, + index: info.index, + date + }) } } tm.addListener('PlayerCheckpoint', playerCheckpoint) @@ -127,22 +150,42 @@ if (config.isEnabled) { const time = info.time - (info.checkpoints[index - 1] ?? 0) const isStunts = tm.getGameMode() === 'Stunts' if (playerSectors === undefined) { - currentPlayerSecs.push({ login: info.login, sectors: [time], nickname: info.nickname }) + currentPlayerSecs.push({ + login: info.login, + sectors: [time], + nickname: info.nickname + }) void allSecsDB.add(currentMapDBId, info.login, [time]) - emitEvent('PlayerSector', { login: info.login, nickname: info.nickname, index: index }) + emitEvent('PlayerSector', { + login: info.login, + nickname: info.nickname, + index: index + }) } else if (((playerSectors.sectors[index] ?? Infinity) > time && !isStunts) || ((playerSectors.sectors[index] ?? -1) < time && isStunts)) { playerSectors.sectors[index] = time void allSecsDB.update(currentMapDBId, info.login, playerSectors.sectors.map(a => a === undefined ? -1 : a)) - emitEvent('PlayerSector', { login: info.login, nickname: info.nickname, index: index }) + emitEvent('PlayerSector', { + login: info.login, + nickname: info.nickname, + index: index + }) } const sector = currentBestSecs[index]?.sector if (sector === undefined || (sector > time && !isStunts) || (sector < time && isStunts)) { currentBestSecs[index] = { - login: info.login, nickname: info.nickname, sector: time, date: date + login: info.login, + nickname: info.nickname, + sector: time, + date: date } - sector === undefined ? void bestSecsDB.add(currentMapDBId, info.login, index, time, - date) : void bestSecsDB.update(currentMapDBId, info.login, index, time, date) - emitEvent('BestSector', { login: info.login, nickname: info.nickname, index: index, date }) + sector === undefined ? void bestSecsDB.add(currentMapDBId, info.login, index, time, date) : + void bestSecsDB.update(currentMapDBId, info.login, index, time, date) + emitEvent('BestSector', { + login: info.login, + nickname: info.nickname, + index: index, + date + }) } } tm.addListener('PlayerFinish', playerFinish) @@ -168,8 +211,14 @@ if (config.isEnabled) { tm.addListener('PlayerJoin', playerJoin) tm.commands.add({ - aliases: config.commands.deletemysector.aliases, help: config.commands.deletemysector.help, - params: [{ name: 'sectorIndex', type: 'int', optional: true }], callback(info, sectorIndex?: number) { + aliases: config.commands.deletemysector.aliases, + help: config.commands.deletemysector.help, + params: [{ + name: 'sectorIndex', + type: 'int', + optional: true + }], + callback(info, sectorIndex?: number) { if (currentBestSecs === undefined) { tm.sendMessage(config.noSectorRecords, info.login) return @@ -183,7 +232,13 @@ if (config.isEnabled) { const arr: number[] = secs.sectors.filter(a => a !== undefined) as any secs.sectors.length = 0 tm.sendMessage(config.allPlayerSectorsRemoved, info.login) - emitEvent('DeletePlayerSector', { ...info, deletedSectors: arr.map((a, i) => ({ time: a, index: i })) }) + emitEvent('DeletePlayerSector', { + ...info, + deletedSectors: arr.map((a, i) => ({ + time: a, + index: i + })) + }) void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) } else { if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount) { @@ -195,26 +250,47 @@ if (config.isEnabled) { tm.sendMessage(tm.utils.strVar(config.playerSectorRemoved, { index: tm.utils.getOrdinalSuffix(sectorIndex) }), info.login) if (deleted !== undefined) { - emitEvent('DeletePlayerSector', { ...info, deletedSectors: [{ time: deleted, index: sectorIndex }] }) + emitEvent('DeletePlayerSector', { + ...info, + deletedSectors: [{ + time: deleted, + index: sectorIndex + }] + }) } void allSecsDB.update(currentMapDBId, info.login, secs.sectors.map(a => a === undefined ? -1 : a)) } - }, privilege: config.commands.deletemysector.privilege + }, + privilege: config.commands.deletemysector.privilege }, { - aliases: config.commands.deletesector.aliases, help: config.commands.deletesector.help, - params: [{ name: 'sectorIndex', type: 'int', optional: true }], callback(info, sectorIndex?: number) { + aliases: config.commands.deletesector.aliases, + help: config.commands.deletesector.help, + params: [{ + name: 'sectorIndex', + type: 'int', + optional: true + }], + callback(info, sectorIndex?: number) { if (currentBestSecs === undefined) { tm.sendMessage(config.noSectorRecords, info.login) return } if (sectorIndex === undefined) { const arr: { - login: string, nickname: string, sector: number, date: Date + login: string, + nickname: string, + sector: number, + date: Date }[] = currentBestSecs.filter(a => a !== undefined) as any currentBestSecs.length = 0 - tm.sendMessage(tm.utils.strVar(config.allBestSectorsRemoved, - { title: info.title, nickname: tm.utils.strip(info.nickname, true) })) - emitEvent('DeleteBestSector', arr.map((a, i) => ({ ...a, index: i }))) + tm.sendMessage(tm.utils.strVar(config.allBestSectorsRemoved, { + title: info.title, + nickname: tm.utils.strip(info.nickname, true) + })) + emitEvent('DeleteBestSector', arr.map((a, i) => ({ + ...a, + index: i + }))) void bestSecsDB.delete(currentMapDBId) } else { if (sectorIndex < 1 || sectorIndex > tm.maps.current.checkpointsAmount + 1) { @@ -224,36 +300,60 @@ if (config.isEnabled) { const deleted = currentBestSecs[sectorIndex - 1] currentBestSecs[sectorIndex - 1] = undefined tm.sendMessage(tm.utils.strVar(config.bestSectorRemoved, { - title: info.title, nickname: tm.utils.strip(info.nickname, true), + title: info.title, + nickname: tm.utils.strip(info.nickname, true), index: tm.utils.getOrdinalSuffix(sectorIndex) }), config.commands.deletesector.public ? undefined : info.login) if (deleted !== undefined) { - emitEvent('DeleteBestSector', [{ ...deleted, index: sectorIndex }]) + emitEvent('DeleteBestSector', [{ + ...deleted, + index: sectorIndex + }]) } void bestSecsDB.delete(currentMapDBId, sectorIndex - 1) } - }, privilege: config.commands.deletesector.privilege + }, + privilege: config.commands.deletesector.privilege }) } -const getMapSectors = (): ({ login: string, nickname: string, sector: number, date: Date } | null)[] => { +const getMapSectors = (): ({ + login: string, + nickname: string, + sector: number, + date: Date +} | null)[] => { if (currentBestSecs === undefined) { tm.log.debug('getMapSectors called before initialisation, aborting') return [] } - const arr: ({ login: string, nickname: string, sector: number, date: Date } | null)[] = new Array( - tm.maps.current.checkpointsAmount).fill(null) + const arr: ({ + login: string, + nickname: string, + sector: number, + date: Date + } | null)[] = new Array(tm.maps.current.checkpointsAmount).fill(null) for (const [i, e] of currentBestSecs.entries()) { arr[i] = e ?? null } return arr } -const getPlayerSectors = (): ({ login: string, nickname: string, sectors: (number | null)[] })[] => { - const arr: ({ login: string, nickname: string, sectors: (number | null)[] })[] = [] +const getPlayerSectors = (): ({ + login: string, + nickname: string, + sectors: (number | null)[] +})[] => { + const arr: ({ + login: string, + nickname: string, + sectors: (number | null)[] + })[] = [] for (const [i, e] of tm.players.list.entries()) { arr[i] = { - login: e.login, nickname: e.nickname, sectors: new Array(tm.maps.current.checkpointsAmount).fill(null) + login: e.login, + nickname: e.nickname, + sectors: new Array(tm.maps.current.checkpointsAmount).fill(null) .map((a, i) => currentPlayerSecs.find(a => a.login === e.login)?.sectors[i] ?? null) } } diff --git a/plugins/sector_records/SectorRecords.ts b/plugins/sector_records/SectorRecords.ts index a46ed7f7..9b1082ef 100644 --- a/plugins/sector_records/SectorRecords.ts +++ b/plugins/sector_records/SectorRecords.ts @@ -14,14 +14,23 @@ export const sectorRecords = { /** * @returns an array of current map best sector records or null if sector record doesn't exist */ - get mapSectors(): ({ login: string, nickname: string, sector: number, date: Date } | null)[] { + get mapSectors(): ({ + login: string, + nickname: string, + sector: number, + date: Date + } | null)[] { return getMapSectors() }, /** * @returns an array of current map online players and their sector records */ - get playerSectors(): ({ login: string, nickname: string, sectors: (number | null)[] })[] { + get playerSectors(): ({ + login: string, + nickname: string, + sectors: (number | null)[] + })[] { return getPlayerSectors() }, @@ -29,7 +38,7 @@ export const sectorRecords = { * Registers a callback to execute on a given event * @param event Event name * @param callback Callback function to execute - */ + */ addListener, /** diff --git a/plugins/sector_records/SectorTypes.ts b/plugins/sector_records/SectorTypes.ts index f75928dd..d546e9d6 100644 --- a/plugins/sector_records/SectorTypes.ts +++ b/plugins/sector_records/SectorTypes.ts @@ -12,13 +12,34 @@ export interface PlayerSectors { } export interface SectorEventFunctions { - 'BestSector': ((bestSector: Readonly<{ login: string, nickname: string, index: number, date: Date }>) => void) + 'BestSector': ((bestSector: Readonly<{ + login: string, + nickname: string, + index: number, + date: Date + }>) => void) 'SectorsFetch': ((bestSectors: Readonly, playerSectors: readonly Readonly[]) => void) - 'DeleteBestSector': ((deletedSectors: readonly Readonly<{ index: number, login: string, sector: number, date: Date }>[]) => void) + 'DeleteBestSector': ((deletedSectors: readonly Readonly<{ + index: number, + login: string, + sector: number, + date: Date + }>[]) => void) 'DeletePlayerSector': ((player: Readonly<{ - login: string, nickname: string, - deletedSectors: readonly Readonly<{ index: number, time: number }>[] + login: string, + nickname: string, + deletedSectors: readonly Readonly<{ + index: number, + time: number + }>[] + }>) => void) + 'PlayerSector': ((playerSector: Readonly<{ + login: string, + nickname: string, + index: number }>) => void) - 'PlayerSector': ((playerSector: Readonly<{ login: string, nickname: string, index: number }>) => void) - 'NicknameUpdated': ((players: Readonly[]>) => void) + 'NicknameUpdated': ((players: Readonly[]>) => void) } diff --git a/plugins/sector_records/ui/SectorRecords.component.ts b/plugins/sector_records/ui/SectorRecords.component.ts index faafc613..a2ea6c2a 100644 --- a/plugins/sector_records/ui/SectorRecords.component.ts +++ b/plugins/sector_records/ui/SectorRecords.component.ts @@ -3,7 +3,7 @@ * @since 0.3 */ -import { closeButton, componentIds, Grid, centeredText, Paginator, type GridCellFunction, type GridCellObject, PopupWindow } from '../../ui//UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from '../../ui//UI.js' import { sectorRecords } from '../../sector_records/SectorRecords.js' import config from './SectorRecords.config.js' @@ -13,7 +13,8 @@ class SectorRecords extends PopupWindow { private readonly paginator: Paginator constructor() { - super(componentIds.sectorRecords, config.icon, config.title, (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) + super(componentIds.sectorRecords, config.icon, config.title, + (tm.getGameMode() === 'Stunts' ? config.stuntsNavbar : config.navbar)) this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, new Array(config.entries + 2).fill(1), config.grid) this.paginator = new Paginator(this.openId, this.contentWidth, this.footerHeight, @@ -58,15 +59,13 @@ class SectorRecords extends PopupWindow { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } - protected constructContent(login: string, params: { page: number }): string { - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Index ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - (i, j, w, h) => centeredText(' Date ', w, h), - (i, j, w, h) => centeredText(' Best ', w, h), - (i, j, w, h) => centeredText(' Personal ', w, h) - ] + protected constructContent(login: string, params: { + page: number + }): string { + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Index ', w, h), + (i, j, w, h) => centeredText(' Nickname ', w, h), (i, j, w, h) => centeredText(' Login ', w, h), + (i, j, w, h) => centeredText(' Date ', w, h), (i, j, w, h) => centeredText(' Best ', w, h), + (i, j, w, h) => centeredText(' Personal ', w, h)] const sectors = sectorRecords.mapSectors const sectorIndex = config.entries * (params.page - 1) const personalSectors = sectorRecords.playerSectors @@ -76,7 +75,8 @@ class SectorRecords extends PopupWindow { } const nicknameCell: GridCellFunction = (i, j, w, h) => { - return centeredText(tm.utils.safeString(tm.utils.strip(sectors[i + sectorIndex - 1]?.nickname ?? '-', false)), w, h) + return centeredText(tm.utils.safeString(tm.utils.strip(sectors[i + sectorIndex - 1]?.nickname ?? '-', false)), w, + h) } const loginCell: GridCellFunction = (i, j, w, h) => { @@ -87,8 +87,7 @@ class SectorRecords extends PopupWindow { const dateCell: GridCellFunction = (i, j, w, h) => { const sector = sectors[i + sectorIndex - 1] - return centeredText((sector === undefined || sector === null) ? '' : - tm.utils.formatDate(sector.date, true), w, h) + return centeredText((sector === undefined || sector === null) ? '' : tm.utils.formatDate(sector.date, true), w, h) } const bestSectorCell: GridCellFunction = (i, j, w, h) => { @@ -106,7 +105,7 @@ class SectorRecords extends PopupWindow { const bestSec = sectors?.[i + sectorIndex - 1]?.sector let betterSign = '-' let worseSign = '+' - if(isStunts) { + if (isStunts) { betterSign = '+' worseSign = '-' } @@ -128,7 +127,7 @@ class SectorRecords extends PopupWindow { const emptyCell: GridCellObject = { background: '', colspan: 3, - callback: () => '', + callback: () => '' } const totalTimeHeaderCell: GridCellObject = { @@ -154,7 +153,7 @@ class SectorRecords extends PopupWindow { } let betterSign = '-' let worseSign = '+' - if(isStunts) { + if (isStunts) { betterSign = '+' worseSign = '-' } @@ -183,7 +182,9 @@ class SectorRecords extends PopupWindow { return this.grid.constructXml(arr) } - protected constructFooter(login: string, params: { page: number }): string { + protected constructFooter(login: string, params: { + page: number + }): string { return closeButton(this.closeId, this.windowWidth, this.footerHeight) + this.paginator.constructXml(params.page) } diff --git a/plugins/sector_records/ui/SectorRecords.config.js b/plugins/sector_records/ui/SectorRecords.config.js index 8074b93f..929a5980 100644 --- a/plugins/sector_records/ui/SectorRecords.config.js +++ b/plugins/sector_records/ui/SectorRecords.config.js @@ -1,39 +1,48 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: " Sector Records ", + title: ' Sector Records ', icon: icons.clock, entries: 13, - columnProportions: [ - 0.7, - 2, - 2, - 2, - 1, - 1.5 - ], - navbar: [ - { name: 'Checkpoint Records', actionId: ids.checkpointRecords }, - { name: 'Dedi Checkpoints', actionId: ids.dediCps }, - { name: 'Dedi Sectors', actionId: ids.dediSectors }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], - stuntsNavbar: [ - { name: 'Checkpoint Records', actionId: ids.checkpointRecords }, - { name: 'Ulti Records', actionId: ids.ultiRecords }, - { name: 'Local Checkpoints', actionId: ids.localCps }, - { name: 'Local Sectors', actionId: ids.localSectors }, - ], + columnProportions: [0.7, 2, 2, 2, 1, 1.5], + navbar: [{ + name: 'Checkpoint Records', + actionId: ids.checkpointRecords + }, { + name: 'Dedi Checkpoints', + actionId: ids.dediCps + }, { + name: 'Dedi Sectors', + actionId: ids.dediSectors + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], + stuntsNavbar: [{ + name: 'Checkpoint Records', + actionId: ids.checkpointRecords + }, { + name: 'Ulti Records', + actionId: ids.ultiRecords + }, { + name: 'Local Checkpoints', + actionId: ids.localCps + }, { + name: 'Local Sectors', + actionId: ids.localSectors + }], colours: { worse: tm.utils.palette.red, better: tm.utils.palette.purple, equal: tm.utils.palette.yellow }, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -43,4 +52,4 @@ export default { }, noTimeText: '--:--.-', stuntsNoTimeText: '--' -} \ No newline at end of file +} diff --git a/plugins/server_links/Config.js b/plugins/server_links/Config.js index dc76460b..5db69dc5 100644 --- a/plugins/server_links/Config.js +++ b/plugins/server_links/Config.js @@ -1,23 +1,18 @@ export default { - isEnabled: false, - // http server can be used to connect servers running on different computers + isEnabled: false, // http server can be used to connect servers running on different computers useHttpServer: false, httpAddress: '127.0.0.1', httpPort: 7309, refreshTimeout: 10, // In seconds // In seconds. (If more time passes since other servers last update it will be considered down) updateLimit: 30, - dataFilePath: './plugins/server_links/temp/data.txt', - // to connect a remote server set "url" instead of "path" - servers: [ - { - name: 'name1', - path: 'D:///plugins/server_links/temp/data.txt' - }, - { - name: 'name2', - url: 'http://url:port' - } - ], + dataFilePath: './plugins/server_links/temp/data.txt', // to connect a remote server set "url" instead of "path" + servers: [{ + name: 'name1', + path: 'D:///plugins/server_links/temp/data.txt' + }, { + name: 'name2', + url: 'http://url:port' + }], noDataText: '--' } diff --git a/plugins/server_links/ServerLinks.ts b/plugins/server_links/ServerLinks.ts index 39d94b71..335cc583 100644 --- a/plugins/server_links/ServerLinks.ts +++ b/plugins/server_links/ServerLinks.ts @@ -45,8 +45,10 @@ if (config.isEnabled) { } async function updateDataFile() { - const res: { LadderServerLimitMin: number, LadderServerLimitMax: number } | Error = - await tm.client.call('GetLadderServerLimits') + const res: { + LadderServerLimitMin: number, + LadderServerLimitMax: number + } | Error = await tm.client.call('GetLadderServerLimits') if (res instanceof Error) { tm.log.error(`Error when getting server ladder limits`, res.message) return @@ -89,7 +91,7 @@ async function refreshOtherServersData() { let newInfo: Partial try { newInfo = JSON.parse(file) - } catch (err) { + } catch(err) { continue } const infoObj = constructInfoObject(newInfo, e.name) @@ -104,8 +106,7 @@ async function refreshOtherServersData() { } function constructInfoObject(info: Partial, name: string): ServerInfo | undefined { - if (info.login === undefined || info.lastUpdate === undefined || - Date.now() - info.lastUpdate > config.updateLimit * 1000) { return } + if (info.login === undefined || info.lastUpdate === undefined || Date.now() - info.lastUpdate > config.updateLimit * 1000) { return } return { login: info.login, lastUpdate: info.lastUpdate, diff --git a/plugins/server_links/ui/ServerLinks.component.ts b/plugins/server_links/ui/ServerLinks.component.ts index 351bdb45..9cf25b9b 100644 --- a/plugins/server_links/ui/ServerLinks.component.ts +++ b/plugins/server_links/ui/ServerLinks.component.ts @@ -1,5 +1,5 @@ import config from './ServerLinks.config.js' -import { StaticComponent, StaticHeader, type StaticHeaderOptions, componentIds, Grid, type GridCellFunction, centeredText, leftAlignedText, Paginator } from '../../ui/UI.js' +import { centeredText, componentIds, Grid, type GridCellFunction, leftAlignedText, Paginator, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../ui/UI.js' import type { ServerInfo } from '../ServerLinks.js' export default class ServerLinks extends StaticComponent { @@ -23,9 +23,8 @@ export default class ServerLinks extends StaticComponent { constructor() { super(componentIds.serverLinks) this.header = new StaticHeader('race') - this.grid = new Grid(config.width, - config.height - this.header.options.height, - [1], new Array(config.entries).fill(1)) + this.grid = new Grid(config.width, config.height - this.header.options.height, [1], + new Array(config.entries).fill(1)) this.paginator = new Paginator(this.id, 0, 0, 4) this.paginator.onPageChange = (login) => { const obj = this.displayToPlayer(login) @@ -115,7 +114,8 @@ export default class ServerLinks extends StaticComponent { let buttonsXml: string = '' for (let i: number = 0; i < buttonAmount; i++) { const action: string = ids[i] === undefined ? '' : `action="${ids[i]}"` - const icon: string = icons[i] === undefined ? '' : `` buttonsXml += ` @@ -126,7 +126,11 @@ export default class ServerLinks extends StaticComponent { - ${leftAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), headerCfg.height, { textScale: headerCfg.textScale, padding: headerCfg.horizontalPadding })} + ${leftAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), + headerCfg.height, { + textScale: headerCfg.textScale, + padding: headerCfg.horizontalPadding + })} ${buttonsXml} ` @@ -135,29 +139,27 @@ export default class ServerLinks extends StaticComponent { private constructEntry(ii: number, jj: number, ww: number, hh: number, startIndex: number): string { const data = this.serverList[(ii + startIndex) % this.serverList.length] const m = config.margin - const arr: GridCellFunction[] = [ - (i, j, w, h): string => { - return `${this.icon(icons.name, iw, h)} + const arr: GridCellFunction[] = [(i, j, w, h): string => { + return `${this.icon(icons.name, iw, h)} ${this.text(tm.utils.strip(data.name, false), w - iw - m, h, iw + m, config.iconBackground, true)}` - }, - (i, j, w, h): string => { - const width = 7 - return `${this.icon(icons.map, iw, h)} + }, (i, j, w, h): string => { + const width = 7 + return `${this.icon(icons.map, iw, h)} ${this.text(data.currentMap, width, h, iw + m)} ${this.icon(icons.playerCount, iw, h, iw + width + 2 * m)} - ${this.text(`${data.playerCount}/${data.maxPlayerCount}`, w - (2 * iw + width + 3 * m), h, 2 * iw + width + 3 * m)}` - }, - (i, j, w, h): string => { - const width = 7 - return `${this.icon(icons.author, iw, h)} + ${this.text(`${data.playerCount}/${data.maxPlayerCount}`, w - (2 * iw + width + 3 * m), h, + 2 * iw + width + 3 * m)}` + }, (i, j, w, h): string => { + const width = 7 + return `${this.icon(icons.author, iw, h)} ${this.text(data.currentMapAuthor, width, h, iw + m)} ${this.icon(this.gameModeIcons[data.gameMode], iw, h, iw + width + 2 * m)} ${this.text(this.gameModeMap[data.gameMode], w - (2 * iw + width + 3 * m), h, 2 * iw + width + 3 * m)}` - } - ] + }] const iw = config.iconWidth const icons = config.icons - const grid = new Grid(ww + config.margin * 2, hh + config.margin, [1], new Array(3).fill(1), { margin: config.margin }) + const grid = new Grid(ww + config.margin * 2, hh + config.margin, [1], new Array(3).fill(1), + { margin: config.margin }) return `${grid.constructXml(arr)}` } @@ -167,12 +169,19 @@ export default class ServerLinks extends StaticComponent { image="${url}"/>` } - private text(text: string, w: number, h: number, xOffset: number = 0, background?: string, leftAligned?: boolean): string { + private text(text: string, w: number, h: number, xOffset: number = 0, background?: string, + leftAligned?: boolean): string { xOffset = xOffset ?? 0 return ` - ${leftAligned ? leftAlignedText(text, w, h, - { textScale: config.textScale, padding: config.textPadding, xOffset: xOffset + config.margin, }) - : centeredText(text, w, h, { textScale: config.textScale, padding: config.textPadding, xOffset })}` + ${leftAligned ? leftAlignedText(text, w, h, { + textScale: config.textScale, + padding: config.textPadding, + xOffset: xOffset + config.margin + }) : centeredText(text, w, h, { + textScale: config.textScale, + padding: config.textPadding, + xOffset + })}` } } diff --git a/plugins/server_links/ui/ServerLinks.config.js b/plugins/server_links/ui/ServerLinks.config.js index 4dd6f6c8..9e38ac7f 100644 --- a/plugins/server_links/ui/ServerLinks.config.js +++ b/plugins/server_links/ui/ServerLinks.config.js @@ -1,9 +1,9 @@ -import { raceConfig as cfg, icons } from '../../ui/UI.js' +import { icons, raceConfig as cfg } from '../../ui/UI.js' export default { height: 33, width: cfg.width, - title: "Linked Servers", + title: 'Linked Servers', icon: icons.infoList, margin: cfg.margin, entries: 5, @@ -25,10 +25,10 @@ export default { Stunts: icons.stuntsMode } }, - iconBackground: "000a", + iconBackground: '000a', textBackground: cfg.background, textScale: 0.9, textPadding: 0.15, iconWidth: 1.7, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/skip_endscreen/Config.js b/plugins/skip_endscreen/Config.js index 17a81df5..1cbf1613 100644 --- a/plugins/skip_endscreen/Config.js +++ b/plugins/skip_endscreen/Config.js @@ -1,5 +1,4 @@ export default { - isEnabled: false, - // For how long to wait before forcing everybody into specmode (in seconds) + isEnabled: false, // For how long to wait before forcing everybody into specmode (in seconds) waitTime: 5 -} \ No newline at end of file +} diff --git a/plugins/skip_endscreen/SkipEndscreen.ts b/plugins/skip_endscreen/SkipEndscreen.ts index 7571562a..374443ae 100644 --- a/plugins/skip_endscreen/SkipEndscreen.ts +++ b/plugins/skip_endscreen/SkipEndscreen.ts @@ -14,16 +14,13 @@ if (config.isEnabled) { if (specs.find(a => a.login === p.login)) { continue } - tm.client.callNoRes('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 2 }] - }, - { - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 0 }] - }] - ) + tm.client.callNoRes('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 2 }] + }, { + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 0 }] + }]) } specs.length = 0 }) @@ -40,16 +37,13 @@ if (config.isEnabled) { specs.push(p) continue } - tm.client.callNoRes('system.multicall', - [{ - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 1 }] - }, - { - method: 'ForceSpectator', - params: [{ string: p.login }, { int: 0 }] - }] - ) + tm.client.callNoRes('system.multicall', [{ + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 1 }] + }, { + method: 'ForceSpectator', + params: [{ string: p.login }, { int: 0 }] + }]) } }, config.waitTime * 1000) }) diff --git a/plugins/stats/Config.js b/plugins/stats/Config.js index 94aa6674..4a64762d 100644 --- a/plugins/stats/Config.js +++ b/plugins/stats/Config.js @@ -6,7 +6,6 @@ export default { recordsCount: 100000, playtimesCount: 100000, votesCount: 100000, - sumsCount: 100000, - // Playtimes are updated on endmap and on this interval (default 10 minutes) + sumsCount: 100000, // Playtimes are updated on endmap and on this interval (default 10 minutes) playtimesUpdateInterval: 1000 * 60 * 10 -} \ No newline at end of file +} diff --git a/plugins/stats/TopAverages.ts b/plugins/stats/TopAverages.ts index efbb77c2..f9bbd571 100644 --- a/plugins/stats/TopAverages.ts +++ b/plugins/stats/TopAverages.ts @@ -1,12 +1,26 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, average: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, average: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + average: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + average: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { - const res: { login: string, nickname: string, average: number }[] | Error = - await tm.db.query(`SELECT login, nickname, average FROM players + const res: { + login: string, + nickname: string, + average: number + }[] | Error = await tm.db.query(`SELECT login, nickname, average FROM players ORDER BY average ASC, last_online DESC LIMIT ${config.averagesCount}`) @@ -22,7 +36,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize(), true) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -40,16 +57,20 @@ tm.addListener('PlayerDataUpdated', (info) => { tm.addListener('RanksAndAveragesUpdated', async (info) => { const updated: typeof topList = [] for (const e of info) { - if (topList.length !== 0 && topList.length >= config.averagesCount && - e.average >= topList[topList.length - 1].average) { return } + if (topList.length !== 0 && topList.length >= config.averagesCount && e.average >= topList[topList.length - 1].average) { return } const entry = topList.find(a => a.login === e.login) if (entry !== undefined) { entry.average = e.average updated.push(entry) topList.sort((a, b) => a.average - b.average) } else { - const nickname: string | undefined = tm.players.get(e.login)?.nickname ?? (await tm.players.fetch(e.login))?.nickname - const obj = { login: e.login, nickname: nickname ?? e.login, average: e.average } + const nickname: string | undefined = tm.players.get(e.login)?.nickname ?? (await tm.players.fetch( + e.login))?.nickname + const obj = { + login: e.login, + nickname: nickname ?? e.login, + average: e.average + } updated.push(obj) topList.push(obj) topList.sort((a, b) => a.average - b.average) @@ -74,15 +95,23 @@ export const topAverages = { /** * List of players sorted by their average server rank */ - get list(): readonly Readonly<{ login: string, nickname: string, average: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + average: number + }>[] { return topList }, /** - * Add a callback function to execute on top averages list update - * @param callback Function to execute on event. It takes an array of updated objects as a parameter - */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, average: number }>[]) => void): void { + * Add a callback function to execute on top averages list update + * @param callback Function to execute on event. It takes an array of updated objects as a parameter + */ + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + average: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -90,7 +119,10 @@ export const topAverages = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopDonations.ts b/plugins/stats/TopDonations.ts index 76a1ed67..3233e3b7 100644 --- a/plugins/stats/TopDonations.ts +++ b/plugins/stats/TopDonations.ts @@ -1,13 +1,27 @@ import { donations } from '../donations/Donations.js' import config from './Config.js' -let topList: { readonly login: string, nickname: string, amount: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + amount: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { - const res: { amount: number, login: string, nickname: string }[] | Error = - await tm.db.query(`SELECT SUM(amount) AS amount, login, nickname FROM donations + const res: { + amount: number, + login: string, + nickname: string + }[] | Error = await tm.db.query(`SELECT SUM(amount) AS amount, login, nickname FROM donations JOIN players ON players.id=donations.player_id GROUP BY (login, nickname) ORDER BY amount DESC @@ -24,7 +38,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize(), true) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -42,8 +59,7 @@ tm.addListener('PlayerDataUpdated', (info) => { donations.onDonation((info) => { const login = info.login const amount = info.sum - if (topList.length !== 0 && topList.length >= config.donationsCount && - amount <= topList[topList.length - 1].amount) { return } + if (topList.length !== 0 && topList.length >= config.donationsCount && amount <= topList[topList.length - 1].amount) { return } const entry = topList.find(a => a.login === login) const updated: typeof topList = [] if (entry !== undefined) { @@ -51,7 +67,11 @@ donations.onDonation((info) => { updated.push(entry) topList.sort((a, b) => b.amount - a.amount) } else { - const obj = { login, nickname: info.nickname, amount } + const obj = { + login, + nickname: info.nickname, + amount + } updated.push(obj) topList.push(obj) topList.sort((a, b) => b.amount - a.amount) @@ -70,7 +90,11 @@ export const topDonations = { /** * List of players sorted by the amount of coppers donated to the server */ - get list(): readonly Readonly<{ login: string, nickname: string, amount: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[] { return topList }, @@ -78,7 +102,11 @@ export const topDonations = { * Add a callback function to execute on top donations list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -86,7 +114,10 @@ export const topDonations = { * Add a callback function to execute on donator nickname change * @param callback Function to execute on event. It takes donation object as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopPlaytimes.ts b/plugins/stats/TopPlaytimes.ts index 0f4238d3..f77fa40a 100644 --- a/plugins/stats/TopPlaytimes.ts +++ b/plugins/stats/TopPlaytimes.ts @@ -1,13 +1,27 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, playtime: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, playtime: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + playtime: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + playtime: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] let interval: NodeJS.Timeout const initialize = async () => { - const res: { login: string, nickname: string, playtime: number }[] | Error = - await tm.db.query(`SELECT login, nickname, time_played AS playtime FROM players + const res: { + login: string, + nickname: string, + playtime: number + }[] | Error = await tm.db.query(`SELECT login, nickname, time_played AS playtime FROM players ORDER BY time_played DESC, last_online DESC LIMIT ${config.playtimesCount}`) @@ -28,15 +42,18 @@ const update = async () => { const updated: typeof topList = [] for (const e of players) { const pt = e.timePlayed + Date.now() - e.joinTimestamp - if (topList.length !== 0 && topList.length >= config.playtimesCount && - pt <= topList[topList.length - 1].playtime) { return } + if (topList.length !== 0 && topList.length >= config.playtimesCount && pt <= topList[topList.length - 1].playtime) { return } const entry = topList.find(a => a.login === e.login) if (entry !== undefined) { entry.playtime = pt updated.push(entry) topList.sort((a, b) => b.playtime - a.playtime) } else { - const obj = { login: e.login, nickname: e.nickname, playtime: pt } + const obj = { + login: e.login, + nickname: e.nickname, + playtime: pt + } updated.push(obj) topList.push(obj) topList.sort((a, b) => b.playtime - a.playtime) @@ -47,7 +64,10 @@ const update = async () => { } tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -68,7 +88,6 @@ tm.addListener('EndMap', () => { update() }) - /** * Creates and provides utilities for accessing players playtime ranking * @author lythx @@ -79,7 +98,11 @@ export const topPlaytimes = { /** * List of players sorted by their total playtime */ - get list(): readonly Readonly<{ login: string, nickname: string, playtime: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + playtime: number + }>[] { return topList }, @@ -87,7 +110,11 @@ export const topPlaytimes = { * Add a callback function to execute on top playtimes list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, playtime: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + playtime: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -95,7 +122,10 @@ export const topPlaytimes = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopRecords.ts b/plugins/stats/TopRecords.ts index 2d223421..c2d6a458 100644 --- a/plugins/stats/TopRecords.ts +++ b/plugins/stats/TopRecords.ts @@ -1,17 +1,34 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, amount: number }[] = [] -let onlineList: { readonly login: string, nickname: string, amount: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + amount: number +}[] = [] +let onlineList: { + readonly login: string, + nickname: string, + amount: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { const mapIds = await tm.db.getMapId(tm.maps.list.map(a => a.id)) - const res: { readonly login: string, nickname: string, amount: number }[] | Error = - await tm.db.query(`WITH r(player_id, map_id) AS - (SELECT player_id, map_id FROM records WHERE map_id IN(${ - // Use only map ids in the current maplist - mapIds.map(a => `${a.id},`).join('').slice(0, -1)})) + const res: { + readonly login: string, + nickname: string, + amount: number + }[] | Error = await tm.db.query(`WITH r(player_id, map_id) AS + (SELECT player_id, map_id FROM records WHERE map_id IN(${// Use only map ids in the current maplist + mapIds.map(a => `${a.id},`).join('').slice(0, -1)})) SELECT count(*)::int as amount, nickname, login FROM r JOIN players ON players.id=r.player_id GROUP BY (nickname, login, last_online) @@ -29,7 +46,10 @@ const initialize = async () => { } tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -51,11 +71,25 @@ tm.addListener('PlayerDataUpdated', (info) => { } }) -async function getFromDB(login: string): Promise<{ login: string, nickname: string, amount: number } | undefined> -async function getFromDB(logins: string[]): Promise<{ login: string, nickname: string, amount: number }[]> -async function getFromDB(logins: string | string[]): - Promise<{ login: string, nickname: string, amount: number } | undefined | - { login: string, nickname: string, amount: number }[]> { +async function getFromDB(login: string): Promise<{ + login: string, + nickname: string, + amount: number +} | undefined> +async function getFromDB(logins: string[]): Promise<{ + login: string, + nickname: string, + amount: number +}[]> +async function getFromDB(logins: string | string[]): Promise<{ + login: string, + nickname: string, + amount: number +} | undefined | { + login: string, + nickname: string, + amount: number +}[]> { if (typeof logins === 'string') { const id = await tm.db.getPlayerId(logins) if (id === undefined) { return } @@ -73,9 +107,17 @@ async function getFromDB(logins: string | string[]): if (res[0] === undefined) { const player = await tm.players.fetch(logins) if (player === undefined) { return } - return { login: player.login, nickname: player.nickname, amount: 0 } + return { + login: player.login, + nickname: player.nickname, + amount: 0 + } + } + return { + login: logins, + nickname: res[0].nickname, + amount: res[0].amount } - return { login: logins, nickname: res[0].nickname, amount: res[0].amount } } const ids = await tm.db.getPlayerId(logins) if (ids.length === 0) { return [] } @@ -91,11 +133,21 @@ async function getFromDB(logins: string | string[]): tm.log.error(`Failed to get record count info for players ${logins.join(',')}`, res.message, res.stack) return [] } - const ret = res.map(a => ({ login: a.login, nickname: a.nickname, amount: a.amount })) + const ret = res.map(a => ({ + login: a.login, + nickname: a.nickname, + amount: a.amount + })) for (const e of logins) { if (!ret.some(a => a.login === e)) { const player = await tm.players.fetch(e) - if (player !== undefined) { ret.push({ login: e, nickname: player.nickname, amount: 0 }) } + if (player !== undefined) { + ret.push({ + login: e, + nickname: player.nickname, + amount: 0 + }) + } } } return ret @@ -119,8 +171,7 @@ tm.addListener('LocalRecord', info => { const obj = onlineList.find(a => a.login === info.login) if (obj === undefined) { return } obj.amount++ - if (topList.length !== 0 && topList.length >= config.recordsCount && - obj.amount <= topList[topList.length - 1].amount) { return } + if (topList.length !== 0 && topList.length >= config.recordsCount && obj.amount <= topList[topList.length - 1].amount) { return } const entry = topList.find(a => a.login === info.login) if (entry !== undefined) { entry.amount = obj.amount @@ -152,14 +203,22 @@ export const topRecords = { /** * List of players sorted by their record count */ - get list(): readonly Readonly<{ login: string, nickname: string, amount: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[] { return topList }, /** * List of currently online players sorted by their record count */ - get onlineList(): readonly Readonly<{ login: string, nickname: string, amount: number }>[] { + get onlineList(): readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[] { return onlineList }, @@ -167,7 +226,11 @@ export const topRecords = { * Add a callback function to execute on top records list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, amount: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + amount: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -175,7 +238,10 @@ export const topRecords = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopSums.ts b/plugins/stats/TopSums.ts index 7595b619..e5b511f1 100644 --- a/plugins/stats/TopSums.ts +++ b/plugins/stats/TopSums.ts @@ -1,18 +1,29 @@ import config from './Config.js' -const topList: { readonly login: string, nickname: string, sums: [number, number, number, number] }[] = [] +const topList: { + readonly login: string, + nickname: string, + sums: [number, number, number, number] +}[] = [] const updateListeners: ((changes: readonly Readonly<{ - login: string, nickname: string, + login: string, + nickname: string, sums: [number, number, number, number] }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] let initialLocals: tm.LocalRecord[] = [] let refreshNeeded = false const initialize = async () => { refreshNeeded = false - const res: { uid: string, login: string, nickname: string }[] | Error = - await tm.db.query(`SELECT uid, login, nickname FROM records + const res: { + uid: string, + login: string, + nickname: string + }[] | Error = await tm.db.query(`SELECT uid, login, nickname FROM records JOIN map_ids ON map_ids.id=records.map_id JOIN players ON players.id=records.player_id ORDER BY uid ASC, @@ -81,7 +92,10 @@ tm.addListener('BeginMap', (): void => { }) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -146,7 +160,8 @@ export const topSums = { * List of players sorted by their records amount */ get list(): readonly Readonly<{ - login: string, nickname: string, + login: string, + nickname: string, sums: Readonly<[number, number, number, number]> }>[] { return topList @@ -157,7 +172,8 @@ export const topSums = { * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ onUpdate(callback: (changes: readonly Readonly<{ - login: string, nickname: string, + login: string, + nickname: string, sums: Readonly<[number, number, number, number]> }>[]) => void) { updateListeners.push(callback) @@ -167,7 +183,10 @@ export const topSums = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } @@ -175,14 +194,30 @@ export const topSums = { function sortToplist() { topList.sort((a, b) => { - if (a.sums[0] < b.sums[0]) return 1 - if (a.sums[0] > b.sums[0]) return -1 - if (a.sums[1] < b.sums[1]) return 1 - if (a.sums[1] > b.sums[1]) return -1 - if (a.sums[2] < b.sums[2]) return 1 - if (a.sums[2] > b.sums[2]) return -1 - if (a.sums[3] < b.sums[3]) return 1 - if (a.sums[3] > b.sums[3]) return -1 + if (a.sums[0] < b.sums[0]) { + return 1 + } + if (a.sums[0] > b.sums[0]) { + return -1 + } + if (a.sums[1] < b.sums[1]) { + return 1 + } + if (a.sums[1] > b.sums[1]) { + return -1 + } + if (a.sums[2] < b.sums[2]) { + return 1 + } + if (a.sums[2] > b.sums[2]) { + return -1 + } + if (a.sums[3] < b.sums[3]) { + return 1 + } + if (a.sums[3] > b.sums[3]) { + return -1 + } return 0 }) } \ No newline at end of file diff --git a/plugins/stats/TopVisits.ts b/plugins/stats/TopVisits.ts index aca91ace..6e230af8 100644 --- a/plugins/stats/TopVisits.ts +++ b/plugins/stats/TopVisits.ts @@ -1,12 +1,26 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, visits: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, visits: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + visits: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + visits: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { - const res: { login: string, nickname: string, visits: number }[] | Error = - await tm.db.query(`SELECT login, nickname, visits FROM players + const res: { + login: string, + nickname: string, + visits: number + }[] | Error = await tm.db.query(`SELECT login, nickname, visits FROM players ORDER BY visits DESC, last_online DESC LIMIT ${config.visitsCount}`) @@ -22,7 +36,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize()) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -40,8 +57,7 @@ tm.addListener('PlayerDataUpdated', (info) => { tm.addListener('PlayerJoin', (info) => { const login = info.login const visits = info.visits - if (topList.length !== 0 && topList.length >= config.visitsCount && - visits <= topList[topList.length - 1].visits) { return } + if (topList.length !== 0 && topList.length >= config.visitsCount && visits <= topList[topList.length - 1].visits) { return } const entry = topList.find(a => a.login === login) let obj: typeof topList[number] if (entry !== undefined) { @@ -49,7 +65,11 @@ tm.addListener('PlayerJoin', (info) => { obj = entry topList.sort((a, b) => b.visits - a.visits) } else { - obj = { login, visits, nickname: info.nickname } + obj = { + login, + visits, + nickname: info.nickname + } topList.push(obj) topList.sort((a, b) => b.visits - a.visits) topList.length = Math.min(config.visitsCount, topList.length) @@ -69,7 +89,11 @@ export const topVisits = { /** * List of players sorted by their visit count */ - get list(): readonly Readonly<{ login: string, nickname: string, visits: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + visits: number + }>[] { return topList }, @@ -77,7 +101,11 @@ export const topVisits = { * Add a callback function to execute on top visits list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, visits: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + visits: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -85,7 +113,10 @@ export const topVisits = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopVotes.ts b/plugins/stats/TopVotes.ts index 9f9549a7..58924f6c 100644 --- a/plugins/stats/TopVotes.ts +++ b/plugins/stats/TopVotes.ts @@ -1,20 +1,41 @@ import config from './Config.js' -let onlineList: { readonly login: string, nickname: string, count: number }[] = [] +let onlineList: { + readonly login: string, + nickname: string, + count: number +}[] = [] let initialVotes: tm.Vote[] = [] -let topList: { readonly login: string, nickname: string, count: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, count: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + count: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + count: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const addToOnlineList = async (login: string, nickname: string) => { const id: number | undefined = await tm.db.getPlayerId(login) - const res: { count: number }[] | Error = await tm.db.query(`SELECT count(*)::int FROM votes + const res: { + count: number + }[] | Error = await tm.db.query(`SELECT count(*)::int FROM votes WHERE player_id=$1`, id) if (res instanceof Error) { tm.log.error(`Failed to fetch vote count for player ${login}`, res.message, res.stack) return } - onlineList.push({ login, nickname, count: res[0].count }) + onlineList.push({ + login, + nickname, + count: res[0].count + }) } const initialize = async () => { @@ -37,7 +58,10 @@ const initialize = async () => { } tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -70,8 +94,7 @@ const handleVote = (vote: tm.Vote) => { const obj = onlineList.find(a => a.login === vote.login) if (obj === undefined) { return } obj.count++ - if (topList.length !== 0 && topList.length >= config.votesCount && - obj.count <= topList[topList.length - 1].count) { return } + if (topList.length !== 0 && topList.length >= config.votesCount && obj.count <= topList[topList.length - 1].count) { return } const entry = topList.find(a => a.login === obj.login) if (entry !== undefined) { entry.count = obj.count @@ -120,7 +143,11 @@ export const topVotes = { /** * List of players sorted by their votes count */ - get list(): readonly Readonly<{ login: string, nickname: string, count: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + count: number + }>[] { return topList }, @@ -128,7 +155,11 @@ export const topVotes = { * Add a callback function to execute on top votes list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, count: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + count: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -136,7 +167,10 @@ export const topVotes = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/TopWins.ts b/plugins/stats/TopWins.ts index 49341196..5dbdb84c 100644 --- a/plugins/stats/TopWins.ts +++ b/plugins/stats/TopWins.ts @@ -1,8 +1,19 @@ import config from './Config.js' -let topList: { readonly login: string, nickname: string, wins: number }[] = [] -const updateListeners: ((changes: readonly Readonly<{ login: string, nickname: string, wins: number }>[]) => void)[] = [] -const nicknameChangeListeners: ((changes: readonly Readonly<{ login: string, nickname: string }>[]) => void)[] = [] +let topList: { + readonly login: string, + nickname: string, + wins: number +}[] = [] +const updateListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string, + wins: number +}>[]) => void)[] = [] +const nicknameChangeListeners: ((changes: readonly Readonly<{ + login: string, + nickname: string +}>[]) => void)[] = [] const initialize = async () => { const res: any[] | Error = await tm.db.query(`SELECT login, nickname, wins FROM players @@ -21,7 +32,10 @@ const initialize = async () => { tm.addListener('Startup', (): void => void initialize()) tm.addListener('PlayerDataUpdated', (info) => { - const changedObjects: { login: string, nickname: string }[] = [] + const changedObjects: { + login: string, + nickname: string + }[] = [] for (const e of topList) { const newNickname = info.find(a => a.login === e.login)?.nickname if (newNickname !== undefined) { @@ -39,9 +53,7 @@ tm.addListener('PlayerDataUpdated', (info) => { tm.addListener('EndMap', async (info) => { const login = info.winnerLogin const wins = info.winnerWins - if (wins === undefined || login === undefined - || (topList.length !== 0 && topList.length >= config.winsCount - && wins <= topList[topList.length - 1].wins)) { return } + if (wins === undefined || login === undefined || (topList.length !== 0 && topList.length >= config.winsCount && wins <= topList[topList.length - 1].wins)) { return } const entry = topList.find(a => a.login === login) let obj: typeof topList[number] if (entry !== undefined) { @@ -50,7 +62,11 @@ tm.addListener('EndMap', async (info) => { topList.sort((a, b) => b.wins - a.wins) } else { const nickname: string | undefined = tm.players.get(login)?.nickname ?? (await tm.players.fetch(login))?.nickname - obj = { login, wins, nickname: nickname ?? login } + obj = { + login, + wins, + nickname: nickname ?? login + } topList.push(obj) topList.sort((a, b) => b.wins - a.wins) topList.length = Math.min(config.winsCount, topList.length) @@ -68,7 +84,11 @@ export const topWins = { /** * List of players sorted by their wins count */ - get list(): readonly Readonly<{ login: string, nickname: string, wins: number }>[] { + get list(): readonly Readonly<{ + login: string, + nickname: string, + wins: number + }>[] { return topList }, @@ -76,7 +96,11 @@ export const topWins = { * Add a callback function to execute on top wins list update * @param callback Function to execute on event. It takes an array of updated objects as a parameter */ - onUpdate(callback: (changes: readonly Readonly<{ login: string, nickname: string, wins: number }>[]) => void): void { + onUpdate(callback: (changes: readonly Readonly<{ + login: string, + nickname: string, + wins: number + }>[]) => void): void { updateListeners.push(callback) }, @@ -84,7 +108,10 @@ export const topWins = { * Add a callback function to execute on player nickname change * @param callback Function to execute on event. It takes an array of objects containing login and nickname as a parameter */ - onNicknameChange(callback: (changes: readonly Readonly<{ login: string, nickname: string }>[]) => void): void { + onNicknameChange(callback: (changes: readonly Readonly<{ + login: string, + nickname: string + }>[]) => void): void { nicknameChangeListeners.push(callback) } diff --git a/plugins/stats/ui/StatsUi.ts b/plugins/stats/ui/StatsUi.ts index 32eababf..c24ff4df 100644 --- a/plugins/stats/ui/StatsUi.ts +++ b/plugins/stats/ui/StatsUi.ts @@ -7,7 +7,7 @@ import TopVisits from './TopVisits.component.js' import TopVotes from './TopVotes.component.js' import TopWins from './TopWins.component.js' -tm.addListener('Startup', ()=> { +tm.addListener('Startup', () => { new TopDonations() new TopPlaytimes() new TopRanks() diff --git a/plugins/stats/ui/TopDonations.component.ts b/plugins/stats/ui/TopDonations.component.ts index 1d11f487..18ab7a00 100644 --- a/plugins/stats/ui/TopDonations.component.ts +++ b/plugins/stats/ui/TopDonations.component.ts @@ -4,15 +4,18 @@ */ import { stats } from "../Stats.js" -import { componentIds, centeredText } from '../../ui/UI.js' -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from '../../ui/UI.js' import config from './TopDonations.config.js' export default class TopDonations extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, amount: number }[] + private ranks: readonly { + login: string, + nickname: string, + amount: number + }[] constructor() { super(componentIds.topDonations, config.icon, config.title, config.navbar) @@ -29,7 +32,8 @@ export default class TopDonations extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -61,17 +65,17 @@ export default class TopDonations extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopDonations.config.js b/plugins/stats/ui/TopDonations.config.js index 73000471..865cceb0 100644 --- a/plugins/stats/ui/TopDonations.config.js +++ b/plugins/stats/ui/TopDonations.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Donations", + title: 'Top Donations', icon: icons.cash, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Donations', 'Lp.', 'Nickname', 'Login', 'Donations'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top donations.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopPlaytimes.component.ts b/plugins/stats/ui/TopPlaytimes.component.ts index f8add324..36e8d07d 100644 --- a/plugins/stats/ui/TopPlaytimes.component.ts +++ b/plugins/stats/ui/TopPlaytimes.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopPlaytimes.config.js' export default class TopPlaytimes extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, playtime: number }[] + private ranks: readonly { + login: string, + nickname: string, + playtime: number + }[] constructor() { super(componentIds.topPlaytimes, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopPlaytimes extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopPlaytimes extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText((this.ranks[getIndex(i, j)].playtime / (60 * 60 * 1000)).toFixed(0), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText( + (this.ranks[getIndex(i, j)].playtime / (60 * 60 * 1000)).toFixed(0), w, h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopPlaytimes.config.js b/plugins/stats/ui/TopPlaytimes.config.js index 00b6b943..aea9af1b 100644 --- a/plugins/stats/ui/TopPlaytimes.config.js +++ b/plugins/stats/ui/TopPlaytimes.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Playtimes", + title: 'Top Playtimes', icon: icons.clockList, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Hours Played', 'Lp.', 'Nickname', 'Login', 'Hours Played'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top playtimes.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopRanks.component.ts b/plugins/stats/ui/TopRanks.component.ts index 67a1f7bc..07a2cc2f 100644 --- a/plugins/stats/ui/TopRanks.component.ts +++ b/plugins/stats/ui/TopRanks.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopRanks.config.js' export default class TopRanks extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, average: number }[] + private ranks: readonly { + login: string, + nickname: string, + average: number + }[] constructor() { super(componentIds.topRanks, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopRanks extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopRanks extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].average.toFixed(config.precision), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText( + this.ranks[getIndex(i, j)].average.toFixed(config.precision), w, h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopRanks.config.js b/plugins/stats/ui/TopRanks.config.js index b5266b25..6d36b7c6 100644 --- a/plugins/stats/ui/TopRanks.config.js +++ b/plugins/stats/ui/TopRanks.config.js @@ -1,25 +1,38 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Ranks", + title: 'Top Ranks', icon: icons.chartLocal, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Average Rank', 'Lp.', 'Nickname', 'Login', 'Average Rank'], entries: 30, // Has to be even number precision: 4, - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -27,4 +40,4 @@ export default { help: `Display top average ranks.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopRecords.component.ts b/plugins/stats/ui/TopRecords.component.ts index 44826fb1..520ec12e 100644 --- a/plugins/stats/ui/TopRecords.component.ts +++ b/plugins/stats/ui/TopRecords.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopRecords.config.js' export default class TopRecords extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, amount: number }[] + private ranks: readonly { + login: string, + nickname: string, + amount: number + }[] constructor() { super(componentIds.topRecords, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopRecords extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopRecords extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].amount.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopRecords.config.js b/plugins/stats/ui/TopRecords.config.js index 4e41060d..1ed61512 100644 --- a/plugins/stats/ui/TopRecords.config.js +++ b/plugins/stats/ui/TopRecords.config.js @@ -1,23 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' + export default { - title: "Top Records", + title: 'Top Records', icon: icons.stats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Records', 'Lp.', 'Nickname', 'Login', 'Records'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -25,4 +39,4 @@ export default { help: `Display top record amounts.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopSums.component.ts b/plugins/stats/ui/TopSums.component.ts index 8690ef8b..e1d3dc94 100644 --- a/plugins/stats/ui/TopSums.component.ts +++ b/plugins/stats/ui/TopSums.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopSums.config.js' export default class TopSums extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, sums: Readonly<[number, number, number, number]> }[] + private ranks: readonly { + login: string, + nickname: string, + sums: Readonly<[number, number, number, number]> + }[] constructor() { super(componentIds.topSums, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopSums extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,23 +65,23 @@ export default class TopSums extends PopupWindow { const leftColumns = 6 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const firstCell: GridCellFunction = (i, j, w, h) => - centeredText('$' + config.colours.gold + this.ranks[getIndex(i, j)].sums[0].toString(), w, h) - const secondCell: GridCellFunction = (i, j, w, h) => - centeredText('$' + config.colours.silver + this.ranks[getIndex(i, j)].sums[1].toString(), w, h) - const thirdCell: GridCellFunction = (i, j, w, h) => - centeredText('$' + config.colours.bronze + this.ranks[getIndex(i, j)].sums[2].toString(), w, h) - const otherCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].sums[3].toString(), w, h) + const firstCell: GridCellFunction = (i, j, w, h) => centeredText( + '$' + config.colours.gold + this.ranks[getIndex(i, j)].sums[0].toString(), w, h) + const secondCell: GridCellFunction = (i, j, w, h) => centeredText( + '$' + config.colours.silver + this.ranks[getIndex(i, j)].sums[1].toString(), w, h) + const thirdCell: GridCellFunction = (i, j, w, h) => centeredText( + '$' + config.colours.bronze + this.ranks[getIndex(i, j)].sums[2].toString(), w, h) + const otherCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].sums[3].toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopSums.config.js b/plugins/stats/ui/TopSums.config.js index 7744669c..95142e8c 100644 --- a/plugins/stats/ui/TopSums.config.js +++ b/plugins/stats/ui/TopSums.config.js @@ -1,25 +1,38 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Sums", + title: 'Top Sums', icon: icons.stats, gridColumns: [0.8, 4, 3, 1, 1, 1, 1.3, 0.8, 4, 3, 1, 1, 1, 1.3], - headers: ['Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', 'Other', - 'Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', 'Other',], + headers: ['Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', 'Other', 'Lp.', 'Nickname', 'Login', '1st', '2nd', '3rd', + 'Other'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, colours: { @@ -32,4 +45,4 @@ export default { help: `Display top record summaries.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopVisits.component.ts b/plugins/stats/ui/TopVisits.component.ts index eb8239df..64992916 100644 --- a/plugins/stats/ui/TopVisits.component.ts +++ b/plugins/stats/ui/TopVisits.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopVisits.config.js' export default class TopVisits extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, visits: number }[] + private ranks: readonly { + login: string, + nickname: string, + visits: number + }[] constructor() { super(componentIds.topVisits, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopVisits extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopVisits extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].visits.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].visits.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopVisits.config.js b/plugins/stats/ui/TopVisits.config.js index c10cac30..fc065f3f 100644 --- a/plugins/stats/ui/TopVisits.config.js +++ b/plugins/stats/ui/TopVisits.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Visits", + title: 'Top Visits', icon: icons.stats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Visits', 'Lp.', 'Nickname', 'Login', 'Visits'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Votes", actionId: ids.topVotes }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Votes', + actionId: ids.topVotes + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top visitors.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopVotes.component.ts b/plugins/stats/ui/TopVotes.component.ts index b5f4b34c..6cbf2ff6 100644 --- a/plugins/stats/ui/TopVotes.component.ts +++ b/plugins/stats/ui/TopVotes.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopVotes.config.js' export default class TopVisits extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, count: number }[] + private ranks: readonly { + login: string, + nickname: string, + count: number + }[] constructor() { super(componentIds.topVotes, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopVisits extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,17 @@ export default class TopVisits extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].count.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].count.toString(), w, + h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopVotes.config.js b/plugins/stats/ui/TopVotes.config.js index bb4bc215..1a4e7194 100644 --- a/plugins/stats/ui/TopVotes.config.js +++ b/plugins/stats/ui/TopVotes.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Votes", + title: 'Top Votes', icon: icons.karmaStats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Votes', 'Lp.', 'Nickname', 'Login', 'Votes'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Wins", actionId: ids.topWins } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Wins', + actionId: ids.topWins + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top vote amounts.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/stats/ui/TopWins.component.ts b/plugins/stats/ui/TopWins.component.ts index aeec78b8..d0ec9b5d 100644 --- a/plugins/stats/ui/TopWins.component.ts +++ b/plugins/stats/ui/TopWins.component.ts @@ -4,14 +4,18 @@ */ import { stats } from "../../stats/Stats.js" -import { Paginator, Grid, type GridCellFunction, closeButton, type GridCellObject, PopupWindow, componentIds, centeredText } from "../../ui/UI.js" +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, Paginator, PopupWindow } from "../../ui/UI.js" import config from './TopWins.config.js' export default class TopWins extends PopupWindow { private readonly paginator: Paginator private readonly grid: Grid - private ranks: readonly { login: string, nickname: string, wins: number }[] + private ranks: readonly { + login: string, + nickname: string, + wins: number + }[] constructor() { super(componentIds.topWins, config.icon, config.title, config.navbar) @@ -28,7 +32,8 @@ export default class TopWins extends PopupWindow { this.paginator.setPageCount(Math.ceil(this.ranks.length / config.entries)) this.reRender() }) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(this.ranks.length / config.entries)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(this.ranks.length / config.entries)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } @@ -60,17 +65,16 @@ export default class TopWins extends PopupWindow { const leftColumns = 3 const offset = (((page ?? 1) - 1) * config.entries) - 1 const getIndex = (i: number, j: number) => i + offset + (j > leftColumns ? (config.entries / columns) : 0) - const arr: (GridCellFunction | GridCellObject)[] = config.headers.map((a) => - (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) + const arr: (GridCellFunction | GridCellObject)[] = config.headers.map( + (a) => (i: number, j: number, w: number, h: number) => centeredText(a, w, h)) const indexCell: GridCellFunction = (i, j, w, h) => centeredText((getIndex(i, j) + 1).toString(), w, h) - const nicknameCell: GridCellFunction = (i, j, w, h) => - centeredText(tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) + const nicknameCell: GridCellFunction = (i, j, w, h) => centeredText( + tm.utils.safeString(tm.utils.strip(this.ranks[getIndex(i, j)].nickname, false)), w, h) const loginCell: GridCellFunction = (i, j, w, h) => { const colour = this.ranks[getIndex(i, j)].login === login ? `$${config.selfColour} ` : '' return centeredText(colour + this.ranks[getIndex(i, j)].login, w, h) } - const averageCell: GridCellFunction = (i, j, w, h) => - centeredText(this.ranks[getIndex(i, j)].wins.toString(), w, h) + const averageCell: GridCellFunction = (i, j, w, h) => centeredText(this.ranks[getIndex(i, j)].wins.toString(), w, h) const emptyCell: GridCellObject = { callback: (i, j, w, h) => '', background: undefined diff --git a/plugins/stats/ui/TopWins.config.js b/plugins/stats/ui/TopWins.config.js index 45a50fcc..3f76fc32 100644 --- a/plugins/stats/ui/TopWins.config.js +++ b/plugins/stats/ui/TopWins.config.js @@ -1,24 +1,37 @@ -import { icons, componentIds as ids } from '../../ui/UI.js' +import { componentIds as ids, icons } from '../../ui/UI.js' export default { - title: "Top Wins", + title: 'Top Wins', icon: icons.stats, gridColumns: [0.8, 4, 3, 3, 0.8, 4, 3, 3], headers: ['Lp.', 'Nickname', 'Login', 'Wins', 'Lp.', 'Nickname', 'Login', 'Wins'], entries: 30, // Has to be even number - navbar: [ - { name: "Top Donations", actionId: ids.topDonations }, - { name: "Top Playtimes", actionId: ids.topPlaytimes }, - { name: "Top Ranks", actionId: ids.topRanks }, - { name: "Top Records", actionId: ids.topRecords }, - { name: "Top Sums", actionId: ids.topSums }, - { name: "Top Visits", actionId: ids.topVisits }, - { name: "Top Votes", actionId: ids.topVotes } - ], + navbar: [{ + name: 'Top Donations', + actionId: ids.topDonations + }, { + name: 'Top Playtimes', + actionId: ids.topPlaytimes + }, { + name: 'Top Ranks', + actionId: ids.topRanks + }, { + name: 'Top Records', + actionId: ids.topRecords + }, { + name: 'Top Sums', + actionId: ids.topSums + }, { + name: 'Top Visits', + actionId: ids.topVisits + }, { + name: 'Top Votes', + actionId: ids.topVotes + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, selfColour: tm.utils.palette.green, command: { @@ -26,4 +39,4 @@ export default { help: `Display top winners.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/tmx/Tmx.ts b/plugins/tmx/Tmx.ts index 0e49c88a..fdd5bf88 100644 --- a/plugins/tmx/Tmx.ts +++ b/plugins/tmx/Tmx.ts @@ -30,7 +30,8 @@ const getUid = (map: tm.TMXMap | string): string => typeof map === 'string' ? ma const initialize = async (): Promise => { if (queueSize > tm.jukebox.queueCount) { - await tm.log.fatal(`jukeboxQueueSize (${tm.jukebox.queueCount}) can't be lower than tmx queueSize (${queueSize}). Change your tmx config`) + await tm.log.fatal( + `jukeboxQueueSize (${tm.jukebox.queueCount}) can't be lower than tmx queueSize (${queueSize}). Change your tmx config`) } const res: tm.TMXMap | Error = await tm.tmx.fetchMapInfo(tm.maps.current.id) current = res instanceof Error ? tm.maps.current.id : res @@ -56,8 +57,7 @@ const update = async (updateCurrent?: true): Promise => { const curId = tm.maps.current.id if (updateCurrent) { const entry = fetchedMaps.find(a => getUid(a) === curId) - if (entry !== undefined) { current = entry } - else { + if (entry !== undefined) { current = entry } else { const res = await tm.tmx.fetchMapInfo(curId) current = res instanceof Error ? curId : res } @@ -127,7 +127,7 @@ function getFromQueue(uids: string | string[]): Readonly | undefined } /** - * Fetches TMX data for current map and maps in the queue. + * Fetches TMX data for current map and maps in the queue. * Provides utilities for accessing TMX related data. * @author lythx * @since 0.1 @@ -155,7 +155,7 @@ export const tmx = { getFromQueue, /** - * Current map TMX info or null if map is not on tmx + * Current map TMX info or null if map is not on tmx */ get current(): Readonly | null { return typeof current === 'string' ? null : current diff --git a/plugins/tmx/ui/TMXWindow.component.ts b/plugins/tmx/ui/TMXWindow.component.ts index 23faeb99..95df5815 100644 --- a/plugins/tmx/ui/TMXWindow.component.ts +++ b/plugins/tmx/ui/TMXWindow.component.ts @@ -3,7 +3,7 @@ * @since 0.5 */ -import { componentIds, Paginator, Grid, type GridCellFunction, centeredText, closeButton, leftAlignedText, type GridCellObject, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, leftAlignedText, Paginator, PopupWindow } from '../../ui/UI.js' import config from './TMXWindow.config.js' import { tmx } from "../../tmx/Tmx.js" @@ -15,12 +15,15 @@ class TMXWindow extends PopupWindow { constructor() { super(componentIds.TMXWindow, config.icon, config.title, config.navbar) - this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, Math.ceil(config.queueCount / config.itemsPerPage)) + this.paginator = new Paginator(this.openId, this.windowWidth, this.footerHeight, + Math.ceil(config.queueCount / config.itemsPerPage)) this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, page, `${page}/${this.paginator.pageCount}`) } - this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.itemsPerPage).fill(1), [1], - { background: config.gridBackground, margin: config.margin }) + this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.itemsPerPage).fill(1), [1], { + background: config.gridBackground, + margin: config.margin + }) tmx.onMapChange(() => this.reRender()) tmx.onQueueChange(() => this.reRender()) tm.addListener('BeginMap', () => { @@ -66,22 +69,27 @@ class TMXWindow extends PopupWindow { const index = Math.ceil((currentPage - (page + 1)) * config.itemsPerPage) + 1 maps = [tm.jukebox.history?.[index + 2], tm.jukebox.history?.[index + 1], tm.jukebox.history?.[index]] TMXMaps = [tmx.history?.[index + 2], tmx.history?.[index + 1], tmx.history?.[index]] - titles = [`${config.titles.previous} #${index + 3} `, `${config.titles.previous} #${index + 2} `, `${config.titles.previous} #${index + 1} `] + titles = [`${config.titles.previous} #${index + 3} `, `${config.titles.previous} #${index + 2} `, + `${config.titles.previous} #${index + 1} `] } else { const index = Math.ceil((page - (currentPage + 1)) * config.itemsPerPage) + 1 maps = [tm.jukebox.queue?.[index], tm.jukebox.queue?.[index + 1], tm.jukebox.queue?.[index + 2]] TMXMaps = [tmx.queue?.[index], tmx.queue?.[index + 1], tmx.queue?.[index + 2]] - titles = [`${config.titles.next} #${index + 1} `, `${config.titles.next} #${index + 2} `, `${config.titles.next} #${index + 3} `] + titles = [`${config.titles.next} #${index + 1} `, `${config.titles.next} #${index + 2} `, + `${config.titles.next} #${index + 3} `] } const m = maps.filter(a => a !== undefined).map(a => (a as any).id) - const allRecords: Readonly = [...tm.records.getFromHistory(...m), ...tm.records.local, ...tm.records.getFromQueue(...m)] - .sort((a, b) => a.time - b.time) - .filter((a, i, arr) => arr.findIndex(b => b.login === a.login && a.map === b.map) === i) + const allRecords: Readonly = [...tm.records.getFromHistory(...m), ...tm.records.local, + ...tm.records.getFromQueue(...m)] + .sort((a, b) => a.time - b.time) + .filter((a, i, arr) => arr.findIndex(b => b.login === a.login && a.map === b.map) === i) const cell: GridCellFunction = (i, j, w, h) => { const map = maps[j] if (map === undefined) { return '' } - const grid = new Grid(w, h, [1], config.gridRows, - { background: config.info.background, margin: config.margin }) + const grid = new Grid(w, h, [1], config.gridRows, { + background: config.info.background, + margin: config.margin + }) let mapName = map.name if (tm.utils.isMultibyte(mapName)) { mapName = '' @@ -89,16 +97,19 @@ class TMXWindow extends PopupWindow { const TMXMap = TMXMaps[j] ?? undefined const mapRecords = allRecords.filter(a => a.map === map.id) const header: GridCellFunction = (ii, jj, ww, hh) => this.constructHeader(ww, hh, titles[j], map, TMXMap) - const screenshot: GridCellFunction = (ii, jj, ww, hh) => this.constructScreenshot(login, ww, hh, mapRecords, map.environment, TMXMap) - const name: GridCellFunction = (ii, jj, ww, hh) => - this.constructEntry(tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(mapName), false)), config.icons.name, ww, hh, config.iconWidth) + const screenshot: GridCellFunction = (ii, jj, ww, hh) => this.constructScreenshot(login, ww, hh, mapRecords, + map.environment, TMXMap) + const name: GridCellFunction = (ii, jj, ww, hh) => this.constructEntry( + tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(mapName), false)), config.icons.name, ww, hh, + config.iconWidth) const author: GridCellFunction = (ii, jj, ww, hh) => this.constructAuthor(ww, hh, map) - const infos: GridCellFunction = (ii, jj, ww, hh) => - this.constructInfoXml(ww, hh, map, TMXMap) - const tmxRecords: GridCellFunction = (ii, jj, ww, hh) => this.counstructTmxRecordsXml(ww, hh, TMXMap?.validReplays) + const infos: GridCellFunction = (ii, jj, ww, hh) => this.constructInfoXml(ww, hh, map, TMXMap) + const tmxRecords: GridCellFunction = (ii, jj, ww, hh) => this.counstructTmxRecordsXml(ww, hh, + TMXMap?.validReplays) return grid.constructXml([header, screenshot, name, author, infos, tmxRecords]) } - return ` ` + this.grid.constructXml(new Array(config.itemsPerPage).fill(null).map(() => cell)) + return ` ` + this.grid.constructXml( + new Array(config.itemsPerPage).fill(null).map(() => cell)) } protected constructFooter(login: string, page: number): string { @@ -114,34 +125,46 @@ sizen="${config.iconWidth - config.margin * 2} ${height - config.margin * 4}" image="${image}" url="${url}" /> ` } if (TMXMap === undefined) { - return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin), height, config.iconWidth)} + return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin), height, + config.iconWidth)} - ${icon(0, 0, config.icons.dedimania, tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} + ${icon(0, 0, config.icons.dedimania, + tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} ` } - return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin) * 3, height, config.iconWidth)} + return `${this.constructEntry(title, config.icons.header, width - (config.iconWidth + config.margin) * 3, height, + config.iconWidth)} ${icon(0, 0, config.icons.maniaExchange, tm.utils.fixProtocol(TMXMap.pageUrl))} ${icon(config.iconWidth + config.margin, 0, config.icons.downloadGreen, tm.utils.fixProtocol(TMXMap.downloadUrl))} - ${icon((config.iconWidth + config.margin) * 2, 0, config.icons.dedimania, tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} + ${icon((config.iconWidth + config.margin) * 2, 0, config.icons.dedimania, + tm.utils.safeString(`dedimania.net/tmstats/?do=stat&Uid=${map.id}&Show=RECORDS`))} ` } - private constructEntry(text: string, image: string, width: number, height: number, iconWidth: number, useCenteredText?: true): string { + private constructEntry(text: string, image: string, width: number, height: number, iconWidth: number, + useCenteredText?: true): string { return ` - ${useCenteredText === true ? centeredText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, { textScale: config.textscale }) : - leftAlignedText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, { textScale: config.textscale })} + ${useCenteredText === true ? + centeredText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, + { textScale: config.textscale }) : + leftAlignedText(text, width - (iconWidth + config.margin * 3), height - config.margin * 2, + { textScale: config.textscale })} ` } - protected constructScreenshot(login: string, width: number, height: number, records: tm.Record[], environment: tm.Environment, TMXMap?: tm.TMXMap) { + protected constructScreenshot(login: string, width: number, height: number, records: tm.Record[], + environment: tm.Environment, TMXMap?: tm.TMXMap) { const rightW = width - (config.screenshotWidth + config.margin) const count = config.localsCount - const grid = new Grid(rightW, height, [1, 2, 3], new Array(count + 1).fill(1), - { headerBackground: config.iconBackground, background: config.gridBackground, margin: config.margin }) + const grid = new Grid(rightW, height, [1, 2, 3], new Array(count + 1).fill(1), { + headerBackground: config.iconBackground, + background: config.gridBackground, + margin: config.margin + }) const options = { textScale: config.recordTextScale } const index = records.findIndex(a => a.login === login) let personalIndex: number | undefined @@ -153,33 +176,32 @@ image="${image}" url="${url}" /> ` } const indexCell: GridCellObject = { callback: (i, j, w, h) => { - return centeredText(personalIndex === i ? (index + 1).toString() : (records[i - 1] === undefined ? config.defaultText : i.toString()), w, h, options) + return centeredText(personalIndex === i ? (index + 1).toString() : + (records[i - 1] === undefined ? config.defaultText : i.toString()), w, h, options) }, background: config.iconBackground } const nameCell: GridCellFunction = (i, j, w, h) => { let nickname: string | undefined = records[i - 1]?.nickname - if (((records[i - 1] === undefined && i === 1) || (records[i - 1] === undefined && records[i - 2] !== undefined)) && - (index > count || index === -1)) { + if (((records[i - 1] === undefined && i === 1) || (records[i - 1] === undefined && records[i - 2] !== undefined)) && (index > count || index === -1)) { nickname = tm.players.get(login)?.nickname } return leftAlignedText(tm.utils.safeString(tm.utils.strip(nickname ?? config.defaultText, false)), w, h, options) } - const timeCell: GridCellFunction = (i, j, w, h) => centeredText(records[i - 1] !== undefined ? - tm.utils.getTimeString(records[i - 1]?.time) : config.defaultTime, w, h, options) - const arr: (GridCellFunction | GridCellObject)[] = [ - (i, j, w, h) => centeredText('Lp.', w, h, options), - (i, j, w, h) => centeredText('Time', w, h, options), - (i, j, w, h) => centeredText('Name', w, h, options) - ] + const timeCell: GridCellFunction = (i, j, w, h) => centeredText( + records[i - 1] !== undefined ? tm.utils.getTimeString(records[i - 1]?.time) : config.defaultTime, w, h, options) + const arr: (GridCellFunction | GridCellObject)[] = [(i, j, w, h) => centeredText('Lp.', w, h, options), + (i, j, w, h) => centeredText('Time', w, h, options), (i, j, w, h) => centeredText('Name', w, h, options)] for (let i = 0; i < count; i++) { arr.push(indexCell, timeCell, nameCell) } - const image = TMXMap === undefined - ? config.noScreenshot[environment] - : tm.utils.safeString(TMXMap.thumbnailUrl + `&.jpeg`) + const image = TMXMap === undefined ? config.noScreenshot[environment] : + tm.utils.safeString(TMXMap.thumbnailUrl + `&.jpeg`) return ` - ${centeredText(config.notLoaded, config.screenshotWidth, height, { textScale: config.notLoadedTextscale, yOffset: -1 })} + ${centeredText(config.notLoaded, config.screenshotWidth, height, { + textScale: config.notLoadedTextscale, + yOffset: -1 + })} ${grid.constructXml(arr)} ` @@ -190,9 +212,11 @@ image="${image}" url="${url}" /> ` if (tm.utils.isMultibyte(author)) { author = '' } - return `${this.constructEntry(tm.utils.safeString(author), config.icons.author, width - config.authorTimeWidth, height, config.iconWidth)} + return `${this.constructEntry(tm.utils.safeString(author), config.icons.author, width - config.authorTimeWidth, + height, config.iconWidth)} - ${this.constructEntry(tm.utils.getTimeString(map.authorTime), config.icons.authorTime, config.authorTimeWidth + config.margin, height, config.iconWidth, true)} + ${this.constructEntry(tm.utils.getTimeString(map.authorTime), config.icons.authorTime, + config.authorTimeWidth + config.margin, height, config.iconWidth, true)} ` } @@ -205,33 +229,26 @@ image="${image}" url="${url}" /> ` if (TMXMap?.isNadeo ?? map?.leaderboardRating === 50000) { lbRating = 'Nadeo' lbIcon = ic.leaderboardRating.nadeo - } - else if (map.isClassic) { + } else if (map.isClassic) { lbRating = 'Classic' lbIcon = ic.leaderboardRating.classic } let awardsIcon = ic.awards.normal - if (lbRating === 'Nadeo') { awardsIcon = ic.awards.nadeo } - else if (lbRating === 'Classic') { awardsIcon = ic.awards.classic } - const infos: [string, string][] = [ - [map.mood, ic.mood[map.mood.toLowerCase() as keyof typeof ic.mood]], + if (lbRating === 'Nadeo') { awardsIcon = ic.awards.nadeo } else if (lbRating === 'Classic') { awardsIcon = ic.awards.classic } + const infos: [string, string][] = [[map.mood, ic.mood[map.mood.toLowerCase() as keyof typeof ic.mood]], [tm.utils.formatDate(map.addDate, true), ic.addDate], [map.voteRatio === -1 ? config.defaultText : map.voteRatio.toFixed(0), ic.voteRatio], - [map.copperPrice.toString(), ic.copperPrice], - [map.environment, ic.environment], - [map?.checkpointsPerLap !== undefined ? `${map.checkpointsPerLap - 1} CPs` : config.defaultText, ic.checkpointsAmount], - [map.voteCount.toString(), ic.voteCount], + [map.copperPrice.toString(), ic.copperPrice], [map.environment, ic.environment], + [map?.checkpointsPerLap !== undefined ? `${map.checkpointsPerLap - 1} CPs` : config.defaultText, + ic.checkpointsAmount], [map.voteCount.toString(), ic.voteCount], [(TMXMap?.awards?.toString() ?? map?.awards?.toString()) ?? config.defaultText, awardsIcon], [TMXMap?.author === undefined ? config.defaultText : (tm.utils.isMultibyte(TMXMap.author) ? '' : tm.utils.safeString(TMXMap.author)), ic.tmxAuthor], [TMXMap !== undefined ? tm.utils.formatDate(TMXMap.lastUpdateDate, true) : config.defaultText, ic.buildDate], - [TMXMap?.style ?? config.defaultText, ic.style], - [lbRating, lbIcon], - [TMXMap?.difficulty ?? config.defaultText, ic.difficulty[(TMXMap?.difficulty?.toLowerCase() as keyof typeof ic.difficulty) ?? 'beginner']], - [TMXMap?.routes ?? config.defaultText, ic.routes], - [TMXMap?.type ?? config.defaultText, ic.type], - [TMXMap?.game ?? config.defaultText, ic.game] - ] + [TMXMap?.style ?? config.defaultText, ic.style], [lbRating, lbIcon], [TMXMap?.difficulty ?? config.defaultText, + ic.difficulty[(TMXMap?.difficulty?.toLowerCase() as keyof typeof ic.difficulty) ?? 'beginner']], + [TMXMap?.routes ?? config.defaultText, ic.routes], [TMXMap?.type ?? config.defaultText, ic.type], + [TMXMap?.game ?? config.defaultText, ic.game]] const cell: GridCellFunction = (i, j, w, h) => { const index = (i * grid.columns) + j return ` @@ -239,33 +256,35 @@ image="${image}" url="${url}" /> ` - ${centeredText(infos?.[index]?.[0] ?? '', w - (config.info.iconWidth + config.margin), h, { textScale: config.info.textscale })} + ${centeredText(infos?.[index]?.[0] ?? '', w - (config.info.iconWidth + config.margin), h, + { textScale: config.info.textscale })} ` } - return grid.constructXml(new Array(config.info.columnsProportions.length * config.info.rows).fill(null).map(() => cell)) + return grid.constructXml( + new Array(config.info.columnsProportions.length * config.info.rows).fill(null).map(() => cell)) } private counstructTmxRecordsXml(width: number, height: number, replays: tm.TMXReplay[] = []): string { - const grid = new Grid(width, height, config.tmxColumns, new Array(config.tmxRecordCount + 1).fill(1), - { margin: config.margin, background: config.gridBackground, headerBackground: config.iconBackground }) + const grid = new Grid(width, height, config.tmxColumns, new Array(config.tmxRecordCount + 1).fill(1), { + margin: config.margin, + background: config.gridBackground, + headerBackground: config.iconBackground + }) const options = { textScale: config.recordTextScale } - const arr: (GridCellFunction | GridCellObject)[] = [ - (i, j, w, h) => centeredText('Lp.', w, h, options), - (i, j, w, h) => centeredText('Time', w, h, options), - (i, j, w, h) => centeredText('Name', w, h, options), - (i, j, w, h) => centeredText('Date', w, h, options), - (i, j, w, h) => centeredText('Dl.', w, h, options), - ] + const arr: (GridCellFunction | GridCellObject)[] = [(i, j, w, h) => centeredText('Lp.', w, h, options), + (i, j, w, h) => centeredText('Time', w, h, options), (i, j, w, h) => centeredText('Name', w, h, options), + (i, j, w, h) => centeredText('Date', w, h, options), (i, j, w, h) => centeredText('Dl.', w, h, options)] const indexCell: GridCellObject = { callback: (i, j, w, h) => centeredText(i.toString(), w, h, options), background: config.iconBackground } - const timeCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) - const nameCell: GridCellFunction = (i, j, w, h) => - leftAlignedText(tm.utils.safeString(replays[i - 1]?.name ?? config.defaultText), w, h, options) - const dateCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultText, w, h, options) + const timeCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) + const nameCell: GridCellFunction = (i, j, w, h) => leftAlignedText( + tm.utils.safeString(replays[i - 1]?.name ?? config.defaultText), w, h, options) + const dateCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultText, w, h, + options) const downloadCell: GridCellObject = { callback: (i, j, w, h) => replays[i - 1] !== undefined ? ` + tm.sendManialink(` @@ -17,13 +16,11 @@ export default class CustomUi { - ` - ) + `) } displayToPlayer(login: string): void { - tm.sendManialink( - ` + tm.sendManialink(` @@ -32,9 +29,7 @@ export default class CustomUi { - `, - login - ) + `, login) } } diff --git a/plugins/ui/Imports.ts b/plugins/ui/Imports.ts index cb6d0fbc..69dad69a 100644 --- a/plugins/ui/Imports.ts +++ b/plugins/ui/Imports.ts @@ -1,8 +1,8 @@ /* ------------------ -STATIC COMPONENTS ------------------ -*/ + ----------------- + STATIC COMPONENTS + ----------------- + */ import RankWidget from './static_components/race/RankWidget.component.js' import DediRanking from './static_components/race/DediRanking.component.js' @@ -43,11 +43,10 @@ import BannerWidget from './static_components/result/BannerWidget.component.js' import UltiRanking from './static_components/race/UltiRanking.component.js' /* ------------------- -DYNAMIC COMPONENTS ------------------- -*/ - + ------------------ + DYNAMIC COMPONENTS + ------------------ + */ import CommandList from './dynamic_components/Commandlist.component.js' import LocalCps from './dynamic_components/checkpoint_windows/LocalCps.component.js' import LiveSectors from './dynamic_components/checkpoint_windows/LiveSectors.component.js' diff --git a/plugins/ui/UI.ts b/plugins/ui/UI.ts index e9693887..53a8050c 100644 --- a/plugins/ui/UI.ts +++ b/plugins/ui/UI.ts @@ -5,7 +5,7 @@ */ import icons from './config/Icons.js' -import { initialize as initalizeKeyListeners } from './utils/KeyListener.js' +import { addKeyListener, initialize as initalizeKeyListeners, removeKeyListener } from './utils/KeyListener.js' import modConfig from './config/Mod.js' import TestWindow from './test_widgets/TestWindow.js' import StaticComponent from './utils/StaticComponent.js' @@ -20,7 +20,6 @@ import componentIds from './config/ComponentIds.js' import { centeredText, horizontallyCenteredText, leftAlignedText, rightAlignedText } from './utils/TextUtils.js' import { getCpTypes } from './utils/GetCpTypes.js' import { closeButton } from './utils/CloseButton.js' -import { addKeyListener, removeKeyListener } from './utils/KeyListener.js' import { List } from './utils/List.js' import StaticHeader, { type StaticHeaderOptions } from './utils/StaticHeader.js' import staticButton from './utils/StaticButton.js' @@ -32,37 +31,53 @@ import { fullScreenListener } from './utils/FullScreenListener.js' import flagIcons from './config/FlagIcons.js' import utilIds from './config/UtilIds.js' import Buttons from './Buttons.js' +// Has to be like that due to circular dependencies +import './Imports.js' let customUi: CustomUi -const currentModIndex = { 'Stadium': 0, 'Desert': 0, 'Snow': 0, 'Bay': 0, 'Coast': 0, 'Island': 0, 'Rally': 0 } +const currentModIndex = { + 'Stadium': 0, + 'Desert': 0, + 'Snow': 0, + 'Bay': 0, + 'Coast': 0, + 'Island': 0, + 'Rally': 0 +} const loadMod = (): void => { - const mods: { struct: { Env: { string: string }, Url: { string: string } } }[] = [] + const mods: { + struct: { + Env: { + string: string + }, + Url: { + string: string + } + } + }[] = [] for (const obj of modConfig) { if (obj.modLinks.length === 0) { continue } mods.push({ struct: { Env: { string: (tm.utils.environmentToNadeoEnvironment(obj.environment as tm.Environment) as string) }, Url: { - string: obj.randomOrder - ? tm.utils.fixProtocol(obj.modLinks[~~(Math.random() * obj.modLinks.length)]) - : tm.utils.fixProtocol(obj.modLinks[currentModIndex[obj.environment as keyof typeof currentModIndex] % obj.modLinks.length]) + string: obj.randomOrder ? tm.utils.fixProtocol(obj.modLinks[~~(Math.random() * obj.modLinks.length)]) : + tm.utils.fixProtocol( + obj.modLinks[currentModIndex[obj.environment as keyof typeof currentModIndex] % obj.modLinks.length]) } } }) } - tm.client.callNoRes('SetForcedMods', - [{ - boolean: true - }, - { - array: mods - }]) + tm.client.callNoRes('SetForcedMods', [{ + boolean: true + }, { + array: mods + }]) } -const iconArr = Object.values(icons.preloadedIcons).map(a => - ``) +const iconArr = Object.values(icons.preloadedIcons).map(a => ``) const preloadIcons = (login?: string): void => { tm.sendManialink(` @@ -81,55 +96,50 @@ let staticUpdateNeeded = false tm.client.addProxy(['SetGameMode'], () => { staticUpdateNeeded = true }) -const events: tm.Listener[] = [ - { - event: 'Startup', - callback: async (): Promise => { - await tm.client.call('SendHideManialinkPage') - preloadIcons() - loadMod() - initalizeKeyListeners() - customUi = new CustomUi() - customUi.display() - StaticComponent.refreshStaticLayouts() - for (const c of Object.values(staticComponents)) { - c.updateIsDisplayed() - c.updatePosition() - const ret = c.display() - StaticComponent.sendMultipleManialinks(ret) - } - StaticComponent.refreshStaticLayouts() - new Buttons() - new TestWindow() - for (const e of loadListeners) { e() } - StaticComponent.onComponentCreated(() => { - StaticComponent.refreshStaticLayouts() - }) - } - }, - { - event: 'BeginMap', - callback: async (): Promise => { - if (staticUpdateNeeded) { - staticUpdateNeeded = false - for (const e of staticComponents) { e.updatePosition() } - } - } - }, - { - event: 'EndMap', - callback: async (): Promise => { - currentModIndex[tm.maps.current.environment]++ - loadMod() +const events: tm.Listener[] = [{ + event: 'Startup', + callback: async (): Promise => { + await tm.client.call('SendHideManialinkPage') + preloadIcons() + loadMod() + initalizeKeyListeners() + customUi = new CustomUi() + customUi.display() + StaticComponent.refreshStaticLayouts() + for (const c of Object.values(staticComponents)) { + c.updateIsDisplayed() + c.updatePosition() + const ret = c.display() + StaticComponent.sendMultipleManialinks(ret) } - }, - { - event: 'PlayerJoin', - callback: (info: tm.JoinInfo): void => { - preloadIcons(info.login) + StaticComponent.refreshStaticLayouts() + new Buttons() + new TestWindow() + for (const e of loadListeners) { e() } + StaticComponent.onComponentCreated(() => { + StaticComponent.refreshStaticLayouts() + }) + } +}, { + event: 'BeginMap', + callback: async (): Promise => { + if (staticUpdateNeeded) { + staticUpdateNeeded = false + for (const e of staticComponents) { e.updatePosition() } } } -] +}, { + event: 'EndMap', + callback: async (): Promise => { + currentModIndex[tm.maps.current.environment]++ + loadMod() + } +}, { + event: 'PlayerJoin', + callback: (info: tm.JoinInfo): void => { + preloadIcons(info.login) + } +}] for (const event of events) { tm.addListener(event.event, event.callback, true) } @@ -140,16 +150,16 @@ for (const event of events) { tm.addListener(event.event, event.callback, true) */ const components = { - /** - * Finds a static component based on given class name + /** + * Finds a static component based on given class name * @param className Component class name */ findStatic(className: string): StaticComponent | undefined { return staticComponents.find(a => a.constructor.name === className) }, - /** - * Finds a dynamic component based on given class name + /** + * Finds a dynamic component based on given class name * @param className Component class name */ findDynamic(className: string): DynamicComponent | undefined { @@ -180,21 +190,14 @@ const components = { } /** -* Register a callback function to execute on UI load -* @param callback Function to execute on event -*/ + * Register a callback function to execute on UI load + * @param callback Function to execute on event + */ const addLoadListener = (callback: Function): void => { loadListeners.push(callback) } export { - Paginator, Grid, Navbar, VoteWindow, RecordList, type GridCellFunction, type GridCellObject, List, StaticHeader, - PopupWindow, StaticComponent, DynamicComponent, type StaticHeaderOptions, type RLImage, type RLRecord, - components, componentIds, icons, raceConfig, resultConfig, flagIcons, utilIds, - addKeyListener, removeKeyListener, rightAlignedText, getCpTypes, closeButton, horizontallyCenteredText, - staticButton, fullScreenListener, centeredText, addLoadListener, - leftAlignedText, addManialinkListener, removeManialinkListener, + Paginator, Grid, Navbar, VoteWindow, RecordList, type GridCellFunction, type GridCellObject, List, StaticHeader, PopupWindow, StaticComponent, DynamicComponent, type StaticHeaderOptions, type RLImage, type RLRecord, components, componentIds, icons, raceConfig, resultConfig, flagIcons, utilIds, addKeyListener, removeKeyListener, rightAlignedText, getCpTypes, closeButton, horizontallyCenteredText, staticButton, fullScreenListener, centeredText, addLoadListener, leftAlignedText, addManialinkListener, removeManialinkListener } -// Has to be like that due to circular dependencies -import './Imports.js' diff --git a/plugins/ui/config/Buttons.js b/plugins/ui/config/Buttons.js index e432a91e..f20445cc 100644 --- a/plugins/ui/config/Buttons.js +++ b/plugins/ui/config/Buttons.js @@ -1,13 +1,14 @@ import icons from './Icons.js' import raceUi from './RaceUi.js' import resultUi from './ResultUi.js' + const p = tm.utils.palette /** * In this config you can easily add static buttons to your UI layout. - * + * * REQUIRED PROPS: - * + * * - displayed (bool) - Whether to display the button * - icon (str) - Icon URL * - posX (num) - X position of the button (0 is the middle of the screen) @@ -17,8 +18,8 @@ const p = tm.utils.palette * - zIndex (num) - Z "position" of the button (determines stacking order of elements) * - text1 (str) - Upper text * - text2 (str) - Lower text (leave as empty string if you need only one text line) - * OPTIONAL PROPS: - * + * OPTIONAL PROPS: + * * - iconWidth (num) - Icon width * - iconHeight (num) - Icon height * - topPadding (num) - Gap between the top border of the background and the top border of the icon @@ -36,55 +37,47 @@ export default { /** * RaceUi icons */ - race: [ - { - displayed: false, - icon: icons.placeholder, - posX: 44.9, - posY: 24.2, - width: 4, - height: 6.45, - zIndex: 1, - text1: `$${p.purple}DISCORD`, - text2: 'SERVER', - iconWidth: 3, - iconHeight: 3, - topPadding: 0.3, - equalTexts: false, - // actionId: , - background: raceUi.background, - // link: '', - // text1Scale: , - // text2Scale: , - text1PositionOffset: 1.75, - text2PositionOffset: 1.75, - } - ], + race: [{ + displayed: false, + icon: icons.placeholder, + posX: 44.9, + posY: 24.2, + width: 4, + height: 6.45, + zIndex: 1, + text1: `$${p.purple}DISCORD`, + text2: 'SERVER', + iconWidth: 3, + iconHeight: 3, + topPadding: 0.3, + equalTexts: false, // actionId: , + background: raceUi.background, // link: '', + // text1Scale: , + // text2Scale: , + text1PositionOffset: 1.75, + text2PositionOffset: 1.75 + }], /** * ResultUi icons */ - result: [ - { - displayed: false, - icon: icons.placeholder, - posX: 44.9, - posY: 22.85, - width: 4, - height: 6.1, - zIndex: 1, - text1: `$${p.purple}DISCORD`, - text2: 'SERVER', - iconWidth: 3, - iconHeight: 3, - topPadding: 0.3, - equalTexts: false, - // actionId: , - background: resultUi.background, - link: '', - // text1Scale: , - // text2Scale: , - text1PositionOffset: 1.75, - text2PositionOffset: 1.75, - } - ] + result: [{ + displayed: false, + icon: icons.placeholder, + posX: 44.9, + posY: 22.85, + width: 4, + height: 6.1, + zIndex: 1, + text1: `$${p.purple}DISCORD`, + text2: 'SERVER', + iconWidth: 3, + iconHeight: 3, + topPadding: 0.3, + equalTexts: false, // actionId: , + background: resultUi.background, + link: '', // text1Scale: , + // text2Scale: , + text1PositionOffset: 1.75, + text2PositionOffset: 1.75 + }] } diff --git a/plugins/ui/config/ComponentIds.js b/plugins/ui/config/ComponentIds.js index 8f1af343..877ab039 100644 --- a/plugins/ui/config/ComponentIds.js +++ b/plugins/ui/config/ComponentIds.js @@ -83,7 +83,6 @@ export default { ultiRanking: 530000, ultiRecords: 540000, favouriteButton: 560000, - liveCpsRanking: 550000, - // needs 3.1 mil ids + liveCpsRanking: 550000, // needs 3.1 mil ids mapList: 10000000 } diff --git a/plugins/ui/config/FlagIcons.js b/plugins/ui/config/FlagIcons.js index f728ae5e..47aae909 100644 --- a/plugins/ui/config/FlagIcons.js +++ b/plugins/ui/config/FlagIcons.js @@ -1,94 +1,94 @@ export default { - "ALG": "https://trakman.ptrk.eu/icons/ALG.png", - "ANG": "https://trakman.ptrk.eu/icons/ANG.png", - "ARG": "https://trakman.ptrk.eu/icons/ARG.png", - "AUS": "https://trakman.ptrk.eu/icons/AUS.png", - "AUT": "https://trakman.ptrk.eu/icons/AUT.png", - "BAN": "https://trakman.ptrk.eu/icons/BAN.png", - "BEL": "https://trakman.ptrk.eu/icons/BEL.png", - "BIH": "https://trakman.ptrk.eu/icons/BIH.png", - "BLR": "https://trakman.ptrk.eu/icons/BLR.png", - "BOL": "https://trakman.ptrk.eu/icons/BOL.png", - "BRA": "https://trakman.ptrk.eu/icons/BRA.png", - "BRN": "https://trakman.ptrk.eu/icons/BRN.png", - "BUL": "https://trakman.ptrk.eu/icons/BUL.png", - "CAN": "https://trakman.ptrk.eu/icons/CAN.png", - "CGO": "https://trakman.ptrk.eu/icons/CGO.png", - "CHI": "https://trakman.ptrk.eu/icons/CHI.png", - "CHN": "https://trakman.ptrk.eu/icons/CHN.png", - "CIV": "https://trakman.ptrk.eu/icons/CIV.png", - "CMR": "https://trakman.ptrk.eu/icons/CMR.png", - "COL": "https://trakman.ptrk.eu/icons/COL.png", - "CRO": "https://trakman.ptrk.eu/icons/CRO.png", - "CYP": "https://trakman.ptrk.eu/icons/CYP.png", - "CZE": "https://trakman.ptrk.eu/icons/CZE.png", - "DEN": "https://trakman.ptrk.eu/icons/DEN.png", - "ECU": "https://trakman.ptrk.eu/icons/ECU.png", - "EGY": "https://trakman.ptrk.eu/icons/EGY.png", - "ESP": "https://trakman.ptrk.eu/icons/ESP.png", - "EST": "https://trakman.ptrk.eu/icons/EST.png", - "FIN": "https://trakman.ptrk.eu/icons/FIN.png", - "FRA": "https://trakman.ptrk.eu/icons/FRA.png", - "GBR": "https://trakman.ptrk.eu/icons/GBR.png", - "GER": "https://trakman.ptrk.eu/icons/GER.png", - "GRE": "https://trakman.ptrk.eu/icons/GRE.png", - "GUA": "https://trakman.ptrk.eu/icons/GUA.png", - "HUN": "https://trakman.ptrk.eu/icons/HUN.png", - "INA": "https://trakman.ptrk.eu/icons/INA.png", - "IND": "https://trakman.ptrk.eu/icons/IND.png", - "IRI": "https://trakman.ptrk.eu/icons/IRI.png", - "IRL": "https://trakman.ptrk.eu/icons/IRL.png", - "IRQ": "https://trakman.ptrk.eu/icons/IRQ.png", - "ISR": "https://trakman.ptrk.eu/icons/ISR.png", - "ITA": "https://trakman.ptrk.eu/icons/ITA.png", - "JPN": "https://trakman.ptrk.eu/icons/JPN.png", - "KAZ": "https://trakman.ptrk.eu/icons/KAZ.png", - "KOR": "https://trakman.ptrk.eu/icons/KOR.png", - "KSA": "https://trakman.ptrk.eu/icons/KSA.png", - "KUW": "https://trakman.ptrk.eu/icons/KUW.png", - "LAO": "https://trakman.ptrk.eu/icons/LAO.png", - "LAT": "https://trakman.ptrk.eu/icons/LAT.png", - "LBA": "https://trakman.ptrk.eu/icons/LBA.png", - "LIB": "https://trakman.ptrk.eu/icons/LIB.png", - "LTU": "https://trakman.ptrk.eu/icons/LTU.png", - "LUX": "https://trakman.ptrk.eu/icons/LUX.png", - "MAR": "https://trakman.ptrk.eu/icons/MAR.png", - "MAS": "https://trakman.ptrk.eu/icons/MAS.png", - "MEX": "https://trakman.ptrk.eu/icons/MEX.png", - "MGL": "https://trakman.ptrk.eu/icons/MGL.png", - "MKD": "https://trakman.ptrk.eu/icons/MKD.png", - "NED": "https://trakman.ptrk.eu/icons/NED.png", - "NGR": "https://trakman.ptrk.eu/icons/NGR.png", - "NOR": "https://trakman.ptrk.eu/icons/NOR.png", - "NZL": "https://trakman.ptrk.eu/icons/NZL.png", - "OMA": "https://trakman.ptrk.eu/icons/OMA.png", - "OTH": "https://trakman.ptrk.eu/icons/OTH.png", - "PAK": "https://trakman.ptrk.eu/icons/PAK.png", - "PAN": "https://trakman.ptrk.eu/icons/PAN.png", - "PER": "https://trakman.ptrk.eu/icons/PER.png", - "PHI": "https://trakman.ptrk.eu/icons/PHI.png", - "POL": "https://trakman.ptrk.eu/icons/POL.png", - "POR": "https://trakman.ptrk.eu/icons/POR.png", - "QAT": "https://trakman.ptrk.eu/icons/QAT.png", - "ROU": "https://trakman.ptrk.eu/icons/ROU.png", - "RSA": "https://trakman.ptrk.eu/icons/RSA.png", - "RUS": "https://trakman.ptrk.eu/icons/RUS.png", - "SEN": "https://trakman.ptrk.eu/icons/SEN.png", - "SIN": "https://trakman.ptrk.eu/icons/SIN.png", - "SLO": "https://trakman.ptrk.eu/icons/SLO.png", - "SRB": "https://trakman.ptrk.eu/icons/SRB.png", - "SUI": "https://trakman.ptrk.eu/icons/SUI.png", - "SVK": "https://trakman.ptrk.eu/icons/SVK.png", - "SWE": "https://trakman.ptrk.eu/icons/SWE.png", - "SYR": "https://trakman.ptrk.eu/icons/SYR.png", - "THA": "https://trakman.ptrk.eu/icons/THA.png", - "TUN": "https://trakman.ptrk.eu/icons/TUN.png", - "TUR": "https://trakman.ptrk.eu/icons/TUR.png", - "UAE": "https://trakman.ptrk.eu/icons/UAE.png", - "UKR": "https://trakman.ptrk.eu/icons/UKR.png", - "URU": "https://trakman.ptrk.eu/icons/URU.png", - "USA": "https://trakman.ptrk.eu/icons/USA.png", - "UZB": "https://trakman.ptrk.eu/icons/UZB.png", - "VEN": "https://trakman.ptrk.eu/icons/VEN.png", - "VIE": "https://trakman.ptrk.eu/icons/VIE.png" + 'ALG': 'https://trakman.ptrk.eu/icons/ALG.png', + 'ANG': 'https://trakman.ptrk.eu/icons/ANG.png', + 'ARG': 'https://trakman.ptrk.eu/icons/ARG.png', + 'AUS': 'https://trakman.ptrk.eu/icons/AUS.png', + 'AUT': 'https://trakman.ptrk.eu/icons/AUT.png', + 'BAN': 'https://trakman.ptrk.eu/icons/BAN.png', + 'BEL': 'https://trakman.ptrk.eu/icons/BEL.png', + 'BIH': 'https://trakman.ptrk.eu/icons/BIH.png', + 'BLR': 'https://trakman.ptrk.eu/icons/BLR.png', + 'BOL': 'https://trakman.ptrk.eu/icons/BOL.png', + 'BRA': 'https://trakman.ptrk.eu/icons/BRA.png', + 'BRN': 'https://trakman.ptrk.eu/icons/BRN.png', + 'BUL': 'https://trakman.ptrk.eu/icons/BUL.png', + 'CAN': 'https://trakman.ptrk.eu/icons/CAN.png', + 'CGO': 'https://trakman.ptrk.eu/icons/CGO.png', + 'CHI': 'https://trakman.ptrk.eu/icons/CHI.png', + 'CHN': 'https://trakman.ptrk.eu/icons/CHN.png', + 'CIV': 'https://trakman.ptrk.eu/icons/CIV.png', + 'CMR': 'https://trakman.ptrk.eu/icons/CMR.png', + 'COL': 'https://trakman.ptrk.eu/icons/COL.png', + 'CRO': 'https://trakman.ptrk.eu/icons/CRO.png', + 'CYP': 'https://trakman.ptrk.eu/icons/CYP.png', + 'CZE': 'https://trakman.ptrk.eu/icons/CZE.png', + 'DEN': 'https://trakman.ptrk.eu/icons/DEN.png', + 'ECU': 'https://trakman.ptrk.eu/icons/ECU.png', + 'EGY': 'https://trakman.ptrk.eu/icons/EGY.png', + 'ESP': 'https://trakman.ptrk.eu/icons/ESP.png', + 'EST': 'https://trakman.ptrk.eu/icons/EST.png', + 'FIN': 'https://trakman.ptrk.eu/icons/FIN.png', + 'FRA': 'https://trakman.ptrk.eu/icons/FRA.png', + 'GBR': 'https://trakman.ptrk.eu/icons/GBR.png', + 'GER': 'https://trakman.ptrk.eu/icons/GER.png', + 'GRE': 'https://trakman.ptrk.eu/icons/GRE.png', + 'GUA': 'https://trakman.ptrk.eu/icons/GUA.png', + 'HUN': 'https://trakman.ptrk.eu/icons/HUN.png', + 'INA': 'https://trakman.ptrk.eu/icons/INA.png', + 'IND': 'https://trakman.ptrk.eu/icons/IND.png', + 'IRI': 'https://trakman.ptrk.eu/icons/IRI.png', + 'IRL': 'https://trakman.ptrk.eu/icons/IRL.png', + 'IRQ': 'https://trakman.ptrk.eu/icons/IRQ.png', + 'ISR': 'https://trakman.ptrk.eu/icons/ISR.png', + 'ITA': 'https://trakman.ptrk.eu/icons/ITA.png', + 'JPN': 'https://trakman.ptrk.eu/icons/JPN.png', + 'KAZ': 'https://trakman.ptrk.eu/icons/KAZ.png', + 'KOR': 'https://trakman.ptrk.eu/icons/KOR.png', + 'KSA': 'https://trakman.ptrk.eu/icons/KSA.png', + 'KUW': 'https://trakman.ptrk.eu/icons/KUW.png', + 'LAO': 'https://trakman.ptrk.eu/icons/LAO.png', + 'LAT': 'https://trakman.ptrk.eu/icons/LAT.png', + 'LBA': 'https://trakman.ptrk.eu/icons/LBA.png', + 'LIB': 'https://trakman.ptrk.eu/icons/LIB.png', + 'LTU': 'https://trakman.ptrk.eu/icons/LTU.png', + 'LUX': 'https://trakman.ptrk.eu/icons/LUX.png', + 'MAR': 'https://trakman.ptrk.eu/icons/MAR.png', + 'MAS': 'https://trakman.ptrk.eu/icons/MAS.png', + 'MEX': 'https://trakman.ptrk.eu/icons/MEX.png', + 'MGL': 'https://trakman.ptrk.eu/icons/MGL.png', + 'MKD': 'https://trakman.ptrk.eu/icons/MKD.png', + 'NED': 'https://trakman.ptrk.eu/icons/NED.png', + 'NGR': 'https://trakman.ptrk.eu/icons/NGR.png', + 'NOR': 'https://trakman.ptrk.eu/icons/NOR.png', + 'NZL': 'https://trakman.ptrk.eu/icons/NZL.png', + 'OMA': 'https://trakman.ptrk.eu/icons/OMA.png', + 'OTH': 'https://trakman.ptrk.eu/icons/OTH.png', + 'PAK': 'https://trakman.ptrk.eu/icons/PAK.png', + 'PAN': 'https://trakman.ptrk.eu/icons/PAN.png', + 'PER': 'https://trakman.ptrk.eu/icons/PER.png', + 'PHI': 'https://trakman.ptrk.eu/icons/PHI.png', + 'POL': 'https://trakman.ptrk.eu/icons/POL.png', + 'POR': 'https://trakman.ptrk.eu/icons/POR.png', + 'QAT': 'https://trakman.ptrk.eu/icons/QAT.png', + 'ROU': 'https://trakman.ptrk.eu/icons/ROU.png', + 'RSA': 'https://trakman.ptrk.eu/icons/RSA.png', + 'RUS': 'https://trakman.ptrk.eu/icons/RUS.png', + 'SEN': 'https://trakman.ptrk.eu/icons/SEN.png', + 'SIN': 'https://trakman.ptrk.eu/icons/SIN.png', + 'SLO': 'https://trakman.ptrk.eu/icons/SLO.png', + 'SRB': 'https://trakman.ptrk.eu/icons/SRB.png', + 'SUI': 'https://trakman.ptrk.eu/icons/SUI.png', + 'SVK': 'https://trakman.ptrk.eu/icons/SVK.png', + 'SWE': 'https://trakman.ptrk.eu/icons/SWE.png', + 'SYR': 'https://trakman.ptrk.eu/icons/SYR.png', + 'THA': 'https://trakman.ptrk.eu/icons/THA.png', + 'TUN': 'https://trakman.ptrk.eu/icons/TUN.png', + 'TUR': 'https://trakman.ptrk.eu/icons/TUR.png', + 'UAE': 'https://trakman.ptrk.eu/icons/UAE.png', + 'UKR': 'https://trakman.ptrk.eu/icons/UKR.png', + 'URU': 'https://trakman.ptrk.eu/icons/URU.png', + 'USA': 'https://trakman.ptrk.eu/icons/USA.png', + 'UZB': 'https://trakman.ptrk.eu/icons/UZB.png', + 'VEN': 'https://trakman.ptrk.eu/icons/VEN.png', + 'VIE': 'https://trakman.ptrk.eu/icons/VIE.png' } diff --git a/plugins/ui/config/Icons.js b/plugins/ui/config/Icons.js index 45f70edd..d3d013f1 100644 --- a/plugins/ui/config/Icons.js +++ b/plugins/ui/config/Icons.js @@ -1,192 +1,179 @@ export default { - clock: "https://trakman.ptrk.eu/icons/Clock.png", - close: "https://trakman.ptrk.eu/icons/Close.png", - closeHover: "https://trakman.ptrk.eu/icons/CloseHover.png", - code: "https://trakman.ptrk.eu/icons/Code.png", - codeBranch: "https://trakman.ptrk.eu/icons/CodeBranch.png", - download: "https://trakman.ptrk.eu/icons/Download.png", - downloadHover: "https://trakman.ptrk.eu/icons/DownloadHover.png", - pageDoubleLeft: "https://trakman.ptrk.eu/icons/PageDoubleLeft.png", - pageDoubleLeftHover: "https://trakman.ptrk.eu/icons/PageDoubleLeftHover.png", - pageDoubleRight: "https://trakman.ptrk.eu/icons/PageDoubleRight.png", - pageDoubleRightHover: "https://trakman.ptrk.eu/icons/PageDoubleRightHover.png", - pageFirst: "https://trakman.ptrk.eu/icons/PageFirst.png", - pageFirstHover: "https://trakman.ptrk.eu/icons/PageFirstHover.png", - pageLast: "https://trakman.ptrk.eu/icons/PageLast.png", - pageLastHover: "https://trakman.ptrk.eu/icons/PageLastHover.png", - pageLeft: "https://trakman.ptrk.eu/icons/PageLeft.png", - pageLeftHover: "https://trakman.ptrk.eu/icons/PageLeftHover.png", - pageRight: "https://trakman.ptrk.eu/icons/PageRight.png", - pageRightHover: "https://trakman.ptrk.eu/icons/PageRightHover.png", - people: "https://trakman.ptrk.eu/icons/People.png", - peopleGaming: "https://trakman.ptrk.eu/icons/PeopleGaming.png", - tag: "https://trakman.ptrk.eu/icons/Tag.png", - tagGreen: "https://trakman.ptrk.eu/icons/TagGreen.png", - tagPinkCyan: "https://trakman.ptrk.eu/icons/TagPinkCyan.png", - tagRed: "https://trakman.ptrk.eu/icons/TagRed.png", - tagYellow: "https://trakman.ptrk.eu/icons/TagYellow.png", - numberedList: "https://trakman.ptrk.eu/icons/NumberedList.png", - stats: "https://trakman.ptrk.eu/icons/Stats.png", - clockList: "https://trakman.ptrk.eu/icons/ClockList.png", - voteReplay: "https://trakman.ptrk.eu/icons/VoteReplay.png", - voteSkip: "https://trakman.ptrk.eu/icons/VoteSkip.png", - payReplay: "https://trakman.ptrk.eu/icons/PayReplay.png", - paySkip: "https://trakman.ptrk.eu/icons/PaySkip.png", - karmaStats: "https://trakman.ptrk.eu/icons/KarmaStats.png", - karmaPulse: "https://trakman.ptrk.eu/icons/KarmaPulse.png", - peopleKarma: "https://trakman.ptrk.eu/icons/PeopleKarma.png", - karmaGlobal: "https://trakman.ptrk.eu/icons/KarmaGlobal.png", - karmaLocal: "https://trakman.ptrk.eu/icons/KarmaLocal.png", - banlist: "https://trakman.ptrk.eu/icons/BanList.png", - blacklist: "https://trakman.ptrk.eu/icons/BlacklistList.png", - mutelist: "https://trakman.ptrk.eu/icons/MuteList.png", - guestlist: "https://trakman.ptrk.eu/icons/GuestlistList.png", - infoCircle: "https://trakman.ptrk.eu/icons/InfoCircle.png", - tools: "https://trakman.ptrk.eu/icons/Tools.png", - ban: "https://trakman.ptrk.eu/icons/BanHammer.png", - banHover: "https://trakman.ptrk.eu/icons/BanHover.png", - addToBlacklist: "https://trakman.ptrk.eu/icons/BanHammer.png", - addToBlacklistHover: "https://trakman.ptrk.eu/icons/BlacklistHover.png", - unban: "https://trakman.ptrk.eu/icons/Unban.png", - unbanHover: "https://trakman.ptrk.eu/icons/UnbanHover.png", - unblacklist: "https://trakman.ptrk.eu/icons/Unban.png", - unblacklistHover: "https://trakman.ptrk.eu/icons/UnblacklistHover.png", - addGuest: "https://trakman.ptrk.eu/icons/AddGuest.png", - addGuestHover: "https://trakman.ptrk.eu/icons/AddGuestHover.png", - removeGuest: "https://trakman.ptrk.eu/icons/RemoveGuest.png", - removeGuestHover: "https://trakman.ptrk.eu/icons/RemoveGuestHover.png", - mute: "https://trakman.ptrk.eu/icons/Mute.png", - muteHover: "https://trakman.ptrk.eu/icons/MuteHover.png", - unmute: "https://trakman.ptrk.eu/icons/Unmute.png", - unmuteHover: "https://trakman.ptrk.eu/icons/UnmuteHover.png", - forceSpectator: "https://trakman.ptrk.eu/icons/ForceSpectator.png", - forceGaming: "https://trakman.ptrk.eu/icons/ForceGaming.png", - chartDedi: "https://trakman.ptrk.eu/icons/ChartDedi.png", - chartLive: "https://trakman.ptrk.eu/icons/ChartLive.png", - chartLocal: "https://trakman.ptrk.eu/icons/ChartLocal.png", - bestFinishes: "https://trakman.ptrk.eu/icons/BestFinishes.png", - bestClock: "https://trakman.ptrk.eu/icons/BestClock.png", - ongoingMap: "https://trakman.ptrk.eu/icons/OngoingMap.png", - personBuilder: "https://trakman.ptrk.eu/icons/PersonBuilder.png", - person: "https://trakman.ptrk.eu/icons/Person.png", - personWorse: "https://trakman.ptrk.eu/icons/PersonWorse.png", - personBetter: "https://trakman.ptrk.eu/icons/PersonBetter.png", - clockAuthor: "https://trakman.ptrk.eu/icons/ClockAuthor.png", - maniaExchange: "https://trakman.ptrk.eu/icons/ManiaExchange.png", - cash: "https://trakman.ptrk.eu/icons/Cash.png", - info: "https://trakman.ptrk.eu/icons/Info.png", - gears: "https://trakman.ptrk.eu/icons/Gears.png", - playerList: "https://trakman.ptrk.eu/icons/PlayerList.png", - mapList: "https://trakman.ptrk.eu/icons/MapList.png", - infoList: "https://trakman.ptrk.eu/icons/InfoList.png", - placeholder: "https://trakman.ptrk.eu/icons/Placeholder.png", - checkmark: "https://trakman.ptrk.eu/icons/Checkmark.png", - cross: "https://trakman.ptrk.eu/icons/Cross.png", - addMap: "https://trakman.ptrk.eu/icons/AddMap.png", - removeMap: "https://trakman.ptrk.eu/icons/RemoveMap.png", - thumbsUpF5: "https://trakman.ptrk.eu/icons/ThumbsUpF5.png", - thumbsDownF6: "https://trakman.ptrk.eu/icons/ThumbsDownF6.png", - F5: "https://trakman.ptrk.eu/icons/F5.png", - F6: "https://trakman.ptrk.eu/icons/F6.png", - blank: "http://maniacdn.net/undef.de/uaseco/blank.png", - trackmania: "https://trakman.ptrk.eu/icons/Trackmania.png", - calendarPlus: "https://trakman.ptrk.eu/icons/CalendarPlus.png", - carExplode: "https://trakman.ptrk.eu/icons/CarExplode.png", - moodNight: "https://trakman.ptrk.eu/icons/MoodNight.png", - moodSunrise: "https://trakman.ptrk.eu/icons/MoodSunrise.png", - moodSunset: "https://trakman.ptrk.eu/icons/MoodSunset.png", - moodDay: "https://trakman.ptrk.eu/icons/MoodDay.png", - mapStyle: "https://trakman.ptrk.eu/icons/MapStyle.png", - mapRoutes: "https://trakman.ptrk.eu/icons/MapRoutes.png", - difficultyEasy: "https://trakman.ptrk.eu/icons/DifficultyOne.png", - difficultyMedium: "https://trakman.ptrk.eu/icons/DifficultyTwo.png", - difficultyHard: "https://trakman.ptrk.eu/icons/DifficultyThree.png", - difficultyExtreme: "https://trakman.ptrk.eu/icons/DifficultyFour.png", - puzzle: "https://trakman.ptrk.eu/icons/Puzzle.png", - leaderboardRating: "https://trakman.ptrk.eu/icons/LeaderboardRating.png", - leaderboardRatingClassic: "https://trakman.ptrk.eu/icons/LeaderboardRatingClassic.png", - leaderboardRatingNadeo: "https://trakman.ptrk.eu/icons/LeaderboardRatingNadeo.png", - trophy: "https://trakman.ptrk.eu/icons/Trophy.png", - trophyClassic: "https://trakman.ptrk.eu/icons/TrophyClassic.png", - trophyNadeo: "https://trakman.ptrk.eu/icons/TrophyNadeo.png", - kick: "https://trakman.ptrk.eu/icons/Kick.png", - kickHover: "https://trakman.ptrk.eu/icons/KickHover.png", - forceSpectatorHover: "https://trakman.ptrk.eu/icons/ForceSpectatorHover.png", - forceGamingHover: "https://trakman.ptrk.eu/icons/ForceGamingHover.png", - playerListHover: "https://trakman.ptrk.eu/icons/PlayerListHover.png", - restartHover: "https://trakman.ptrk.eu/icons/RestartHover.png", - restart: "https://trakman.ptrk.eu/icons/Restart.png", - requeueHover: "https://trakman.ptrk.eu/icons/RequeueHover.png", - requeue: "https://trakman.ptrk.eu/icons/Requeue.png", - mapListHover: "https://trakman.ptrk.eu/icons/MapListHover.png", - tagKacky: "https://trakman.ptrk.eu/icons/TagKacky.png", + clock: 'https://trakman.ptrk.eu/icons/Clock.png', + close: 'https://trakman.ptrk.eu/icons/Close.png', + closeHover: 'https://trakman.ptrk.eu/icons/CloseHover.png', + code: 'https://trakman.ptrk.eu/icons/Code.png', + codeBranch: 'https://trakman.ptrk.eu/icons/CodeBranch.png', + download: 'https://trakman.ptrk.eu/icons/Download.png', + downloadHover: 'https://trakman.ptrk.eu/icons/DownloadHover.png', + pageDoubleLeft: 'https://trakman.ptrk.eu/icons/PageDoubleLeft.png', + pageDoubleLeftHover: 'https://trakman.ptrk.eu/icons/PageDoubleLeftHover.png', + pageDoubleRight: 'https://trakman.ptrk.eu/icons/PageDoubleRight.png', + pageDoubleRightHover: 'https://trakman.ptrk.eu/icons/PageDoubleRightHover.png', + pageFirst: 'https://trakman.ptrk.eu/icons/PageFirst.png', + pageFirstHover: 'https://trakman.ptrk.eu/icons/PageFirstHover.png', + pageLast: 'https://trakman.ptrk.eu/icons/PageLast.png', + pageLastHover: 'https://trakman.ptrk.eu/icons/PageLastHover.png', + pageLeft: 'https://trakman.ptrk.eu/icons/PageLeft.png', + pageLeftHover: 'https://trakman.ptrk.eu/icons/PageLeftHover.png', + pageRight: 'https://trakman.ptrk.eu/icons/PageRight.png', + pageRightHover: 'https://trakman.ptrk.eu/icons/PageRightHover.png', + people: 'https://trakman.ptrk.eu/icons/People.png', + peopleGaming: 'https://trakman.ptrk.eu/icons/PeopleGaming.png', + tag: 'https://trakman.ptrk.eu/icons/Tag.png', + tagGreen: 'https://trakman.ptrk.eu/icons/TagGreen.png', + tagPinkCyan: 'https://trakman.ptrk.eu/icons/TagPinkCyan.png', + tagRed: 'https://trakman.ptrk.eu/icons/TagRed.png', + tagYellow: 'https://trakman.ptrk.eu/icons/TagYellow.png', + numberedList: 'https://trakman.ptrk.eu/icons/NumberedList.png', + stats: 'https://trakman.ptrk.eu/icons/Stats.png', + clockList: 'https://trakman.ptrk.eu/icons/ClockList.png', + voteReplay: 'https://trakman.ptrk.eu/icons/VoteReplay.png', + voteSkip: 'https://trakman.ptrk.eu/icons/VoteSkip.png', + payReplay: 'https://trakman.ptrk.eu/icons/PayReplay.png', + paySkip: 'https://trakman.ptrk.eu/icons/PaySkip.png', + karmaStats: 'https://trakman.ptrk.eu/icons/KarmaStats.png', + karmaPulse: 'https://trakman.ptrk.eu/icons/KarmaPulse.png', + peopleKarma: 'https://trakman.ptrk.eu/icons/PeopleKarma.png', + karmaGlobal: 'https://trakman.ptrk.eu/icons/KarmaGlobal.png', + karmaLocal: 'https://trakman.ptrk.eu/icons/KarmaLocal.png', + banlist: 'https://trakman.ptrk.eu/icons/BanList.png', + blacklist: 'https://trakman.ptrk.eu/icons/BlacklistList.png', + mutelist: 'https://trakman.ptrk.eu/icons/MuteList.png', + guestlist: 'https://trakman.ptrk.eu/icons/GuestlistList.png', + infoCircle: 'https://trakman.ptrk.eu/icons/InfoCircle.png', + tools: 'https://trakman.ptrk.eu/icons/Tools.png', + ban: 'https://trakman.ptrk.eu/icons/BanHammer.png', + banHover: 'https://trakman.ptrk.eu/icons/BanHover.png', + addToBlacklist: 'https://trakman.ptrk.eu/icons/BanHammer.png', + addToBlacklistHover: 'https://trakman.ptrk.eu/icons/BlacklistHover.png', + unban: 'https://trakman.ptrk.eu/icons/Unban.png', + unbanHover: 'https://trakman.ptrk.eu/icons/UnbanHover.png', + unblacklist: 'https://trakman.ptrk.eu/icons/Unban.png', + unblacklistHover: 'https://trakman.ptrk.eu/icons/UnblacklistHover.png', + addGuest: 'https://trakman.ptrk.eu/icons/AddGuest.png', + addGuestHover: 'https://trakman.ptrk.eu/icons/AddGuestHover.png', + removeGuest: 'https://trakman.ptrk.eu/icons/RemoveGuest.png', + removeGuestHover: 'https://trakman.ptrk.eu/icons/RemoveGuestHover.png', + mute: 'https://trakman.ptrk.eu/icons/Mute.png', + muteHover: 'https://trakman.ptrk.eu/icons/MuteHover.png', + unmute: 'https://trakman.ptrk.eu/icons/Unmute.png', + unmuteHover: 'https://trakman.ptrk.eu/icons/UnmuteHover.png', + forceSpectator: 'https://trakman.ptrk.eu/icons/ForceSpectator.png', + forceGaming: 'https://trakman.ptrk.eu/icons/ForceGaming.png', + chartDedi: 'https://trakman.ptrk.eu/icons/ChartDedi.png', + chartLive: 'https://trakman.ptrk.eu/icons/ChartLive.png', + chartLocal: 'https://trakman.ptrk.eu/icons/ChartLocal.png', + bestFinishes: 'https://trakman.ptrk.eu/icons/BestFinishes.png', + bestClock: 'https://trakman.ptrk.eu/icons/BestClock.png', + ongoingMap: 'https://trakman.ptrk.eu/icons/OngoingMap.png', + personBuilder: 'https://trakman.ptrk.eu/icons/PersonBuilder.png', + person: 'https://trakman.ptrk.eu/icons/Person.png', + personWorse: 'https://trakman.ptrk.eu/icons/PersonWorse.png', + personBetter: 'https://trakman.ptrk.eu/icons/PersonBetter.png', + clockAuthor: 'https://trakman.ptrk.eu/icons/ClockAuthor.png', + maniaExchange: 'https://trakman.ptrk.eu/icons/ManiaExchange.png', + cash: 'https://trakman.ptrk.eu/icons/Cash.png', + info: 'https://trakman.ptrk.eu/icons/Info.png', + gears: 'https://trakman.ptrk.eu/icons/Gears.png', + playerList: 'https://trakman.ptrk.eu/icons/PlayerList.png', + mapList: 'https://trakman.ptrk.eu/icons/MapList.png', + infoList: 'https://trakman.ptrk.eu/icons/InfoList.png', + placeholder: 'https://trakman.ptrk.eu/icons/Placeholder.png', + checkmark: 'https://trakman.ptrk.eu/icons/Checkmark.png', + cross: 'https://trakman.ptrk.eu/icons/Cross.png', + addMap: 'https://trakman.ptrk.eu/icons/AddMap.png', + removeMap: 'https://trakman.ptrk.eu/icons/RemoveMap.png', + thumbsUpF5: 'https://trakman.ptrk.eu/icons/ThumbsUpF5.png', + thumbsDownF6: 'https://trakman.ptrk.eu/icons/ThumbsDownF6.png', + F5: 'https://trakman.ptrk.eu/icons/F5.png', + F6: 'https://trakman.ptrk.eu/icons/F6.png', + blank: 'http://maniacdn.net/undef.de/uaseco/blank.png', + trackmania: 'https://trakman.ptrk.eu/icons/Trackmania.png', + calendarPlus: 'https://trakman.ptrk.eu/icons/CalendarPlus.png', + carExplode: 'https://trakman.ptrk.eu/icons/CarExplode.png', + moodNight: 'https://trakman.ptrk.eu/icons/MoodNight.png', + moodSunrise: 'https://trakman.ptrk.eu/icons/MoodSunrise.png', + moodSunset: 'https://trakman.ptrk.eu/icons/MoodSunset.png', + moodDay: 'https://trakman.ptrk.eu/icons/MoodDay.png', + mapStyle: 'https://trakman.ptrk.eu/icons/MapStyle.png', + mapRoutes: 'https://trakman.ptrk.eu/icons/MapRoutes.png', + difficultyEasy: 'https://trakman.ptrk.eu/icons/DifficultyOne.png', + difficultyMedium: 'https://trakman.ptrk.eu/icons/DifficultyTwo.png', + difficultyHard: 'https://trakman.ptrk.eu/icons/DifficultyThree.png', + difficultyExtreme: 'https://trakman.ptrk.eu/icons/DifficultyFour.png', + puzzle: 'https://trakman.ptrk.eu/icons/Puzzle.png', + leaderboardRating: 'https://trakman.ptrk.eu/icons/LeaderboardRating.png', + leaderboardRatingClassic: 'https://trakman.ptrk.eu/icons/LeaderboardRatingClassic.png', + leaderboardRatingNadeo: 'https://trakman.ptrk.eu/icons/LeaderboardRatingNadeo.png', + trophy: 'https://trakman.ptrk.eu/icons/Trophy.png', + trophyClassic: 'https://trakman.ptrk.eu/icons/TrophyClassic.png', + trophyNadeo: 'https://trakman.ptrk.eu/icons/TrophyNadeo.png', + kick: 'https://trakman.ptrk.eu/icons/Kick.png', + kickHover: 'https://trakman.ptrk.eu/icons/KickHover.png', + forceSpectatorHover: 'https://trakman.ptrk.eu/icons/ForceSpectatorHover.png', + forceGamingHover: 'https://trakman.ptrk.eu/icons/ForceGamingHover.png', + playerListHover: 'https://trakman.ptrk.eu/icons/PlayerListHover.png', + restartHover: 'https://trakman.ptrk.eu/icons/RestartHover.png', + restart: 'https://trakman.ptrk.eu/icons/Restart.png', + requeueHover: 'https://trakman.ptrk.eu/icons/RequeueHover.png', + requeue: 'https://trakman.ptrk.eu/icons/Requeue.png', + mapListHover: 'https://trakman.ptrk.eu/icons/MapListHover.png', + tagKacky: 'https://trakman.ptrk.eu/icons/TagKacky.png', noScreenshot: { - Bay: "https://trakman.ptrk.eu/icons/ScreenshotBay.png", - Coast: "https://trakman.ptrk.eu/icons/ScreenshotCoast.png", - Desert: "https://trakman.ptrk.eu/icons/ScreenshotDesert.png", - Island: "https://trakman.ptrk.eu/icons/ScreenshotIsland.png", - Rally: "https://trakman.ptrk.eu/icons/ScreenshotRally.png", - Snow: "https://trakman.ptrk.eu/icons/ScreenshotSnow.png", - Stadium: "https://trakman.ptrk.eu/icons/ScreenshotStadium.png" + Bay: 'https://trakman.ptrk.eu/icons/ScreenshotBay.png', + Coast: 'https://trakman.ptrk.eu/icons/ScreenshotCoast.png', + Desert: 'https://trakman.ptrk.eu/icons/ScreenshotDesert.png', + Island: 'https://trakman.ptrk.eu/icons/ScreenshotIsland.png', + Rally: 'https://trakman.ptrk.eu/icons/ScreenshotRally.png', + Snow: 'https://trakman.ptrk.eu/icons/ScreenshotSnow.png', + Stadium: 'https://trakman.ptrk.eu/icons/ScreenshotStadium.png' }, - waypoint: "https://trakman.ptrk.eu/icons/Waypoint.png", - bgGreyOpaque50: "https://trakman.ptrk.eu/icons/GreyOpaque50.png", - shuffleHover: "https://trakman.ptrk.eu/icons/ShuffleHover.png", - shuffle: "https://trakman.ptrk.eu/icons/Shuffle.png", - tagArcade: "https://trakman.ptrk.eu/icons/TagArcade.png", - pause: "https://trakman.ptrk.eu/icons/Pause.png", - pauseGreen: "https://trakman.ptrk.eu/icons/PauseHover.png", - minus: "https://trakman.ptrk.eu/icons/Minus.png", - minusGreen: "https://trakman.ptrk.eu/icons/MinusHover.png", - plus: "https://trakman.ptrk.eu/icons/Plus.png", - plusGreen: "https://trakman.ptrk.eu/icons/PlusHover.png", - play: "https://trakman.ptrk.eu/icons/Play.png", - playGreen: "https://trakman.ptrk.eu/icons/PlayHover.png", - clockLap: "https://trakman.ptrk.eu/icons/ClockLap.png", - lap: "https://trakman.ptrk.eu/icons/Laps.png", - trakman: "https://trakman.ptrk.eu/icons/Trakman.png", - flagFinish: "https://trakman.ptrk.eu/icons/FlagFinish.png", - trophySilver: "https://trakman.ptrk.eu/icons/TrophySilver.png", - trophyBronze: "https://trakman.ptrk.eu/icons/TrophyBronze.png", - clockFire: "https://trakman.ptrk.eu/icons/ClockFire.png", - lapsMode: "https://trakman.ptrk.eu/icons/LapsMode.png", - teamsMode: "https://trakman.ptrk.eu/icons/TeamsMode.png", - roundsMode: "https://trakman.ptrk.eu/icons/RoundsMode.png", - stuntsMode: "https://trakman.ptrk.eu/icons/StuntsMode.png", - servers: "https://trakman.ptrk.eu/icons/Servers.png", - musicRemove: "https://trakman.ptrk.eu/icons/MusicRemove.png", - musicRemoveHover: "https://trakman.ptrk.eu/icons/MusicRemoveHover.png", - musicAdd: "https://trakman.ptrk.eu/icons/MusicAdd.png", - musicAddHover: "https://trakman.ptrk.eu/icons/MusicAddHover.png", - musicList: "https://trakman.ptrk.eu/icons/MusicList.png", - disk: "https://trakman.ptrk.eu/icons/Disk.png", - music: "https://trakman.ptrk.eu/icons/Music.png", - exclamation: "https://trakman.ptrk.eu/icons/Exclamation.png", - warn: "https://trakman.ptrk.eu/icons/Warn.png", - personMusic: "https://trakman.ptrk.eu/icons/PersonMusic.png", - promotePlayer: "https://trakman.ptrk.eu/icons/PromotePlayer.png", - promotePlayerHover: "https://trakman.ptrk.eu/icons/PromotePlayerHover.png", - demotePlayer: "https://trakman.ptrk.eu/icons/DemotePlayer.png", - demotePlayerHover: "https://trakman.ptrk.eu/icons/DemotePlayerHover.png", - trashcanDelete: "https://trakman.ptrk.eu/icons/TrashDelete.png", - trashcanDeleteHover: "https://trakman.ptrk.eu/icons/TrashDeleteHover.png", + waypoint: 'https://trakman.ptrk.eu/icons/Waypoint.png', + bgGreyOpaque50: 'https://trakman.ptrk.eu/icons/GreyOpaque50.png', + shuffleHover: 'https://trakman.ptrk.eu/icons/ShuffleHover.png', + shuffle: 'https://trakman.ptrk.eu/icons/Shuffle.png', + tagArcade: 'https://trakman.ptrk.eu/icons/TagArcade.png', + pause: 'https://trakman.ptrk.eu/icons/Pause.png', + pauseGreen: 'https://trakman.ptrk.eu/icons/PauseHover.png', + minus: 'https://trakman.ptrk.eu/icons/Minus.png', + minusGreen: 'https://trakman.ptrk.eu/icons/MinusHover.png', + plus: 'https://trakman.ptrk.eu/icons/Plus.png', + plusGreen: 'https://trakman.ptrk.eu/icons/PlusHover.png', + play: 'https://trakman.ptrk.eu/icons/Play.png', + playGreen: 'https://trakman.ptrk.eu/icons/PlayHover.png', + clockLap: 'https://trakman.ptrk.eu/icons/ClockLap.png', + lap: 'https://trakman.ptrk.eu/icons/Laps.png', + trakman: 'https://trakman.ptrk.eu/icons/Trakman.png', + flagFinish: 'https://trakman.ptrk.eu/icons/FlagFinish.png', + trophySilver: 'https://trakman.ptrk.eu/icons/TrophySilver.png', + trophyBronze: 'https://trakman.ptrk.eu/icons/TrophyBronze.png', + clockFire: 'https://trakman.ptrk.eu/icons/ClockFire.png', + lapsMode: 'https://trakman.ptrk.eu/icons/LapsMode.png', + teamsMode: 'https://trakman.ptrk.eu/icons/TeamsMode.png', + roundsMode: 'https://trakman.ptrk.eu/icons/RoundsMode.png', + stuntsMode: 'https://trakman.ptrk.eu/icons/StuntsMode.png', + servers: 'https://trakman.ptrk.eu/icons/Servers.png', + musicRemove: 'https://trakman.ptrk.eu/icons/MusicRemove.png', + musicRemoveHover: 'https://trakman.ptrk.eu/icons/MusicRemoveHover.png', + musicAdd: 'https://trakman.ptrk.eu/icons/MusicAdd.png', + musicAddHover: 'https://trakman.ptrk.eu/icons/MusicAddHover.png', + musicList: 'https://trakman.ptrk.eu/icons/MusicList.png', + disk: 'https://trakman.ptrk.eu/icons/Disk.png', + music: 'https://trakman.ptrk.eu/icons/Music.png', + exclamation: 'https://trakman.ptrk.eu/icons/Exclamation.png', + warn: 'https://trakman.ptrk.eu/icons/Warn.png', + personMusic: 'https://trakman.ptrk.eu/icons/PersonMusic.png', + promotePlayer: 'https://trakman.ptrk.eu/icons/PromotePlayer.png', + promotePlayerHover: 'https://trakman.ptrk.eu/icons/PromotePlayerHover.png', + demotePlayer: 'https://trakman.ptrk.eu/icons/DemotePlayer.png', + demotePlayerHover: 'https://trakman.ptrk.eu/icons/DemotePlayerHover.png', + trashcanDelete: 'https://trakman.ptrk.eu/icons/TrashDelete.png', + trashcanDeleteHover: 'https://trakman.ptrk.eu/icons/TrashDeleteHover.png', get preloadedIcons() { - return [ - this.close, - this.closeHover, - this.pageDoubleLeft, - this.pageDoubleLeftHover, - this.pageDoubleRight, - this.pageDoubleRightHover, - this.pageFirst, - this.pageFirstHover, - this.pageLast, - this.pageLastHover, - this.pageLeft, - this.pageLeftHover, - this.pageRight, - this.pageRightHover - ] + return [this.close, this.closeHover, this.pageDoubleLeft, this.pageDoubleLeftHover, this.pageDoubleRight, + this.pageDoubleRightHover, this.pageFirst, this.pageFirstHover, this.pageLast, this.pageLastHover, this.pageLeft, + this.pageLeftHover, this.pageRight, this.pageRightHover] } } diff --git a/plugins/ui/config/Mod.js b/plugins/ui/config/Mod.js index b0859ff5..6857cfb1 100644 --- a/plugins/ui/config/Mod.js +++ b/plugins/ui/config/Mod.js @@ -1,40 +1,31 @@ -export default -// Environments are Stadium, Desert, Snow, Bay, Coast, Island, Rally +export default // Environments are Stadium, Desert, Snow, Bay, Coast, Island, Rally // If random order is disabled, mods array will be applied in order -[ - { - environment: 'Stadium', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Desert', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Snow', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Bay', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Coast', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Island', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, - { - environment: 'Rally', - modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], - randomOrder: true, - }, -] +[{ + environment: 'Stadium', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Desert', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Snow', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Bay', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Coast', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Island', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}, { + environment: 'Rally', + modLinks: [`https://trakman.ptrk.eu/TrakmanMod.zip`], + randomOrder: true +}] diff --git a/plugins/ui/config/PopupWindow.js b/plugins/ui/config/PopupWindow.js index d2e4b672..191c2e75 100644 --- a/plugins/ui/config/PopupWindow.js +++ b/plugins/ui/config/PopupWindow.js @@ -1,6 +1,6 @@ export default { - headerBg: "000C", - bg: "000C", + headerBg: '000C', + bg: '000C', margin: 0.15, titleHeight: 5, windowHeight: 60, @@ -9,4 +9,4 @@ export default { footerHeight: 4, headerPageWidth: 10, textScale: 1 -} \ No newline at end of file +} diff --git a/plugins/ui/config/RaceUi.js b/plugins/ui/config/RaceUi.js index a64f1d94..db98bfce 100644 --- a/plugins/ui/config/RaceUi.js +++ b/plugins/ui/config/RaceUi.js @@ -3,122 +3,38 @@ export default { reduxModeDisablePlayerAmount: 18, margin: 0.15, marginBig: 0.27, - format: "$s", + format: '$s', textScale: 0.7, padding: 0.2, yOffset: -0.1, - background: "0006", + background: '0006', width: 14.65, topBorder: 47.85, rightPosition: 49.13, - leftPosition: -63.75, - // Put component class names here, if you want a margin between + leftPosition: -63.75, // Put component class names here, if you want a margin between // components then put a number - rightSideOrder: [ - "MapWidget", - "PreviousAndBest", - // 3.5, <- example margin - "TMXRanking", - "TimerWidget", - "LocalRanking", - "LiveRanking", - "DonationPanel" - ], - leftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "AdminPanel" - ], + rightSideOrder: ['MapWidget', 'PreviousAndBest', // 3.5, <- example margin + 'TMXRanking', 'TimerWidget', 'LocalRanking', 'LiveRanking', 'DonationPanel'], + leftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'AdminPanel'], otherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - roundsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "RoundScore", - "RoundsPointsRanking", - "DonationPanel" - ], - roundsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "LocalRanking", - "AdminPanel" - ], + roundsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'RoundScore', + 'RoundsPointsRanking', 'DonationPanel'], + roundsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'LocalRanking', 'AdminPanel'], roundsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - cupRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "RoundScore", - "RoundsPointsRanking", - "DonationPanel" - ], - cupLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "LocalRanking", - "AdminPanel" - ], + cupRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'RoundScore', 'RoundsPointsRanking', + 'DonationPanel'], + cupLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'LocalRanking', 'AdminPanel'], cupOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - teamsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "RoundScore", - "LocalRanking", - "TeamScore", - "DonationPanel" - ], - teamsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "AdminPanel" - ], + teamsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'RoundScore', 'LocalRanking', + 'TeamScore', 'DonationPanel'], + teamsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'AdminPanel'], teamsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - lapsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "TMXRanking", - "TimerWidget", - "LiveRanking", - "LocalRanking", - "DonationPanel" - ], - lapsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "DediRanking", - "LapRanking", - "AdminPanel" - ], + lapsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'TMXRanking', 'TimerWidget', 'LiveRanking', 'LocalRanking', + 'DonationPanel'], + lapsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'DediRanking', 'LapRanking', 'AdminPanel'], lapsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'], - stuntsRightSideOrder: [ - "MapWidget", - "PreviousAndBest", - "DonationPanel", - "TimerWidget", - "LocalRanking", - "LiveRanking", - ], - stuntsLeftSideOrder: [ - "ButtonsWidget", - "RankWidget", - "KarmaWidget", - "TMXRanking", - "UltiRanking", - "AdminPanel" - ], + stuntsRightSideOrder: ['MapWidget', 'PreviousAndBest', 'DonationPanel', 'TimerWidget', 'LocalRanking', + 'LiveRanking'], + stuntsLeftSideOrder: ['ButtonsWidget', 'RankWidget', 'KarmaWidget', 'TMXRanking', 'UltiRanking', 'AdminPanel'], stuntsOtherComponents: ['BestCps', 'BestFinishes', 'CpCounter'] -} \ No newline at end of file +} diff --git a/plugins/ui/config/ResultUi.js b/plugins/ui/config/ResultUi.js index 5229f4f5..67964ecd 100644 --- a/plugins/ui/config/ResultUi.js +++ b/plugins/ui/config/ResultUi.js @@ -1,32 +1,18 @@ export default { margin: 0.15, marginBig: 0.27, - format: "$s", + format: '$s', textScale: 0.7, padding: 0.2, yOffset: -0.1, - background: "CCC6", + background: 'CCC6', width: 14.65, topBorder: 47.85, rightPosition: 49.13, leftPosition: -63.75, - rightSideOrder: [ - "MapWidgetResult", - "NextMapRecords", - "TimerWidgetResult", - "KarmaRanking", - "VotersRanking", - "VisitorsRanking", - "DonationPanelResult" - ], - leftSideOrder: [ - "AveragesRanking", - "RankWidgetResult", - "KarmaWidgetResult", - "LocalRankingResult", - "DediRankingResult", - "RoundAveragesRanking", - "AdminPanelResult" - ], - otherComponents: ["DonatorsRanking", "MostRecordsRanking", "PlaytimeRanking", "WinnersRanking", "BannerWidget"] -} \ No newline at end of file + rightSideOrder: ['MapWidgetResult', 'NextMapRecords', 'TimerWidgetResult', 'KarmaRanking', 'VotersRanking', + 'VisitorsRanking', 'DonationPanelResult'], + leftSideOrder: ['AveragesRanking', 'RankWidgetResult', 'KarmaWidgetResult', 'LocalRankingResult', 'DediRankingResult', + 'RoundAveragesRanking', 'AdminPanelResult'], + otherComponents: ['DonatorsRanking', 'MostRecordsRanking', 'PlaytimeRanking', 'WinnersRanking', 'BannerWidget'] +} diff --git a/plugins/ui/dynamic_components/Changelog.component.ts b/plugins/ui/dynamic_components/Changelog.component.ts index 64ccf372..5e811735 100644 --- a/plugins/ui/dynamic_components/Changelog.component.ts +++ b/plugins/ui/dynamic_components/Changelog.component.ts @@ -3,11 +3,13 @@ * @since 0.3 */ -import { Grid, componentIds, type GridCellFunction, centeredText, closeButton, PopupWindow, Paginator } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, Paginator, PopupWindow } from '../UI.js' import config from './Changelog.config.js' import fs from 'fs/promises' -export default class Changelog extends PopupWindow<{ page: number }> { +export default class Changelog extends PopupWindow<{ + page: number +}> { private readonly versions: { version: string @@ -31,7 +33,8 @@ export default class Changelog extends PopupWindow<{ page: number }> { this.paginator.onPageChange = (login, page) => { this.displayToPlayer(login, { page }, `${page}/${this.paginator.pageCount}`) } - this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.entries).fill(1), [1], { margin: config.marginBig }) + this.grid = new Grid(this.contentWidth, this.contentHeight, new Array(config.entries).fill(1), [1], + { margin: config.marginBig }) this.readChangelog() } @@ -55,7 +58,11 @@ export default class Changelog extends PopupWindow<{ page: number }> { } }).slice(2, 2 + config.lineCount).join('\n') if (![version, date, content].includes(undefined as any)) { - this.versions.push({ version, date, content }) + this.versions.push({ + version, + date, + content + }) } } this.paginator.setPageCount(Math.ceil(this.versions.length / config.entries)) @@ -67,7 +74,9 @@ export default class Changelog extends PopupWindow<{ page: number }> { this.displayToPlayer(info.login, { page }, `${page}/${this.paginator.pageCount}`) } - protected constructContent(login: string, { page }: { page: number }): string { + protected constructContent(login: string, { page }: { + page: number + }): string { const index = config.entries * (page - 1) const cells: GridCellFunction[] = [] const cell: GridCellFunction = (i, j, w, h) => { @@ -86,10 +95,16 @@ export default class Changelog extends PopupWindow<{ page: number }> { const dateW = w - (versionWidth + this.margin) return ` - ${centeredText(`$s$${tm.utils.palette.green}${title}`, versionWidth, headerH, { padding: this.margin, textScale: config.textScale })} + ${centeredText(`$s$${tm.utils.palette.green}${title}`, versionWidth, headerH, { + padding: this.margin, + textScale: config.textScale + })} - ${centeredText(`$s${date}`, dateW, headerH, { padding: this.margin, textScale: config.textScale })} + ${centeredText(`$s${date}`, dateW, headerH, { + padding: this.margin, + textScale: config.textScale + })} @@ -99,7 +114,9 @@ export default class Changelog extends PopupWindow<{ page: number }> { ` } - protected constructFooter(login: string, { page }: { page: number }): string { + protected constructFooter(login: string, { page }: { + page: number + }): string { return closeButton(this.closeId, this.windowWidth, this.footerHeight) + this.paginator.constructXml(page) } diff --git a/plugins/ui/dynamic_components/Changelog.config.js b/plugins/ui/dynamic_components/Changelog.config.js index c019c558..ebed89a7 100644 --- a/plugins/ui/dynamic_components/Changelog.config.js +++ b/plugins/ui/dynamic_components/Changelog.config.js @@ -2,13 +2,14 @@ import ids from '../config/ComponentIds.js' import icons from '../config/Icons.js' export default { - title: "Changelog", + title: 'Changelog', icon: icons.codeBranch, - navbar: [ - { name: 'Command List', actionId: ids.commandList } - ], + navbar: [{ + name: 'Command List', + actionId: ids.commandList + }], textScale: 1.4, - tileBackground: "9996", + tileBackground: '9996', marginBig: 1, entries: 2, versionWidth: 6, @@ -20,4 +21,4 @@ export default { help: `Display the controller changelog.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/Chatlog.component.ts b/plugins/ui/dynamic_components/Chatlog.component.ts index 5bb38446..e4eb6a3e 100644 --- a/plugins/ui/dynamic_components/Chatlog.component.ts +++ b/plugins/ui/dynamic_components/Chatlog.component.ts @@ -3,10 +3,12 @@ * @since 1.0 */ -import { componentIds, Grid, leftAlignedText, centeredText, closeButton, Paginator, type GridCellFunction, PopupWindow } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, leftAlignedText, Paginator, PopupWindow } from '../UI.js' import config from './Chatlog.config.js' -export default class ChatLog extends PopupWindow<{ page: number }> { +export default class ChatLog extends PopupWindow<{ + page: number +}> { readonly grid: Grid readonly paginator: Paginator @@ -46,21 +48,21 @@ export default class ChatLog extends PopupWindow<{ page: number }> { } } - protected constructContent(login: string, params: { page: number }): string { + protected constructContent(login: string, params: { + page: number + }): string { const page = params.page const index = (page - 1) * (config.entries - 1) - 1 const messages = tm.chat.messages.reverse() - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' Date ', w, h), - (i, j, w, h) => centeredText(' Nickname ', w, h), - (i, j, w, h) => centeredText(' Login ', w, h), - (i, j, w, h) => centeredText(' Message ', w, h), - ] + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' Date ', w, h), + (i, j, w, h) => centeredText(' Nickname ', w, h), (i, j, w, h) => centeredText(' Login ', w, h), + (i, j, w, h) => centeredText(' Message ', w, h)] const dateCell: GridCellFunction = (i, j, w, h) => { return centeredText(tm.utils.formatDate(messages[index + i].date, true), w, h) } const nicknameCell: GridCellFunction = (i, j, w, h) => { - return centeredText(tm.utils.safeString(tm.utils.safeString(tm.utils.strip(messages[index + i].nickname, false))), w, h) + return centeredText(tm.utils.safeString(tm.utils.safeString(tm.utils.strip(messages[index + i].nickname, false))), + w, h) } const loginCell: GridCellFunction = (i, j, w, h) => { const playerLogin = messages[index + i].login diff --git a/plugins/ui/dynamic_components/Chatlog.config.js b/plugins/ui/dynamic_components/Chatlog.config.js index d5a41d9c..e0e91de3 100644 --- a/plugins/ui/dynamic_components/Chatlog.config.js +++ b/plugins/ui/dynamic_components/Chatlog.config.js @@ -1,8 +1,9 @@ import icons from '../config/Icons.js' + const p = tm.utils.palette export default { - title: " Chat Log ", + title: ' Chat Log ', kickPrivilege: 1, forceSpecPrivilege: 1, privilege: 0, // Privilege required to open the window @@ -11,19 +12,13 @@ export default { iconHeight: 2, public: true, entries: 16, - columnProportions: [ - 2, - 3, - 2.5, - 7, - ], - navbar: [ - ], + columnProportions: [2, 3, 2.5, 7], + navbar: [], selfColour: p.green, grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, command: { aliases: ['chatlog', 'cl'], diff --git a/plugins/ui/dynamic_components/Commandlist.component.ts b/plugins/ui/dynamic_components/Commandlist.component.ts index fc50a026..87c07e8d 100644 --- a/plugins/ui/dynamic_components/Commandlist.component.ts +++ b/plugins/ui/dynamic_components/Commandlist.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { Paginator, Grid, centeredText, componentIds, closeButton, Navbar, type GridCellFunction, addManialinkListener, PopupWindow } from "../UI.js" +import { addManialinkListener, centeredText, closeButton, componentIds, Grid, type GridCellFunction, Paginator, PopupWindow } from "../UI.js" import config from './Commandlist.config.js' interface DisplayParams { @@ -29,7 +29,12 @@ export default class CommandList extends PopupWindow { private adminCommands: tm.Command[] = [] private masteradminCommands: tm.Command[] = [] private ownerCommands: tm.Command[] = [] - private searchQueries: { login: string, privilege: number, list: tm.Command[], paginator: Paginator }[] = [] + private searchQueries: { + login: string, + privilege: number, + list: tm.Command[], + paginator: Paginator + }[] = [] private paginatorIdOffset = 0 private readonly paginatorIdRange = 400 // Starts at 600 @@ -51,13 +56,19 @@ export default class CommandList extends PopupWindow { if (entry !== undefined) { const page = entry[0].getPageByLogin(info.login) this.displayToPlayer(info.login, { - paginator: entry[0], commands: entry[1], - page, privilege: info.privilege, singleType: true + paginator: entry[0], + commands: entry[1], + page, + privilege: info.privilege, + singleType: true }, `${page}/${entry[0].pageCount}`, info.privilege, entry[2]) } }) tm.addListener("PrivilegeChanged", (info) => { - const p: { login: string, params: DisplayParams } | undefined = this.getPlayersWithWindowOpen(true).find(a => a.login === info.login) + const p: { + login: string, + params: DisplayParams + } | undefined = this.getPlayersWithWindowOpen(true).find(a => a.login === info.login) if (p !== undefined) { // Handling list change on search query would be quite complex and its // very unlikely to occur so I just leave the window as it is in that case @@ -68,16 +79,27 @@ export default class CommandList extends PopupWindow { const paginator: Paginator = this.paginators[info.newPrivilege] const commands: tm.Command[] = this.commandLists[info.newPrivilege] const page = paginator.getPageByLogin(info.login) - this.displayToPlayer(info.login, { page, commands, paginator, privilege: info.newPrivilege }, `${page}/${paginator.pageCount}`, info.newPrivilege) + this.displayToPlayer(info.login, { + page, + commands, + paginator, + privilege: info.newPrivilege + }, `${page}/${paginator.pageCount}`, info.newPrivilege) } else { - this.displayToPlayer(info.login, { ...p.params, privilege: info.newPrivilege }, `0/${p.params.paginator.pageCount}`, info.newPrivilege) + this.displayToPlayer(info.login, { + ...p.params, + privilege: info.newPrivilege + }, `0/${p.params.paginator.pageCount}`, info.newPrivilege) } } }) tm.commands.add({ aliases: config.command.aliases, help: config.command.help, - params: [{ name: 'query', optional: true }], + params: [{ + name: 'query', + optional: true + }], callback: (info: tm.MessageInfo, query?: string): void => { if (query !== undefined) { this.openWithQuery(info.login, info.privilege, query) @@ -105,38 +127,58 @@ export default class CommandList extends PopupWindow { this.adminCommands = commandList.filter(a => a.help !== undefined && a.privilege === 2) this.masteradminCommands = commandList.filter(a => a.help !== undefined && a.privilege === 3) this.ownerCommands = commandList.filter(a => a.help !== undefined && a.privilege === 4) - this.userPaginator = new Paginator(this.openId + 100, this.contentWidth, this.footerHeight, Math.ceil(this.userCommands.length / config.entries)) - this.opPaginator = new Paginator(this.openId + 200, this.contentWidth, this.footerHeight, Math.ceil(this.opCommands.length / config.entries)) - this.adminPaginator = new Paginator(this.openId + 300, this.contentWidth, this.footerHeight, Math.ceil(this.adminCommands.length / config.entries)) - this.masteradminPaginator = new Paginator(this.openId + 400, this.contentWidth, this.footerHeight, Math.ceil(this.masteradminCommands.length / config.entries)) - this.ownerPaginator = new Paginator(this.openId + 500, this.contentWidth, this.footerHeight, Math.ceil(this.ownerCommands.length / config.entries)) - const paginators = [this.userPaginator, this.opPaginator, this.adminPaginator, this.masteradminPaginator, this.ownerPaginator] - const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, this.ownerCommands] + this.userPaginator = new Paginator(this.openId + 100, this.contentWidth, this.footerHeight, + Math.ceil(this.userCommands.length / config.entries)) + this.opPaginator = new Paginator(this.openId + 200, this.contentWidth, this.footerHeight, + Math.ceil(this.opCommands.length / config.entries)) + this.adminPaginator = new Paginator(this.openId + 300, this.contentWidth, this.footerHeight, + Math.ceil(this.adminCommands.length / config.entries)) + this.masteradminPaginator = new Paginator(this.openId + 400, this.contentWidth, this.footerHeight, + Math.ceil(this.masteradminCommands.length / config.entries)) + this.ownerPaginator = new Paginator(this.openId + 500, this.contentWidth, this.footerHeight, + Math.ceil(this.ownerCommands.length / config.entries)) + const paginators = [this.userPaginator, this.opPaginator, this.adminPaginator, this.masteradminPaginator, + this.ownerPaginator] + const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, + this.ownerCommands] for (const e of paginators) { e.onPageChange = (login: string, page: number): void => { const privilege = tm.players.get(login)?.privilege ?? 0 this.displayToPlayer(login, { - page, paginator: e, commands: commands[paginators.indexOf(e)], privilege, singleType: true + page, + paginator: e, + commands: commands[paginators.indexOf(e)], + privilege, + singleType: true }, `${page}/${e.pageCount}`, privilege) } } this.commandLists.length = 0 for (let i = 0; i <= 4; i++) { - const arr = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, this.ownerCommands].slice(0, i + 1) + const arr = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, + this.ownerCommands].slice(0, i + 1) const commands: tm.Command[] = arr.flat(1) this.commandLists.push(commands) const pageCount: number = Math.ceil(commands.length / config.entries) - const paginator: Paginator = new Paginator(this.openId + (i * 10), this.contentWidth, this.footerHeight, pageCount) + const paginator: Paginator = new Paginator(this.openId + (i * 10), this.contentWidth, this.footerHeight, + pageCount) paginator.onPageChange = (login: string, page: number, info): void => { - this.displayToPlayer(login, { page, paginator, commands, privilege: i }, `${page}/${paginator.pageCount}`, info.privilege) + this.displayToPlayer(login, { + page, + paginator, + commands, + privilege: i + }, `${page}/${paginator.pageCount}`, info.privilege) } this.paginators.push(paginator) } - this.table = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, new Array(config.entries).fill(1), config.grid) + this.table = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, + new Array(config.entries).fill(1), config.grid) } openWithQuery(login: string, privilege: number, query: string) { - const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, this.ownerCommands].slice(0, privilege + 1).flat(1) + const commands = [this.userCommands, this.opCommands, this.adminCommands, this.masteradminCommands, + this.ownerCommands].slice(0, privilege + 1).flat(1) const aliases = commands.map(a => a.aliases).flat(1) const matchedAliases = [] let aliasIndices: number[] @@ -150,18 +192,23 @@ export default class CommandList extends PopupWindow { } let indices: number[] indices = tm.utils.matchString(query, commands.map(a => a.help ?? '')) - const list: tm.Command[] = [...indices.map(a => commands[a]), ...matchedAliases].filter((a, i, arr) => arr.indexOf(a) === i) + const list: tm.Command[] = [...indices.map(a => commands[a]), ...matchedAliases].filter( + (a, i, arr) => arr.indexOf(a) === i) if (list.length === 0) { tm.sendMessage(tm.utils.strVar(config.noMatchesMessage, { query }), login) return } const paginatorId = (this.openId + 600 + this.paginatorIdOffset) % this.paginatorIdRange this.paginatorIdOffset++ - const paginator = new Paginator(paginatorId, this.contentWidth, - this.footerHeight, Math.ceil(list.length / config.entries)) + const paginator = new Paginator(paginatorId, this.contentWidth, this.footerHeight, + Math.ceil(list.length / config.entries)) paginator.onPageChange = (login: string, page: number, info): void => { - this.displayToPlayer(login, { page, paginator, commands: list, privilege }, - `${page}/${paginator.pageCount}`, info.privilege) + this.displayToPlayer(login, { + page, + paginator, + commands: list, + privilege + }, `${page}/${paginator.pageCount}`, info.privilege) } this.searchQueries.push({ login, @@ -169,8 +216,12 @@ export default class CommandList extends PopupWindow { list, paginator }) - this.displayToPlayer(login, { page: 1, commands: list, paginator, privilege }, - `1/${paginator.pageCount}`, privilege) + this.displayToPlayer(login, { + page: 1, + commands: list, + paginator, + privilege + }, `1/${paginator.pageCount}`, privilege) } protected onOpen(info: tm.ManialinkClickInfo): void { @@ -184,7 +235,12 @@ export default class CommandList extends PopupWindow { this.searchQueries[index].paginator.destroy() this.searchQueries.splice(index, 1) } - this.displayToPlayer(info.login, { page, commands, paginator, privilege: player.privilege }, `${page}/${paginator.pageCount}`, info.privilege) + this.displayToPlayer(info.login, { + page, + commands, + paginator, + privilege: player.privilege + }, `${page}/${paginator.pageCount}`, info.privilege) } protected onClose(info: tm.ManialinkClickInfo): void { @@ -198,16 +254,15 @@ export default class CommandList extends PopupWindow { protected constructContent(login: string, params: DisplayParams): string { const n = ((params.page - 1) * config.entries) - 1 - const headers: GridCellFunction[] = [ - (i, j, w, h) => centeredText(' P ', w, h), - (i, j, w, h) => centeredText(' Aliases ', w, h), - (i, j, w, h) => centeredText(' Arguments ', w, h), - (i, j, w, h) => centeredText(' Comment ', w, h), // Space to prevent translation + const headers: GridCellFunction[] = [(i, j, w, h) => centeredText(' P ', w, h), + (i, j, w, h) => centeredText(' Aliases ', w, h), (i, j, w, h) => centeredText(' Arguments ', w, h), + (i, j, w, h) => centeredText(' Comment ', w, h) // Space to prevent translation ] const privCell: GridCellFunction = (i, j, w, h) => { const command: tm.Command = params.commands[i + n] if (command === undefined) { return '' } - const text: string = config.slashesForPrivilege ? (command.privilege > 0 ? '//' : '/') : command.privilege.toString() + const text: string = config.slashesForPrivilege ? (command.privilege > 0 ? '//' : '/') : + command.privilege.toString() return centeredText(tm.utils.safeString(tm.utils.strip(text, true)), w, h) } const nameCell: GridCellFunction = (i, j, w, h) => { @@ -236,7 +291,8 @@ export default class CommandList extends PopupWindow { } protected constructFooter(login: string, params: DisplayParams): string { - return closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin) + params.paginator.constructXml(params.page) + return closeButton(this.closeId, this.windowWidth, this.headerHeight - this.margin) + params.paginator.constructXml( + params.page) } } \ No newline at end of file diff --git a/plugins/ui/dynamic_components/Commandlist.config.js b/plugins/ui/dynamic_components/Commandlist.config.js index a4735a29..2129839c 100644 --- a/plugins/ui/dynamic_components/Commandlist.config.js +++ b/plugins/ui/dynamic_components/Commandlist.config.js @@ -4,54 +4,42 @@ import icons from '../config/Icons.js' const p = tm.utils.palette export default { - title: "Command List", + title: 'Command List', entries: 15, icon: icons.infoList, textScale: 0.7, - navbar: [ - { - name: "All Commands", - actionId: ids.commandList, - privilege: 1 - }, - { - name: "User Commands", - actionId: ids.commandList + 100, - privilege: 1 - }, - { - name: "Operator Commands", - actionId: ids.commandList + 200, - privilege: 1 - }, - { - name: "Admin Commands", - actionId: ids.commandList + 300, - privilege: 2 - }, - { - name: "Masteradmin Commands", - actionId: ids.commandList + 400, - privilege: 3 - }, - { - name: "Server Owner Commands", - actionId: ids.commandList + 500, - privilege: 4 - } - ], + navbar: [{ + name: 'All Commands', + actionId: ids.commandList, + privilege: 1 + }, { + name: 'User Commands', + actionId: ids.commandList + 100, + privilege: 1 + }, { + name: 'Operator Commands', + actionId: ids.commandList + 200, + privilege: 1 + }, { + name: 'Admin Commands', + actionId: ids.commandList + 300, + privilege: 2 + }, { + name: 'Masteradmin Commands', + actionId: ids.commandList + 400, + privilege: 3 + }, { + name: 'Server Owner Commands', + actionId: ids.commandList + 500, + privilege: 4 + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, slashesForPrivilege: true, // Whether to display slashes or privilege level in the window - columnProportions: [ - 0.2, - 1, - 2, - 2 - ], + columnProportions: [0.2, 1, 2, 2], command: { aliases: ['h', 'help', 'helpall'], help: `Display the list of controller commands. Can be used with optional search query.`, @@ -59,4 +47,4 @@ export default { }, noMatchesMessage: `${p.error}Nothing found for ${p.highlight}#{query}${p.error}.`, aliasSearch: true // Whether to search for alias on query -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/PlayerStats.component.ts b/plugins/ui/dynamic_components/PlayerStats.component.ts index 9b6a4c69..b3d370a5 100644 --- a/plugins/ui/dynamic_components/PlayerStats.component.ts +++ b/plugins/ui/dynamic_components/PlayerStats.component.ts @@ -3,7 +3,7 @@ * @since 1.3.3 */ -import { Grid, componentIds, type GridCellFunction, type GridCellObject, centeredText, closeButton, PopupWindow } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, PopupWindow } from '../UI.js' import { titles } from '../../../config/Titles.js' import { stats } from '../../stats/Stats.js' import config from './PlayerStats.config.js' @@ -17,9 +17,13 @@ export default class PlayerStatsWindow extends PopupWindow { tm.commands.add({ aliases: config.command.aliases, help: config.command.help, - params: [{ name: 'login', optional: true }], + params: [{ + name: 'login', + optional: true + }], callback: async (info: tm.MessageInfo, login?: string): Promise => { - const player: tm.OfflinePlayer | undefined = tm.players.get(login ?? info.login) ?? await tm.players.fetch(login ?? info.login) + const player: tm.OfflinePlayer | undefined = tm.players.get(login ?? info.login) ?? await tm.players.fetch( + login ?? info.login) if (player === undefined) { tm.sendMessage(config.command.error, info.login) return @@ -50,34 +54,28 @@ export default class PlayerStatsWindow extends PopupWindow { } } - protected constructContent(login: string, params?: { player: tm.OfflinePlayer }): string { + protected constructContent(login: string, params?: { + player: tm.OfflinePlayer + }): string { if (params === undefined) { return '' } - const data: string[] = [ - params.player.login, - tm.utils.safeString(tm.utils.strip(params.player.nickname, false)), + const data: string[] = [params.player.login, tm.utils.safeString(tm.utils.strip(params.player.nickname, false)), `${params.player.country} / ${params.player.countryCode}`, titles.privileges[params.player.privilege as keyof typeof titles.privileges], (stats.averages.list.findIndex(a => a.login === params.player.login) + 1).toString(), - params.player.average.toString(), - tm.utils.getVerboseTime(params.player.timePlayed), + params.player.average.toString(), tm.utils.getVerboseTime(params.player.timePlayed), stats.records.list.find(a => a.login === params.player.login)?.amount.toString() ?? `0`, stats.votes.list.find(a => a.login === params.player.login)?.count.toString() ?? `0`, stats.sums.list.find(a => a.login === params.player.login)?.sums.slice(0, 4) // TODO: Find out how does the index go out of bounds here - .map((a, i) => `$${config.sumsColours[i]}${a}$FFF`).join(' / ') ?? `None`, - params.player.visits.toString(), + .map((a, i) => `$${config.sumsColours[i]}${a}$FFF`).join(' / ') ?? `None`, params.player.visits.toString(), stats.donations.list.find(a => a.login === params.player.login)?.amount.toString() ?? `0`, - params.player.wins.toString(), - params.player.isUnited ? `United` : `Nations`, - tm.utils.formatDate(params.player.lastOnline ?? new Date(), true) - ] - const header: GridCellObject[] = [ - { - background: config.grid.headerBackground, - colspan: 2, - callback: (i, j, w, h): string => centeredText(config.cellHeader, w, h), - } - ] + params.player.wins.toString(), params.player.isUnited ? `United` : `Nations`, + tm.utils.formatDate(params.player.lastOnline ?? new Date(), true)] + const header: GridCellObject[] = [{ + background: config.grid.headerBackground, + colspan: 2, + callback: (i, j, w, h): string => centeredText(config.cellHeader, w, h) + }] const nameCell: GridCellObject = { background: config.nameColumnBackground, callback: (i, j, w, h): string => { diff --git a/plugins/ui/dynamic_components/PlayerStats.config.js b/plugins/ui/dynamic_components/PlayerStats.config.js index b526a2d8..c55d015e 100644 --- a/plugins/ui/dynamic_components/PlayerStats.config.js +++ b/plugins/ui/dynamic_components/PlayerStats.config.js @@ -7,42 +7,26 @@ export default { width: 60, title: `Personal Stats`, icon: icons.person, - navbar: [ - { name: `All Stats`, actionId: ids.topRanks } - ], + navbar: [{ + name: `All Stats`, + actionId: ids.topRanks + }], grid: { background: '9996', margin: 0.15, headerBackground: '333C' }, - sumsColours: ['FD0','CCC', 'C73', 'FFF'], + sumsColours: ['FD0', 'CCC', 'C73', 'FFF'], nameColumnBackground: '5559', - columnProportions: [ - 2, - 2, - ], + columnProportions: [2, 2], cellHeader: ` Player Info `, - cells: [ - ` Player Login `, - ` Player Nickname `, - ` Player Nation `, - ` Player Privilege `, - ` Server Rank `, - ` Server Average `, - ` Total Playtime `, - ` Total Records `, - ` Total Map Votes `, - ` Top Sums `, - ` Visits Amount `, - ` Donations Amount `, - ` Wins Amount `, - ` Account Type `, - ` Last Seen ` - ], + cells: [` Player Login `, ` Player Nickname `, ` Player Nation `, ` Player Privilege `, ` Server Rank `, + ` Server Average `, ` Total Playtime `, ` Total Records `, ` Total Map Votes `, ` Top Sums `, ` Visits Amount `, + ` Donations Amount `, ` Wins Amount `, ` Account Type `, ` Last Seen `], command: { aliases: ['ps', 'stats', 'playerstats', 'pls'], help: `Display the detailed stats page for a player.`, privilege: 0, error: `${p.error}There's no information about this player in the database.` } -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/ServerInfo.component.ts b/plugins/ui/dynamic_components/ServerInfo.component.ts index 95dc8c00..1c389fa7 100644 --- a/plugins/ui/dynamic_components/ServerInfo.component.ts +++ b/plugins/ui/dynamic_components/ServerInfo.component.ts @@ -3,10 +3,10 @@ * @since 1.1 */ -import { Grid, componentIds, type GridCellFunction, type GridCellObject, centeredText, closeButton, PopupWindow } from '../UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, PopupWindow } from '../UI.js' import config from './ServerInfo.config.js' -import { arch, cpus, loadavg, totalmem, freemem, platform } from 'node:os' -import { uptime, version, memoryUsage } from 'node:process' +import { arch, cpus, freemem, loadavg, platform, totalmem } from 'node:os' +import { memoryUsage, uptime, version } from 'node:process' import 'dotenv/config' export default class ServerInfoWindow extends PopupWindow { @@ -43,9 +43,8 @@ export default class ServerInfoWindow extends PopupWindow { const serverMaxRecs: string = tm.config.controller.localRecordsLimit.toString() const serverMapCount: string = tm.maps.count.toString() const serverVisitorsCount: string = tm.players.totalCount.toString() - return [dedicatedUptime, dedicatedVersion, dedicatedBuild, serverName, serverLogin, - serverZone, serverRights, serverMaxPlayers, serverMaxSpecs, serverMaxRecs, - serverMapCount, serverVisitorsCount] + return [dedicatedUptime, dedicatedVersion, dedicatedBuild, serverName, serverLogin, serverZone, serverRights, + serverMaxPlayers, serverMaxSpecs, serverMaxRecs, serverMapCount, serverVisitorsCount] } private getHostInfo(): string[] { @@ -62,9 +61,8 @@ export default class ServerInfoWindow extends PopupWindow { // Node information const nodeVersion: string = version const nodeRAMUsage = String(~~(memoryUsage().heapTotal / (1024 ** 2)) + ` MB`) - return [osUptime, osArch, osCPU, osCPULoad, osRAM, osKernel, - trakmanVersion, trakmanUptime, nodeVersion, nodeRAMUsage, - tm.db.dbVersion, tm.db.dbSize] + return [osUptime, osArch, osCPU, osCPULoad, osRAM, osKernel, trakmanVersion, trakmanUptime, nodeVersion, + nodeRAMUsage, tm.db.dbVersion, tm.db.dbSize] } protected onOpen(info: tm.ManialinkClickInfo): void { @@ -90,18 +88,15 @@ export default class ServerInfoWindow extends PopupWindow { protected async constructContent(): Promise { const serverInfo: string[] = await this.getServerInfo() const hostInfo: string[] = await this.getHostInfo() - const headers: GridCellObject[] = [ - { - background: config.grid.headerBackground, - colspan: 2, - callback: (i, j, w, h): string => centeredText(config.serverCellHeader, w, h), - }, - { - background: config.grid.headerBackground, - colspan: 2, - callback: (i, j, w, h): string => centeredText(config.hostCellHeader, w, h), - }, - ] + const headers: GridCellObject[] = [{ + background: config.grid.headerBackground, + colspan: 2, + callback: (i, j, w, h): string => centeredText(config.serverCellHeader, w, h) + }, { + background: config.grid.headerBackground, + colspan: 2, + callback: (i, j, w, h): string => centeredText(config.hostCellHeader, w, h) + }] const nameCell: GridCellObject = { background: config.nameColumnBackground, callback: (i, j, w, h): string => { diff --git a/plugins/ui/dynamic_components/ServerInfo.config.js b/plugins/ui/dynamic_components/ServerInfo.config.js index f358c977..141f92d7 100644 --- a/plugins/ui/dynamic_components/ServerInfo.config.js +++ b/plugins/ui/dynamic_components/ServerInfo.config.js @@ -4,54 +4,28 @@ import icons from '../config/Icons.js' export default { title: `Server Overview`, icon: icons.trakman, - navbar: [ - { name: `Changelog`, actionId: ids.changelog } - ], + navbar: [{ + name: `Changelog`, + actionId: ids.changelog + }], grid: { - background: "9996", + background: '9996', margin: 0.15, - headerBackground: "333C" + headerBackground: '333C' }, nameColumnBackground: '5559', - columnProportions: [ - 2, - 2, - 2, - 2 - ], + columnProportions: [2, 2, 2, 2], serverCellHeader: ` Server Information `, - serverCells: [ - ` Dedicated Server Uptime `, - ` Dedicated Server Version `, - ` Dedicated Server Build `, - ` Server Name `, - ` Server Login `, - ` Server Zone `, - ` Server Rights `, - ` Server Max Players `, - ` Server Max Spectators `, - ` Server Max Records `, - ` Server Map Count `, - ` Server Visitor Count`, - ], + serverCells: [` Dedicated Server Uptime `, ` Dedicated Server Version `, ` Dedicated Server Build `, ` Server Name `, + ` Server Login `, ` Server Zone `, ` Server Rights `, ` Server Max Players `, ` Server Max Spectators `, + ` Server Max Records `, ` Server Map Count `, ` Server Visitor Count`], hostCellHeader: ` Host Information `, - hostCells: [ - ` System Uptime `, - ` System Architecture `, - ` System CPU `, - ` System CPU Load `, - ` System Free RAM `, - ` System Kernel `, - ` Trakman Version `, - ` Trakman Uptime `, - ` NodeJS Version `, - ` NodeJS RAM Usage `, - ` PostgreSQL Version `, - ` PostgreSQL DB Size ` - ], + hostCells: [` System Uptime `, ` System Architecture `, ` System CPU `, ` System CPU Load `, ` System Free RAM `, + ` System Kernel `, ` Trakman Version `, ` Trakman Uptime `, ` NodeJS Version `, ` NodeJS RAM Usage `, + ` PostgreSQL Version `, ` PostgreSQL DB Size `], command: { aliases: ['server', 'serverinfo', 'sinfo', 'si'], help: `Display the detailed server information.`, privilege: 0 } -} \ No newline at end of file +} diff --git a/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts b/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts index bcd3065a..eda0cda3 100644 --- a/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts +++ b/plugins/ui/dynamic_components/TMXDetailsWindow.component.ts @@ -1,9 +1,9 @@ -/** +/** * @author wiseraven * @since 1.3.3 */ -import { componentIds, Grid, type GridCellFunction, centeredText, closeButton, leftAlignedText, type GridCellObject, PopupWindow } from '../../ui/UI.js' +import { centeredText, closeButton, componentIds, Grid, type GridCellFunction, type GridCellObject, leftAlignedText, PopupWindow } from '../../ui/UI.js' import config from './TMXDetailsWindow.config.js' import { tmx } from '../../tmx/Tmx.js' @@ -24,10 +24,10 @@ export default class TMXDetailsWindow extends PopupWindow { tmx.onMapChange((): void => { this.reRender() }) - this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, config.rowProportions, config.grid) + this.grid = new Grid(this.contentWidth, this.contentHeight, config.columnProportions, config.rowProportions, + config.grid) } - private reRender(): void { const logins: string[] = this.getPlayersWithWindowOpen() for (const login of logins) { @@ -36,36 +36,37 @@ export default class TMXDetailsWindow extends PopupWindow { } private constructScreenshot(width: number, height: number, map: tm.TMXMap): string { - const image: string = map === undefined - ? config.noScreenshot["Stadium"] // this shouldn't ever be used anyway cause the window isn't constructed if the map is undefined + const image: string = map === undefined ? config.noScreenshot["Stadium"] // this shouldn't ever be used anyway cause the window isn't constructed if the map is undefined : tm.utils.safeString(map.thumbnailUrl + `&.jpeg`) return ` - ${centeredText(config.notLoaded, width, height, { textScale: config.notLoadedTextscale, yOffset: -1 })}` + ${centeredText(config.notLoaded, width, height, { + textScale: config.notLoadedTextscale, + yOffset: -1 + })}` } private constructTmxRecordsXml(width: number, height: number, replays: tm.TMXReplay[] = []): string { - const grid = new Grid(width, height, config.tmxRecordsProportions, new Array(config.tmxRecordsAmount + 1).fill(1), - { margin: config.margin, background: config.gridBackground, headerBackground: config.iconBackground }) + const grid = new Grid(width, height, config.tmxRecordsProportions, new Array(config.tmxRecordsAmount + 1).fill(1), { + margin: config.margin, + background: config.gridBackground, + headerBackground: config.iconBackground + }) const options = { textScale: config.recordTextScale } - const arr: (GridCellFunction | GridCellObject)[] = [ - (i, j, w, h) => centeredText(' Lp. ', w, h, options), - (i, j, w, h) => centeredText(' Time ', w, h, options), - (i, j, w, h) => centeredText(' Name ', w, h, options), - (i, j, w, h) => centeredText(' Date ', w, h, options), - (i, j, w, h) => centeredText(' Dl. ', w, h, options), - ] + const arr: (GridCellFunction | GridCellObject)[] = [(i, j, w, h) => centeredText(' Lp. ', w, h, options), + (i, j, w, h) => centeredText(' Time ', w, h, options), (i, j, w, h) => centeredText(' Name ', w, h, options), + (i, j, w, h) => centeredText(' Date ', w, h, options), (i, j, w, h) => centeredText(' Dl. ', w, h, options)] const indexCell: GridCellObject = { callback: (i, j, w, h) => centeredText(i.toString(), w, h, options), background: config.iconBackground } - const timeCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) - const nameCell: GridCellFunction = (i, j, w, h) => - tm.utils.isMultibyte(replays[i - 1]?.name) - ? leftAlignedText(tm.utils.safeString(config.defaultTime), w, h, options) - : leftAlignedText(tm.utils.safeString(replays[i - 1]?.name ?? config.defaultTime), w, h, options) - const dateCell: GridCellFunction = (i, j, w, h) => centeredText(replays[i - 1] !== undefined ? - tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultTime, w, h, options) + const timeCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.getTimeString(replays[i - 1]?.time) : config.defaultTime, w, h, options) + const nameCell: GridCellFunction = (i, j, w, h) => tm.utils.isMultibyte(replays[i - 1]?.name) ? + leftAlignedText(tm.utils.safeString(config.defaultTime), w, h, options) : + leftAlignedText(tm.utils.safeString(replays[i - 1]?.name ?? config.defaultTime), w, h, options) + const dateCell: GridCellFunction = (i, j, w, h) => centeredText( + replays[i - 1] !== undefined ? tm.utils.formatDate(replays[i - 1]?.recordDate, true) : config.defaultTime, w, h, + options) const downloadCell: GridCellObject = { callback: (i, j, w, h) => replays[i - 1] !== undefined ? ` ` diff --git a/plugins/ui/static_components/race/AdminPanel.config.js b/plugins/ui/static_components/race/AdminPanel.config.js index 0d21af63..566d7ed8 100644 --- a/plugins/ui/static_components/race/AdminPanel.config.js +++ b/plugins/ui/static_components/race/AdminPanel.config.js @@ -1,5 +1,6 @@ import cfg from '../../config/RaceUi.js' import icons from '../../config/Icons.js' + const p = tm.utils.palette export default { @@ -8,7 +9,7 @@ export default { privilege: 1, margin: cfg.margin, background: cfg.background, - title: "Admin Panel", + title: 'Admin Panel', icon: icons.gears, disabledColour: '333C', public: true, @@ -37,4 +38,4 @@ export default { restart: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has restarted the ongoing map.`, endRound: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has forced the ongoing round to end.` } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/BestCps.component.ts b/plugins/ui/static_components/race/BestCps.component.ts index f8309e4c..2faa3e4f 100644 --- a/plugins/ui/static_components/race/BestCps.component.ts +++ b/plugins/ui/static_components/race/BestCps.component.ts @@ -3,12 +3,16 @@ * @since 0.1 */ -import { componentIds, Grid, centeredText, rightAlignedText, leftAlignedText, Paginator, StaticHeader, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { centeredText, componentIds, Grid, leftAlignedText, Paginator, rightAlignedText, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import config from './BestCps.config.js' export default class BestCps extends StaticComponent { - private readonly bestCps: { login: string, time: number, nickname: string }[] = [] + private readonly bestCps: { + login: string, + time: number, + nickname: string + }[] = [] private readonly header: StaticHeader private readonly headerBg: string private readonly headerHeight: number @@ -28,15 +32,18 @@ export default class BestCps extends StaticComponent { this.grid = this.getGrid() this.paginator = new Paginator(this.id, 0, 0, 0) this.renderOnEvent('PlayerCheckpoint', (info: tm.CheckpointInfo) => { - if (this.bestCps[info.index] === undefined || - (this.bestCps[info.index].time > info.time && tm.getGameMode() !== 'Stunts') || - (this.bestCps[info.index].time < info.time && tm.getGameMode() === 'Stunts')) { - this.bestCps[info.index] = { login: info.player.login, time: info.time, nickname: info.player.nickname } + if (this.bestCps[info.index] === undefined || (this.bestCps[info.index].time > info.time && tm.getGameMode() !== 'Stunts') || (this.bestCps[info.index].time < info.time && tm.getGameMode() === 'Stunts')) { + this.bestCps[info.index] = { + login: info.player.login, + time: info.time, + nickname: info.player.nickname + } this.paginator.setPageCount(Math.ceil(this.bestCps.length / config.entries)) this.newestCp = info.index return this.display() } - const page: number = this.paginator.setPageForLogin(info.player.login, Math.ceil((info.index + 1) / config.entries)) + const page: number = this.paginator.setPageForLogin(info.player.login, + Math.ceil((info.index + 1) / config.entries)) return this.displayToPlayer(info.player.login, { page }) }) this.renderOnEvent('BeginMap', () => { @@ -70,8 +77,9 @@ export default class BestCps extends StaticComponent { getGrid(): Grid { if (config.horizontal) { const rows = Math.min(config.horizontalMaxRows, Math.ceil(tm.maps.current.checkpointsAmount / config.entries)) - return new Grid(config.horizontalModeWidth, config.entryHeight * rows, new Array(config.entries).fill(config.columnProportions).flat(), - new Array(rows).fill(1), { margin: config.margin }) + return new Grid(config.horizontalModeWidth, config.entryHeight * rows, + new Array(config.entries).fill(config.columnProportions).flat(), new Array(rows).fill(1), + { margin: config.margin }) } return new Grid(config.width + config.margin * 2, this.contentHeight, config.columnProportions, new Array(config.entries).fill(1), { margin: config.margin }) @@ -91,7 +99,9 @@ export default class BestCps extends StaticComponent { return arr } - displayToPlayer(login: string, params?: { page?: number }) { + displayToPlayer(login: string, params?: { + page?: number + }) { if (!this.isDisplayed) { return } if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) @@ -107,7 +117,8 @@ export default class BestCps extends StaticComponent { ${this.constructText(login, 1)} - `, login + `, + login } } return { @@ -121,7 +132,8 @@ export default class BestCps extends StaticComponent { ${this.constructText(login, page)} - `, login + `, + login } } @@ -149,7 +161,8 @@ export default class BestCps extends StaticComponent { let buttonsXml: string = '' for (let i: number = 0; i < buttonAmount; i++) { const action: string = ids[i] === undefined ? '' : `action="${ids[i]}"` - const icon: string = icons[i] === undefined ? '' : `` buttonsXml += ` @@ -157,7 +170,11 @@ export default class BestCps extends StaticComponent { ` } return ` - ${rightAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), this.headerHeight, { textScale: config.textScale, yOffset: -0.1 })} + ${rightAlignedText(config.title, config.width - (headerCfg.squareWidth + config.margin) * (1 + buttonAmount), + this.headerHeight, { + textScale: config.textScale, + yOffset: -0.1 + })} @@ -175,7 +192,10 @@ export default class BestCps extends StaticComponent { const indexCell = (i: number, j: number, w: number, h: number): string => { const index = config.horizontal ? i * config.entries + j / 3 : i + cpIndex const bg = `` - return this.bestCps[index] === undefined ? '' : bg + (centeredText((index + 1).toString(), w, h, { textScale: config.textScale, padding: config.textPadding })) + return this.bestCps[index] === undefined ? '' : bg + (centeredText((index + 1).toString(), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const timeCell = (i: number, j: number, w: number, h: number): string => { @@ -185,17 +205,20 @@ export default class BestCps extends StaticComponent { if (cp === undefined) { return '' } let format: string = cp.login === login ? `` : '' if (index === this.newestCp) { format = `` } - return bg + format + centeredText(tm.utils.getTimeString(cp.time), w, h, { textScale: config.textScale, padding: config.textPadding }) + return bg + format + centeredText(tm.utils.getTimeString(cp.time), w, h, { + textScale: config.textScale, + padding: config.textPadding + }) } const nicknameCell = (i: number, j: number, w: number, h: number): string => { const index = config.horizontal ? i * config.entries + (j - 2) / 3 : i + cpIndex const bg = `` - return this.bestCps[index] === undefined ? '' : - bg + (this.bestCps[index] === undefined ? '' : - leftAlignedText(tm.utils.safeString(tm.utils.strip( - this.bestCps[index].nickname, false)), w, h, - { textScale: config.textScale, padding: config.textPadding })) + return this.bestCps[index] === undefined ? '' : bg + (this.bestCps[index] === undefined ? '' : + leftAlignedText(tm.utils.safeString(tm.utils.strip(this.bestCps[index].nickname, false)), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const cpsToDisplay: number = this.cpAmount - cpIndex diff --git a/plugins/ui/static_components/race/BestCps.config.js b/plugins/ui/static_components/race/BestCps.config.js index ce076444..442bef52 100644 --- a/plugins/ui/static_components/race/BestCps.config.js +++ b/plugins/ui/static_components/race/BestCps.config.js @@ -10,15 +10,11 @@ export default { horizontalModePosX: cfg.leftPosition + cfg.width + cfg.margin, posY: cfg.topBorder, side: false, - title: "Best Cps", + title: 'Best Cps', icon: icons.bestClock, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 2.8, - 4 - ], + columnProportions: [1, 2.8, 4], selfColour: `${tm.utils.palette.green}F`, newestColour: `${tm.utils.palette.yellow}F`, textScale: 0.87, @@ -29,5 +25,5 @@ export default { downIconHover: icons.pageRightHover, horizontal: false, horizontalMaxRows: 5, - hidePanel: true, + hidePanel: true } diff --git a/plugins/ui/static_components/race/BestFinishes.component.ts b/plugins/ui/static_components/race/BestFinishes.component.ts index 532c48db..53867e63 100644 --- a/plugins/ui/static_components/race/BestFinishes.component.ts +++ b/plugins/ui/static_components/race/BestFinishes.component.ts @@ -3,12 +3,16 @@ * @since 0.1 */ -import { componentIds, Grid, centeredText, leftAlignedText, StaticHeader, StaticComponent } from '../../UI.js' +import { centeredText, componentIds, Grid, leftAlignedText, StaticComponent, StaticHeader } from '../../UI.js' import config from './BestFinishes.config.js' export default class BestFinishes extends StaticComponent { - private readonly bestFinishes: { login: string, time: number, nickname: string }[] = [] + private readonly bestFinishes: { + login: string, + time: number, + nickname: string + }[] = [] private readonly header: StaticHeader private readonly headerBg: string private readonly headerHeight: number @@ -21,8 +25,8 @@ export default class BestFinishes extends StaticComponent { this.headerBg = this.header.options.textBackground this.headerHeight = this.header.options.height if (config.horizontal) { - this.grid = new Grid(config.horizontalModeWidth, config.entryHeight, new Array(config.entries).fill(config.columnProportions).flat(), - [1], { margin: config.margin }) + this.grid = new Grid(config.horizontalModeWidth, config.entryHeight, + new Array(config.entries).fill(config.columnProportions).flat(), [1], { margin: config.margin }) } else { const contentHeight = ((config.entryHeight + config.margin * 2) * config.entries) - (this.headerHeight + config.margin) this.grid = new Grid(config.width + config.margin * 2, contentHeight, config.columnProportions, @@ -38,7 +42,11 @@ export default class BestFinishes extends StaticComponent { } if (index === -1) { index = this.bestFinishes.length } if (index < config.entries) { - this.bestFinishes.splice(index, 0, { login: info.login, time: info.time, nickname: info.nickname }) + this.bestFinishes.splice(index, 0, { + login: info.login, + time: info.time, + nickname: info.nickname + }) this.bestFinishes.length = Math.min(config.entries, this.bestFinishes.length) this.newestFinish = index return this.display() @@ -106,7 +114,8 @@ export default class BestFinishes extends StaticComponent { ${this.constructGrid(login)} - `, login + `, + login } } @@ -125,8 +134,10 @@ export default class BestFinishes extends StaticComponent { index = i + 1 } const bg = `` - return this.bestFinishes[i] === undefined ? '' : bg + (centeredText((index).toString(), w, h, - { textScale: config.textScale, padding: config.textPadding })) + return this.bestFinishes[i] === undefined ? '' : bg + (centeredText((index).toString(), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const timeCell = (i: number, j: number, w: number, h: number): string => { @@ -136,15 +147,20 @@ export default class BestFinishes extends StaticComponent { if (fin === undefined) { return '' } let format: string = fin.login === login ? `` : '' if (index === this.newestFinish) { format = `` } - return bg + format + centeredText(tm.utils.getTimeString(fin.time), w, h, { textScale: config.textScale, padding: config.textPadding }) + return bg + format + centeredText(tm.utils.getTimeString(fin.time), w, h, { + textScale: config.textScale, + padding: config.textPadding + }) } const nicknameCell = (i: number, j: number, w: number, h: number): string => { const index = config.horizontal ? (j - 2) / 3 : i const bg = `` - return this.bestFinishes[index] === undefined ? '' : bg + - (leftAlignedText(tm.utils.safeString(tm.utils.strip(this.bestFinishes[index].nickname, false)), w, h, - { textScale: config.textScale, padding: config.textPadding })) + return this.bestFinishes[index] === undefined ? '' : + bg + (leftAlignedText(tm.utils.safeString(tm.utils.strip(this.bestFinishes[index].nickname, false)), w, h, { + textScale: config.textScale, + padding: config.textPadding + })) } const arr: ((i: number, j: number, w: number, h: number) => string)[] = [] diff --git a/plugins/ui/static_components/race/BestFinishes.config.js b/plugins/ui/static_components/race/BestFinishes.config.js index a228f8e3..bb189917 100644 --- a/plugins/ui/static_components/race/BestFinishes.config.js +++ b/plugins/ui/static_components/race/BestFinishes.config.js @@ -12,19 +12,15 @@ export default { horizontalModePosX: cfg.leftPosition + cfg.width * 2 + cfg.margin * 2, posY: cfg.topBorder, side: true, - title: "Best Finishes", + title: 'Best Finishes', icon: icons.bestFinishes, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 2.8, - 4 - ], + columnProportions: [1, 2.8, 4], selfColour: `${tm.utils.palette.green}F`, newestColour: `${tm.utils.palette.yellow}F`, textScale: 0.87, textPadding: 0.2, horizontal: false, - hidePanel: true, + hidePanel: true } diff --git a/plugins/ui/static_components/race/CpCounter.component.ts b/plugins/ui/static_components/race/CpCounter.component.ts index a221266d..4d42ab0d 100644 --- a/plugins/ui/static_components/race/CpCounter.component.ts +++ b/plugins/ui/static_components/race/CpCounter.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, StaticHeader, centeredText, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { centeredText, componentIds, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import config from './CpCounter.config.js' import { dedimania, type DediRecord } from '../../../dedimania/Dedimania.js' @@ -17,8 +17,18 @@ interface CheckpointData { export default class CpCounter extends StaticComponent { private readonly header: StaticHeader - private prevTimes: { login: string, best?: number, current: number, isFinish: boolean }[] = [] - private prevLapTimes: { login: string, best?: number, current: number, isFinish: boolean }[] = [] + private prevTimes: { + login: string, + best?: number, + current: number, + isFinish: boolean + }[] = [] + private prevLapTimes: { + login: string, + best?: number, + current: number, + isFinish: boolean + }[] = [] constructor() { super(componentIds.cpCounter) @@ -29,7 +39,9 @@ export default class CpCounter extends StaticComponent { const player = tm.players.get(login) if (player === undefined) { return } let best: number | undefined - let lap: undefined | CheckpointData & { cpIndex: number } + let lap: undefined | CheckpointData & { + cpIndex: number + } if (time !== 0) { const local: tm.LocalRecord | undefined = tm.records.getLocal(login) const dedi: DediRecord | undefined = dedimania.isUploadingLaps ? undefined : dedimania.getRecord(login) @@ -39,8 +51,7 @@ export default class CpCounter extends StaticComponent { } if (tm.maps.current.isInLapsMode && tm.maps.current.lapsAmount !== 1) { const local: tm.LocalRecord | undefined = tm.records.getLap(login) - const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? - undefined : dedimania.getRecord(login) + const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? undefined : dedimania.getRecord(login) let pb: number | undefined = dedi?.time ?? local?.time if (dedi !== undefined && local !== undefined) { pb = Math.min(local?.time, dedi?.time) @@ -48,15 +59,19 @@ export default class CpCounter extends StaticComponent { const startIndex = tm.maps.current.checkpointsAmount - (tm.maps.current.checkpointsPerLap + 1) lap = { cpIndex: 0, - index: 1, best: pb, + index: 1, + best: pb, current: time - (player.currentCheckpoints[startIndex]?.time ?? 0), isFinish: true } } } const obj = this.displayToPlayer(login, { - index: 0, current: time === 0 ? undefined : time, - best, isFinish: time !== 0, lap + index: 0, + current: time === 0 ? undefined : time, + best, + isFinish: time !== 0, + lap }) if (obj !== undefined) { tm.sendManialink(obj.xml, obj.login) @@ -87,7 +102,10 @@ export default class CpCounter extends StaticComponent { } const cpInfo = target.currentCheckpoints.at(-1) if (cpInfo === undefined) { return } - const ret = this.calculateData({ ...cpInfo, player: target }) + const ret = this.calculateData({ + ...cpInfo, + player: target + }) if (ret === undefined) { return } tm.sendManialink(ret.xml, player.login) }) @@ -117,11 +135,14 @@ export default class CpCounter extends StaticComponent { if (dedi !== undefined && local !== undefined) { pb = Math.min(local?.checkpoints?.[info.index], dedi?.checkpoints?.[info.index]) } - let lap: undefined | CheckpointData & { cpIndex: number } + let lap: undefined | CheckpointData & { + cpIndex: number + } if (tm.maps.current.isInLapsMode && tm.maps.current.lapsAmount !== 1) { let cpIndex = info.lapCheckpointIndex + 1 const local: tm.LocalRecord | undefined = tm.records.getLap(info.player.login) - const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? undefined : dedimania.getRecord(info.player.login) + const dedi: DediRecord | undefined = !dedimania.isUploadingLaps ? undefined : + dedimania.getRecord(info.player.login) let pb: number | undefined if (info.isLapFinish) { cpIndex = 0 @@ -130,27 +151,30 @@ export default class CpCounter extends StaticComponent { pb = Math.min(local?.time, dedi?.time) } } else { - pb = dedi?.checkpoints?.[info.lapCheckpointIndex] ?? - local?.checkpoints?.[info.lapCheckpointIndex] + pb = dedi?.checkpoints?.[info.lapCheckpointIndex] ?? local?.checkpoints?.[info.lapCheckpointIndex] if (dedi !== undefined && local !== undefined) { - pb = Math.min(local?.checkpoints?.[info.lapCheckpointIndex], - dedi?.checkpoints?.[info.lapCheckpointIndex]) + pb = Math.min(local?.checkpoints?.[info.lapCheckpointIndex], dedi?.checkpoints?.[info.lapCheckpointIndex]) } } lap = { - cpIndex, index: info.lap + 1, best: pb, + cpIndex, + index: info.lap + 1, + best: pb, current: info.lapCheckpointTime, isFinish: info.isLapFinish } } return this.displayToPlayer(info.player.login, { index: info.index + 1, - best: pb, current: info.time, isFinish: false, lap + best: pb, + current: info.time, + isFinish: false, + lap }) } - private constructTimeXml(login: string, isLap: boolean, icon: string, - isFinish?: boolean, currentTime?: number, bestTime?: number): string { + private constructTimeXml(login: string, isLap: boolean, icon: string, isFinish?: boolean, currentTime?: number, + bestTime?: number): string { const isStunts = tm.getGameMode() === 'Stunts' const arr = isLap ? this.prevLapTimes : this.prevTimes const prev = arr.find(a => a.login === login) @@ -160,7 +184,12 @@ export default class CpCounter extends StaticComponent { bestTime = prev?.best isFinish = prev?.isFinish } else if (prev === undefined) { - arr.push({ login, best: bestTime, current: currentTime, isFinish: isFinish === true }) + arr.push({ + login, + best: bestTime, + current: currentTime, + isFinish: isFinish === true + }) } else { prev.best = bestTime prev.current = currentTime @@ -187,15 +216,21 @@ export default class CpCounter extends StaticComponent { const h: StaticHeaderOptions = this.header.options const w: number = ((config.width - h.squareWidth) - config.margin) / 2 const timeColour: string = '$' + (isFinish === true ? config.colours.finish : config.colours.default) - return `${this.header.constructXml(timeColour + tm.utils.getTimeString(currentTime), - icon, config.side, { rectangleWidth: w, centerText: true })} + return `${this.header.constructXml(timeColour + tm.utils.getTimeString(currentTime), icon, config.side, { + rectangleWidth: w, + centerText: true + })} ${centeredText('$' + config.colours.default + differenceString, w, h.height, h)} ` } - displayToPlayer(login: string, params?: CheckpointData & { lap?: CheckpointData & { cpIndex: number } }) { + displayToPlayer(login: string, params?: CheckpointData & { + lap?: CheckpointData & { + cpIndex: number + } + }) { if (!this.isDisplayed) { return } if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) @@ -240,9 +275,10 @@ export default class CpCounter extends StaticComponent { let times = '' if (config.times) { - times = ` - ${cpAmount === 0 ? '' : this.constructTimeXml(login, false, config.iconBottom, - params?.isFinish, params?.current, params?.best)}` + times = ` + ${cpAmount === 0 ? '' : + this.constructTimeXml(login, false, config.iconBottom, params?.isFinish, params?.current, + params?.best)}` } return { @@ -251,16 +287,21 @@ export default class CpCounter extends StaticComponent { ${this.getLapsXml(login, params?.lap)} - ${this.header.constructXml('$' + config.colours.default + text, config.icon, config.side, - { rectangleWidth, centerText })} + ${this.header.constructXml('$' + config.colours.default + text, config.icon, config.side, { + rectangleWidth, + centerText + })} ${counterXml} ${times} - `, login: logins + `, + login: logins } } - private getLapsXml(login: string, data?: CheckpointData & { cpIndex: number }) { + private getLapsXml(login: string, data?: CheckpointData & { + cpIndex: number + }) { if (!tm.maps.current.isInLapsMode || tm.maps.current.lapsAmount === 1) { return '' } const h: StaticHeaderOptions = this.header.options const lapCounterW: number = config.lap.lapCounterWidth @@ -292,11 +333,11 @@ export default class CpCounter extends StaticComponent { ` return ` - ${cpAmount === 0 ? '' : this.constructTimeXml(login, true, config.lap.iconTop, - data?.isFinish, data?.current, data?.best)} + ${cpAmount === 0 ? '' : + this.constructTimeXml(login, true, config.lap.iconTop, data?.isFinish, data?.current, data?.best)} - ${this.header.constructXml('$' + config.colours.default + config.lap.lapText, config.lap.iconBottom, - config.side, { rectangleWidth: lapTextW })} + ${this.header.constructXml('$' + config.colours.default + config.lap.lapText, config.lap.iconBottom, config.side, + { rectangleWidth: lapTextW })} ${lapCounterXml} diff --git a/plugins/ui/static_components/race/CpCounter.config.js b/plugins/ui/static_components/race/CpCounter.config.js index efcc9cf1..6486c852 100644 --- a/plugins/ui/static_components/race/CpCounter.config.js +++ b/plugins/ui/static_components/race/CpCounter.config.js @@ -1,11 +1,11 @@ import cfg from '../../config/RaceUi.js' import icons from '../../config/Icons.js' + const p = tm.utils.palette export default { height: 2.17, - width: cfg.width, - // Change this ONLY if you want the widget to be on the side + width: cfg.width, // Change this ONLY if you want the widget to be on the side useRelative: false, posX: 12, posY: -37, @@ -16,8 +16,7 @@ export default { icon: icons.waypoint, iconBottom: icons.clock, text: 'Checkpoint', - noCpsText: 'No Checkpoints', - // 1.7 is the squarewidth + noCpsText: 'No Checkpoints', // 1.7 is the squarewidth noCpsWidth: cfg.width - cfg.margin - 1.7, defaultDifference: '$CCC-:--.--', finishText: `$${p.green}Finish`, @@ -36,9 +35,9 @@ export default { lapTextWidth: 2.7, lapCounterWidth: 3.2, checkpointText: 'CP', - checkpointTextWidth: 2, + checkpointTextWidth: 2 }, background: cfg.background, finishTextDuration: 3000, - hidePanel: false, -} \ No newline at end of file + hidePanel: false +} diff --git a/plugins/ui/static_components/race/DediRanking.component.ts b/plugins/ui/static_components/race/DediRanking.component.ts index ca70bd69..a5aee3d5 100644 --- a/plugins/ui/static_components/race/DediRanking.component.ts +++ b/plugins/ui/static_components/race/DediRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { dedimania } from '../../../dedimania/Dedimania.js' import config from './DediRanking.config.js' @@ -53,7 +53,10 @@ export default class DediRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.dediCps })} - ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ ...a, name: a.nickname })))} + ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ + ...a, + name: a.nickname + })))} `, @@ -64,11 +67,14 @@ export default class DediRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsEntries } return config.entries @@ -81,11 +87,14 @@ export default class DediRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsTopCount } return config.topCount @@ -98,8 +107,9 @@ export default class DediRanking extends StaticComponent { private getRecordList(): void { this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - this.getEntries(), this.side, this.getTopCount(), this.maxDedis, config.displayNoRecordEntry) + this.recordList = new RecordList('race', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), this.getEntries(), this.side, this.getTopCount(), + this.maxDedis, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/DediRanking.config.js b/plugins/ui/static_components/race/DediRanking.config.js index 72539ff6..f1e166f0 100644 --- a/plugins/ui/static_components/race/DediRanking.config.js +++ b/plugins/ui/static_components/race/DediRanking.config.js @@ -9,7 +9,7 @@ export default { lapsEntries: 7, entryHeight: 2.15, width: cfg.width, - title: "Dedi Records", + title: 'Dedi Records', icon: icons.chartDedi, margin: cfg.margin, topCount: 5, diff --git a/plugins/ui/static_components/race/DonationPanel.component.ts b/plugins/ui/static_components/race/DonationPanel.component.ts index c4a2a82d..f692cf24 100644 --- a/plugins/ui/static_components/race/DonationPanel.component.ts +++ b/plugins/ui/static_components/race/DonationPanel.component.ts @@ -4,7 +4,7 @@ */ import { donations } from '../../../donations/Donations.js' -import { componentIds, StaticHeader, centeredText, StaticComponent } from '../../UI.js' +import { centeredText, componentIds, StaticComponent, StaticHeader } from '../../UI.js' import config from './DonationPanel.config.js' export default class DonationPanel extends StaticComponent { @@ -33,7 +33,10 @@ export default class DonationPanel extends StaticComponent { display() { if (!this.isDisplayed) { return } - return { xml: this.xml, login: tm.players.list.filter(a => a.isUnited).map(a => a.login) } + return { + xml: this.xml, + login: tm.players.list.filter(a => a.isUnited).map(a => a.login) + } } displayToPlayer(login: string) { @@ -42,7 +45,10 @@ export default class DonationPanel extends StaticComponent { return this.hideToPlayer(login) } if (tm.players.get(login)?.isUnited) { - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } } diff --git a/plugins/ui/static_components/race/DonationPanel.config.js b/plugins/ui/static_components/race/DonationPanel.config.js index f82b653d..30723054 100644 --- a/plugins/ui/static_components/race/DonationPanel.config.js +++ b/plugins/ui/static_components/race/DonationPanel.config.js @@ -2,19 +2,12 @@ import cfg from '../../config/RaceUi.js' import icons from '../../config/Icons.js' export default { - title: "Donations", + title: 'Donations', height: 4.49, width: cfg.width, icon: icons.cash, margin: cfg.margin, background: cfg.background, hidePanel: true, - amounts: [ - 50, - 100, - 200, - 500, - 1000, - 5000 - ] -} \ No newline at end of file + amounts: [50, 100, 200, 500, 1000, 5000] +} diff --git a/plugins/ui/static_components/race/KarmaWidget.component.ts b/plugins/ui/static_components/race/KarmaWidget.component.ts index 5388c026..73f097be 100644 --- a/plugins/ui/static_components/race/KarmaWidget.component.ts +++ b/plugins/ui/static_components/race/KarmaWidget.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { centeredText, Grid, type GridCellFunction, componentIds, StaticHeader, addManialinkListener, StaticComponent } from '../../UI.js' +import { addManialinkListener, centeredText, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import { maniakarma, type MKMapVotes } from '../../../maniakarma/Maniakarma.js' import config from './KarmaWidget.config.js' import { actions } from '../../../actions/Actions.js' @@ -20,8 +20,11 @@ export default class KarmaWidget extends StaticComponent { super(componentIds.karma) this.header = new StaticHeader('race') this.headerH = this.header.options.height - this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, config.margin + config.height - this.headerH, - new Array(3).fill(1), new Array(3).fill(1), { background: config.background, margin: config.margin }) + this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, + config.margin + config.height - this.headerH, new Array(3).fill(1), new Array(3).fill(1), { + background: config.background, + margin: config.margin + }) this.renderOnEvent('KarmaVote', () => this.display()) maniakarma.onMapFetch(() => this.sendMultipleManialinks(this.display())) maniakarma.onVote(() => this.sendMultipleManialinks(this.display())) @@ -51,7 +54,10 @@ export default class KarmaWidget extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - return { xml: this.constructXml(login), login } + return { + xml: this.constructXml(login), + login + } } private constructXml(login: string): string { @@ -106,9 +112,11 @@ export default class KarmaWidget extends StaticComponent { const karmaStr: string = karma !== -1 ? (~~karma).toString() : config.defaultText const mkKarmaStr: string = mkVoteCount !== 0 ? (~~mkKarma).toString() : config.defaultText const mkCountStr: string = maniakarma.isEnabled ? mkVoteCount.toString() : config.defaultText - const options = { padding: config.textPadding, textScale: config.textScale } - const arr: GridCellFunction[] = [ - (i, j, w, h): string => ``, + const options = { + padding: config.textPadding, + textScale: config.textScale + } + const arr: GridCellFunction[] = [(i, j, w, h): string => ``, (i, j, w, h): string => ``, (i, j, w, h): string => ` centeredText(karmaStr, w, h, options), (i, j, w, h): string => centeredText(mkKarmaStr, w, h, options), - (i, j, w, h): string => ``, + (i, j, w, + h): string => ``, (i, j, w, h): string => centeredText(voteCount.toString(), w, h, options), - (i, j, w, h): string => centeredText(mkCountStr, w, h, options), - ] + (i, j, w, h): string => centeredText(mkCountStr, w, h, options)] return this.grid.constructXml(arr) } @@ -135,8 +143,12 @@ export default class KarmaWidget extends StaticComponent { const colour: string = this.options[i] === personalVote ? config.selfColour : config.mkColours[i] ret += ` - ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, - { xOffset: config.margin, yOffset: config.margin + h * i + offsetFix, padding: 0, textScale })}` + ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, { + xOffset: config.margin, + yOffset: config.margin + h * i + offsetFix, + padding: 0, + textScale + })}` } return ret } diff --git a/plugins/ui/static_components/race/KarmaWidget.config.js b/plugins/ui/static_components/race/KarmaWidget.config.js index e928ee66..055550f4 100644 --- a/plugins/ui/static_components/race/KarmaWidget.config.js +++ b/plugins/ui/static_components/race/KarmaWidget.config.js @@ -4,16 +4,11 @@ import icons from '../../config/Icons.js' export default { height: 8.8, width: cfg.width, - title: "Karma", + title: 'Karma', icon: icons.karmaStats, margin: cfg.margin, hidePanel: true, - icons: [ - icons.karmaLocal, - icons.karmaGlobal, - icons.karmaPulse, - icons.peopleKarma - ], + icons: [icons.karmaLocal, icons.karmaGlobal, icons.karmaPulse, icons.peopleKarma], colours: ['0F8A', '0D6A', '0B4A', 'BO4A', 'D06A', 'F08A'], mkColours: ['0F0A', '0D0A', '0B0A', 'BOOA', 'D00A', 'F00A'], options: ['+++', '++', '+', '-', '--', '---'], @@ -31,4 +26,4 @@ export default { scale: 1, offset: -0.3 } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/LapRanking.component.ts b/plugins/ui/static_components/race/LapRanking.component.ts index d48f175a..e2eaf8d9 100644 --- a/plugins/ui/static_components/race/LapRanking.component.ts +++ b/plugins/ui/static_components/race/LapRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './LapRanking.config.js' @@ -50,8 +50,9 @@ export default class LapRanking extends StaticComponent { let entries = config.entries let height = this.getHeight() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, config.topCount, tm.records.maxLocalsAmount, config.displayNoRecordEntry) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, config.topCount, + tm.records.maxLocalsAmount, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -75,11 +76,18 @@ export default class LapRanking extends StaticComponent { xml: ` - ${this.header.constructXml(config.title, config.icon, this.side, { actionId:/*TODO MAKE WINDOW FOR THIS */ componentIds.localCps })} + ${this.header.constructXml(config.title, config.icon, this.side, + { actionId:/*TODO MAKE WINDOW FOR THIS */ componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.lap - .map(a => ({ name: a.nickname, time: a.time, date: a.date, checkpoints: a.checkpoints, login: a.login })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/race/LapRanking.config.js b/plugins/ui/static_components/race/LapRanking.config.js index b6d8cea8..071d3881 100644 --- a/plugins/ui/static_components/race/LapRanking.config.js +++ b/plugins/ui/static_components/race/LapRanking.config.js @@ -5,10 +5,10 @@ export default { entries: 7, entryHeight: 2.15, width: cfg.width, - title: "Lap Records", + title: 'Lap Records', icon: icons.chartLocal, margin: cfg.margin, topCount: 3, displayNoRecordEntry: true, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/LiveRanking.component.ts b/plugins/ui/static_components/race/LiveRanking.component.ts index 1d885aa1..675a5d3c 100644 --- a/plugins/ui/static_components/race/LiveRanking.component.ts +++ b/plugins/ui/static_components/race/LiveRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent, type RLRecord } from '../../UI.js' +import { componentIds, RecordList, type RLRecord, StaticComponent, StaticHeader } from '../../UI.js' import config from './LiveRanking.config.js' export default class LiveRanking extends StaticComponent { @@ -39,7 +39,8 @@ export default class LiveRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Laps') { return config.lapsEntries - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.stuntsEntries } return config.entries @@ -52,7 +53,8 @@ export default class LiveRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Laps') { return config.lapsTopCount - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.stuntsTopCount } return config.topCount @@ -91,17 +93,26 @@ export default class LiveRanking extends StaticComponent { text = `${cpCount}/${mapCps}` } return { - cpCount, time: 0, login: a.login, name: a.nickname, + cpCount, + time: 0, + login: a.login, + name: a.nickname, checkpoints: record === undefined ? a.currentCheckpoints.map(a => a.time) : record.checkpoints, - text, finishTime: record?.time + text, + finishTime: record?.time } }).filter(a => a !== null) as any) - .sort((a: any, b: any) => b.cpCount - a.cpCount) // Sort secondary by cp amount - .sort((a: any, b: any) => a.finishTime - b.finishTime) as RLRecord[]) // Sort primary by finish time + .sort((a: any, b: any) => b.cpCount - a.cpCount) // Sort secondary by cp amount + .sort((a: any, b: any) => a.finishTime - b.finishTime) as RLRecord[]) // Sort primary by finish time content = this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, list) } else { content = this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.live - .map(a => ({ name: a.nickname, time: a.time, checkpoints: a.checkpoints, login: a.login }))) + .map(a => ({ + name: a.nickname, + time: a.time, + checkpoints: a.checkpoints, + login: a.login + }))) } return { xml: ` @@ -129,9 +140,12 @@ export default class LiveRanking extends StaticComponent { noRecordEntryText = config.stuntsNoRecordEntry } this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, - { dontParseTime, noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { + dontParseTime, + noRecordEntryText + }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/LiveRanking.config.js b/plugins/ui/static_components/race/LiveRanking.config.js index abaa8bd7..ef904c72 100644 --- a/plugins/ui/static_components/race/LiveRanking.config.js +++ b/plugins/ui/static_components/race/LiveRanking.config.js @@ -7,8 +7,8 @@ export default { stuntsEntries: 8, entryHeight: 2.15, width: cfg.width, - title: "Live Records", - lapsTitle: "Live Ranking", + title: 'Live Records', + lapsTitle: 'Live Ranking', icon: icons.chartLive, margin: cfg.margin, topCount: 3, @@ -20,4 +20,4 @@ export default { cpsCollectedColour: tm.utils.palette.green, hidePanel: true, maxRecordsAmount: 3000 // If more records than this get driven in one round the click listener will break -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/LocalRanking.component.ts b/plugins/ui/static_components/race/LocalRanking.component.ts index ff8212da..92cb020f 100644 --- a/plugins/ui/static_components/race/LocalRanking.component.ts +++ b/plugins/ui/static_components/race/LocalRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './LocalRanking.config.js' @@ -35,13 +35,17 @@ export default class LocalRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsEntries - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.stuntsEntries } return config.entries @@ -50,13 +54,17 @@ export default class LocalRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsTopCount - } if (tm.getGameMode() === 'Stunts') { + } + if (tm.getGameMode() === 'Stunts') { return config.lapsTopCount } return config.topCount @@ -80,8 +88,9 @@ export default class LocalRanking extends StaticComponent { const height = this.getHeight() const entries = this.getEntries() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { noRecordEntryText: tm.getGameMode() === 'Stunts' ? config.stuntsNoRecordEntry : undefined }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } @@ -109,8 +118,14 @@ export default class LocalRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.local - .map(a => ({ name: a.nickname, time: a.time, date: a.date, checkpoints: a.checkpoints, login: a.login })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/race/LocalRanking.config.js b/plugins/ui/static_components/race/LocalRanking.config.js index 9727c949..f4a84a14 100644 --- a/plugins/ui/static_components/race/LocalRanking.config.js +++ b/plugins/ui/static_components/race/LocalRanking.config.js @@ -10,7 +10,7 @@ export default { stuntsEntries: 12, entryHeight: 2.15, width: cfg.width, - title: "Local Records", + title: 'Local Records', icon: icons.chartLocal, margin: cfg.margin, topCount: 5, @@ -22,4 +22,4 @@ export default { displayNoRecordEntry: true, hidePanel: true, stuntsNoRecordEntry: '--' -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/MapWidget.component.ts b/plugins/ui/static_components/race/MapWidget.component.ts index 58930150..9779e9b7 100644 --- a/plugins/ui/static_components/race/MapWidget.component.ts +++ b/plugins/ui/static_components/race/MapWidget.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { componentIds, Grid, StaticHeader, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { componentIds, Grid, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import flags from '../../config/FlagIcons.js' import { tmx } from '../../../tmx/Tmx.js' import { webservices } from '../../../webservices/Webservices.js' @@ -39,7 +39,10 @@ export default class MapWidget extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private updateXML(): void { @@ -65,13 +68,10 @@ export default class MapWidget extends StaticComponent { timeOrScoreIcon = ic.goldScore } } - const infos: [string, string][] = [ - [config.title, ic.header], + const infos: [string, string][] = [[config.title, ic.header], [tm.utils.safeString(tm.utils.decodeURI(map.name)), this.getTag(map, TMXMap ?? undefined)], - [tm.utils.safeString(author), authorIcon], - [timeOrScore, timeOrScoreIcon], - [date === undefined ? config.noDateText : tm.utils.formatDate(date), ic.buildDate] - ] + [tm.utils.safeString(author), authorIcon], [timeOrScore, timeOrScoreIcon], + [date === undefined ? config.noDateText : tm.utils.formatDate(date), ic.buildDate]] const headerCfg: StaticHeaderOptions = this.header.options const cell = (i: number, j: number, w: number, h: number): string => { if (i === 3) { @@ -84,24 +84,23 @@ export default class MapWidget extends StaticComponent { textBackground: config.background })} - + ${this.header.constructXml(infos[i + 1][0], infos[i + 1][1], this.side, { - rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), - textScale: config.textScale, - centerText: true, - textBackground: config.background - })} + rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), + textScale: config.textScale, + centerText: true, + textBackground: config.background + })} ` } return ` ${i === 0 ? this.header.constructXml(infos[i][0], infos[i][1], this.side) : - this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { - textScale: config.textScale, - textBackground: config.background, - horizontalPadding: config.mapPadding - })} + this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { + textScale: config.textScale, + textBackground: config.background, + horizontalPadding: config.mapPadding + })} ` } const arr: any[] = new Array(this.rows).fill(cell) @@ -116,9 +115,9 @@ export default class MapWidget extends StaticComponent { private getTag(map: tm.Map, TMXMap?: tm.TMXMap): string { for (const e of config.customTags) { - if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || - e?.names?.some(a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || - (TMXMap !== undefined ? (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { + if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || e?.names?.some( + a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || (TMXMap !== undefined ? + (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { return e.icon } } diff --git a/plugins/ui/static_components/race/MapWidget.config.js b/plugins/ui/static_components/race/MapWidget.config.js index 610ff3f1..af168a0b 100644 --- a/plugins/ui/static_components/race/MapWidget.config.js +++ b/plugins/ui/static_components/race/MapWidget.config.js @@ -6,21 +6,28 @@ export default { height: 9.13, margin: cfg.margin, width: cfg.width, - title: "Ongoing", + title: 'Ongoing', background: cfg.background, mapPadding: 0.3, - customTags: [ - /* Custom tags allow you to display special tag icon for certain authors or maps - If tmx name or login matches one of the authors or - map name matches one of the names regexes special tag will be displayed - map name is tested both stripped and unstripped from special characters - Tags are read from top to bottom - */ - { authors: ['Fwo Niro'], icon: icons.tagRed }, - { authors: ['tony89300'], icon: icons.tagPinkCyan }, - { names: [/{Kackiest Kacky}*/], icon: icons.tagKacky }, - { names: [/\$w\$i\$f70ARC_*/], icon: icons.tagArcade } - ], + customTags: [/* Custom tags allow you to display special tag icon for certain authors or maps + If tmx name or login matches one of the authors or + map name matches one of the names regexes special tag will be displayed + map name is tested both stripped and unstripped from special characters + Tags are read from top to bottom + */ + { + authors: ['Fwo Niro'], + icon: icons.tagRed + }, { + authors: ['tony89300'], + icon: icons.tagPinkCyan + }, { + names: [/{Kackiest Kacky}*/], + icon: icons.tagKacky + }, { + names: [/\$w\$i\$f70ARC_*/], + icon: icons.tagArcade + }], icons: { header: icons.ongoingMap, tags: { @@ -35,10 +42,10 @@ export default { authorScore: icons.clockAuthor }, textScale: 1, - noDateText: "N/A", + noDateText: 'N/A', titles: { - lastTrack: "Last Map", - currTrack: "Ongoing", - nextTrack: "Next Map" + lastTrack: 'Last Map', + currTrack: 'Ongoing', + nextTrack: 'Next Map' } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/PreviousAndBest.component.ts b/plugins/ui/static_components/race/PreviousAndBest.component.ts index f9aa5ada..2fd93719 100644 --- a/plugins/ui/static_components/race/PreviousAndBest.component.ts +++ b/plugins/ui/static_components/race/PreviousAndBest.component.ts @@ -31,7 +31,10 @@ export default class PreviousAndBest extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml(): void { diff --git a/plugins/ui/static_components/race/PreviousAndBest.config.js b/plugins/ui/static_components/race/PreviousAndBest.config.js index 004c522a..b256d373 100644 --- a/plugins/ui/static_components/race/PreviousAndBest.config.js +++ b/plugins/ui/static_components/race/PreviousAndBest.config.js @@ -1,8 +1,8 @@ import cfg from '../../config/RaceUi.js' export default { - height: 4.95, - width: cfg.width, - margin: cfg.margin, - background: cfg.background -} \ No newline at end of file + height: 4.95, + width: cfg.width, + margin: cfg.margin, + background: cfg.background +} diff --git a/plugins/ui/static_components/race/RankWidget.component.ts b/plugins/ui/static_components/race/RankWidget.component.ts index 9f9e1779..6283ce41 100644 --- a/plugins/ui/static_components/race/RankWidget.component.ts +++ b/plugins/ui/static_components/race/RankWidget.component.ts @@ -32,7 +32,10 @@ export default class RankWidget extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml(): void { diff --git a/plugins/ui/static_components/race/RankWidget.config.js b/plugins/ui/static_components/race/RankWidget.config.js index 07d38a85..5fd3439e 100644 --- a/plugins/ui/static_components/race/RankWidget.config.js +++ b/plugins/ui/static_components/race/RankWidget.config.js @@ -1,7 +1,7 @@ import cfg from '../../config/RaceUi.js' export default { - height: 3.8, - width: cfg.width, - background: cfg.background -} \ No newline at end of file + height: 3.8, + width: cfg.width, + background: cfg.background +} diff --git a/plugins/ui/static_components/race/RoundScore.component.ts b/plugins/ui/static_components/race/RoundScore.component.ts index b8cacb78..7ff65eb1 100644 --- a/plugins/ui/static_components/race/RoundScore.component.ts +++ b/plugins/ui/static_components/race/RoundScore.component.ts @@ -3,7 +3,7 @@ * @since 1.2 */ -import { RecordList, componentIds, StaticHeader, StaticComponent, type RLImage } from '../../UI.js' +import { componentIds, RecordList, type RLImage, StaticComponent, StaticHeader } from '../../UI.js' import config from './RoundScore.config.js' @@ -29,7 +29,8 @@ export default class RoundScore extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries } return config.roundsEntries @@ -42,7 +43,8 @@ export default class RoundScore extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount } return config.roundsTopCount @@ -70,13 +72,16 @@ export default class RoundScore extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.roundRecords - .map(a => ({ - name: a.nickname, time: a.time, checkpoints: a.checkpoints, - login: a.login, points: a.roundPoints, - colour: a.team !== undefined ? config.markerBackgrounds[a.team] : undefined, - markerImage: this.getCupImage(a) - })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + checkpoints: a.checkpoints, + login: a.login, + points: a.roundPoints, + colour: a.team !== undefined ? config.markerBackgrounds[a.team] : undefined, + markerImage: this.getCupImage(a) + })) + .slice(0, tm.records.maxLocalsAmount))} `, @@ -93,8 +98,9 @@ export default class RoundScore extends StaticComponent { const height = this.getHeight() const entries = this.getEntries() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), 250, config.displayNoRecordEntry) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), 250, + config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/RoundScore.config.js b/plugins/ui/static_components/race/RoundScore.config.js index 97effd0c..8a091a53 100644 --- a/plugins/ui/static_components/race/RoundScore.config.js +++ b/plugins/ui/static_components/race/RoundScore.config.js @@ -7,7 +7,7 @@ export default { cupEntries: 12, entryHeight: 2.15, width: cfg.width, - title: "Round Score", + title: 'Round Score', icon: icons.chartLocal, margin: cfg.margin, roundsTopCount: 5, @@ -22,4 +22,4 @@ export default { blue: tm.utils.palette.purple + '6' }, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/RoundsPointsRanking.component.ts b/plugins/ui/static_components/race/RoundsPointsRanking.component.ts index 88202d8c..ae7dc2cb 100644 --- a/plugins/ui/static_components/race/RoundsPointsRanking.component.ts +++ b/plugins/ui/static_components/race/RoundsPointsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent, type RLImage } from '../../UI.js' +import { componentIds, RecordList, type RLImage, StaticComponent, StaticHeader } from '../../UI.js' import config from './RoundsPointsRanking.config.js' export default class RoundsPointsRanking extends StaticComponent { @@ -17,15 +17,15 @@ export default class RoundsPointsRanking extends StaticComponent { this.getRecordList() this.renderOnEvent('PlayerJoin', (info: tm.JoinInfo) => { if (tm.rounds.pointsRanking.filter(a => a.roundsPoints !== 0) - .some(a => a.login === info.login)) { return this.display() } + .some(a => a.login === info.login)) { return this.display() } }) this.renderOnEvent('PlayerLeave', (info: tm.LeaveInfo) => { if (tm.rounds.pointsRanking.filter(a => a.roundsPoints !== 0) - .some(a => a.login === info.login)) { return this.display() } + .some(a => a.login === info.login)) { return this.display() } }) this.renderOnEvent('PlayerDataUpdated', (info) => { if (info.some(a => tm.rounds.pointsRanking - .filter(b => b.roundsPoints !== 0).some(b => b.login === a.login))) { return this.display() } + .filter(b => b.roundsPoints !== 0).some(b => b.login === a.login))) { return this.display() } }) this.renderOnEvent('PlayerFinish', () => this.display()) this.onPanelHide((player) => { @@ -65,9 +65,13 @@ export default class RoundsPointsRanking extends StaticComponent { let height = this.getHeight() let entries = this.getEntries() this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, height - (this.header.options.height + config.margin), - entries, this.side, this.getTopCount(), tm.records.maxLocalsAmount, config.displayNoRecordEntry, - { dontParseTime: true, columnProportions: config.columnProportions, noRecordEntryText: config.noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + height - (this.header.options.height + config.margin), entries, this.side, this.getTopCount(), + tm.records.maxLocalsAmount, config.displayNoRecordEntry, { + dontParseTime: true, + columnProportions: config.columnProportions, + noRecordEntryText: config.noRecordEntryText + }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -94,12 +98,15 @@ export default class RoundsPointsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.rounds.pointsRanking - .filter(a => a.roundsPoints !== 0) - .map(a => ({ - name: a.nickname, time: a.roundsPoints, login: a.login, - checkpoints: a.roundTimes.map(a => a === -1 ? undefined : a), image: this.getCupImage(a) - })) - .slice(0, tm.records.maxLocalsAmount))} + .filter(a => a.roundsPoints !== 0) + .map(a => ({ + name: a.nickname, + time: a.roundsPoints, + login: a.login, + checkpoints: a.roundTimes.map(a => a === -1 ? undefined : a), + image: this.getCupImage(a) + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/race/RoundsPointsRanking.config.js b/plugins/ui/static_components/race/RoundsPointsRanking.config.js index 9411f404..90f6aeb0 100644 --- a/plugins/ui/static_components/race/RoundsPointsRanking.config.js +++ b/plugins/ui/static_components/race/RoundsPointsRanking.config.js @@ -6,7 +6,7 @@ export default { cupEntries: 8, entryHeight: 2.15, width: cfg.width, - title: "Rounds Points", + title: 'Rounds Points', icon: icons.chartLocal, margin: cfg.margin, topCount: 5, @@ -20,4 +20,4 @@ export default { cupImageVerticalPadding: 0.1, cupImageHorizontalPadding: 0.3, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/TMXRanking.component.ts b/plugins/ui/static_components/race/TMXRanking.component.ts index 3d2892ae..c3915592 100644 --- a/plugins/ui/static_components/race/TMXRanking.component.ts +++ b/plugins/ui/static_components/race/TMXRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { tmx } from '../../../tmx/Tmx.js' import config from './TMXRanking.config.js' @@ -34,8 +34,9 @@ export default class TMXRanking extends StaticComponent { createRecordList(): void { this.recordList?.destroy() - this.recordList = new RecordList('race', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), config.entries, - this.side, config.topCount, config.entries, config.displayNoRecordEntry, { getColoursFromPb: true }) + this.recordList = new RecordList('race', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, config.topCount, + config.entries, config.displayNoRecordEntry, { getColoursFromPb: true }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -64,13 +65,20 @@ export default class TMXRanking extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - let replays: { name: string, time: number, date: Date, login?: string }[] = [] + let replays: { + name: string, + time: number, + date: Date, + login?: string + }[] = [] const tmxInfo: tm.TMXMap | null = tmx.current const isStunts = tm.getGameMode() === 'Stunts' if (tmxInfo !== null) { replays = tmxInfo.validReplays.map(a => ({ name: a.name, - time: isStunts ? (a.score ?? 0) : a.time, date: a.recordDate, url: a.url + time: isStunts ? (a.score ?? 0) : a.time, + date: a.recordDate, + url: a.url })) } return { diff --git a/plugins/ui/static_components/race/TMXRanking.config.js b/plugins/ui/static_components/race/TMXRanking.config.js index 56407369..4f2ebe17 100644 --- a/plugins/ui/static_components/race/TMXRanking.config.js +++ b/plugins/ui/static_components/race/TMXRanking.config.js @@ -6,15 +6,10 @@ export default { entryHeight: 2.15, width: cfg.width, margin: cfg.margin, - title: "TMX Records", + title: 'TMX Records', icon: icons.maniaExchange, - columnProportions: [ - 1, - 1, - 2.8, - 4 - ], + columnProportions: [1, 1, 2.8, 4], topCount: 3, displayNoRecordEntry: false, hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/TeamScore.component.ts b/plugins/ui/static_components/race/TeamScore.component.ts index f7c80d4f..632073be 100644 --- a/plugins/ui/static_components/race/TeamScore.component.ts +++ b/plugins/ui/static_components/race/TeamScore.component.ts @@ -2,7 +2,7 @@ * @author lythx * @since 1.2 */ -import { componentIds, StaticHeader, centeredText, StaticComponent, type GridCellFunction, Grid } from '../../UI.js' +import { centeredText, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import config from './TeamScore.config.js' export default class TeamScore extends StaticComponent { @@ -42,7 +42,10 @@ export default class TeamScore extends StaticComponent { if (config.hidePanel && this.hasPanelsHidden(login)) { return this.hideToPlayer(login) } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml() { diff --git a/plugins/ui/static_components/race/TeamScore.config.js b/plugins/ui/static_components/race/TeamScore.config.js index 93a839a3..a96afa6c 100644 --- a/plugins/ui/static_components/race/TeamScore.config.js +++ b/plugins/ui/static_components/race/TeamScore.config.js @@ -5,16 +5,14 @@ export default { height: 5.5, width: cfg.width, margin: cfg.margin, - title: "Team Score", + title: 'Team Score', icon: icons.teamsMode, - entries: 3, - columnProportions: [ - 1, - 1, - 2.8, - 4 - ], - text: { specialFont: true, textScale: 0.5 }, + entries: 3, + columnProportions: [1, 1, 2.8, 4], + text: { + specialFont: true, + textScale: 0.5 + }, colours: { left: tm.utils.palette.purple + '8', middle: cfg.background, @@ -24,4 +22,4 @@ export default { displayNoRecordEntry: false, noMaxScore: '-', hidePanel: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/race/TimerWidget.component.ts b/plugins/ui/static_components/race/TimerWidget.component.ts index f0da3847..ade79fd1 100644 --- a/plugins/ui/static_components/race/TimerWidget.component.ts +++ b/plugins/ui/static_components/race/TimerWidget.component.ts @@ -3,7 +3,7 @@ * @since 0.1 */ -import { componentIds, StaticHeader, StaticComponent, centeredText, addManialinkListener } from '../../UI.js' +import { addManialinkListener, centeredText, componentIds, StaticComponent, StaticHeader } from '../../UI.js' import config from './TimerWidget.config.js' export default class TimerWidget extends StaticComponent { @@ -140,9 +140,15 @@ export default class TimerWidget extends StaticComponent { // } privilege ??= tm.players.get(login)?.privilege ?? 0 if (this.isOnRestart || !tm.timer.isDynamic || privilege < config.timerActionsPrivilege) { - return { xml: this.noButtonXml, login } + return { + xml: this.noButtonXml, + login + } } else { - return { xml: this.xmlWithButtons, login } + return { + xml: this.xmlWithButtons, + login + } } } @@ -154,8 +160,7 @@ export default class TimerWidget extends StaticComponent { private constructXml(isDynamic: boolean): string { const headerHeight: number = this.header.options.height - let headerXml = isDynamic ? this.getButtonsXml() : - this.header.constructXml(config.title, config.icon, this.side) + let headerXml = isDynamic ? this.getButtonsXml() : this.header.constructXml(config.title, config.icon, this.side) let timeXml = '' let bottomH = config.height - (headerHeight + config.margin) const isStunts = tm.getGameMode() === 'Stunts' @@ -164,8 +169,11 @@ export default class TimerWidget extends StaticComponent { } if (tm.timer.isDynamic && !this.isOnRestart) { if (tm.timer.isPaused) { - timeXml = centeredText(config.pausedText, config.width, bottomH, - { specialFont: true, yOffset: -0.3, xOffset: 0.2 }) + timeXml = centeredText(config.pausedText, config.width, bottomH, { + specialFont: true, + yOffset: -0.3, + xOffset: 0.2 + }) } else { const time = Math.floor(tm.timer.remainingRaceTime / 1000) let timeColour = config.timeColours[0] @@ -179,8 +187,10 @@ export default class TimerWidget extends StaticComponent { const minutes = (~~(time / 60) % 60).toString().padStart(2, '0') const seconds = (time % 60).toString().padStart(2, '0') const timeStr = hoursAmount < 100 ? `${hours}${minutes}:${seconds}` : `${hoursAmount} hours` - timeXml = centeredText('$' + timeColour + timeStr, config.width, bottomH, - { specialFont: true, yOffset: config.textYOffset }) + timeXml = centeredText('$' + timeColour + timeStr, config.width, bottomH, { + specialFont: true, + yOffset: config.textYOffset + }) } } let stuntsMargin = (isStunts && isDynamic) ? config.stuntsDynamicMarginTop : 0 @@ -237,8 +247,8 @@ export default class TimerWidget extends StaticComponent { buttonXml += ` ` } - return this.header.constructXml(config.title, config.icon, - this.side, { rectangleWidth: headerRectWidth }) + buttonXml + return this.header.constructXml(config.title, config.icon, this.side, + { rectangleWidth: headerRectWidth }) + buttonXml } } diff --git a/plugins/ui/static_components/race/TimerWidget.config.js b/plugins/ui/static_components/race/TimerWidget.config.js index f5cba7ab..e0de51e3 100644 --- a/plugins/ui/static_components/race/TimerWidget.config.js +++ b/plugins/ui/static_components/race/TimerWidget.config.js @@ -1,5 +1,6 @@ import icons from '../../config/Icons.js' import cfg from '../../config/RaceUi.js' + const p = tm.utils.palette export default { @@ -7,7 +8,7 @@ export default { stuntsDynamicMarginTop: 5.5, stuntsHeight: 6.45 + 5.5, width: cfg.width, - title: "Timer", + title: 'Timer', icon: icons.clock, margin: cfg.margin, background: cfg.background, @@ -21,7 +22,7 @@ export default { pause: icons.pause, resume: icons.play, add: icons.plus, - subtract: icons.minus, + subtract: icons.minus }, iconsHover: { pause: icons.pauseGreen, @@ -36,5 +37,5 @@ export default { timeSubtractedOnClick: 5 * 60 * 1000, //msec pause: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has ${p.highlight}paused ${p.admin}the timer.`, resume: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has ${p.highlight}resumed ${p.admin}the timer.`, - set: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has set the remaining time to ${p.highlight}#{time}${p.admin}.`, -} \ No newline at end of file + set: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has set the remaining time to ${p.highlight}#{time}${p.admin}.` +} diff --git a/plugins/ui/static_components/race/UltiRanking.component.ts b/plugins/ui/static_components/race/UltiRanking.component.ts index 2cae3441..d63f2c6c 100644 --- a/plugins/ui/static_components/race/UltiRanking.component.ts +++ b/plugins/ui/static_components/race/UltiRanking.component.ts @@ -3,7 +3,7 @@ * @since 1.5.0 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { ultimania } from '../../../ultimania/Ultimania.js' import config from './UltiRanking.config.js' @@ -12,6 +12,7 @@ export default class UltiRanking extends StaticComponent { private readonly header: StaticHeader private recordList!: RecordList + constructor() { super(componentIds.ultiRanking) this.header = new StaticHeader('race') @@ -51,7 +52,11 @@ export default class UltiRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.ultiRecords })} - ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, ultimania.records.map(a => ({ ...a, name: a.nickname, time: a.score })))} + ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, ultimania.records.map(a => ({ + ...a, + name: a.nickname, + time: a.score + })))} `, @@ -62,11 +67,14 @@ export default class UltiRanking extends StaticComponent { getEntries(): number { if (tm.getGameMode() === 'Teams') { return config.teamsEntries - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsEntries - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupEntries - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsEntries } return config.entries @@ -79,11 +87,14 @@ export default class UltiRanking extends StaticComponent { getTopCount(): number { if (tm.getGameMode() === 'Teams') { return config.teamsTopCount - } if (tm.getGameMode() === 'Rounds') { + } + if (tm.getGameMode() === 'Rounds') { return config.roundsTopCount - } if (tm.getGameMode() === 'Cup') { + } + if (tm.getGameMode() === 'Cup') { return config.cupTopCount - } if (tm.getGameMode() === 'Laps') { + } + if (tm.getGameMode() === 'Laps') { return config.lapsTopCount } return config.topCount @@ -96,9 +107,9 @@ export default class UltiRanking extends StaticComponent { private getRecordList(): void { this.recordList?.destroy?.() - this.recordList = new RecordList('race', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - this.getEntries(), this.side, this.getTopCount(), config.maxRecordCount, config.displayNoRecordEntry, - { noRecordEntryText: config.noRecordEntryText }) + this.recordList = new RecordList('race', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), this.getEntries(), this.side, this.getTopCount(), + config.maxRecordCount, config.displayNoRecordEntry, { noRecordEntryText: config.noRecordEntryText }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) diff --git a/plugins/ui/static_components/race/UltiRanking.config.js b/plugins/ui/static_components/race/UltiRanking.config.js index e33d05d6..9eca4c0e 100644 --- a/plugins/ui/static_components/race/UltiRanking.config.js +++ b/plugins/ui/static_components/race/UltiRanking.config.js @@ -9,7 +9,7 @@ export default { lapsEntries: 7, entryHeight: 2.15, width: cfg.width, - title: "Ultimania Records", + title: 'Ultimania Records', icon: icons.chartDedi, margin: cfg.margin, topCount: 5, diff --git a/plugins/ui/static_components/race/buttons/ButtonData.ts b/plugins/ui/static_components/race/buttons/ButtonData.ts index 652fb1d8..dc01ebe0 100644 --- a/plugins/ui/static_components/race/buttons/ButtonData.ts +++ b/plugins/ui/static_components/race/buttons/ButtonData.ts @@ -9,7 +9,7 @@ export interface ButtonData { actionId?: number, link?: string, /** - * Enables per-player rendering. Buttons using per-player rendering should override the + * Enables per-player rendering. Buttons using per-player rendering should override the * `UiButton.renderForPlayer()` function (see `MedalButton.ts`) */ perPlayer?: boolean diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts index d3eb488f..82004bfe 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.component.ts @@ -1,49 +1,36 @@ -import {componentIds, Grid, staticButton, type GridCellFunction, StaticComponent} from '../../../UI.js' -import {VisitCounter} from './VisitCounter.js' -import {TimeButton} from './TimeButton.js' -import {PlayerCounter} from './PlayerCounter.js' -import {VersionButton} from './VersionButton.js' -import {MapsButton} from './MapsButton.js' -import {PayReplay} from './PayReplay.js' -import {PaySkip} from './PaySkip.js' -import {VoteReplay} from './VoteReplay.js' -import {VoteSkip} from './VoteSkip.js' -import {StatsButton} from './StatsButton.js' -import {CommandListButton} from './CommandListButton.js' -import {SectorsButton} from './SectorsButton.js' -import {UiButton} from './UiButton.js' -import {MedalButton} from "./MedalButton.js"; -import {FavouriteButton} from "./FavouriteButton.js"; +import { componentIds, Grid, type GridCellFunction, staticButton, StaticComponent } from '../../../UI.js' +import { VisitCounter } from './VisitCounter.js' +import { TimeButton } from './TimeButton.js' +import { PlayerCounter } from './PlayerCounter.js' +import { VersionButton } from './VersionButton.js' +import { MapsButton } from './MapsButton.js' +import { PayReplay } from './PayReplay.js' +import { PaySkip } from './PaySkip.js' +import { VoteReplay } from './VoteReplay.js' +import { VoteSkip } from './VoteSkip.js' +import { StatsButton } from './StatsButton.js' +import { CommandListButton } from './CommandListButton.js' +import { SectorsButton } from './SectorsButton.js' +import { UiButton } from './UiButton.js' +import { MedalButton } from "./MedalButton.js"; +import { FavouriteButton } from "./FavouriteButton.js"; import config from './ButtonsWidget.config.js' export default class ButtonsWidget extends StaticComponent { - private readonly width: number = config.width - private readonly height: number = config.height - private readonly grid: Grid - private readonly buttons: UiButton[] = [] - private xml: string = '' + private readonly width: number = config.width + private readonly height: number = config.height + private readonly grid: Grid + private readonly buttons: UiButton[] = [] + private xml: string = '' constructor() { super(componentIds.buttons) - this.grid = new Grid(this.width + config.margin, this.height + config.margin, - new Array(config.columns).fill(1), new Array(config.rows).fill(1)) - const allButtons = [ - new VisitCounter(), - new TimeButton(), - new PlayerCounter(), - new VersionButton(), - new MapsButton(), - new StatsButton(), - new CommandListButton(), - new SectorsButton(), - new FavouriteButton(), - new PayReplay(this.id), - new PaySkip(this.id), - new VoteReplay(this.id), - new VoteSkip(this.id), - new MedalButton() - ] + this.grid = new Grid(this.width + config.margin, this.height + config.margin, new Array(config.columns).fill(1), + new Array(config.rows).fill(1)) + const allButtons = [new VisitCounter(), new TimeButton(), new PlayerCounter(), new VersionButton(), + new MapsButton(), new StatsButton(), new CommandListButton(), new SectorsButton(), new FavouriteButton(), + new PayReplay(this.id), new PaySkip(this.id), new VoteReplay(this.id), new VoteSkip(this.id), new MedalButton()] for (const e of config.order) { const b = allButtons.find(a => a.constructor.name === e) if (b === undefined) { @@ -57,22 +44,23 @@ export default class ButtonsWidget extends StaticComponent { UiButton.onUpdate(() => { let calls = [] if (this.isDisplayed) { - for (const player of tm.players.list) + for (const player of tm.players.list) { calls.push({ method: 'SendDisplayManialinkPageToLogin', - params: [{ string: player.login }, { string: this.constructXml(player.login) }, { int: 0 }, { boolean: false }] + params: [{ string: player.login }, { string: this.constructXml(player.login) }, { int: 0 }, + { boolean: false }] }) + } if (calls.length > 0) { tm.client.call('system.multicall', calls) } } }) - this.onPanelHide((player) => { // todo: this does not work properly due to button update - this.sendMultipleManialinks(this.displayToPlayer(player.login)) - }) - } - else { + this.onPanelHide((player) => { // todo: this does not work properly due to button update + this.sendMultipleManialinks(this.displayToPlayer(player.login)) + }) + } else { UiButton.onUpdate(() => { const xml = this.display() if (xml !== undefined) { @@ -81,7 +69,6 @@ export default class ButtonsWidget extends StaticComponent { }) } - } getHeight(): number { @@ -93,15 +80,16 @@ export default class ButtonsWidget extends StaticComponent { return this.constructXml() } - - displayToPlayer(login: string) { - if (!this.isDisplayed) { return } - if (config.hidePanel && this.hasPanelsHidden(login)) { - return this.hideToPlayer(login) - } - return { xml: this.xml, login } + displayToPlayer(login: string) { + if (!this.isDisplayed) { return } + if (config.hidePanel && this.hasPanelsHidden(login)) { + return this.hideToPlayer(login) } - + return { + xml: this.xml, + login + } + } private constructXml(login?: string): string { const arr: GridCellFunction[] = [] @@ -110,13 +98,14 @@ export default class ButtonsWidget extends StaticComponent { if (e.buttonData.perPlayer) { arr.push((i, j, w, h) => e.renderForPlayer(login ?? "", i, j, w, h)) } else { - arr.push((i, j, w, h) => - staticButton(data.icon, data.text1, data.text2, w - config.margin, - h - config.margin, { - iconWidth: data.iconWidth, iconHeight: data.iconHeight, topPadding: data.padding, - equalTexts: data.equalTexts === true ? true : undefined, - actionId: data.actionId, link: data.link - })) + arr.push((i, j, w, h) => staticButton(data.icon, data.text1, data.text2, w - config.margin, h - config.margin, { + iconWidth: data.iconWidth, + iconHeight: data.iconHeight, + topPadding: data.padding, + equalTexts: data.equalTexts === true ? true : undefined, + actionId: data.actionId, + link: data.link + })) } } return ` @@ -126,5 +115,4 @@ export default class ButtonsWidget extends StaticComponent { ` } - } diff --git a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js index 151a2079..bc8da6ed 100644 --- a/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js +++ b/plugins/ui/static_components/race/buttons/ButtonsWidget.config.js @@ -9,91 +9,129 @@ export default { margin: cfg.margin, rows: 3, columns: 4, - order: [ - 'VisitCounter', - 'PlayerCounter', - 'VersionButton', - 'TimeButton', - 'MapsButton', - 'StatsButton', - 'SectorsButton', - 'CommandListButton', - 'VoteSkip', - 'VoteReplay', - 'PaySkip', - 'PayReplay' - ], + order: ['VisitCounter', 'PlayerCounter', 'VersionButton', 'TimeButton', 'MapsButton', 'StatsButton', 'SectorsButton', + 'CommandListButton', 'VoteSkip', 'VoteReplay', 'PaySkip', 'PayReplay'], visitCounter: { - texts: { 0: "#{count}", 1: "$AF4VISITOR#{plural}", equal: false }, + texts: { + 0: '#{count}', + 1: '$AF4VISITOR#{plural}', + equal: false + }, icon: icons.people, width: 2, height: 2, padding: 0.3 }, playerCounter: { - texts: { 0: "#{count} $AF4SPEC#{plural}", 1: "#{count} $AF4PLAYER#{plural}", equal: true }, + texts: { + 0: '#{count} $AF4SPEC#{plural}', + 1: '#{count} $AF4PLAYER#{plural}', + equal: true + }, icon: icons.peopleGaming, width: 2, height: 2, padding: 0.3 }, versionButton: { - texts: { 0: "#{version}", 1: "$AF4VERSION", equal: false }, + texts: { + 0: '#{version}', + 1: '$AF4VERSION', + equal: false + }, icon: icons.codeBranch, width: 1.8, height: 1.8, padding: 0.3 }, favouriteButton: { - texts: {0: "$AF4ADD TO", 1: "FAVOURITES", equal: false}, - icon: icons.karmaGlobal, - width: 1.8, - height: 1.8, - padding: 0.3 + texts: { + 0: '$AF4ADD TO', + 1: 'FAVOURITES', + equal: false + }, + icon: icons.karmaGlobal, + width: 1.8, + height: 1.8, + padding: 0.3 }, timeButton: { - texts: { 0: "#{time}", 1: "$AF4TIME", equal: false }, + texts: { + 0: '#{time}', + 1: '$AF4TIME', + equal: false + }, icon: icons.clock, width: 1.8, height: 1.8, padding: 0.2 }, mapsButton: { - texts: { 0: "#{count}", 1: "$AF4MAPS", equal: false }, + texts: { + 0: '#{count}', + 1: '$AF4MAPS', + equal: false + }, icon: icons.numberedList, width: 1.8, height: 1.8, padding: 0.2 }, statsButton: { - texts: { 0: "PLAYER", 1: "$AF4STATS", equal: true }, + texts: { + 0: 'PLAYER', + 1: '$AF4STATS', + equal: true + }, icon: icons.stats, width: 1.9, height: 1.9, padding: 0.2 }, sectorsButton: { - texts: { 0: "SECTOR", 1: "$AF4RECORDS", equal: true }, + texts: { + 0: 'SECTOR', + 1: '$AF4RECORDS', + equal: true + }, icon: icons.clockList, width: 1.8, height: 1.8, padding: 0.2 }, commandListButton: { - texts: { 0: "COMMAND", 1: "$AF4LIST", equal: true }, + texts: { + 0: 'COMMAND', + 1: '$AF4LIST', + equal: true + }, icon: icons.infoCircle, width: 1.8, height: 1.8, padding: 0.2 }, voteSkip: { - texts: [ - { 0: " VOTE ", 1: "TO $E22SKIP", equal: false }, - { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, - { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, - ], + texts: [{ + 0: ' VOTE ', + 1: 'TO $E22SKIP', + equal: false + }, { + 0: 'SKIP IN', + 1: '$E22#{seconds} $FFFSECS', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO RES', + equal: true + }, { + 0: 'MAP IS', + 1: '$E22SKIPPED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO EXT', + equal: true + }], header: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.red}SKIP${tm.utils.palette.highlight} the ongoing map`, icon: icons.voteSkip, voteIcon: icons.voteSkip, @@ -114,14 +152,31 @@ export default { } }, voteReplay: { - texts: [ - { 0: " VOTE ", 1: "TO $AF4RES", equal: false }, - { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, - { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, - { 0: "VOTE TO", 1: "$AF4EXTEND", equal: true }, - { 0: "LIMIT", 1: "$AF4REACHED", equal: true } - ], + texts: [{ + 0: ' VOTE ', + 1: 'TO $AF4RES', + equal: false + }, { + 0: '$AF4RES$z$s LIMIT', + 1: 'REACHED', + equal: true + }, { + 0: 'MAP IS', + 1: '$AF4REPLAYED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO SKIP', + equal: true + }, { + 0: 'VOTE TO', + 1: '$AF4EXTEND', + equal: true + }, { + 0: 'LIMIT', + 1: '$AF4REACHED', + equal: true + }], timeExtension: 1000 * 60 * 5, // 5 minutes extensionsLimit: 2, // 0 to disable limit resHeader: `${tm.utils.palette.highlight}Vote to $${tm.utils.palette.green}REPLAY${tm.utils.palette.highlight} the ongoing map`, @@ -150,13 +205,27 @@ export default { } }, paySkip: { - texts: [ - { 0: "PAY $E22#{cost}", 1: "TO $E22SKIP", equal: false }, - { 0: "SKIP IN", 1: "$E22#{seconds} $FFFSECS", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO RES", equal: true }, - { 0: "MAP IS", 1: "$E22SKIPPED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO EXT", equal: true }, - ], + texts: [{ + 0: 'PAY $E22#{cost}', + 1: 'TO $E22SKIP', + equal: false + }, { + 0: 'SKIP IN', + 1: '$E22#{seconds} $FFFSECS', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO RES', + equal: true + }, { + 0: 'MAP IS', + 1: '$E22SKIPPED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO EXT', + equal: true + }], billMessage: 'Pay to skip the ongoing map', icon: icons.paySkip, width: 1.8, @@ -167,27 +236,38 @@ export default { actionId: 3 }, payReplay: { - texts: [ - { 0: "PAY $AF4#{cost}", 1: "TO $AF4RES", equal: false }, - { 0: "$AF4RES$z$s LIMIT", 1: "REACHED", equal: true }, - { 0: "MAP IS", 1: "$AF4REPLAYED", equal: true }, - { 0: "$E22DISABLED", 1: "DUE TO SKIP", equal: true }, - { 0: "PAY $AF4#{cost}", 1: "TO $AF4EXT", equal: false }, - { 0: "$AF4EXT$z$s LIMIT", 1: "REACHED", equal: true } - ], + texts: [{ + 0: 'PAY $AF4#{cost}', + 1: 'TO $AF4RES', + equal: false + }, { + 0: '$AF4RES$z$s LIMIT', + 1: 'REACHED', + equal: true + }, { + 0: 'MAP IS', + 1: '$AF4REPLAYED', + equal: true + }, { + 0: '$E22DISABLED', + 1: 'DUE TO SKIP', + equal: true + }, { + 0: 'PAY $AF4#{cost}', + 1: 'TO $AF4EXT', + equal: false + }, { + 0: '$AF4EXT$z$s LIMIT', + 1: 'REACHED', + equal: true + }], timeExtension: 1000 * 60 * 5, // 5 minutes billMessage: 'Pay to replay the ongoing map or extend the timer', icon: icons.payReplay, width: 1.8, height: 1.8, padding: 0.2, - costs: [ - 100, - 200, - 300, - 400, - 500 - ], + costs: [100, 200, 300, 400, 500], actionId: 4 }, medalButton: { @@ -195,11 +275,11 @@ export default { height: 3, padding: 0.3, textYOffset: 3.6, - textNoMedal: "NO MEDAL", + textNoMedal: 'NO MEDAL', textBronzeMedal: `$da5BRONZE`, - textSilverMedal: "SILVER", + textSilverMedal: 'SILVER', textGoldMedal: `$${palette.yellow}GOLD`, textAuthorMedal: `$${palette.green}AUTHOR` }, - hidePanel: false, -} \ No newline at end of file + hidePanel: false +} diff --git a/plugins/ui/static_components/race/buttons/MedalButton.ts b/plugins/ui/static_components/race/buttons/MedalButton.ts index aeb2684e..6db3eb1e 100644 --- a/plugins/ui/static_components/race/buttons/MedalButton.ts +++ b/plugins/ui/static_components/race/buttons/MedalButton.ts @@ -38,27 +38,46 @@ export class MedalButton extends UiButton { const height = h - config.margin const topPadding = this.buttonData.padding + type MedalInfo = { + limit: number, + text: string, + icon: string + } - type MedalInfo = { limit: number, text: string, icon: string } - - const thresholds: MedalInfo[] = [ - { limit: map.authorTime ?? Infinity, text: cfg.textAuthorMedal, icon: "MedalNadeo" }, - { limit: map.goldTime ?? Infinity, text: cfg.textGoldMedal, icon: "MedalGold" }, - { limit: map.silverTime ?? Infinity, text: cfg.textSilverMedal, icon: "MedalSilver" }, - { limit: map.bronzeTime ?? Infinity, text: cfg.textBronzeMedal, icon: "MedalBronze" }, - ] + const thresholds: MedalInfo[] = [{ + limit: map.authorTime ?? Infinity, + text: cfg.textAuthorMedal, + icon: "MedalNadeo" + }, { + limit: map.goldTime ?? Infinity, + text: cfg.textGoldMedal, + icon: "MedalGold" + }, { + limit: map.silverTime ?? Infinity, + text: cfg.textSilverMedal, + icon: "MedalSilver" + }, { + limit: map.bronzeTime ?? Infinity, + text: cfg.textBronzeMedal, + icon: "MedalBronze" + }] let medalText = cfg.textNoMedal let iconSub = "MedalSlot" if (record > 0) { const hit = thresholds.find(t => record <= t.limit) - if (hit) { medalText = hit.text; iconSub = hit.icon } + if (hit) { + medalText = hit.text; + iconSub = hit.icon + } } - const t1: string = horizontallyCenteredText(medalText, width, height, - { yOffset: cfg.textYOffset, textScale: buttonConfig.textScale, padding: buttonConfig.padding }) - + const t1: string = horizontallyCenteredText(medalText, width, height, { + yOffset: cfg.textYOffset, + textScale: buttonConfig.textScale, + padding: buttonConfig.padding + }) return ` => { - if (this.isLastMapReplay || this.isReplay - || this.isSkip || tm.getState() === 'result') { return } + if (this.isLastMapReplay || this.isReplay || this.isSkip || tm.getState() === 'result') { return } const res: boolean | Error = await tm.utils.sendCoppers(login, cfg.cost, cfg.billMessage) if (res instanceof Error) { tm.sendMessage(msg.paymentFail, login) @@ -114,9 +113,9 @@ export class PaySkip extends UiButton { this.isReplay = true } if (this.isSkip || this.isLastMapReplay) { return } - this.buttonData.text1 = cfg.texts[2+plus][0] - this.buttonData.text2 = cfg.texts[2+plus][1] - this.buttonData.equalTexts = cfg.texts[2+plus].equal + this.buttonData.text1 = cfg.texts[2 + plus][0] + this.buttonData.text2 = cfg.texts[2 + plus][1] + this.buttonData.equalTexts = cfg.texts[2 + plus].equal this.buttonData.actionId = undefined this.emitUpdate() } diff --git a/plugins/ui/static_components/race/buttons/UiButton.ts b/plugins/ui/static_components/race/buttons/UiButton.ts index 478ec497..a6859673 100644 --- a/plugins/ui/static_components/race/buttons/UiButton.ts +++ b/plugins/ui/static_components/race/buttons/UiButton.ts @@ -42,7 +42,7 @@ export abstract class UiButton { * of static rendering based on `ButtonData` (see `MedalButton.ts`) */ public renderForPlayer(login: string, i: number, j: number, w: number, h: number): string { - return `` + return `` } } \ No newline at end of file diff --git a/plugins/ui/static_components/race/buttons/VoteReplay.ts b/plugins/ui/static_components/race/buttons/VoteReplay.ts index 11cd19b7..e1185c18 100644 --- a/plugins/ui/static_components/race/buttons/VoteReplay.ts +++ b/plugins/ui/static_components/race/buttons/VoteReplay.ts @@ -84,7 +84,10 @@ export class VoteReplay extends UiButton { } return } - const startMsg: string = tm.utils.strVar(msg.start, { action, nickname: tm.utils.strip(nickname, true) }) + const startMsg: string = tm.utils.strVar(msg.start, { + action, + nickname: tm.utils.strip(nickname, true) + }) const header = tm.timer.isDynamic ? cfg.extendHeader : cfg.resHeader const voteWindow: VoteWindow = new VoteWindow(login, cfg.goal, header, startMsg, cfg.time, cfg.voteIcon) const result = await voteWindow.startAndGetResult(tm.players.list) diff --git a/plugins/ui/static_components/race/buttons/VoteSkip.ts b/plugins/ui/static_components/race/buttons/VoteSkip.ts index c0c785ca..60306a49 100644 --- a/plugins/ui/static_components/race/buttons/VoteSkip.ts +++ b/plugins/ui/static_components/race/buttons/VoteSkip.ts @@ -50,8 +50,7 @@ export class VoteSkip extends UiButton { } private async handleClick(login: string, nickname: string): Promise { - if (this.isLastMapReplay || this.isReplay - || this.isSkip || tm.getState() === 'result') { return } + if (this.isLastMapReplay || this.isReplay || this.isSkip || tm.getState() === 'result') { return } const autopass = tm.players.count === 1 if (!autopass && tm.timer.remainingRaceTime <= cfg.minimumRemainingTime) { tm.sendMessage(msg.tooLate, login) @@ -169,9 +168,9 @@ export class VoteSkip extends UiButton { this.isReplay = true } if (this.isSkip || this.isLastMapReplay) { return } - this.buttonData.text1 = cfg.texts[2+plus][0] - this.buttonData.text2 = cfg.texts[2+plus][1] - this.buttonData.equalTexts = cfg.texts[2+plus].equal + this.buttonData.text1 = cfg.texts[2 + plus][0] + this.buttonData.text2 = cfg.texts[2 + plus][1] + this.buttonData.equalTexts = cfg.texts[2 + plus].equal this.buttonData.actionId = undefined this.emitUpdate() } diff --git a/plugins/ui/static_components/result/AdminPanelResult.component.ts b/plugins/ui/static_components/result/AdminPanelResult.component.ts index 1dbd8a0d..ff70b3a9 100644 --- a/plugins/ui/static_components/result/AdminPanelResult.component.ts +++ b/plugins/ui/static_components/result/AdminPanelResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, StaticHeader, Grid, type GridCellFunction, addManialinkListener, StaticComponent } from '../../UI.js' +import { addManialinkListener, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import config from './AdminPanelResult.config.js' import { actions } from '../../../actions/Actions.js' @@ -24,7 +24,8 @@ export default class AdminPanelResult extends StaticComponent { constructor() { super(componentIds.adminResult) this.header = new StaticHeader('result') - this.grid = new Grid(config.width + config.margin * 2, config.height - this.header.options.height, new Array(5).fill(1), [1], { margin: config.margin }) + this.grid = new Grid(config.width + config.margin * 2, config.height - this.header.options.height, + new Array(5).fill(1), [1], { margin: config.margin }) this.renderOnEvent('PrivilegeChanged', (info) => { return this.displayToPlayer(info.login) }) @@ -45,9 +46,10 @@ export default class AdminPanelResult extends StaticComponent { tm.openManialink(componentIds.jukebox, info.login) }) addManialinkListener(this.id + this.actions.shuffle, info => { - tm.sendMessage(tm.utils.strVar(config.messages.shuffle, - { title: info.title, adminName: tm.utils.strip(info.nickname) }), - config.public ? undefined : info.login) + tm.sendMessage(tm.utils.strVar(config.messages.shuffle, { + title: info.title, + adminName: tm.utils.strip(info.nickname) + }), config.public ? undefined : info.login) tm.jukebox.shuffle(info) }) } @@ -62,34 +64,51 @@ export default class AdminPanelResult extends StaticComponent { this.noShuffleXml = this.constructXml(true) const playerList = tm.players.list const shuffleLogins = playerList - .filter(a => a.privilege >= config.shufflePrivilege) - .map(a => a.login) + .filter(a => a.privilege >= config.shufflePrivilege) + .map(a => a.login) const noShuffleLogins = playerList - .filter(a => a.privilege < config.shufflePrivilege && a.privilege >= config.privilege) - .map(a => a.login) + .filter(a => a.privilege < config.shufflePrivilege && a.privilege >= config.privilege) + .map(a => a.login) const noPrivilegeLogins = playerList - .filter(a => a.privilege < config.privilege) - .map(a => a.login) - return [{ xml: this.xml, login: shuffleLogins }, - { xml: this.noShuffleXml, login: noShuffleLogins }, - { xml: ``, login: noPrivilegeLogins }] + .filter(a => a.privilege < config.privilege) + .map(a => a.login) + return [{ + xml: this.xml, + login: shuffleLogins + }, { + xml: this.noShuffleXml, + login: noShuffleLogins + }, { + xml: ``, + login: noPrivilegeLogins + }] } displayToPlayer(login: string) { if (!this.isDisplayed) { return } const privilege: number = tm.players.get(login)?.privilege ?? 0 if (privilege >= config.shufflePrivilege) { - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } else if (privilege >= config.privilege) { - return { xml: this.noShuffleXml, login } + return { + xml: this.noShuffleXml, + login + } } else { - return { xml: ``, login } + return { + xml: ``, + login + } } } private constructButton(width: number, height: number, icon: string, hoverIcon: string, actionId?: number): string { const actionIdStr: string = actionId === undefined ? '' : ` action="${actionId + this.id}"` - const cover: string = actionId !== undefined ? '' : `` + const cover: string = actionId !== undefined ? '' : + `` return `${cover} - this.constructButton(w, h, config.icons.players, config.iconsHover.players, this.actions.players) + const playersButton: GridCellFunction = (i, j, w, h): string => this.constructButton(w, h, config.icons.players, + config.iconsHover.players, this.actions.players) const previousButton: GridCellFunction = (i, j, w, h): string => { if (tm.jukebox.history.length < 1) { return this.constructButton(w, h, config.icons.previous, config.iconsHover.previous) } return this.constructButton(w, h, config.icons.previous, config.iconsHover.previous, this.actions.previous) } - const replayButton: GridCellFunction = (i, j, w, h): string => - this.constructButton(w, h, config.icons.requeue, config.iconsHover.requeue, this.actions.requeue) + const replayButton: GridCellFunction = (i, j, w, h): string => this.constructButton(w, h, config.icons.requeue, + config.iconsHover.requeue, this.actions.requeue) const shuffleButton: GridCellFunction = (i, j, w, h): string => { if (noShuffle) { return this.constructButton(w, h, config.icons.shuffle, config.iconsHover.shuffle) } return this.constructButton(w, h, config.icons.shuffle, config.iconsHover.shuffle, this.actions.shuffle) } - const jukeboxButton: GridCellFunction = (i, j, w, h): string => - this.constructButton(w, h, config.icons.jukebox, config.iconsHover.jukebox, this.actions.jukebox) + const jukeboxButton: GridCellFunction = (i, j, w, h): string => this.constructButton(w, h, config.icons.jukebox, + config.iconsHover.jukebox, this.actions.jukebox) return ` diff --git a/plugins/ui/static_components/result/AdminPanelResult.config.js b/plugins/ui/static_components/result/AdminPanelResult.config.js index 431b3394..32f8dcf5 100644 --- a/plugins/ui/static_components/result/AdminPanelResult.config.js +++ b/plugins/ui/static_components/result/AdminPanelResult.config.js @@ -1,5 +1,6 @@ import cfg from '../../config/ResultUi.js' import icons from '../../config/Icons.js' + const p = tm.utils.palette export default { @@ -9,7 +10,7 @@ export default { margin: cfg.margin, background: cfg.background, shufflePrivilege: 2, - title: "Admin Panel", + title: 'Admin Panel', icon: icons.gears, disabledColour: '333C', public: true, @@ -31,4 +32,4 @@ export default { previous: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has requeued the previous map.`, shuffle: `${p.admin}#{title} ${p.highlight}#{adminName} ${p.admin}has shuffled the queue.` } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/AveragesRanking.component.ts b/plugins/ui/static_components/result/AveragesRanking.component.ts index f27ef148..ec4004b1 100644 --- a/plugins/ui/static_components/result/AveragesRanking.component.ts +++ b/plugins/ui/static_components/result/AveragesRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { List, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './AveragesRanking.config.js' @@ -16,9 +16,11 @@ export default class AveragesRanking extends StaticComponent { constructor() { super(componentIds.averagesRanking) this.header = new StaticHeader('result') - this.list = new List(config.entries, config.width, - this.getHeight() - (this.header.options.height + config.margin), config.columnProportions, - { background: config.background, headerBg: this.header.options.textBackground }) + this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.averages.onUpdate(() => this.sendMultipleManialinks(this.display())) stats.averages.onNicknameChange(() => this.sendMultipleManialinks(this.display())) } @@ -35,7 +37,10 @@ export default class AveragesRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -45,7 +50,8 @@ export default class AveragesRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(list.map(a => a.average.toFixed(2)), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.average.toFixed(2)), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/AveragesRanking.config.js b/plugins/ui/static_components/result/AveragesRanking.config.js index 71c95519..fcca42d6 100644 --- a/plugins/ui/static_components/result/AveragesRanking.config.js +++ b/plugins/ui/static_components/result/AveragesRanking.config.js @@ -5,15 +5,11 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Top Ranks", + title: 'Top Ranks', icon: icons.stats, background: cfg.background, margin: cfg.margin, - columnProportions: [ - 1, - 1.9, - 5.1 - ], + columnProportions: [1, 1.9, 5.1], topCount: 5, displayNoRecordEntry: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/BannerWidget.component.ts b/plugins/ui/static_components/result/BannerWidget.component.ts index 0bbeffb8..2e015f7b 100644 --- a/plugins/ui/static_components/result/BannerWidget.component.ts +++ b/plugins/ui/static_components/result/BannerWidget.component.ts @@ -29,7 +29,8 @@ export default class BannerWidget extends StaticComponent { - + ${this.constructDonateButtons(w, m)} @@ -56,7 +57,10 @@ export default class BannerWidget extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructDonateButtons(width: number, margin: number) { @@ -69,8 +73,11 @@ export default class BannerWidget extends StaticComponent { const action = i === 0 ? '' : `action="${this.id + i}"` const bg = i === 0 ? config.mainTextBackground : config.donateBackground ret += ` - ${centeredText(arr[i], w, config.buttonHeight, - { xOffset: x, yOffset: margin, textScale: config.textScale })}` + ${centeredText(arr[i], w, config.buttonHeight, { + xOffset: x, + yOffset: margin, + textScale: config.textScale + })}` x += w + margin } return ret diff --git a/plugins/ui/static_components/result/DediRankingResult.component.ts b/plugins/ui/static_components/result/DediRankingResult.component.ts index d06d1154..db83fc80 100644 --- a/plugins/ui/static_components/result/DediRankingResult.component.ts +++ b/plugins/ui/static_components/result/DediRankingResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import { dedimania } from '../../../dedimania/Dedimania.js' import config from './DediRankingResult.config.js' @@ -16,10 +16,11 @@ export default class DediRankingResult extends StaticComponent { constructor() { super(componentIds.dedisResult) this.header = new StaticHeader('result') - this.recordList = new RecordList('result', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.entries, this.side, config.topCount, this.maxDedis, config.displayNoRecordEntry) + this.recordList = new RecordList('result', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, config.topCount, + this.maxDedis, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { - if(this.reduxModeEnabled) { return } + if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) if (obj !== undefined) { tm.sendManialink(obj.xml, obj.login) @@ -40,7 +41,7 @@ export default class DediRankingResult extends StaticComponent { display() { if (!this.isDisplayed) { return } - if(this.reduxModeEnabled) { return this.displayToPlayer('')?.xml } + if (this.reduxModeEnabled) { return this.displayToPlayer('')?.xml } const arr = [] for (const player of tm.players.list) { arr.push(this.displayToPlayer(player.login)) @@ -56,7 +57,10 @@ export default class DediRankingResult extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.dediCps })} - ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ ...a, name: a.nickname })))} + ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, dedimania.records.map(a => ({ + ...a, + name: a.nickname + })))} `, diff --git a/plugins/ui/static_components/result/DediRankingResult.config.js b/plugins/ui/static_components/result/DediRankingResult.config.js index 3684b515..0b71b876 100644 --- a/plugins/ui/static_components/result/DediRankingResult.config.js +++ b/plugins/ui/static_components/result/DediRankingResult.config.js @@ -5,7 +5,7 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Dedi Records", + title: 'Dedi Records', icon: icons.chartDedi, margin: cfg.margin, topCount: 5, diff --git a/plugins/ui/static_components/result/DonatorsRanking.component.ts b/plugins/ui/static_components/result/DonatorsRanking.component.ts index f61dbfc6..c5198238 100644 --- a/plugins/ui/static_components/result/DonatorsRanking.component.ts +++ b/plugins/ui/static_components/result/DonatorsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './DonatorsRanking.config.js' @@ -17,7 +17,10 @@ export default class DonatorsRanking extends StaticComponent { super(componentIds.donatorsRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.donations.onUpdate(() => this.sendMultipleManialinks(this.display())) stats.donations.onNicknameChange(() => this.sendMultipleManialinks(this.display())) } @@ -34,7 +37,10 @@ export default class DonatorsRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -44,7 +50,8 @@ export default class DonatorsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, config.side)} - ${this.list.constructXml(list.map(a => a.amount.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.amount.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/DonatorsRanking.config.js b/plugins/ui/static_components/result/DonatorsRanking.config.js index 3819cf7c..2bff059c 100644 --- a/plugins/ui/static_components/result/DonatorsRanking.config.js +++ b/plugins/ui/static_components/result/DonatorsRanking.config.js @@ -5,16 +5,12 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - posX: cfg.rightPosition - (cfg.marginBig + cfg.width) * 2, + posX: cfg.rightPosition - (cfg.marginBig + cfg.width) * 2, posY: cfg.topBorder, side: true, - title: "Top Donators", + title: 'Top Donators', icon: icons.cash, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/KarmaRanking.component.ts b/plugins/ui/static_components/result/KarmaRanking.component.ts index 1056fec8..6f852dba 100644 --- a/plugins/ui/static_components/result/KarmaRanking.component.ts +++ b/plugins/ui/static_components/result/KarmaRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import config from './KarmaRanking.config.js' export default class KarmaRanking extends StaticComponent { @@ -16,7 +16,10 @@ export default class KarmaRanking extends StaticComponent { super(componentIds.karmaRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) this.renderOnEvent('KarmaVote', () => { return this.display() }) @@ -34,17 +37,22 @@ export default class KarmaRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { - const list: Readonly[] = tm.maps.list.sort((a, b): number => b.voteRatio - a.voteRatio).filter(a => a.voteCount > config.minimumVotes).slice(0, config.entries) + const list: Readonly[] = tm.maps.list.sort((a, b): number => b.voteRatio - a.voteRatio) + .filter(a => a.voteCount > config.minimumVotes).slice(0, config.entries) this.xml = ` ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(list.map(a => a.voteRatio === -1 ? config.defaultText : a.voteRatio.toFixed(0)), list.map(a => tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(a.name), false))))} + ${this.list.constructXml(list.map(a => a.voteRatio === -1 ? config.defaultText : a.voteRatio.toFixed(0)), + list.map(a => tm.utils.safeString(tm.utils.strip(tm.utils.decodeURI(a.name), false))))} ` diff --git a/plugins/ui/static_components/result/KarmaRanking.config.js b/plugins/ui/static_components/result/KarmaRanking.config.js index 70f1a98c..658ef9db 100644 --- a/plugins/ui/static_components/result/KarmaRanking.config.js +++ b/plugins/ui/static_components/result/KarmaRanking.config.js @@ -5,15 +5,11 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Best Maps", + title: 'Best Maps', defaultText: '-', icon: icons.karmaStats, margin: cfg.margin, background: cfg.background, minimumVotes: 5, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/KarmaWidgetResult.component.ts b/plugins/ui/static_components/result/KarmaWidgetResult.component.ts index 1e7ac0d2..77ba1ea0 100644 --- a/plugins/ui/static_components/result/KarmaWidgetResult.component.ts +++ b/plugins/ui/static_components/result/KarmaWidgetResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { centeredText, Grid, type GridCellFunction, componentIds, StaticHeader, addManialinkListener, StaticComponent } from '../../UI.js' +import { addManialinkListener, centeredText, componentIds, Grid, type GridCellFunction, StaticComponent, StaticHeader } from '../../UI.js' import { maniakarma, type MKMapVotes } from '../../../maniakarma/Maniakarma.js' import config from './KarmaWidgetResult.config.js' import { actions } from '../../../actions/Actions.js' @@ -20,8 +20,11 @@ export default class KarmaWidgetResult extends StaticComponent { super(componentIds.karmaResult) this.header = new StaticHeader('result') this.headerH = this.header.options.height - this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, config.margin + config.height - this.headerH, - new Array(3).fill(1), new Array(3).fill(1), { background: config.background, margin: config.margin }) + this.grid = new Grid((config.width + config.margin - config.buttonWidth) / 2, + config.margin + config.height - this.headerH, new Array(3).fill(1), new Array(3).fill(1), { + background: config.background, + margin: config.margin + }) this.renderOnEvent('KarmaVote', () => { return this.display() }) maniakarma.onMapFetch(() => this.sendMultipleManialinks(this.display())) maniakarma.onVote(() => this.sendMultipleManialinks(this.display())) @@ -45,7 +48,10 @@ export default class KarmaWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.constructXml(login), login } + return { + xml: this.constructXml(login), + login + } } private constructXml(login: string): string { @@ -100,9 +106,11 @@ export default class KarmaWidgetResult extends StaticComponent { const karmaStr: string = karma !== -1 ? (~~karma).toString() : config.defaultText const mkKarmaStr: string = mkVoteCount !== 0 ? (~~mkKarma).toString() : config.defaultText const mkCountStr: string = maniakarma.isEnabled ? mkVoteCount.toString() : config.defaultText - const options = { padding: config.textPadding, textScale: config.textScale } - const arr: GridCellFunction[] = [ - (i, j, w, h): string => ``, + const options = { + padding: config.textPadding, + textScale: config.textScale + } + const arr: GridCellFunction[] = [(i, j, w, h): string => ``, (i, j, w, h): string => ``, (i, j, w, h): string => ` centeredText(karmaStr, w, h, options), (i, j, w, h): string => centeredText(mkKarmaStr, w, h, options), - (i, j, w, h): string => ``, + (i, j, w, + h): string => ``, (i, j, w, h): string => centeredText(voteCount.toString(), w, h, options), - (i, j, w, h): string => centeredText(mkCountStr, w, h, options), - ] + (i, j, w, h): string => centeredText(mkCountStr, w, h, options)] return this.grid.constructXml(arr) } @@ -129,8 +137,12 @@ export default class KarmaWidgetResult extends StaticComponent { const colour: string = this.options[i] === personalVote ? config.selfColour : config.mkColours[i] ret += ` - ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, - { xOffset: config.margin, yOffset: config.margin + h * i + offsetFix, padding: 0, textScale })}` + ${centeredText(e, config.buttonWidth - (config.margin * 2), h - config.margin, { + xOffset: config.margin, + yOffset: config.margin + h * i + offsetFix, + padding: 0, + textScale + })}` } return ret } diff --git a/plugins/ui/static_components/result/KarmaWidgetResult.config.js b/plugins/ui/static_components/result/KarmaWidgetResult.config.js index 9ca71acb..1bafda26 100644 --- a/plugins/ui/static_components/result/KarmaWidgetResult.config.js +++ b/plugins/ui/static_components/result/KarmaWidgetResult.config.js @@ -4,15 +4,10 @@ import icons from '../../config/Icons.js' export default { height: 8.8, width: cfg.width, - title: "Karma", + title: 'Karma', icon: icons.karmaStats, margin: cfg.margin, - icons: [ - icons.karmaLocal, - icons.karmaGlobal, - icons.karmaPulse, - icons.peopleKarma - ], + icons: [icons.karmaLocal, icons.karmaGlobal, icons.karmaPulse, icons.peopleKarma], colours: ['0F8A', '0D6A', '0B4A', 'BO4A', 'D06A', 'F08A'], mkColours: ['0F0A', '0D0A', '0B0A', 'BOOA', 'D00A', 'F00A'], options: ['+++', '++', '+', '-', '--', '---'], @@ -30,4 +25,4 @@ export default { scale: 1, offset: -0.3 } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/LocalRankingResult.component.ts b/plugins/ui/static_components/result/LocalRankingResult.component.ts index e916a744..714df8d6 100644 --- a/plugins/ui/static_components/result/LocalRankingResult.component.ts +++ b/plugins/ui/static_components/result/LocalRankingResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { RecordList, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './LocalRankingResult.config.js' export default class LocalRankingResult extends StaticComponent { @@ -14,8 +14,9 @@ export default class LocalRankingResult extends StaticComponent { constructor() { super(componentIds.localsResult) this.header = new StaticHeader('result') - this.recordList = new RecordList('result', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.entries, this.side, config.topCount, tm.records.maxLocalsAmount, config.displayNoRecordEntry) + this.recordList = new RecordList('result', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, config.topCount, + tm.records.maxLocalsAmount, config.displayNoRecordEntry) this.recordList.onClick((info: tm.ManialinkClickInfo) => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -56,8 +57,14 @@ export default class LocalRankingResult extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side, { actionId: componentIds.localCps })} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, tm.records.local - .map(a => ({ name: a.nickname, time: a.time, date: a.date, checkpoints: a.checkpoints, login: a.login })) - .slice(0, tm.records.maxLocalsAmount))} + .map(a => ({ + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login + })) + .slice(0, tm.records.maxLocalsAmount))} `, diff --git a/plugins/ui/static_components/result/LocalRankingResult.config.js b/plugins/ui/static_components/result/LocalRankingResult.config.js index 59608b82..85395ceb 100644 --- a/plugins/ui/static_components/result/LocalRankingResult.config.js +++ b/plugins/ui/static_components/result/LocalRankingResult.config.js @@ -5,9 +5,9 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Local Records", + title: 'Local Records', icon: icons.chartLocal, margin: cfg.margin, topCount: 5, displayNoRecordEntry: true -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/MapWidgetResult.component.ts b/plugins/ui/static_components/result/MapWidgetResult.component.ts index 9fab4eb4..1917f9ee 100644 --- a/plugins/ui/static_components/result/MapWidgetResult.component.ts +++ b/plugins/ui/static_components/result/MapWidgetResult.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, Grid, StaticHeader, StaticComponent, type StaticHeaderOptions } from '../../UI.js' +import { componentIds, Grid, StaticComponent, StaticHeader, type StaticHeaderOptions } from '../../UI.js' import flags from '../../config/FlagIcons.js' import { tmx } from '../../../tmx/Tmx.js' import { webservices, type WebservicesInfo } from '../../../webservices/Webservices.js' @@ -14,7 +14,8 @@ export default class MapWidgetResult extends StaticComponent { private readonly rows: number = 5 private readonly header: StaticHeader private readonly grid: Grid - private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : process.env.SERVER_PACKMASK !== "nations" + private readonly displayEnvironment: boolean = config.displayEnvironment !== undefined ? config.displayEnvironment : + process.env.SERVER_PACKMASK !== "nations" private isRestart: boolean = false private xml: string = '' @@ -49,7 +50,10 @@ export default class MapWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private updateXML(): void { @@ -77,15 +81,14 @@ export default class MapWidgetResult extends StaticComponent { timeOrScoreIcon = ic.goldScore } } - const infos: [string, string][] = [ - [config.title, ic.header], - [tm.utils.safeString(tm.utils.decodeURI(map.name)), obj.tag], - [tm.utils.safeString(author), authorIcon], + const infos: [string, string][] = [[config.title, ic.header], + [tm.utils.safeString(tm.utils.decodeURI(map.name)), obj.tag], [tm.utils.safeString(author), authorIcon], [timeOrScore, timeOrScoreIcon], [date === undefined ? config.noDateText : tm.utils.formatDate(date), ic.buildDate], [TMXMap?.awards === undefined ? config.noAwardsText : TMXMap.awards.toString(), obj.award], - this.displayEnvironment ? [map.environment, ic.environment] : [TMXMap?.validReplays?.[0]?.time === undefined ? config.noWrText : tm.utils.getTimeString(TMXMap.validReplays[0].time), ic.tmxWr] - ] + this.displayEnvironment ? [map.environment, ic.environment] : + [TMXMap?.validReplays?.[0]?.time === undefined ? config.noWrText : + tm.utils.getTimeString(TMXMap.validReplays[0].time), ic.tmxWr]] const headerCfg: StaticHeaderOptions = this.header.options const cell = (i: number, j: number, w: number, h: number): string => { if (i === 4) { @@ -97,14 +100,13 @@ export default class MapWidgetResult extends StaticComponent { textBackground: config.background })} - + ${this.header.constructXml(infos[i + 2][0], infos[i + 2][1], this.side, { - rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), - textScale: config.textScale, - centerText: true, - textBackground: config.background - })} + rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), + textScale: config.textScale, + centerText: true, + textBackground: config.background + })} ` } else if (i === 3) { return ` @@ -116,24 +118,23 @@ export default class MapWidgetResult extends StaticComponent { textBackground: config.background })} - + ${this.header.constructXml(infos[i + 1][0], infos[i + 1][1], this.side, { - rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), - textScale: config.textScale, - centerText: true, - textBackground: config.background - })} + rectangleWidth: (headerCfg.rectangleWidth / 2) - (headerCfg.margin + (headerCfg.squareWidth / 2)), + textScale: config.textScale, + centerText: true, + textBackground: config.background + })} ` } return ` ${i === 0 ? this.header.constructXml(infos[i][0], infos[i][1], this.side) : - this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { - textScale: config.textScale, - textBackground: config.background, - horizontalPadding: config.mapPadding - })} + this.header.constructXml(tm.utils.strip(infos[i][0], false), infos[i][1], this.side, { + textScale: config.textScale, + textBackground: config.background, + horizontalPadding: config.mapPadding + })} ` } const arr: any[] = new Array(this.rows).fill(cell) @@ -146,7 +147,10 @@ export default class MapWidgetResult extends StaticComponent { ` } - private getTagAndAward(map: tm.Map, TMXMap?: tm.TMXMap): { tag: string, award: string } { + private getTagAndAward(map: tm.Map, TMXMap?: tm.TMXMap): { + tag: string, + award: string + } { let tag: string = config.icons.tags.normal let award: string = config.icons.awards.normal if (map.isNadeo) { @@ -158,12 +162,15 @@ export default class MapWidgetResult extends StaticComponent { award = config.icons.awards.classic } for (const e of config.customTags) { - if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || - e?.names?.some(a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || - (TMXMap !== undefined ? (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { + if (e?.authors?.some(a => a === map.author || a === TMXMap?.author) || e?.names?.some( + a => a.test(map.name) || a.test(tm.utils.strip(map.name)) || (TMXMap !== undefined ? + (a.test(TMXMap.name) || a.test(tm.utils.strip(TMXMap.name))) : false))) { tag = e.icon } } - return { tag, award } + return { + tag, + award + } } } diff --git a/plugins/ui/static_components/result/MapWidgetResult.config.js b/plugins/ui/static_components/result/MapWidgetResult.config.js index 51819edc..2022c6a7 100644 --- a/plugins/ui/static_components/result/MapWidgetResult.config.js +++ b/plugins/ui/static_components/result/MapWidgetResult.config.js @@ -6,21 +6,28 @@ export default { height: 11.4, margin: cfg.margin, width: cfg.width, - title: "Next", + title: 'Next', background: cfg.background, mapPadding: 0.3, - customTags: [ - /* Custom tags allow you to display special tag icon for certain authors or maps - If tmx name or login matches one of the authors or - map name matches one of the names regexes special tag will be displayed - map name is tested both stripped and unstripped from special characters - Tags are read from top to bottom - */ - { authors: ['Fwo Niro'], icon: icons.tagRed }, - { authors: ['tony89300'], icon: icons.tagPinkCyan }, - { names: [/{Kackiest Kacky}*/], icon: icons.tagKacky }, - { names: [/\$w\$i\$f70ARC_*/], icon: icons.tagArcade } - ], + customTags: [/* Custom tags allow you to display special tag icon for certain authors or maps + If tmx name or login matches one of the authors or + map name matches one of the names regexes special tag will be displayed + map name is tested both stripped and unstripped from special characters + Tags are read from top to bottom + */ + { + authors: ['Fwo Niro'], + icon: icons.tagRed + }, { + authors: ['tony89300'], + icon: icons.tagPinkCyan + }, { + names: [/{Kackiest Kacky}*/], + icon: icons.tagKacky + }, { + names: [/\$w\$i\$f70ARC_*/], + icon: icons.tagArcade + }], icons: { header: icons.ongoingMap, tags: { @@ -43,12 +50,12 @@ export default { }, textScale: 1, displayEnvironment: undefined, // Display environment instead of TMX time - noDateText: "N/A", - noAwardsText: "N/A", - noWrText: "-:--.--", + noDateText: 'N/A', + noAwardsText: 'N/A', + noWrText: '-:--.--', titles: { - lastTrack: "Last Map", - currTrack: "Ongoing", - nextTrack: "Next Map" + lastTrack: 'Last Map', + currTrack: 'Ongoing', + nextTrack: 'Next Map' } -} \ No newline at end of file +} diff --git a/plugins/ui/static_components/result/MostRecordsRanking.component.ts b/plugins/ui/static_components/result/MostRecordsRanking.component.ts index 344497fa..bd47eb49 100644 --- a/plugins/ui/static_components/result/MostRecordsRanking.component.ts +++ b/plugins/ui/static_components/result/MostRecordsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './MostRecordsRanking.config.js' @@ -17,7 +17,10 @@ export default class MostRecordsRanking extends StaticComponent { super(componentIds.mostRecordsRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) this.constructXml() tm.addListener('EndMap', (): void => { this.constructXml() @@ -38,7 +41,10 @@ export default class MostRecordsRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -48,7 +54,8 @@ export default class MostRecordsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, config.side)} - ${this.list.constructXml(list.map(a => a.amount.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.amount.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/MostRecordsRanking.config.js b/plugins/ui/static_components/result/MostRecordsRanking.config.js index 710f2411..311a3748 100644 --- a/plugins/ui/static_components/result/MostRecordsRanking.config.js +++ b/plugins/ui/static_components/result/MostRecordsRanking.config.js @@ -8,13 +8,9 @@ export default { posX: cfg.leftPosition + (cfg.width + cfg.margin) * 2 + cfg.margin, posY: cfg.topBorder, side: false, - title: "Most Records", + title: 'Most Records', icon: icons.personBetter, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/NextMapRecords.component.ts b/plugins/ui/static_components/result/NextMapRecords.component.ts index 03b9199f..c9c66948 100644 --- a/plugins/ui/static_components/result/NextMapRecords.component.ts +++ b/plugins/ui/static_components/result/NextMapRecords.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, RecordList, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, RecordList, StaticComponent, StaticHeader } from '../../UI.js' import config from './NextMapRecords.config.js' export default class NextMapRecords extends StaticComponent { @@ -15,8 +15,9 @@ export default class NextMapRecords extends StaticComponent { constructor() { super(componentIds.nextMapRecords) this.header = new StaticHeader('result') - this.recordList = new RecordList('result', this.id, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.entries, this.side, 5, 5, false, { getColoursFromPb: true }) + this.recordList = new RecordList('result', this.id, config.width, + this.getHeight() - (this.header.options.height + config.margin), config.entries, this.side, 5, 5, false, + { getColoursFromPb: true }) this.recordList.onClick((info: tm.ManialinkClickInfo): void => { if (this.reduxModeEnabled) { return } const obj = this.displayToPlayer(info.login) @@ -31,8 +32,8 @@ export default class NextMapRecords extends StaticComponent { } else { const mapId: string = tm.jukebox.queue[0].id this.records = tm.records.getFromQueue(mapId) - .sort((a, b) => a.time - b.time) - .filter((a, i, arr) => arr.findIndex(b => b.login === a.login && a.map === b.map) === i) + .sort((a, b) => a.time - b.time) + .filter((a, i, arr) => arr.findIndex(b => b.login === a.login && a.map === b.map) === i) return this.display() } }) @@ -73,8 +74,11 @@ export default class NextMapRecords extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} ${this.recordList.constructXml(this.reduxModeEnabled ? undefined : login, this.records.map(a => ({ - name: a.nickname, time: a.time, - date: a.date, checkpoints: a.checkpoints, login: a.login + name: a.nickname, + time: a.time, + date: a.date, + checkpoints: a.checkpoints, + login: a.login })), pb)} diff --git a/plugins/ui/static_components/result/NextMapRecords.config.js b/plugins/ui/static_components/result/NextMapRecords.config.js index 0ca27c83..8c4563bb 100644 --- a/plugins/ui/static_components/result/NextMapRecords.config.js +++ b/plugins/ui/static_components/result/NextMapRecords.config.js @@ -5,14 +5,10 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Next Map Records", + title: 'Next Map Records', icon: icons.chartLive, margin: cfg.margin, - columnProportions: [ - 1, - 2.9, - 4.1 - ], + columnProportions: [1, 2.9, 4.1], topCount: 5, displayNoRecordEntry: true } diff --git a/plugins/ui/static_components/result/PlaytimeRanking.component.ts b/plugins/ui/static_components/result/PlaytimeRanking.component.ts index 0398da9f..065c9599 100644 --- a/plugins/ui/static_components/result/PlaytimeRanking.component.ts +++ b/plugins/ui/static_components/result/PlaytimeRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './PlaytimeRanking.config.js' @@ -17,7 +17,10 @@ export default class PlaytimeRanking extends StaticComponent { super(componentIds.playtimeRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.playtimes.onUpdate((): void => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class PlaytimeRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml() { diff --git a/plugins/ui/static_components/result/PlaytimeRanking.config.js b/plugins/ui/static_components/result/PlaytimeRanking.config.js index 6de1f21a..8a4aad4a 100644 --- a/plugins/ui/static_components/result/PlaytimeRanking.config.js +++ b/plugins/ui/static_components/result/PlaytimeRanking.config.js @@ -8,13 +8,9 @@ export default { posX: cfg.rightPosition - (cfg.marginBig + cfg.width), posY: cfg.topBorder, side: true, - title: "Top Hours Played", + title: 'Top Hours Played', icon: icons.clockList, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] -} \ No newline at end of file + columnProportions: [1, 1.9, 5.1] +} diff --git a/plugins/ui/static_components/result/RankWidgetResult.component.ts b/plugins/ui/static_components/result/RankWidgetResult.component.ts index 2db574d8..98a943bf 100644 --- a/plugins/ui/static_components/result/RankWidgetResult.component.ts +++ b/plugins/ui/static_components/result/RankWidgetResult.component.ts @@ -26,7 +26,10 @@ export default class RankWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } protected onPositionChange(): void { diff --git a/plugins/ui/static_components/result/RoundAveragesRanking.component.ts b/plugins/ui/static_components/result/RoundAveragesRanking.component.ts index a3a4bfa0..bb1e9fda 100644 --- a/plugins/ui/static_components/result/RoundAveragesRanking.component.ts +++ b/plugins/ui/static_components/result/RoundAveragesRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { List, componentIds, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import config from './RoundAveragesRanking.config.js' export default class RoundAveragesRanking extends StaticComponent { @@ -11,17 +11,30 @@ export default class RoundAveragesRanking extends StaticComponent { private readonly list: List private readonly header: StaticHeader private xml: string = '' - private averages: { nickname: string, login: string, average: number, finishcount: number }[] = [] + private averages: { + nickname: string, + login: string, + average: number, + finishcount: number + }[] = [] constructor() { super(componentIds.roundAveragesRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) tm.addListener('PlayerFinish', async (info): Promise => { const entry = this.averages.find(a => a.login === info.login) if (entry === undefined) { - this.averages.push({ nickname: info.nickname, login: info.login, average: info.time, finishcount: 1 }) + this.averages.push({ + nickname: info.nickname, + login: info.login, + average: info.time, + finishcount: 1 + }) } else { entry.average = ~~((entry.average * entry.finishcount + info.time) / (entry.finishcount + 1)) entry.finishcount++ @@ -48,7 +61,10 @@ export default class RoundAveragesRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -57,7 +73,8 @@ export default class RoundAveragesRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(this.averages.map(a => tm.utils.getTimeString(a.average)), this.averages.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(this.averages.map(a => tm.utils.getTimeString(a.average)), + this.averages.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/RoundAveragesRanking.config.js b/plugins/ui/static_components/result/RoundAveragesRanking.config.js index ab7c78af..59dcc45d 100644 --- a/plugins/ui/static_components/result/RoundAveragesRanking.config.js +++ b/plugins/ui/static_components/result/RoundAveragesRanking.config.js @@ -8,14 +8,10 @@ export default { posX: cfg.rightPosition - (cfg.marginBig + cfg.width), posY: cfg.topBorder, side: true, - title: "Round Averages", + title: 'Round Averages', icon: icons.clockList, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 2.9, - 4.1 - ] + columnProportions: [1, 2.9, 4.1] } diff --git a/plugins/ui/static_components/result/TimerWidgetResult.component.ts b/plugins/ui/static_components/result/TimerWidgetResult.component.ts index 1fa1160b..48acaa5f 100644 --- a/plugins/ui/static_components/result/TimerWidgetResult.component.ts +++ b/plugins/ui/static_components/result/TimerWidgetResult.component.ts @@ -18,11 +18,11 @@ export default class TimerWidgetResult extends StaticComponent { getHeight(): number { return config.height } - + protected onPositionChange(): void { this.constructXml() const xml = this.display() - if(xml !== undefined) { tm.sendManialink(xml) } + if (xml !== undefined) { tm.sendManialink(xml) } } display() { @@ -32,7 +32,10 @@ export default class TimerWidgetResult extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } private constructXml(): void { diff --git a/plugins/ui/static_components/result/VisitorsRanking.component.ts b/plugins/ui/static_components/result/VisitorsRanking.component.ts index 7aefe940..307e36b6 100644 --- a/plugins/ui/static_components/result/VisitorsRanking.component.ts +++ b/plugins/ui/static_components/result/VisitorsRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './VisitorsRanking.config.js' @@ -17,7 +17,10 @@ export default class VisitorsRanking extends StaticComponent { super(componentIds.visitorsRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.visits.onUpdate((): void => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class VisitorsRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -50,7 +56,8 @@ export default class VisitorsRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(list.map(a => a.visits.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.visits.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/VisitorsRanking.config.js b/plugins/ui/static_components/result/VisitorsRanking.config.js index a547e3f5..05a05b16 100644 --- a/plugins/ui/static_components/result/VisitorsRanking.config.js +++ b/plugins/ui/static_components/result/VisitorsRanking.config.js @@ -5,13 +5,9 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Most Visits", + title: 'Most Visits', icon: icons.person, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/VotersRanking.component.ts b/plugins/ui/static_components/result/VotersRanking.component.ts index 812026f6..59212ecb 100644 --- a/plugins/ui/static_components/result/VotersRanking.component.ts +++ b/plugins/ui/static_components/result/VotersRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './VotersRanking.config.js' @@ -17,7 +17,10 @@ export default class VotersRanking extends StaticComponent { super(componentIds.votersRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.votes.onUpdate((): void => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class VotersRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -50,7 +56,8 @@ export default class VotersRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, this.side)} - ${this.list.constructXml(votes.map(a => a.count.toString()), votes.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(votes.map(a => a.count.toString()), + votes.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/VotersRanking.config.js b/plugins/ui/static_components/result/VotersRanking.config.js index e23b2039..e2b41f67 100644 --- a/plugins/ui/static_components/result/VotersRanking.config.js +++ b/plugins/ui/static_components/result/VotersRanking.config.js @@ -5,13 +5,9 @@ export default { entries: 5, entryHeight: 2.15, width: cfg.width, - title: "Most Votes", + title: 'Most Votes', icon: icons.peopleKarma, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/static_components/result/WinnersRanking.component.ts b/plugins/ui/static_components/result/WinnersRanking.component.ts index 92262b1d..051ef63f 100644 --- a/plugins/ui/static_components/result/WinnersRanking.component.ts +++ b/plugins/ui/static_components/result/WinnersRanking.component.ts @@ -3,7 +3,7 @@ * @since 0.4 */ -import { componentIds, List, StaticHeader, StaticComponent } from '../../UI.js' +import { componentIds, List, StaticComponent, StaticHeader } from '../../UI.js' import { stats } from '../../../stats/Stats.js' import config from './WinnersRanking.config.js' @@ -17,7 +17,10 @@ export default class WinnersRanking extends StaticComponent { super(componentIds.winnersRanking) this.header = new StaticHeader('result') this.list = new List(config.entries, config.width, this.getHeight() - (this.header.options.height + config.margin), - config.columnProportions, { background: config.background, headerBg: this.header.options.textBackground }) + config.columnProportions, { + background: config.background, + headerBg: this.header.options.textBackground + }) stats.wins.onUpdate(() => { const xml = this.display() if (xml !== undefined) { tm.sendManialink(xml) } @@ -40,7 +43,10 @@ export default class WinnersRanking extends StaticComponent { displayToPlayer(login: string) { if (!this.isDisplayed) { return } - return { xml: this.xml, login } + return { + xml: this.xml, + login + } } constructXml(): void { @@ -50,7 +56,8 @@ export default class WinnersRanking extends StaticComponent { ${this.header.constructXml(config.title, config.icon, config.side)} - ${this.list.constructXml(list.map(a => a.wins.toString()), list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} + ${this.list.constructXml(list.map(a => a.wins.toString()), + list.map(a => tm.utils.safeString(tm.utils.strip(a.nickname, false))))} ` diff --git a/plugins/ui/static_components/result/WinnersRanking.config.js b/plugins/ui/static_components/result/WinnersRanking.config.js index c44e26b5..9b38e4e8 100644 --- a/plugins/ui/static_components/result/WinnersRanking.config.js +++ b/plugins/ui/static_components/result/WinnersRanking.config.js @@ -8,13 +8,9 @@ export default { posX: cfg.leftPosition + cfg.marginBig + cfg.width, posY: cfg.topBorder, side: false, - title: "Most Wins", + title: 'Most Wins', icon: icons.bestFinishes, margin: cfg.margin, background: cfg.background, - columnProportions: [ - 1, - 1.9, - 5.1 - ] + columnProportions: [1, 1.9, 5.1] } diff --git a/plugins/ui/test_widgets/TestWindow.ts b/plugins/ui/test_widgets/TestWindow.ts index 64960420..fd4ab6f7 100644 --- a/plugins/ui/test_widgets/TestWindow.ts +++ b/plugins/ui/test_widgets/TestWindow.ts @@ -3,43 +3,44 @@ import config from './TestWidget.config.js' export default class TestWindow { - private intervals: { login: string, interval: NodeJS.Timeout }[] = [] + private intervals: { + login: string, + interval: NodeJS.Timeout + }[] = [] constructor() { - tm.commands.add( - { - aliases: config.commands.displaytest.aliases, - help: config.commands.displaytest.help, - callback: (info): void => { - if (this.intervals.some(a => a.login === info.login)) { return } - this.intervals.push({ - interval: setInterval((): void => { - this.displayToPlayer(info.login) - }, config.refreshTimeout), - login: info.login - }) - }, - privilege: config.commands.displaytest.privilege + tm.commands.add({ + aliases: config.commands.displaytest.aliases, + help: config.commands.displaytest.help, + callback: (info): void => { + if (this.intervals.some(a => a.login === info.login)) { return } + this.intervals.push({ + interval: setInterval((): void => { + this.displayToPlayer(info.login) + }, config.refreshTimeout), + login: info.login + }) }, - { - aliases: config.commands.hidetest.aliases, - help: config.commands.hidetest.help, - callback: (info): void => { - const entry = this.intervals.find(a => a.login === info.login) - if (entry !== undefined) { - this.hideToPlayer(entry.login) - clearInterval(entry.interval) - this.intervals = this.intervals.filter(a => a.login !== info.login) - } - }, - privilege: config.commands.hidetest.privilege - } - ) + privilege: config.commands.displaytest.privilege + }, { + aliases: config.commands.hidetest.aliases, + help: config.commands.hidetest.help, + callback: (info): void => { + const entry = this.intervals.find(a => a.login === info.login) + if (entry !== undefined) { + this.hideToPlayer(entry.login) + clearInterval(entry.interval) + this.intervals = this.intervals.filter(a => a.login !== info.login) + } + }, + privilege: config.commands.hidetest.privilege + }) } async displayToPlayer(login: string): Promise { if (!config.isEnabled) { return } - const file: Buffer | Error = await fs.readFile(`./plugins/ui/test_widgets/${config.file}`).catch((err: Error) => err) + const file: Buffer | Error = await fs.readFile(`./plugins/ui/test_widgets/${config.file}`) + .catch((err: Error) => err) if (file instanceof Error) { return } tm.sendManialink(`${file.toString()}`, login) } diff --git a/plugins/ui/utils/CloseButton.config.js b/plugins/ui/utils/CloseButton.config.js index 404e89ea..c0211ca7 100644 --- a/plugins/ui/utils/CloseButton.config.js +++ b/plugins/ui/utils/CloseButton.config.js @@ -1,10 +1,10 @@ import icons from '../config/Icons.js' export default { - buttonWidth: 3, - buttonHeight: 3, - icon: icons.close, - iconHover: icons.closeHover, - padding: 0.2, - background: "000D" -} \ No newline at end of file + buttonWidth: 3, + buttonHeight: 3, + icon: icons.close, + iconHover: icons.closeHover, + padding: 0.2, + background: '000D' +} diff --git a/plugins/ui/utils/CloseButton.ts b/plugins/ui/utils/CloseButton.ts index 1a25b54c..bcb3a189 100644 --- a/plugins/ui/utils/CloseButton.ts +++ b/plugins/ui/utils/CloseButton.ts @@ -14,8 +14,8 @@ interface CloseButtonOptions { * @param options Optional properties * @returns Button XML string */ -export const closeButton = (actionId: number, parentWidth: number, - parentHeight: number, options?: CloseButtonOptions): string => { +export const closeButton = (actionId: number, parentWidth: number, parentHeight: number, + options?: CloseButtonOptions): string => { const width: number = options?.width ?? config.buttonWidth const height: number = options?.height ?? config.buttonHeight const padding: number = options?.padding ?? config.padding diff --git a/plugins/ui/utils/DynamicComponent.ts b/plugins/ui/utils/DynamicComponent.ts index 12849df6..9f332351 100644 --- a/plugins/ui/utils/DynamicComponent.ts +++ b/plugins/ui/utils/DynamicComponent.ts @@ -36,7 +36,7 @@ export default abstract class DynamicComponent { } /** - * Add a callback function to execute when new component object gets created + * Add a callback function to execute when new component object gets created * @param callback Function to execute on event */ static onComponentCreated(callback: (component: DynamicComponent) => void) { diff --git a/plugins/ui/utils/FullScreenListener.ts b/plugins/ui/utils/FullScreenListener.ts index 61891e35..b393381b 100644 --- a/plugins/ui/utils/FullScreenListener.ts +++ b/plugins/ui/utils/FullScreenListener.ts @@ -1,7 +1,7 @@ /** * Constructs an invisible manialink covering the entire screen with given actionId and zIndex. * @param actionId Manialink Action ID - * @param zIndex Manialink Z position + * @param zIndex Manialink Z position * @returns Manialink XML string */ export const fullScreenListener = (actionId: number, zIndex: number = -100): string => { diff --git a/plugins/ui/utils/GetCpTypes.ts b/plugins/ui/utils/GetCpTypes.ts index 55ddbbf6..c3e15596 100644 --- a/plugins/ui/utils/GetCpTypes.ts +++ b/plugins/ui/utils/GetCpTypes.ts @@ -16,7 +16,8 @@ export const getCpTypes = (checkpoints: number[][]): ('best' | 'worst' | 'equal' cps[j][i] = cpRow[j] } } - const cpTypes: ('best' | 'worst' | 'equal' | undefined)[][] = Array.from(Array(cps[0].length), (): any[] => new Array(cps.length).fill(undefined)) + const cpTypes: ('best' | 'worst' | 'equal' | undefined)[][] = Array.from(Array(cps[0].length), + (): any[] => new Array(cps.length).fill(undefined)) for (const [i, e] of cps.entries()) { if (cps?.[0]?.length < 2) { break diff --git a/plugins/ui/utils/Grid.ts b/plugins/ui/utils/Grid.ts index 6a6f87bb..104e6028 100644 --- a/plugins/ui/utils/Grid.ts +++ b/plugins/ui/utils/Grid.ts @@ -39,7 +39,11 @@ export class Grid { * @param rowProportions Grid row proportions * @param options Optional properties */ - constructor(width: number, height: number, columnProportions: number[], rowProportions: number[], options?: { background?: string, margin?: number, headerBackground?: string }) { + constructor(width: number, height: number, columnProportions: number[], rowProportions: number[], options?: { + background?: string, + margin?: number, + headerBackground?: string + }) { this.width = width this.height = height this.margin = options?.margin ?? 0 @@ -83,8 +87,10 @@ export class Grid { } else { callback = obj } - const posY: number = -this.rowHeights.filter((val, index): boolean => index < i).reduce((acc, cur): number => acc += cur, 0) - this.margin - const posX: number = this.columnWidths.filter((val, index): boolean => index < j).reduce((acc, cur): number => acc += cur, 0) + this.margin + const posY: number = -this.rowHeights.filter((val, index): boolean => index < i) + .reduce((acc, cur): number => acc += cur, 0) - this.margin + const posX: number = this.columnWidths.filter((val, index): boolean => index < j) + .reduce((acc, cur): number => acc += cur, 0) + this.margin let h: number = -this.margin for (let k: number = i; k < i + rowspan; k++) { h += this.rowHeights[k] diff --git a/plugins/ui/utils/KeyListener.ts b/plugins/ui/utils/KeyListener.ts index a32dad83..dc6bbd0e 100644 --- a/plugins/ui/utils/KeyListener.ts +++ b/plugins/ui/utils/KeyListener.ts @@ -1,6 +1,10 @@ import utilIds from '../config/UtilIds.js' -let keyListeners: { callback: ((info: tm.ManialinkClickInfo) => void), key: 'F5' | 'F6' | 'F7', importance: number }[] = [] +let keyListeners: { + callback: ((info: tm.ManialinkClickInfo) => void), + key: 'F5' | 'F6' | 'F7', + importance: number +}[] = [] /** * Registers a callback function to execute on given key press. @@ -8,8 +12,13 @@ let keyListeners: { callback: ((info: tm.ManialinkClickInfo) => void), key: 'F5' * @param callback Function to execute on key press, it takes ManialinkClickInfo as a parameter * @param importance Importance index - only the function with highest importance index will get executed */ -export const addKeyListener = (key: 'F5' | 'F6' | 'F7', callback: (info: tm.ManialinkClickInfo) => void, importance: number) => { - keyListeners.unshift({ callback, key, importance }) +export const addKeyListener = (key: 'F5' | 'F6' | 'F7', callback: (info: tm.ManialinkClickInfo) => void, + importance: number) => { + keyListeners.unshift({ + callback, + key, + importance + }) } /** @@ -49,7 +58,7 @@ tm.addListener('PlayerJoin', (info): void => { }) tm.addListener('ManialinkClick', (info: tm.ManialinkClickInfo): void => { - switch (info.actionId) { + switch(info.actionId) { case utilIds.F5: keyListeners.find(a => a.key === 'F5')?.callback(info) break diff --git a/plugins/ui/utils/List.ts b/plugins/ui/utils/List.ts index a02ba805..f4881dd4 100644 --- a/plugins/ui/utils/List.ts +++ b/plugins/ui/utils/List.ts @@ -28,7 +28,10 @@ export class List { * @param columnProportions List column proportions (proportions are relative to eachother like in CSS flexbox) * @param options Optional properties */ - constructor(entries: number, width: number, height: number, columnProportions: number[], options?: { background?: string, headerBg?: string }) { + constructor(entries: number, width: number, height: number, columnProportions: number[], options?: { + background?: string, + headerBg?: string + }) { this.entries = entries this.height = height this.width = width @@ -47,14 +50,17 @@ export class List { constructXml(col1: string[], col2: string[]): string { const index: GridCellObject = { - callback: (i, j, w, h): string => - centeredText((col1[i] === undefined || col2[i] === undefined) ? '' : (i + 1).toString(), w, h, { textScale: config.textScale }), + callback: (i, j, w, h): string => centeredText( + (col1[i] === undefined || col2[i] === undefined) ? '' : (i + 1).toString(), w, h, + { textScale: config.textScale }), background: this.headerBg } - const col1Function: GridCellFunction = (i, j, w, h): string => centeredText(col1[i] ?? '', w, h, { textScale: config.textScale }) + const col1Function: GridCellFunction = (i, j, w, h): string => centeredText(col1[i] ?? '', w, h, + { textScale: config.textScale }) - const col2Function: GridCellFunction = (i, j, w, h): string => leftAlignedText(col2[i] ?? '', w, h, { textScale: config.textScale }) + const col2Function: GridCellFunction = (i, j, w, h): string => leftAlignedText(col2[i] ?? '', w, h, + { textScale: config.textScale }) const arr: (GridCellFunction | GridCellObject)[] = [] for (let i: number = 0; i < this.entries; i++) { @@ -62,7 +68,10 @@ export class List { } const grid = new Grid(this.width + config.margin * 2, this.height + config.margin * 2, this.columnProportions, - new Array(this.entries).fill(1), { background: this.background, margin: config.margin }) + new Array(this.entries).fill(1), { + background: this.background, + margin: config.margin + }) return ` ${grid.constructXml(arr)} ` diff --git a/plugins/ui/utils/ManialinkListener.ts b/plugins/ui/utils/ManialinkListener.ts index 6c3d9bee..c390f757 100644 --- a/plugins/ui/utils/ManialinkListener.ts +++ b/plugins/ui/utils/ManialinkListener.ts @@ -1,4 +1,8 @@ -let listeners: { callback: ((info: tm.ManialinkClickInfo, actionIdOffset: number) => void), actionId: number, range?: number }[] = [] +let listeners: { + callback: ((info: tm.ManialinkClickInfo, actionIdOffset: number) => void), + actionId: number, + range?: number +}[] = [] /** * Adds a callback function to execute on given Action ID. @@ -12,14 +16,22 @@ function addManialinkListener(actionId: number, callback: (info: tm.ManialinkCli * @param range Range of Action IDs (inludes starting Action ID) * @param callback Callback function, it takes ManialinkClickInfo and id offset as a parameter */ -function addManialinkListener(actionId: number, range: number, callback: (info: tm.ManialinkClickInfo, actionIdOffset: number) => void): void +function addManialinkListener(actionId: number, range: number, + callback: (info: tm.ManialinkClickInfo, actionIdOffset: number) => void): void function addManialinkListener(actionId: number, rangeOrCallback: ((info: tm.ManialinkClickInfo) => void) | number, callback?: (info: tm.ManialinkClickInfo, actionIdOffset: number) => void): void { if (typeof rangeOrCallback === 'number') { if (rangeOrCallback < 1) { throw new Error('Manialink listener range must be > 0') } - listeners.push({ actionId, range: rangeOrCallback, callback: callback as any }) + listeners.push({ + actionId, + range: rangeOrCallback, + callback: callback as any + }) } else { - listeners.push({ actionId, callback: rangeOrCallback }) + listeners.push({ + actionId, + callback: rangeOrCallback + }) } } diff --git a/plugins/ui/utils/Navbar.ts b/plugins/ui/utils/Navbar.ts index 09324a39..5a0613ac 100644 --- a/plugins/ui/utils/Navbar.ts +++ b/plugins/ui/utils/Navbar.ts @@ -10,7 +10,11 @@ export default class Navbar { readonly width: number /** Navbar height */ readonly height: number - private buttons: { name: string, actionId: number, privilege: number }[] + private buttons: { + name: string, + actionId: number, + privilege: number + }[] /** Background image displayed on button hover */ readonly hoverImage: string /** Background colour */ @@ -25,13 +29,17 @@ export default class Navbar { * @param hoverImgUrl Background image to display on button hover */ constructor(buttons: { - name: string, actionId: number, - privilege?: number - }[], width: number, height: number | null = config.height, - background: string = config.background, hoverImgUrl: string = config.hoverImage) { + name: string, + actionId: number, + privilege?: number + }[], width: number, height: number | null = config.height, background: string = config.background, + hoverImgUrl: string = config.hoverImage) { this.width = width this.height = height ?? config.height - this.buttons = buttons.map(a => ({ ...a, privilege: a.privilege ?? 0 })) + this.buttons = buttons.map(a => ({ + ...a, + privilege: a.privilege ?? 0 + })) this.hoverImage = hoverImgUrl this.background = background } diff --git a/plugins/ui/utils/Paginator.config.js b/plugins/ui/utils/Paginator.config.js index 73f89b57..78571c7a 100644 --- a/plugins/ui/utils/Paginator.config.js +++ b/plugins/ui/utils/Paginator.config.js @@ -5,21 +5,9 @@ export default { buttonHeight: 3, margin: 0.4, padding: 0.2, - background: "000D", - icons: [ - icons.pageFirst, - icons.pageDoubleLeft, - icons.pageLeft, - icons.pageRight, - icons.pageDoubleRight, - icons.pageLast - ], - iconsHover: [ - icons.pageFirstHover, - icons.pageDoubleLeftHover, - icons.pageLeftHover, - icons.pageRightHover, - icons.pageDoubleRightHover, - icons.pageLastHover - ] -} \ No newline at end of file + background: '000D', + icons: [icons.pageFirst, icons.pageDoubleLeft, icons.pageLeft, icons.pageRight, icons.pageDoubleRight, + icons.pageLast], + iconsHover: [icons.pageFirstHover, icons.pageDoubleLeftHover, icons.pageLeftHover, icons.pageRightHover, + icons.pageDoubleRightHover, icons.pageLastHover] +} diff --git a/plugins/ui/utils/Paginator.ts b/plugins/ui/utils/Paginator.ts index 16a5060d..b7eee7d4 100644 --- a/plugins/ui/utils/Paginator.ts +++ b/plugins/ui/utils/Paginator.ts @@ -12,7 +12,10 @@ export default class Paginator { private _buttonCount: number = 0 /** Parent element ID */ readonly parentId: number - private readonly loginPages: { readonly login: string, page: number }[] = [] + private readonly loginPages: { + readonly login: string, + page: number + }[] = [] /** Default page (will be displayed if no page is specified) */ defaultPage: number /** Button width */ @@ -53,7 +56,8 @@ export default class Paginator { * @param defaultPage Default page (will be displayed if no page is specified) * @param noMidGap If true there will be no bonus gap between previous and next buttons */ - constructor(parentId: number, parentWidth: number, parentHeight: number, pageCount: number, defaultPage: number = 1, noMidGap?: true) { + constructor(parentId: number, parentWidth: number, parentHeight: number, pageCount: number, defaultPage: number = 1, + noMidGap?: true) { this.parentId = parentId this.width = parentWidth this.height = parentHeight @@ -62,23 +66,15 @@ export default class Paginator { this.yPos = -(parentHeight / 2) this.noMidGap = noMidGap ?? false if (noMidGap === undefined) { - this.xPos = [ - parentWidth / 2 - (this.buttonW + this.margin) * 3, - parentWidth / 2 - (this.buttonW + this.margin) * 2, - parentWidth / 2 - (this.buttonW + this.margin) * 1, - parentWidth / 2 + (this.buttonW + this.margin) * 1, - parentWidth / 2 + (this.buttonW + this.margin) * 2, - parentWidth / 2 + (this.buttonW + this.margin) * 3, - ] + this.xPos = [parentWidth / 2 - (this.buttonW + this.margin) * 3, + parentWidth / 2 - (this.buttonW + this.margin) * 2, parentWidth / 2 - (this.buttonW + this.margin) * 1, + parentWidth / 2 + (this.buttonW + this.margin) * 1, parentWidth / 2 + (this.buttonW + this.margin) * 2, + parentWidth / 2 + (this.buttonW + this.margin) * 3] } else { - this.xPos = [ - parentWidth / 2 - (this.buttonW + this.margin) * 2.5, - parentWidth / 2 - (this.buttonW + this.margin) * 1.5, - parentWidth / 2 - (this.buttonW + this.margin) * 0.5, - parentWidth / 2 + (this.buttonW + this.margin) * 0.5, - parentWidth / 2 + (this.buttonW + this.margin) * 1.5, - parentWidth / 2 + (this.buttonW + this.margin) * 2.5, - ] + this.xPos = [parentWidth / 2 - (this.buttonW + this.margin) * 2.5, + parentWidth / 2 - (this.buttonW + this.margin) * 1.5, parentWidth / 2 - (this.buttonW + this.margin) * 0.5, + parentWidth / 2 + (this.buttonW + this.margin) * 0.5, parentWidth / 2 + (this.buttonW + this.margin) * 1.5, + parentWidth / 2 + (this.buttonW + this.margin) * 2.5] } this.ids = Object.entries(ID).map(a => this.parentId + a[1]) if (pageCount > 1) { this._buttonCount = 1 } @@ -89,7 +85,10 @@ export default class Paginator { const playerPage = this.loginPages.find(a => a.login === info.login) if (playerPage === undefined) { // Should never happen const page: number = this.getPageFromClick(info.actionId, this.defaultPage) - this.loginPages.push({ login: info.login, page: page }) + this.loginPages.push({ + login: info.login, + page: page + }) this._onPageChange(info.login, page, info) return } @@ -137,7 +136,10 @@ export default class Paginator { if (page > this._pageCount) { page = this._pageCount } if (page < 1) { page = 1 } if (loginPage === undefined) { - this.loginPages.push({ login, page }) + this.loginPages.push({ + login, + page + }) } else { loginPage.page = page } @@ -168,7 +170,7 @@ export default class Paginator { } private getPageFromClick(id: number, page: number): number { - switch (id) { + switch(id) { case this.parentId + ID.previous: page-- if (page < 1) { return 1 } @@ -178,7 +180,8 @@ export default class Paginator { page++ if (page > lastPage) { return lastPage } return page - } case this.parentId + ID.first: + } + case this.parentId + ID.first: return 1 case this.parentId + ID.last: return this._pageCount @@ -240,8 +243,7 @@ export default class Paginator { ` - } - else if (this._buttonCount > 1) { + } else if (this._buttonCount > 1) { xml += `` @@ -265,8 +267,7 @@ export default class Paginator { ` - } - else if (this._buttonCount > 1) { + } else if (this._buttonCount > 1) { xml += `` diff --git a/plugins/ui/utils/PopupWindow.ts b/plugins/ui/utils/PopupWindow.ts index 76311b75..f58d03fd 100644 --- a/plugins/ui/utils/PopupWindow.ts +++ b/plugins/ui/utils/PopupWindow.ts @@ -1,9 +1,8 @@ -import { componentIds } from '../UI.js' +import { centeredText, componentIds } from '../UI.js' import DynamicComponent from './DynamicComponent.js' import utilIds from '../config/UtilIds.js' import Navbar from './Navbar.js' import config from '../config/PopupWindow.js' -import { centeredText } from '../UI.js' /** * Abstract class for manialink popup windows. @@ -50,7 +49,11 @@ export default abstract class PopupWindow extends DynamicCo protected readonly footerHeight: number = config.footerHeight /** Right header part width (usually used for page display) */ protected readonly headerPageWidth: number = config.headerPageWidth - private static readonly playersWithWindowOpen: { login: string, id: number, params: any }[] = [] + private static readonly playersWithWindowOpen: { + login: string, + id: number, + params: any + }[] = [] /** * Abstract class for manialink popup windows @@ -61,9 +64,11 @@ export default abstract class PopupWindow extends DynamicCo * @param windowWidth Window width * @param windowHeight Window height */ - constructor(windowId: number, headerIcon: string, title: string, - navbar: { name: string, actionId: number, privilege?: number }[] = [], - windowWidth: number = config.windowWidth, windowHeight: number = config.windowHeight) { + constructor(windowId: number, headerIcon: string, title: string, navbar: { + name: string, + actionId: number, + privilege?: number + }[] = [], windowWidth: number = config.windowWidth, windowHeight: number = config.windowHeight) { super(componentIds.PopupWindow) this.headerIcon = headerIcon this.title = title @@ -75,14 +80,14 @@ export default abstract class PopupWindow extends DynamicCo this.navbarHeight = this.navbar.height this.contentWidth = windowWidth this.contentHeight = windowHeight - (2 * this.headerHeight + this.navbarHeight + 2 * this.margin); - [this.headerLeft, this.headerRight, this.frameMidTop, - this.frameMidBottom, this.frameBottom, this.noNavbarMidTop, this.noNavbarBottom] = this.constructFrame() + [this.headerLeft, this.headerRight, this.frameMidTop, this.frameMidBottom, this.frameBottom, this.noNavbarMidTop, + this.noNavbarBottom] = this.constructFrame() tm.addListener('ManialinkClick', (info: tm.ManialinkClickInfo): void => { if (info.actionId === this.openId) { - tm.log.trace(`Player ${tm.utils.strip(info.nickname)} (${info.login}) opened ${this.constructor.name} manialink.`) + tm.log.trace( + `Player ${tm.utils.strip(info.nickname)} (${info.login}) opened ${this.constructor.name} manialink.`) this.onOpen(info) - } - else if (info.actionId === this.closeId) { + } else if (info.actionId === this.closeId) { const index: number = PopupWindow.playersWithWindowOpen.findIndex(a => a.login === info.login) if (index !== -1) { PopupWindow.playersWithWindowOpen.splice(index, 1) @@ -94,7 +99,10 @@ export default abstract class PopupWindow extends DynamicCo const index: number = PopupWindow.playersWithWindowOpen.findIndex(a => a.login === info.login) if (index !== -1) { PopupWindow.playersWithWindowOpen.splice(index, 1) - this.onClose({ ...info, actionId: this.closeId }) + this.onClose({ + ...info, + actionId: this.closeId + }) } }) } @@ -122,41 +130,34 @@ export default abstract class PopupWindow extends DynamicCo -