import { e_server_types_enum } from "~/generated/zeus";
import { effectivePluginRuntime } from "~/constants/rconCommands";
-
-// both plugins release out of the one repo, so the tag carries the framework
-const PLUGIN_RELEASE_TAG_PREFIXES: Record = {
- counterstrikesharp: "css",
- swiftlys2: "sw",
-};
+import { pluginReleaseUrl } from "~/constants/gameServerReleases";
export default {
props: {
@@ -79,15 +74,13 @@ export default {
return useApplicationSettingsStore().gameServerPluginRuntime;
},
pluginReleaseLink() {
- const prefix =
- PLUGIN_RELEASE_TAG_PREFIXES[
- effectivePluginRuntime(
- this.server.plugin_runtime,
- this.gameServerPluginRuntime,
- )
- ];
-
- return `https://github.com/5stackgg/game-server/releases/tag/${prefix}-v${this.currentPluginVersion}`;
+ return pluginReleaseUrl(
+ effectivePluginRuntime(
+ this.server.plugin_runtime,
+ this.gameServerPluginRuntime,
+ ),
+ this.currentPluginVersion,
+ );
},
pluginVersionMismatch() {
if (this.server.type !== e_server_types_enum.Ranked) {
diff --git a/constants/gameServerReleases.ts b/constants/gameServerReleases.ts
new file mode 100644
index 00000000..8d86b3a0
--- /dev/null
+++ b/constants/gameServerReleases.ts
@@ -0,0 +1,62 @@
+export const GAME_SERVER_RELEASES_URL =
+ "https://github.com/5stackgg/game-server/releases";
+
+// Both plugins release out of the one repo, so the tag carries the framework.
+const PLUGIN_RELEASE_TAG_PREFIXES: Record = {
+ counterstrikesharp: "css",
+ swiftlys2: "sw",
+};
+
+export const PLAYER_MANAGEMENT_CONFIG_PATHS: Record = {
+ counterstrikesharp:
+ "addons/counterstrikesharp/configs/plugins/PlayerManagement/PlayerManagement.json",
+ swiftlys2: "addons/swiftlys2/configs/plugins/PlayerManagement/config.jsonc",
+};
+
+function pluginReleaseTag(runtime: string, version: string) {
+ return `${PLUGIN_RELEASE_TAG_PREFIXES[runtime] ?? "sw"}-v${version}`;
+}
+
+export function pluginReleaseUrl(runtime: string, version: string) {
+ return `${GAME_SERVER_RELEASES_URL}/tag/${pluginReleaseTag(runtime, version)}`;
+}
+
+// The player management zip is published on the same release as the match
+// plugin, named after its tag, so a version is all a link needs.
+export function playerManagementDownloadUrl(
+ runtime: string,
+ version: string | null | undefined,
+) {
+ if (!version) {
+ return GAME_SERVER_RELEASES_URL;
+ }
+
+ const tag = pluginReleaseTag(runtime, version);
+
+ return `${GAME_SERVER_RELEASES_URL}/download/${tag}/PlayerManagement-${tag}.zip`;
+}
+
+// SwiftlyS2 binds the "PlayerManagement" section of config.jsonc, while
+// CounterStrikeSharp loads PlayerManagement.json as a flat object.
+export function playerManagementConfig(
+ runtime: string,
+ settings: {
+ apiDomain: string;
+ serverId: string;
+ apiPassword: string | null | undefined;
+ },
+) {
+ const config = {
+ API_DOMAIN: settings.apiDomain,
+ SERVER_ID: settings.serverId,
+ SERVER_API_PASSWORD: settings.apiPassword ?? "",
+ };
+
+ return JSON.stringify(
+ runtime === "counterstrikesharp"
+ ? { Version: 1, ...config }
+ : { PlayerManagement: config },
+ null,
+ 2,
+ );
+}
diff --git a/i18n/locales/ar_SA.json b/i18n/locales/ar_SA.json
index c45f9ff2..0ebf1403 100644
--- a/i18n/locales/ar_SA.json
+++ b/i18n/locales/ar_SA.json
@@ -3675,8 +3675,18 @@
"files": "ملفات",
"player_management": "إدارة اللاعبين",
"no_players": "لا يوجد لاعبون على هذا السيرفر حاليًا",
- "sanctions_wip_title": "قيد التطوير",
- "sanctions_wip": "الطرد يعمل اليوم. أما كتم الصوت والدردشة المباشر على السيرفرات العامة فما زال قيد التطوير — إضافة 5Stack جديدة للسيرفرات العامة قادمة قريبًا، وسيُطبَّق الكتم تلقائيًا بمجرد إطلاقها.",
+ "player_management_plugin": {
+ "active": "إضافة إدارة اللاعبين نشطة",
+ "not_detected": "لم تتصل إضافة إدارة اللاعبين من هذا الخادم بعد، لذلك لا يُطبَّق الكتم والمنع من الكتابة مباشرةً حتى الآن.",
+ "not_detected_node": "لم تتصل إضافة إدارة اللاعبين بعد. تحصل الخوادم التي أُنشئت قبل إصدارها عليها بعد إعادة النشر: أوقف تشغيل الخادم ثم شغّله مجددًا.",
+ "not_detected_external": "لا يُطبَّق الكتم والمنع من الكتابة والحظر مباشرةً إلا بعد تثبيت إضافة إدارة اللاعبين من 5Stack على هذا الخادم.",
+ "install": "تثبيت الإضافة",
+ "install_steps": "خطوات التثبيت",
+ "step_download": "نزّل الإضافة الخاصة بإطار العمل الذي يعمل به خادمك.",
+ "step_extract": "فك ضغط الملف في مجلد game/csgo على الخادم.",
+ "step_config": "احفظ هذا الإعداد على الخادم في:",
+ "step_restart": "أعد تشغيل الخادم. ستظهر الإضافة هنا كنشطة خلال دقيقة."
+ },
"status": {
"disconnected": "السيرفر غير متصل",
"no_rcon": "السيرفر متصل لكن RCON غير متاح",
diff --git a/i18n/locales/da_DK.json b/i18n/locales/da_DK.json
index 8ebcf446..153d3d11 100644
--- a/i18n/locales/da_DK.json
+++ b/i18n/locales/da_DK.json
@@ -3675,8 +3675,18 @@
"files": "Filer",
"player_management": "Spillerstyring",
"no_players": "Ingen spillere på denne server lige nu",
- "sanctions_wip_title": "Under udvikling",
- "sanctions_wip": "Kick virker allerede. Live mute/gag på offentlige servere er stadig under udvikling — et nyt offentligt 5Stack-serverplugin er på vej, og mutes/gags håndhæves automatisk, når det udkommer.",
+ "player_management_plugin": {
+ "active": "Player Management-plugin aktivt",
+ "not_detected": "Player Management-pluginet har ikke meldt sig fra denne server, så mutes og gags håndhæves ikke live endnu.",
+ "not_detected_node": "Player Management-pluginet har ikke meldt sig. Servere oprettet før det kom, får det, når de udrulles igen: slå serveren fra og til igen.",
+ "not_detected_external": "Mutes, gags og bans håndhæves først live, når 5Stack Player Management-pluginet er installeret på denne server.",
+ "install": "Installer plugin",
+ "install_steps": "Installationstrin",
+ "step_download": "Download pluginet til det framework, din server kører.",
+ "step_extract": "Pak zip-filen ud i serverens game/csgo-mappe.",
+ "step_config": "Gem denne konfiguration på serveren her:",
+ "step_restart": "Genstart serveren. Pluginet vises som aktivt her inden for et minut."
+ },
"status": {
"disconnected": "Serveren er afbrudt",
"no_rcon": "Serveren er forbundet, men RCON er ikke tilgængelig",
diff --git a/i18n/locales/de_DE.json b/i18n/locales/de_DE.json
index d8863d80..a2970d88 100644
--- a/i18n/locales/de_DE.json
+++ b/i18n/locales/de_DE.json
@@ -3675,8 +3675,18 @@
"files": "Dateien",
"player_management": "Spielerverwaltung",
"no_players": "Aktuell keine Spieler auf diesem Server",
- "sanctions_wip_title": "In Arbeit",
- "sanctions_wip": "Kicks funktionieren bereits. Live-Mutes und -Gags auf öffentlichen Servern sind noch in Arbeit — ein neues öffentliches 5Stack-Server-Plugin kommt bald, danach greifen Mutes und Gags automatisch.",
+ "player_management_plugin": {
+ "active": "Player-Management-Plugin aktiv",
+ "not_detected": "Das Player-Management-Plugin hat sich von diesem Server noch nicht gemeldet, daher werden Stummschaltungen und Chatsperren noch nicht live durchgesetzt.",
+ "not_detected_node": "Das Player-Management-Plugin hat sich noch nicht gemeldet. Server, die vor seiner Veröffentlichung erstellt wurden, erhalten es nach einer Neubereitstellung: Schalte den Server aus und wieder ein.",
+ "not_detected_external": "Stummschaltungen, Chatsperren und Banns werden erst live durchgesetzt, wenn das 5Stack-Player-Management-Plugin auf diesem Server installiert ist.",
+ "install": "Plugin installieren",
+ "install_steps": "Installationsschritte",
+ "step_download": "Lade das Plugin für das Framework herunter, das auf deinem Server läuft.",
+ "step_extract": "Entpacke die ZIP-Datei in den Ordner game/csgo des Servers.",
+ "step_config": "Speichere diese Konfiguration auf dem Server unter:",
+ "step_restart": "Starte den Server neu. Das Plugin wird hier innerhalb einer Minute als aktiv angezeigt."
+ },
"status": {
"disconnected": "Server ist getrennt",
"no_rcon": "Server ist verbunden, aber RCON ist nicht verfügbar",
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index 5a06132e..b12a1424 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -3675,8 +3675,18 @@
"files": "Files",
"player_management": "Player Management",
"no_players": "No players currently on this server",
- "sanctions_wip_title": "Work in progress",
- "sanctions_wip": "Kicks work today. Live mute/gag enforcement on public servers is a work in progress — a new 5Stack public server plugin is coming soon, and mutes/gags will apply automatically once it ships.",
+ "player_management_plugin": {
+ "active": "Player Management plugin active",
+ "not_detected": "The Player Management plugin has not checked in from this server, so mutes and gags are not enforced live yet.",
+ "not_detected_node": "The Player Management plugin has not checked in. Servers created before it shipped get it once redeployed: switch the server off and back on.",
+ "not_detected_external": "Mutes, gags and bans are only enforced live once the 5Stack Player Management plugin is installed on this server.",
+ "install": "Install Plugin",
+ "install_steps": "Install Steps",
+ "step_download": "Download the plugin for the framework your server runs.",
+ "step_extract": "Extract the zip into the server's game/csgo folder.",
+ "step_config": "Save this config on the server at:",
+ "step_restart": "Restart the server. The plugin shows as active here within a minute."
+ },
"status": {
"disconnected": "Server is disconnected",
"no_rcon": "Server is connected but RCON is not available",
diff --git a/i18n/locales/es_ES.json b/i18n/locales/es_ES.json
index 8024b13f..a1fcfbd7 100644
--- a/i18n/locales/es_ES.json
+++ b/i18n/locales/es_ES.json
@@ -3675,8 +3675,18 @@
"files": "Archivos",
"player_management": "Gestión de jugadores",
"no_players": "Ahora mismo no hay jugadores en este servidor",
- "sanctions_wip_title": "En desarrollo",
- "sanctions_wip": "Los kicks ya funcionan. El silenciado y el gag en directo en servidores públicos siguen en desarrollo: pronto llegará un nuevo plugin público de 5Stack y, en cuanto salga, los mutes y gags se aplicarán automáticamente.",
+ "player_management_plugin": {
+ "active": "Plugin de gestión de jugadores activo",
+ "not_detected": "El plugin de gestión de jugadores aún no se ha comunicado desde este servidor, así que los muteos y silencios todavía no se aplican en directo.",
+ "not_detected_node": "El plugin de gestión de jugadores aún no se ha comunicado. Los servidores creados antes de su lanzamiento lo reciben al volver a desplegarse: desactiva el servidor y vuelve a activarlo.",
+ "not_detected_external": "Los muteos, silencios y baneos solo se aplican en directo cuando el plugin de gestión de jugadores de 5Stack está instalado en este servidor.",
+ "install": "Instalar plugin",
+ "install_steps": "Pasos de instalación",
+ "step_download": "Descarga el plugin para el framework que usa tu servidor.",
+ "step_extract": "Extrae el zip en la carpeta game/csgo del servidor.",
+ "step_config": "Guarda esta configuración en el servidor en:",
+ "step_restart": "Reinicia el servidor. El plugin aparecerá aquí como activo en menos de un minuto."
+ },
"status": {
"disconnected": "El servidor está desconectado",
"no_rcon": "El servidor está conectado, pero RCON no está disponible",
diff --git a/i18n/locales/fr_FR.json b/i18n/locales/fr_FR.json
index b373bb08..353e4054 100644
--- a/i18n/locales/fr_FR.json
+++ b/i18n/locales/fr_FR.json
@@ -3675,8 +3675,18 @@
"files": "Fichiers",
"player_management": "Gestion des joueurs",
"no_players": "Aucun joueur actuellement sur ce serveur",
- "sanctions_wip_title": "En cours de développement",
- "sanctions_wip": "Les kicks fonctionnent déjà. Le mute/gag en direct sur les serveurs publics est encore en cours de développement — un nouveau plugin de serveur public 5Stack arrive bientôt, et les mutes/gags s'appliqueront automatiquement dès sa sortie.",
+ "player_management_plugin": {
+ "active": "Plugin de gestion des joueurs actif",
+ "not_detected": "Le plugin de gestion des joueurs ne s'est pas encore signalé depuis ce serveur, les mutes et bâillons ne sont donc pas encore appliqués en direct.",
+ "not_detected_node": "Le plugin de gestion des joueurs ne s'est pas encore signalé. Les serveurs créés avant sa sortie le reçoivent une fois redéployés : désactivez puis réactivez le serveur.",
+ "not_detected_external": "Les mutes, bâillons et bannissements ne sont appliqués en direct qu'une fois le plugin de gestion des joueurs 5Stack installé sur ce serveur.",
+ "install": "Installer le plugin",
+ "install_steps": "Étapes d'installation",
+ "step_download": "Téléchargez le plugin pour le framework utilisé par votre serveur.",
+ "step_extract": "Extrayez le zip dans le dossier game/csgo du serveur.",
+ "step_config": "Enregistrez cette configuration sur le serveur à l'emplacement :",
+ "step_restart": "Redémarrez le serveur. Le plugin apparaîtra ici comme actif en moins d'une minute."
+ },
"status": {
"disconnected": "Le serveur est déconnecté",
"no_rcon": "Le serveur est connecté mais RCON n'est pas disponible",
diff --git a/i18n/locales/it_IT.json b/i18n/locales/it_IT.json
index a3d9bc9b..7777cb39 100644
--- a/i18n/locales/it_IT.json
+++ b/i18n/locales/it_IT.json
@@ -3675,8 +3675,18 @@
"files": "File",
"player_management": "Gestione giocatori",
"no_players": "Nessun giocatore attualmente su questo server",
- "sanctions_wip_title": "Lavori in corso",
- "sanctions_wip": "I kick funzionano già. Il mute/gag live sui server pubblici è ancora in lavorazione: presto arriverà un nuovo plugin 5Stack per i server pubblici e mute e gag verranno applicati automaticamente.",
+ "player_management_plugin": {
+ "active": "Plugin di gestione giocatori attivo",
+ "not_detected": "Il plugin di gestione giocatori non ha ancora comunicato da questo server, quindi mute e gag non vengono ancora applicati in tempo reale.",
+ "not_detected_node": "Il plugin di gestione giocatori non ha ancora comunicato. I server creati prima del suo rilascio lo ricevono dopo un nuovo deploy: disattiva e riattiva il server.",
+ "not_detected_external": "Mute, gag e ban vengono applicati in tempo reale solo dopo aver installato il plugin di gestione giocatori di 5Stack su questo server.",
+ "install": "Installa plugin",
+ "install_steps": "Passaggi di installazione",
+ "step_download": "Scarica il plugin per il framework usato dal tuo server.",
+ "step_extract": "Estrai lo zip nella cartella game/csgo del server.",
+ "step_config": "Salva questa configurazione sul server in:",
+ "step_restart": "Riavvia il server. Il plugin risulterà attivo qui entro un minuto."
+ },
"status": {
"disconnected": "Il server è disconnesso",
"no_rcon": "Il server è connesso ma RCON non è disponibile",
diff --git a/i18n/locales/ja_JP.json b/i18n/locales/ja_JP.json
index 1fc45029..c6917a2b 100644
--- a/i18n/locales/ja_JP.json
+++ b/i18n/locales/ja_JP.json
@@ -3675,8 +3675,18 @@
"files": "ファイル",
"player_management": "プレイヤー管理",
"no_players": "現在このサーバーにプレイヤーはいません",
- "sanctions_wip_title": "対応中の機能です",
- "sanctions_wip": "キックは現時点で利用できます。パブリックサーバーでのミュート・ゲグの即時反映は現在対応中です。新しい5Stackパブリックサーバープラグインが近日公開予定で、リリース後はミュート・ゲグも自動的に適用されます。",
+ "player_management_plugin": {
+ "active": "プレイヤー管理プラグイン稼働中",
+ "not_detected": "このサーバーからプレイヤー管理プラグインの応答がまだないため、ミュートとチャット禁止はまだリアルタイムで適用されません。",
+ "not_detected_node": "プレイヤー管理プラグインの応答がまだありません。公開前に作成したサーバーは再デプロイで導入されます:サーバーを一度無効にしてから再度有効にしてください。",
+ "not_detected_external": "ミュート、チャット禁止、BANは、このサーバーに5Stackのプレイヤー管理プラグインをインストールするまでリアルタイムで適用されません。",
+ "install": "プラグインをインストール",
+ "install_steps": "インストール手順",
+ "step_download": "サーバーで使用しているフレームワーク用のプラグインをダウンロードします。",
+ "step_extract": "zipをサーバーのgame/csgoフォルダーに展開します。",
+ "step_config": "この設定をサーバーの次の場所に保存します:",
+ "step_restart": "サーバーを再起動します。1分以内にここで稼働中と表示されます。"
+ },
"status": {
"disconnected": "サーバーは切断されています",
"no_rcon": "サーバーは接続していますがRCONが利用できません",
diff --git a/i18n/locales/ko_KR.json b/i18n/locales/ko_KR.json
index 9b7deb1a..1ee12192 100644
--- a/i18n/locales/ko_KR.json
+++ b/i18n/locales/ko_KR.json
@@ -3675,8 +3675,18 @@
"files": "파일",
"player_management": "플레이어 관리",
"no_players": "현재 이 서버에 접속 중인 플레이어가 없습니다",
- "sanctions_wip_title": "개발 진행 중",
- "sanctions_wip": "킥 기능은 현재 정상 작동합니다. 퍼블릭 서버의 실시간 뮤트/각 적용은 아직 개발 중입니다. 새로운 5Stack 퍼블릭 서버 플러그인이 곧 출시되며, 출시 후에는 뮤트/각이 자동으로 적용됩니다.",
+ "player_management_plugin": {
+ "active": "플레이어 관리 플러그인 활성",
+ "not_detected": "이 서버에서 플레이어 관리 플러그인이 아직 응답하지 않아 음성 및 채팅 차단이 아직 실시간으로 적용되지 않습니다.",
+ "not_detected_node": "플레이어 관리 플러그인이 아직 응답하지 않았습니다. 플러그인 출시 전에 만든 서버는 다시 배포해야 적용됩니다: 서버를 껐다가 다시 켜세요.",
+ "not_detected_external": "음성 차단, 채팅 차단, 밴은 이 서버에 5Stack 플레이어 관리 플러그인을 설치해야 실시간으로 적용됩니다.",
+ "install": "플러그인 설치",
+ "install_steps": "설치 단계",
+ "step_download": "서버에서 사용하는 프레임워크용 플러그인을 다운로드하세요.",
+ "step_extract": "zip 파일을 서버의 game/csgo 폴더에 압축 해제하세요.",
+ "step_config": "이 설정을 서버의 다음 위치에 저장하세요:",
+ "step_restart": "서버를 다시 시작하세요. 1분 안에 여기에 활성으로 표시됩니다."
+ },
"status": {
"disconnected": "서버 연결이 끊겼습니다",
"no_rcon": "서버는 연결되었지만 RCON을 사용할 수 없습니다",
diff --git a/i18n/locales/pl_PL.json b/i18n/locales/pl_PL.json
index 531fd8db..b0f18b59 100644
--- a/i18n/locales/pl_PL.json
+++ b/i18n/locales/pl_PL.json
@@ -3675,8 +3675,18 @@
"files": "Pliki",
"player_management": "Zarządzanie graczami",
"no_players": "Obecnie brak graczy na tym serwerze",
- "sanctions_wip_title": "W trakcie prac",
- "sanctions_wip": "Kicki działają już teraz. Egzekwowanie wyciszeń na żywo na serwerach publicznych jest w trakcie prac — nowy publiczny plugin 5Stack pojawi się wkrótce, a wyciszenia zaczną obowiązywać automatycznie po jego wydaniu.",
+ "player_management_plugin": {
+ "active": "Wtyczka zarządzania graczami aktywna",
+ "not_detected": "Wtyczka zarządzania graczami nie zgłosiła się jeszcze z tego serwera, więc wyciszenia i blokady czatu nie są jeszcze egzekwowane na żywo.",
+ "not_detected_node": "Wtyczka zarządzania graczami jeszcze się nie zgłosiła. Serwery utworzone przed jej wydaniem otrzymają ją po ponownym wdrożeniu: wyłącz serwer i włącz go ponownie.",
+ "not_detected_external": "Wyciszenia, blokady czatu i bany są egzekwowane na żywo dopiero po zainstalowaniu wtyczki zarządzania graczami 5Stack na tym serwerze.",
+ "install": "Zainstaluj wtyczkę",
+ "install_steps": "Kroki instalacji",
+ "step_download": "Pobierz wtyczkę dla frameworka, na którym działa twój serwer.",
+ "step_extract": "Rozpakuj plik zip do folderu game/csgo serwera.",
+ "step_config": "Zapisz tę konfigurację na serwerze w:",
+ "step_restart": "Zrestartuj serwer. Wtyczka pojawi się tutaj jako aktywna w ciągu minuty."
+ },
"status": {
"disconnected": "Serwer jest rozłączony",
"no_rcon": "Serwer jest połączony, ale RCON jest niedostępny",
diff --git a/i18n/locales/pt_BR.json b/i18n/locales/pt_BR.json
index 68d8ef93..a9ebc6ef 100644
--- a/i18n/locales/pt_BR.json
+++ b/i18n/locales/pt_BR.json
@@ -3675,8 +3675,18 @@
"files": "Arquivos",
"player_management": "Gerenciamento de Jogadores",
"no_players": "Nenhum jogador atualmente neste servidor",
- "sanctions_wip_title": "Em desenvolvimento",
- "sanctions_wip": "Expulsões já funcionam hoje. A aplicação de mudo/gag ao vivo em servidores públicos está em desenvolvimento — um novo plugin público do 5Stack está chegando em breve, e os mutes/gags serão aplicados automaticamente assim que for lançado.",
+ "player_management_plugin": {
+ "active": "Plugin de gerenciamento de jogadores ativo",
+ "not_detected": "O plugin de gerenciamento de jogadores ainda não se comunicou a partir deste servidor, então mutes e silenciamentos ainda não são aplicados ao vivo.",
+ "not_detected_node": "O plugin de gerenciamento de jogadores ainda não se comunicou. Servidores criados antes do lançamento o recebem ao serem reimplantados: desative o servidor e ative-o novamente.",
+ "not_detected_external": "Mutes, silenciamentos e banimentos só são aplicados ao vivo depois que o plugin de gerenciamento de jogadores da 5Stack é instalado neste servidor.",
+ "install": "Instalar plugin",
+ "install_steps": "Passos de instalação",
+ "step_download": "Baixe o plugin para o framework que seu servidor usa.",
+ "step_extract": "Extraia o zip na pasta game/csgo do servidor.",
+ "step_config": "Salve esta configuração no servidor em:",
+ "step_restart": "Reinicie o servidor. O plugin aparecerá aqui como ativo em até um minuto."
+ },
"status": {
"disconnected": "O servidor está desconectado",
"no_rcon": "O servidor está conectado, mas o RCON não está disponível",
diff --git a/i18n/locales/ru_RU.json b/i18n/locales/ru_RU.json
index 7d354ba4..6ffec69b 100644
--- a/i18n/locales/ru_RU.json
+++ b/i18n/locales/ru_RU.json
@@ -3675,8 +3675,18 @@
"files": "Файлы",
"player_management": "Управление игроками",
"no_players": "На этом сервере сейчас нет игроков",
- "sanctions_wip_title": "В разработке",
- "sanctions_wip": "Кики работают уже сейчас. Применение мута/гага в реальном времени на публичных серверах пока в разработке — скоро выйдет новый публичный плагин 5Stack, и муты/гаги начнут применяться автоматически после его выпуска.",
+ "player_management_plugin": {
+ "active": "Плагин управления игроками активен",
+ "not_detected": "Плагин управления игроками ещё не отозвался с этого сервера, поэтому заглушение и запрет чата пока не применяются в реальном времени.",
+ "not_detected_node": "Плагин управления игроками ещё не отозвался. Серверы, созданные до его выхода, получат его после повторного развёртывания: выключите сервер и включите его снова.",
+ "not_detected_external": "Заглушение, запрет чата и баны применяются в реальном времени только после установки плагина управления игроками 5Stack на этот сервер.",
+ "install": "Установить плагин",
+ "install_steps": "Шаги установки",
+ "step_download": "Скачайте плагин для фреймворка, на котором работает ваш сервер.",
+ "step_extract": "Распакуйте zip в папку game/csgo сервера.",
+ "step_config": "Сохраните эту конфигурацию на сервере по пути:",
+ "step_restart": "Перезапустите сервер. Плагин появится здесь как активный в течение минуты."
+ },
"status": {
"disconnected": "Сервер отключён",
"no_rcon": "Сервер подключён, но RCON недоступен",
diff --git a/i18n/locales/sv_SE.json b/i18n/locales/sv_SE.json
index 637e1fba..e5335cf8 100644
--- a/i18n/locales/sv_SE.json
+++ b/i18n/locales/sv_SE.json
@@ -3675,8 +3675,18 @@
"files": "Filer",
"player_management": "Spelarhantering",
"no_players": "Inga spelare på servern just nu",
- "sanctions_wip_title": "Under utveckling",
- "sanctions_wip": "Kickar fungerar redan idag. Live mute/gag-tillämpning på publika servrar är under utveckling — en ny 5Stack-serverplugin är på gång, och mute/gag kommer att tillämpas automatiskt när den släpps.",
+ "player_management_plugin": {
+ "active": "Plugin för spelarhantering aktivt",
+ "not_detected": "Pluginet för spelarhantering har inte hört av sig från den här servern ännu, så tystningar och chattförbud tillämpas inte live än.",
+ "not_detected_node": "Pluginet för spelarhantering har inte hört av sig ännu. Servrar som skapades innan det släpptes får det när de driftsätts om: stäng av servern och slå på den igen.",
+ "not_detected_external": "Tystningar, chattförbud och avstängningar tillämpas live först när 5Stacks plugin för spelarhantering är installerat på den här servern.",
+ "install": "Installera plugin",
+ "install_steps": "Installationssteg",
+ "step_download": "Ladda ner pluginet för det ramverk din server kör.",
+ "step_extract": "Packa upp zip-filen i serverns game/csgo-mapp.",
+ "step_config": "Spara den här konfigurationen på servern i:",
+ "step_restart": "Starta om servern. Pluginet visas som aktivt här inom en minut."
+ },
"status": {
"disconnected": "Servern är frånkopplad",
"no_rcon": "Servern är ansluten men RCON är inte tillgängligt",
diff --git a/i18n/locales/tr_TR.json b/i18n/locales/tr_TR.json
index 24abe40b..37785837 100644
--- a/i18n/locales/tr_TR.json
+++ b/i18n/locales/tr_TR.json
@@ -3675,8 +3675,18 @@
"files": "Dosyalar",
"player_management": "Oyuncu yönetimi",
"no_players": "Bu sunucuda şu anda oyuncu yok",
- "sanctions_wip_title": "Geliştirme aşamasında",
- "sanctions_wip": "Kick işlemi bugün çalışıyor. Herkese açık sunucularda canlı sessize alma/susturma uygulaması henüz geliştirme aşamasında — yeni bir 5Stack herkese açık sunucu eklentisi yakında geliyor, yayınlandığında sessize alma/susturma otomatik olarak uygulanacak.",
+ "player_management_plugin": {
+ "active": "Oyuncu yönetimi eklentisi etkin",
+ "not_detected": "Oyuncu yönetimi eklentisi bu sunucudan henüz bildirim göndermedi, bu yüzden susturmalar ve sohbet yasakları henüz canlı uygulanmıyor.",
+ "not_detected_node": "Oyuncu yönetimi eklentisi henüz bildirim göndermedi. Eklenti yayınlanmadan önce oluşturulan sunucular yeniden dağıtıldığında onu alır: sunucuyu kapatıp yeniden açın.",
+ "not_detected_external": "Susturmalar, sohbet yasakları ve yasaklamalar ancak 5Stack oyuncu yönetimi eklentisi bu sunucuya kurulduğunda canlı uygulanır.",
+ "install": "Eklentiyi Kur",
+ "install_steps": "Kurulum Adımları",
+ "step_download": "Sunucunuzun çalıştırdığı çerçeve için eklentiyi indirin.",
+ "step_extract": "Zip dosyasını sunucunun game/csgo klasörüne çıkarın.",
+ "step_config": "Bu yapılandırmayı sunucuda şu konuma kaydedin:",
+ "step_restart": "Sunucuyu yeniden başlatın. Eklenti bir dakika içinde burada etkin olarak görünür."
+ },
"status": {
"disconnected": "Sunucu bağlı değil",
"no_rcon": "Sunucu bağlı ama RCON kullanılamıyor",
diff --git a/i18n/locales/uk_UA.json b/i18n/locales/uk_UA.json
index b3c807c7..972d0dc6 100644
--- a/i18n/locales/uk_UA.json
+++ b/i18n/locales/uk_UA.json
@@ -3675,8 +3675,18 @@
"files": "Файли",
"player_management": "Керування гравцями",
"no_players": "Наразі на цьому сервері немає гравців",
- "sanctions_wip_title": "У розробці",
- "sanctions_wip": "Кіки вже працюють. Застосування живих мутів/гегів на публічних серверах ще в розробці — новий публічний плагін 5Stack скоро вийде, і мути/геги застосовуватимуться автоматично, щойно він з'явиться.",
+ "player_management_plugin": {
+ "active": "Плагін керування гравцями активний",
+ "not_detected": "Плагін керування гравцями ще не відгукнувся з цього сервера, тому заглушення та заборона чату поки не застосовуються в реальному часі.",
+ "not_detected_node": "Плагін керування гравцями ще не відгукнувся. Сервери, створені до його випуску, отримають його після повторного розгортання: вимкніть сервер і ввімкніть його знову.",
+ "not_detected_external": "Заглушення, заборона чату та бани застосовуються в реальному часі лише після встановлення плагіна керування гравцями 5Stack на цей сервер.",
+ "install": "Встановити плагін",
+ "install_steps": "Кроки встановлення",
+ "step_download": "Завантажте плагін для фреймворку, на якому працює ваш сервер.",
+ "step_extract": "Розпакуйте zip у папку game/csgo сервера.",
+ "step_config": "Збережіть цю конфігурацію на сервері за шляхом:",
+ "step_restart": "Перезапустіть сервер. Плагін з'явиться тут як активний протягом хвилини."
+ },
"status": {
"disconnected": "Сервер відключено",
"no_rcon": "Сервер підключено, але RCON недоступний",
diff --git a/i18n/locales/zh_Hans.json b/i18n/locales/zh_Hans.json
index 7c9eed78..f1156637 100644
--- a/i18n/locales/zh_Hans.json
+++ b/i18n/locales/zh_Hans.json
@@ -3675,8 +3675,18 @@
"files": "文件",
"player_management": "玩家管理",
"no_players": "此服务器上当前无玩家",
- "sanctions_wip_title": "功能开发中",
- "sanctions_wip": "踢出功能现已可用。公共服务器上的实时静音/禁言执行正在开发中——新的 5Stack 公共服务器插件即将推出,上线后静音/禁言将自动应用。",
+ "player_management_plugin": {
+ "active": "玩家管理插件运行中",
+ "not_detected": "玩家管理插件尚未从此服务器报到,因此禁麦和禁言暂时无法实时生效。",
+ "not_detected_node": "玩家管理插件尚未报到。在它发布前创建的服务器需要重新部署才能获得它:请先停用服务器再重新启用。",
+ "not_detected_external": "只有在此服务器上安装 5Stack 玩家管理插件后,禁麦、禁言和封禁才会实时生效。",
+ "install": "安装插件",
+ "install_steps": "安装步骤",
+ "step_download": "下载与服务器所用框架对应的插件。",
+ "step_extract": "将 zip 解压到服务器的 game/csgo 文件夹。",
+ "step_config": "将此配置保存到服务器上的以下位置:",
+ "step_restart": "重启服务器。插件会在一分钟内在此显示为运行中。"
+ },
"status": {
"disconnected": "服务器已断开",
"no_rcon": "服务器已连接但 RCON 不可用",
diff --git a/i18n/locales/zh_Hant.json b/i18n/locales/zh_Hant.json
index 61b076ce..cf2913b3 100644
--- a/i18n/locales/zh_Hant.json
+++ b/i18n/locales/zh_Hant.json
@@ -3675,8 +3675,18 @@
"files": "檔案",
"player_management": "玩家管理",
"no_players": "此伺服器上目前沒有玩家",
- "sanctions_wip_title": "功能開發中",
- "sanctions_wip": "踢出功能現已可用。公開伺服器上的即時靜音/禁言執行仍在開發中——全新的 5Stack 公開伺服器外掛即將推出,上線後靜音/禁言將自動套用。",
+ "player_management_plugin": {
+ "active": "玩家管理外掛運作中",
+ "not_detected": "玩家管理外掛尚未從此伺服器回報,因此禁麥和禁言暫時無法即時生效。",
+ "not_detected_node": "玩家管理外掛尚未回報。在它發布前建立的伺服器需要重新部署才能取得它:請先停用伺服器再重新啟用。",
+ "not_detected_external": "只有在此伺服器上安裝 5Stack 玩家管理外掛後,禁麥、禁言和封鎖才會即時生效。",
+ "install": "安裝外掛",
+ "install_steps": "安裝步驟",
+ "step_download": "下載與伺服器所用框架對應的外掛。",
+ "step_extract": "將 zip 解壓縮到伺服器的 game/csgo 資料夾。",
+ "step_config": "將此設定儲存到伺服器上的以下位置:",
+ "step_restart": "重新啟動伺服器。外掛會在一分鐘內在此顯示為運作中。"
+ },
"status": {
"disconnected": "伺服器已中斷連線",
"no_rcon": "伺服器已連線,但 RCON 無法使用",
diff --git a/pages/dedicated-servers/[id].vue b/pages/dedicated-servers/[id].vue
index 24435634..8e7513e7 100644
--- a/pages/dedicated-servers/[id].vue
+++ b/pages/dedicated-servers/[id].vue
@@ -259,6 +259,9 @@ const titleClasses =
diff --git a/stores/ApplicationSettings.ts b/stores/ApplicationSettings.ts
index 19c88272..2bf68f38 100644
--- a/stores/ApplicationSettings.ts
+++ b/stores/ApplicationSettings.ts
@@ -93,6 +93,15 @@ export const useApplicationSettingsStore = defineStore(
);
});
+ // Newest first, so the first match is the latest release of a runtime.
+ const latestPluginVersion = (runtime: string): string | null => {
+ return (
+ pluginVersions.value.find((pluginVersion) => {
+ return pluginVersion.runtime === runtime;
+ })?.version ?? null
+ );
+ };
+
const subscribeToPluginVersion = async () => {
const { subscribe } = useSubscriptionManager();
const authStore = useAuthStore();
@@ -732,6 +741,7 @@ export const useApplicationSettingsStore = defineStore(
defaultBroadcastHud,
canCreateMatch,
currentPluginVersion,
+ latestPluginVersion,
gameServerPluginRuntime,
settingsLoaded,
brandName,
diff --git a/tests/components/PlayerSanctions.spec.ts b/tests/components/PlayerSanctions.spec.ts
index c97f1092..4bb3c0f6 100644
--- a/tests/components/PlayerSanctions.spec.ts
+++ b/tests/components/PlayerSanctions.spec.ts
@@ -6,6 +6,13 @@ import { useAuthStore } from "~/stores/AuthStore";
import { e_player_roles_enum } from "~/generated/zeus";
const PLAYER = "76561198000000001";
+
+const { toast } = vi.hoisted(() => ({ toast: vi.fn() }));
+
+vi.mock("@/components/ui/toast", async (importOriginal) => ({
+ ...(await importOriginal()),
+ toast,
+}));
const MODERATOR = "76561198000000099";
const AMBER_BAR = "bg-[hsl(var(--tac-amber))]";
@@ -29,6 +36,7 @@ const tomorrow = () => new Date(Date.now() + 86_400_000).toISOString();
const yesterday = () => new Date(Date.now() - 86_400_000).toISOString();
let rows: any[] = [];
+let removal = { enforced: false, message: "ok" };
let mutate: ReturnType;
let unmount: (() => void) | null = null;
@@ -36,6 +44,8 @@ afterEach(() => {
unmount?.();
unmount = null;
useAuthStore().me = undefined;
+ removal = { enforced: false, message: "ok" };
+ toast.mockReset();
vi.restoreAllMocks();
});
@@ -51,12 +61,12 @@ function serve() {
},
}));
mutate = vi.fn().mockResolvedValue({
- data: { unsanctionServerPlayer: { enforced: false, message: "ok" } },
+ data: { unsanctionServerPlayer: removal },
});
vi.spyOn(client, "mutate").mockImplementation(mutate);
}
-async function mountSheet(sanctions: any[]) {
+async function mountSheet(sanctions: any[], serverId?: string) {
rows = sanctions;
useAuthStore().me = {
steam_id: MODERATOR,
@@ -70,6 +80,7 @@ async function mountSheet(sanctions: any[]) {
player: { steam_id: PLAYER, name: "Player" },
variant: "external",
open: true,
+ serverId,
},
attachTo: document.body,
global: {
@@ -261,3 +272,50 @@ describe("PlayerSanctions warnings", () => {
});
});
});
+
+describe("PlayerSanctions removal on a server", () => {
+ async function remove(label: string) {
+ card(label)
+ .querySelector('[aria-label="Remove"]')!
+ .click();
+ await flushPromises();
+
+ Array.from(
+ document.body.querySelectorAll(
+ '[role="alertdialog"] button',
+ ),
+ )
+ .find((button) => button.textContent?.trim() === "Confirm")!
+ .click();
+ await flushPromises();
+ }
+
+ it("tells the moderator why a lifted sanction did not reach the server", async () => {
+ removal = {
+ enforced: false,
+ message:
+ "sanction removed; the Player Management plugin is not installed on this server",
+ };
+ await mountSheet([sanction("mute-1", "mute", tomorrow())], "server-1");
+
+ await remove("Mute");
+
+ expect(toast).toHaveBeenCalledWith(
+ expect.objectContaining({
+ description:
+ "sanction removed; the Player Management plugin is not installed on this server",
+ }),
+ );
+ });
+
+ it("never repeats a warning's removal message, which no server enforces", async () => {
+ removal = { enforced: false, message: "warning removed" };
+ await mountSheet([sanction("warning-1", "warning")], "server-1");
+
+ await remove("Warning");
+
+ expect(toast).toHaveBeenCalledWith(
+ expect.objectContaining({ description: undefined }),
+ );
+ });
+});
diff --git a/tests/components/SanctionPlayer.spec.ts b/tests/components/SanctionPlayer.spec.ts
index dce49efc..45ec05fe 100644
--- a/tests/components/SanctionPlayer.spec.ts
+++ b/tests/components/SanctionPlayer.spec.ts
@@ -6,6 +6,13 @@ import SanctionPlayer from "~/components/SanctionPlayer.vue";
const PLAYER = "76561198000000001";
+const { toast } = vi.hoisted(() => ({ toast: vi.fn() }));
+
+vi.mock("@/components/ui/toast", async (importOriginal) => ({
+ ...(await importOriginal()),
+ toast,
+}));
+
let mutate: ReturnType;
let unmount: (() => void) | null = null;
@@ -15,14 +22,20 @@ afterEach(() => {
vi.restoreAllMocks();
});
-async function openDrawer(type: string, serverId?: string) {
+async function openDrawer(
+ type: string,
+ serverId?: string,
+ result: { enforced: boolean; message: string } = {
+ enforced: false,
+ message: "warning saved",
+ },
+) {
const client = (useNuxtApp() as any).$apollo.defaultClient;
mutate = vi.fn().mockResolvedValue({
data: {
sanctionServerPlayer: {
id: "sanction-1",
- enforced: false,
- message: "warning saved",
+ ...result,
},
},
});
@@ -160,3 +173,63 @@ describe("SanctionPlayer warnings", () => {
});
});
});
+
+describe("SanctionPlayer on a server", () => {
+ afterEach(() => {
+ toast.mockReset();
+ });
+
+ it("tells the moderator why the server could not enforce it live", async () => {
+ const wrapper = await openDrawer("mute", "server-1", {
+ enforced: false,
+ message:
+ "sanction saved; the Player Management plugin is not installed on this server",
+ });
+
+ await submit(wrapper);
+
+ expect(toast).toHaveBeenCalledWith(
+ expect.objectContaining({
+ description:
+ "sanction saved; the Player Management plugin is not installed on this server",
+ }),
+ );
+ });
+
+ it("keeps the toast plain once the server has applied it", async () => {
+ const wrapper = await openDrawer("gag", "server-1", {
+ enforced: true,
+ message: "sanction saved and synced to server",
+ });
+
+ await submit(wrapper);
+
+ expect(toast).toHaveBeenCalledWith(
+ expect.objectContaining({ description: undefined }),
+ );
+ });
+
+ it("adds nothing when the sanction was not aimed at a server", async () => {
+ const wrapper = await openDrawer("mute", undefined, {
+ enforced: false,
+ message: "sanction saved",
+ });
+
+ await submit(wrapper);
+
+ expect(toast).toHaveBeenCalledWith(
+ expect.objectContaining({ description: undefined }),
+ );
+ });
+
+ it("never repeats a warning's save message, which no server enforces", async () => {
+ const wrapper = await openDrawer("warning", "server-1");
+
+ await typeReason("Griefing");
+ await submit(wrapper);
+
+ expect(toast).toHaveBeenCalledWith(
+ expect.objectContaining({ description: undefined }),
+ );
+ });
+});
diff --git a/tests/components/servers/ServerPlayerManagement.spec.ts b/tests/components/servers/ServerPlayerManagement.spec.ts
new file mode 100644
index 00000000..cbad5295
--- /dev/null
+++ b/tests/components/servers/ServerPlayerManagement.spec.ts
@@ -0,0 +1,149 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { flushPromises } from "@vue/test-utils";
+import { mountSuspended } from "@nuxt/test-utils/runtime";
+import ServerPlayerManagement from "~/components/servers/ServerPlayerManagement.vue";
+import { useAuthStore } from "~/stores/AuthStore";
+import { useApplicationSettingsStore } from "~/stores/ApplicationSettings";
+
+const state = vi.hoisted(() => ({
+ server: null as Record | null,
+}));
+
+vi.mock("~/graphql/getGraphqlClient", async () => {
+ const { print } = await import("graphql");
+
+ return {
+ default: () => ({
+ query: async () => ({ data: {} }),
+ subscribe: ({ query }: any) => ({
+ subscribe: ({ next }: any) => {
+ const text = typeof query === "string" ? query : print(query);
+
+ next({
+ data: text.includes("player_management_seen_at")
+ ? { servers_by_pk: state.server }
+ : {},
+ });
+
+ return { unsubscribe() {} };
+ },
+ }),
+ }),
+ };
+});
+
+const community = (seenAt: string | null) => ({
+ id: "server-1",
+ type: "Casual",
+ game: "cs2",
+ is_dedicated: true,
+ player_management_version: seenAt ? "0.0.412" : null,
+ player_management_runtime: seenAt ? "swiftlys2" : null,
+ player_management_seen_at: seenAt,
+});
+
+async function mountCard(props: Record = {}) {
+ const wrapper = await mountSuspended(ServerPlayerManagement, {
+ props: { serverId: "server-1", ...props },
+ });
+ await flushPromises();
+ return wrapper;
+}
+
+beforeEach(() => {
+ state.server = null;
+ vi.spyOn(
+ useApplicationSettingsStore(),
+ "latestPluginVersion",
+ ).mockImplementation((runtime: string) =>
+ runtime === "swiftlys2" ? "0.0.412" : "0.0.390",
+ );
+ useAuthStore().me = {
+ steam_id: "76561198000000001",
+ role: "administrator",
+ } as any;
+});
+
+describe("ServerPlayerManagement plugin status", () => {
+ it("shows a community server whose plugin checked in as active", async () => {
+ state.server = community(new Date().toISOString());
+
+ const wrapper = await mountCard({ gameServerNodeId: "node-1" });
+
+ expect(wrapper.text()).toContain("Player Management plugin active");
+ expect(wrapper.text()).toContain("v0.0.412");
+ expect(wrapper.text()).toContain("SwiftlyS2");
+ expect(wrapper.text()).not.toContain("has not checked in");
+ });
+
+ it("treats a heartbeat older than a few minutes as the plugin being gone", async () => {
+ state.server = community(
+ new Date(Date.now() - 10 * 60 * 1000).toISOString(),
+ );
+
+ const wrapper = await mountCard({ gameServerNodeId: "node-1" });
+
+ expect(wrapper.text()).toContain("switch the server off and back on");
+ expect(wrapper.text()).not.toContain("Install Plugin");
+ });
+
+ it("offers an administrator the download and config for a server outside a node", async () => {
+ state.server = community(null);
+
+ const wrapper = await mountCard({
+ gameServerNodeId: null,
+ apiPassword: "secret-password",
+ });
+
+ expect(wrapper.text()).toContain(
+ "only enforced live once the 5Stack Player Management plugin is installed",
+ );
+
+ const install = wrapper
+ .findAll("button")
+ .find((button) => button.text() === "Install Plugin");
+ await install!.trigger("click");
+
+ const download = wrapper.find('a[href*="PlayerManagement"]');
+ expect(download.attributes("href")).toBe(
+ "https://github.com/5stackgg/game-server/releases/download/sw-v0.0.412/PlayerManagement-sw-v0.0.412.zip",
+ );
+ expect(wrapper.text()).toContain(
+ "addons/swiftlys2/configs/plugins/PlayerManagement/config.jsonc",
+ );
+
+ // The config carries the api password, so it stays hidden like the
+ // page's own plugin config until asked for.
+ expect(wrapper.html()).not.toContain("secret-password");
+
+ const show = wrapper
+ .findAll("button")
+ .find((button) => button.text() === "Show Config");
+ await show!.trigger("click");
+
+ expect(wrapper.find("pre").text()).toContain('"PlayerManagement"');
+ expect(wrapper.find("pre").text()).toContain("secret-password");
+ });
+
+ it("never shows install steps to someone who is not an administrator", async () => {
+ useAuthStore().me = {
+ steam_id: "76561198000000002",
+ role: "moderator",
+ } as any;
+ state.server = community(null);
+
+ const wrapper = await mountCard({ gameServerNodeId: null });
+
+ expect(wrapper.text()).toContain("installed on this server");
+ expect(wrapper.text()).not.toContain("Install Plugin");
+ });
+
+ it("says nothing about the plugin on a Ranked server", async () => {
+ state.server = { ...community(null), type: "Ranked" };
+
+ const wrapper = await mountCard({ gameServerNodeId: "node-1" });
+
+ expect(wrapper.text()).not.toContain("Player Management plugin");
+ expect(wrapper.text()).not.toContain("has not checked in");
+ });
+});
diff --git a/tests/constants/gameServerReleases.spec.ts b/tests/constants/gameServerReleases.spec.ts
new file mode 100644
index 00000000..504a6853
--- /dev/null
+++ b/tests/constants/gameServerReleases.spec.ts
@@ -0,0 +1,70 @@
+// @vitest-environment node
+import { describe, expect, it } from "vitest";
+import {
+ GAME_SERVER_RELEASES_URL,
+ PLAYER_MANAGEMENT_CONFIG_PATHS,
+ playerManagementConfig,
+ playerManagementDownloadUrl,
+ pluginReleaseUrl,
+} from "~/constants/gameServerReleases";
+
+describe("gameServerReleases", () => {
+ it("links a plugin release by its framework-prefixed tag", () => {
+ expect(pluginReleaseUrl("swiftlys2", "0.0.412")).toBe(
+ `${GAME_SERVER_RELEASES_URL}/tag/sw-v0.0.412`,
+ );
+ expect(pluginReleaseUrl("counterstrikesharp", "0.0.390")).toBe(
+ `${GAME_SERVER_RELEASES_URL}/tag/css-v0.0.390`,
+ );
+ });
+
+ // The asset name is set in game-server's plugin-build workflow; this is the
+ // other half of that contract.
+ it("downloads the player management zip published on that release", () => {
+ expect(playerManagementDownloadUrl("swiftlys2", "0.0.412")).toBe(
+ `${GAME_SERVER_RELEASES_URL}/download/sw-v0.0.412/PlayerManagement-sw-v0.0.412.zip`,
+ );
+ expect(playerManagementDownloadUrl("counterstrikesharp", "0.0.390")).toBe(
+ `${GAME_SERVER_RELEASES_URL}/download/css-v0.0.390/PlayerManagement-css-v0.0.390.zip`,
+ );
+ });
+
+ it("falls back to the releases page before any version is known", () => {
+ expect(playerManagementDownloadUrl("swiftlys2", null)).toBe(
+ GAME_SERVER_RELEASES_URL,
+ );
+ });
+
+ it("nests the SwiftlyS2 config under its section and keeps CounterStrikeSharp flat", () => {
+ const settings = {
+ apiDomain: "https://api.example.com",
+ serverId: "server-1",
+ apiPassword: "secret",
+ };
+
+ expect(JSON.parse(playerManagementConfig("swiftlys2", settings))).toEqual({
+ PlayerManagement: {
+ API_DOMAIN: "https://api.example.com",
+ SERVER_ID: "server-1",
+ SERVER_API_PASSWORD: "secret",
+ },
+ });
+ expect(
+ JSON.parse(playerManagementConfig("counterstrikesharp", settings)),
+ ).toEqual({
+ Version: 1,
+ API_DOMAIN: "https://api.example.com",
+ SERVER_ID: "server-1",
+ SERVER_API_PASSWORD: "secret",
+ });
+ });
+
+ it("points each framework at the file its plugin reads", () => {
+ expect(PLAYER_MANAGEMENT_CONFIG_PATHS.swiftlys2).toBe(
+ "addons/swiftlys2/configs/plugins/PlayerManagement/config.jsonc",
+ );
+ expect(PLAYER_MANAGEMENT_CONFIG_PATHS.counterstrikesharp).toBe(
+ "addons/counterstrikesharp/configs/plugins/PlayerManagement/PlayerManagement.json",
+ );
+ });
+});