From 13310c4307b2a10140d51acaeef0e26e0cb5ebb7 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 31 Jul 2022 02:46:41 +0200 Subject: [PATCH 001/104] Make nightly builds draft by default --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 010cc46b7..eee7928c1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,6 +92,7 @@ jobs: token: "${{ secrets.GITHUB_TOKEN }}" artifacts: "artifacts/${{ needs.build.outputs.filename }}.zip,artifacts/${{ needs.build.outputs.filename }}.tar.gz" prerelease: true + draft: true commit: "${{ github.sha }}" tag: "v${{ needs.build.outputs.get5-version }}-${{ needs.build.outputs.sha-short }}" name: "Nightly ${{ needs.build.outputs.get5-version }}-${{ needs.build.outputs.sha-short }}" From bcab0c68bf24a4933e0b90f364b57fac6b50db56 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 31 Jul 2022 16:18:58 +0200 Subject: [PATCH 002/104] Add hungarian translation (#805) * Add hungarian translation --- translations/hu/get5.phrases.txt | 387 +++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 translations/hu/get5.phrases.txt diff --git a/translations/hu/get5.phrases.txt b/translations/hu/get5.phrases.txt new file mode 100644 index 000000000..0d439b917 --- /dev/null +++ b/translations/hu/get5.phrases.txt @@ -0,0 +1,387 @@ +"Phrases" +{ + "ReadyToVetoInfoMessage" + { + "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha a csapatod készen áll a vétóra." + } + "WaitingForCastersReadyInfoMessage" + { + "hu" "A {1} várunk, hogy beírja a {GREEN}!ready{NORMAL} parancsot a kezdéshez." + } + "ReadyToRestoreBackupInfoMessage" + { + "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha a készen állsz a mérkőzés visszatöltésre." + } + "ReadyToKnifeInfoMessage" + { + "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha készen állsz a késelésre." + } + "ReadyToStartInfoMessage" + { + "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha készenállsz a kezdésre." + } + "YouAreReady" + { + "hu" "Készen állsz a mérkőzés megkezdésére." + } + "YouAreReadyAuto" + { + "hu" "INFO: Felkészült állapotba kerültél a játék aktívitásod által. Írd be, hogy !unready ha nem állsz még készen." + } + "YouAreNotReady" + { + "hu" "Nem állsz készen a mérkőzés megkezdésére." + } + "WaitingForEnemySwapInfoMessage" + { + "hu" "{1} nyerte meg a kés kört. Várakozunk, hogy kiválasszák a megfelelő oldalt. {GREEN}!stay{NORMAL} vagy {GREEN}!swap{NORMAL}." + } + "WaitingForGOTVBrodcastEndingInfoMessage" + { + "hu" "A pálya akkor fog váltani, ha a GOTV adás befejeződött." + } + "WaitingForGOTVVetoInfoMessage" + { + "hu" "A pálya akkor fog váltani, ha a GOTV adás megjelenítette a pálya vétókat." + } + "NoMatchSetupInfoMessage" + { + "hu" "Nincs mérkőzés előkészítve" + } + "YouAreNotAPlayerInfoMessage" + { + "hu" "Nem vagy játékos ezen a mérkőzésen" + } + "TeamIsFullInfoMessage" + { + "hu" "A csapatod megtelt" + } + "TeamForfeitInfoMessage" + { + "hu" "{1} nem sikerült időben felkészülni, ezért vesztettetek." + } + "MinutesToForfeitMessage" + { + "hu" "{1} {2} perc maradt, hogy felkészüljenek vagy elveszítik a mérkőzést." + } + "SecondsToForfeitInfoMessage" + { + "hu" "{1} {2} másodperc maradt, hogy felkészüljenek vagy elveszítik a mérkőzést." + } + "10SecondsToForfeitInfoMessage" + { + "hu" "{1} 10 másodperc maradt, hogy felkészüljenek vagy elveszítik a mérkőzést." + } + "MaxPausesUsedInfoMessage" + { + "hu" "A {2} felhasználta a maximális ({1}) időkérést." + } + "MaxPausesTimeUsedInfoMessage" + { + "hu" "A {2} felhasználta a maximális ({1}) időkérésre használható időt." + } + "MatchPausedByTeamMessage" + { + "hu" "{1} taktikai időkérést kért." + } + "MatchTechPausedByTeamMessage" + { + "hu" "{1} technikai időkérésért kért." + } + "PausesNotEnabled" + { + "hu" "Az időkérések nincsenek engedélyezve." + } + "TechPausesNotEnabled" + { + "hu" "A technikai időkérések nincsenek engedélyezve." + } + "TechnicalPauseMidSentence" + { + "hu" "technikai szünet" + } + "TacticalPauseMidSentence" + { + "hu" "taktikai szünet" + } + "TimeRemainingBeforeAnyoneCanUnpausePrefix" + { + "hu" "Ennyi idő maradt, mielőtt bárki elindíthatja a mérkőzést" + } + "PauseTimeRemainingPrefix" + { + "hu" "Hátralévő idő a szünetből" + } + "AwaitingUnpause" + { + "hu" "Várakozás a folytatásra" + } + "PausedByAdministrator" + { + "hu" "Egy admin leszünetelte a mérkőzést." + } + "PausedForBackup" + { + "hu" "A játék vissza lett töltve a kör mentésből. Mindkét csapatnak be kell írnia az {GREEN}!unpause{NORMAL} parancsot a folytatáshoz." + } + "UserCannotUnpauseAdmin" + { + "hu" "Egy admin kérte ezt az időkérést, ő is fogja tudni folytatni." + } + "PausingTeamCannotUnpauseUntilFreezeTime" + { + "hu" "Az időkérést, nem lehet visszavonni." + } + "PauseRunoutInfoMessage" + { + "hu" "{1} csapatnak, elfogyott a szüneteltetési ideje. Folytatódik a mérkőzés." + } + "TechPauseRunoutInfoMessage" + { + "hu" "A technikai szünetre szánt idő, elfogyott. Bárki tudja folytatni a mérkőést." + } + "TechPauseNoTimeRemaining" + { + "hu" "A {1} csapatnak, nem maradt több ideje technikai időkérésre. Használja a taktikai időkérést." + } + "TechPauseNoPausesRemaining" + { + "hu" "A {1} csapatnak, nem maradt több technikai időkérése. Használja a taktikai időkérést." + } + "TechPausePausesRemaining" + { + "hu" "Megmaradt technikai időkérések a {1} csapatnak: {2}" + } + "MatchUnpauseInfoMessage" + { + "hu" "{1} elindította a mérkőzést." + } + "WaitingForUnpauseInfoMessage" + { + "hu" "{1} szeretné folytatni. Várakozunk a {2} csapatra, hogy beírják a {GREEN}!unpause{NORMAL} parancsot." + } + "PausesLeftInfoMessage" + { + "hu" "Megmaradt taktikai időkérések a {1} csapatnak: {2}" + } + "PrereleaseVersionWarning" + { + "hu" "A Get5 ({1}) változatát futtatod, ami kifejezetten fejlesztésre és tesztelésre van. Ez az üzenet kikapcsolható a {2} parancs segítségével." + } + "NewVersionAvailable" + { + "hu" "Egy új változat elérhető a Get5-ból. Látogasd meg a {1} a frissítéssel kapcsolatban." + } + "TeamFailToReadyMinPlayerCheck" + { + "hu" "A szerveren legalább {1} játékosnak készen kell állnia a kezdéshez." + } + "TeamReadyToVetoInfoMessage" + { + "hu" "{1} készenáll a vétó megkezdésére." + } + "TeamReadyToRestoreBackupInfoMessage" + { + "hu" "{1} készen áll a mérkőzés visszaállítására." + } + "TeamReadyToKnifeInfoMessage" + { + "hu" "{1} készenáll, hogy késeljen az oldalért." + } + "TeamReadyToBeginInfoMessage" + { + "hu" "{1} készen áll a mérkőzés megkezdésére." + } + "TeamNotReadyInfoMessage" + { + "hu" "{1} már nem áll készen." + } + "ForceReadyInfoMessage" + { + "hu" "Beírhatod a {GREEN}!forceready{NORMAL} parancsot, ha fel szeretnéd készíteni a csapatod, ha kevesebb mint {GREEN}{1}{NORMAL} játékosod van." + } + "TeammateForceReadied" + { + "hu" "A csapatod felkészült {GREEN}{1} által." + } + "AdminForceReadyInfoMessage" + { + "hu" "Egy admin, minden csapatot felkészített." + } + "AdminForceEndInfoMessage" + { + "hu" "Egy admin, véget vetett a mérkőzésnek." + } + "AdminForcePauseInfoMessage" + { + "hu" "Egy admin, szüneteli a mérkőzést." + } + "AdminForceUnPauseInfoMessage" + { + "hu" "Egy admin, a mérkőzés folytatását hajtotta végre." + } + "TeamWantsToReloadLastRoundInfoMessage" + { + "hu" "{1} szeretne megállni és visszatölteni az előző kört. {2} el kell fogadni a {GREEN}!stop{NORMAL} paranccsal." + } + "TeamWinningSeriesInfoMessage" + { + "hu" "{1} áll nyerésre {2}-{3}" + } + "SeriesTiedInfoMessage" + { + "hu" "Az állás döntetlen. {1}-{2}" + } + "NextSeriesMapInfoMessage" + { + "hu" "A következő pálya a szériában {GREEN}{1}" + } + "TeamWonMatchInfoMessage" + { + "hu" "{1} megnyerte a mérkőzést." + } + "TeamTiedMatchInfoMessage" + { + "hu" "{1} és a {2} döntetlent játszott." + } + "TeamsSplitSeriesBO2InfoMessage" + { + "hu" "{1} és a {2} kiegyenlítettek. 1-1-re." + } + "TeamWonSeriesInfoMessage" + { + "hu" "{1} nyerte a szériát. {2}-{3}" + } + "MatchFinishedInfoMessage" + { + "hu" "A mérkőzés befejeződött" + } + "CurrentScoreInfoMessage" + { + "hu" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" + } + "StopCommandNotEnabled" + { + "hu" "A stop parancs nincs engedélyezve." + } + "BackupLoadedInfoMessage" + { + "hu" "Sikeresen visszatöltődött a körmentés {1}." + } + "MatchBeginInSecondsInfoMessage" + { + "hu" "A mérkőzés {1} másodperc múlva kezdődik." + } + "MatchIsLiveInfoMessage" + { + "hu" "A mérkőzés {GREEN}ÉLES" + } + "KnifeIn5SecInfoMessage" + { + "hu" "A kés kör 5 másodperc múlva kezdődik." + } + "KnifeInfoMessage" + { + "hu" "Kés!" + } + "TeamDecidedToStayInfoMessage" + { + "hu" "{1} úgy döntött, hogy maradnak." + } + "TeamDecidedToSwapInfoMessage" + { + "hu" "{1} úgy döntött, hogy térfelet cserélnek." + } + "TeamLostTimeToDecideInfoMessage" + { + "hu" "{1} maradnak, mivel idő letelte előtt, nem választottak." + } + "ChangingMapInfoMessage" + { + "hu" "Pályaváltás a következőre {GREEN}{1}..." + } + "MapDecidedInfoMessage" + { + "hu" "A pályák ellettek döntve:" + } + "MapIsInfoMessage" + { + "hu" "{1}. Pálya: {GREEN}{2}{NORMAL}" + } + "TeamPickedMapInfoMessage" + { + "hu" "{1} választotta a {GREEN}{2}{NORMAL} {3}. pályának." + } + "TeamSelectSideInfoMessage" + { + "hu" "{1} választott, hogy kezd {GREEN}{2}{NORMAL} oldalon a(z) {3}. pályán." + } + "TeamVetoedMapInfoMessage" + { + "hu" "{1} szavazott {LIGHT_RED}{2}{NORMAL}." + } + "CaptainLeftOnVetoInfoMessage" + { + "hu" "A csapatkapitány kilépett a vétó során, a vétó szünetel." + } + "ReadyToResumeVetoInfoMessage" + { + "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot ha készenállsz a vétó folytatására." + } + "MatchConfigLoadedInfoMessage" + { + "hu" "Mérkőzés beállítás betöltödött." + } + "MoveToCoachInfoMessage" + { + "hu" "Mivel a csapatod megtelt, így edzői poziccióba lettél átmozgatva." + } + "ReadyTag" + { + "hu" "[FELKÉSZÜLT]" + } + "NotReadyTag" + { + "hu" "[NEM ÁLL KÉSZEN]" + } + "MatchPoweredBy" + { + "hu" "{YELLOW}Get5{NORMAL} által megvalósítva" + } + "MapVetoPickMenuText" + { + "hu" "Válassz pályát, amin játszanál:" + } + "MapVetoPickConfirmMenuText" + { + "hu" "Erősítse meg, hogy játszana {1}:" + } + "MapVetoBanMenuText" + { + "hu" "Válaszon egy pályát amit vétózna:" + } + "MapVetoBanConfirmMenuText" + { + "hu" "Erősítse meg, hogy vétózza a {1} pályát:" + } + "MapVetoSidePickMenuText" + { + "hu" "Válasszon oldalt {1}:" + } + "MapVetoSidePickConfirmMenuText" + { + "hu" "Erősítse meg, hogy {1}-ben kezd:" + } + "ConfirmPositiveOptionText" + { + "hu" "Igen" + } + "ConfirmNegativeOptionText" + { + "hu" "Nem" + } + "VetoCountdown" + { + "hu" "Kezdődik a vétózás {GREEN}{1}{NORMAL} másodperc múlva." + } +} From e7f63ea80df0711d4f5f320f87b9fd6c826a99f5 Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Sun, 31 Jul 2022 11:12:50 -0600 Subject: [PATCH 003/104] Add in bug label for bug fixes on release note generation. (#813) --- .github/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/release.yml b/.github/release.yml index e6b97c026..461d7ec71 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -9,6 +9,9 @@ changelog: - title: Exciting New Features 🎉 labels: - enhancement + - title: Bug Fixes 🐞 + labels: + - "bug" - title: Other Changes labels: - "*" From ea2e3c57428aec94692b77f328a3fb1a51b53d59 Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Sun, 31 Jul 2022 19:21:45 -0600 Subject: [PATCH 004/104] Check for SVG files first, error if no PNG is found. (#810) --- scripting/get5/matchconfig.sp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 6c9f3294c..4634e99f6 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -1230,10 +1230,20 @@ static void AddTeamLogoToDownloadTable(const char[] logoName) { return; char logoPath[PLATFORM_MAX_PATH + 1]; - Format(logoPath, sizeof(logoPath), "resource/flash/econ/tournaments/teams/%s.png", logoName); - - LogDebug("Adding file %s to download table", logoName); - AddFileToDownloadsTable(logoPath); + Format(logoPath, sizeof(logoPath), "materials/panorama/images/tournaments/teams/%s.svg", logoName); + if (FileExists(logoPath)) { + LogDebug("Adding file %s to download table", logoName); + AddFileToDownloadsTable(logoPath); + } else { + Format(logoPath, sizeof(logoPath), "resource/flash/econ/tournaments/teams/%s.png", logoName); + if (FileExists(logoPath)) { + LogDebug("Adding file %s to download table", logoName); + AddFileToDownloadsTable(logoPath); + } else { + LogError("Error in locating file %s. Please ensure the file exists on your game server.", logoPath); + } + } + } public void CheckTeamNameStatus(Get5Team team) { From 1ca886d63fa6e9beb6080fa972935afed52b9774 Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Sun, 31 Jul 2022 19:25:04 -0600 Subject: [PATCH 005/104] Only print MoveToCoachInfoMessage if the user has not been already coaching. (#809) --- scripting/get5/teamlogic.sp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index a90e1eaca..fc229f33f 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -94,9 +94,15 @@ public Action Command_JoinTeam(int client, const char[] command, int argc) { } else { // Only attempt to move to coach if we are not full on coaches already. if (GetTeamCoaches(correctTeam).Length <= g_CoachesPerTeam) { + char auth[AUTH_LENGTH]; LogDebug("Forcing player %N to coach", client); + GetAuth(client, auth, sizeof(auth)); + // Only output MoveToCoachInfoMessage if we are not + // in the coach array already. + if (!IsAuthOnTeamCoach(auth, correctTeam)) { + Get5_Message(client, "%t", "MoveToCoachInfoMessage"); + } MoveClientToCoach(client); - Get5_Message(client, "%t", "MoveToCoachInfoMessage"); } else { KickClient(client, "%t", "TeamIsFullInfoMessage"); } From 9e9ffb3cc70b4993013245aee5dece20acba22b9 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 30 Jul 2022 16:22:33 +0200 Subject: [PATCH 006/104] Doc progress --- .github/workflows/generate_docs.yml | 1 + documentation/docs/commands.md | 17 +++-- documentation/docs/configuration.md | 11 ++- documentation/docs/index.md | 10 +-- documentation/docs/installation.md | 4 +- documentation/docs/match_schema.md | 2 +- documentation/docs/stats_system.md | 109 +++++++++++++++++++--------- documentation/docs/translations.md | 10 +-- scripting/get5/matchconfig.sp | 9 ++- 9 files changed, 109 insertions(+), 64 deletions(-) diff --git a/.github/workflows/generate_docs.yml b/.github/workflows/generate_docs.yml index 7bc5c5ecb..09ac6837a 100644 --- a/.github/workflows/generate_docs.yml +++ b/.github/workflows/generate_docs.yml @@ -4,6 +4,7 @@ on: branches: - "master" - "development" + - "adjust_docs" jobs: build: name: Deploy docs diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 0b4092fdc..674522261 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -96,13 +96,14 @@ You should put the `url` argument inside quotation marks (`""`). ####`get5_endmatch` : Force ends the current match. No winner is set (draw). -####`get5_creatematch` -: Creates a BO1 match with the current players on the server on the current map. +####`get5_creatematch [map name] [matchid]` +: Creates a BO1 match with the current players on the server. `map name` defaults to the current map and `matchid` + defaults to `manual`. You should **not** provide a match ID if you use the [MySQL extension](../stats_system/#mysql). ####`get5_scrim [opposing team name] [map name] [matchid]` {: #get5_scrim } -: Creates a [scrim](../getting_started/#scrims) on the current map. For example, if you're - playing *fnatic* on `de_dust2` you might run `get5_scrim fnatic de_dust2`. The other team name defaults to "away" - and the map defaults to the current map. `matchid` defaults to an empty string. +: Creates a [scrim](../getting_started/#scrims) on the current map. The opposing team name defaults to `Away` + and the map defaults to the current map. `matchid` defaults to `scrim`. You should **not** provide a match ID if + you use the [MySQL extension](../stats_system/#mysql). ####`get5_addplayer [name]` {: #get5_addplayer } : Adds a Steam ID to a team (can be any format for the Steam ID). The name parameter optionally locks the player's @@ -124,9 +125,6 @@ name. ####`get5_forceready` : Marks all teams as ready. `get5_forcestart` does the same thing. -####`get5_dumpstats` -: Dumps current match stats to a file. - ####`get5_status` : Replies with JSON formatted match state (available to all clients). @@ -229,6 +227,9 @@ to [`!ringer`](../commands/#ringer) ####`get5_debuginfo [file]` {: #get5_debuginfo } : Dumps debug info to a file (`addons/sourcemod/logs/get5_debuginfo.txt` if no file parameter is provided). +####`get5_dumpstats [file]` {: #get5_dumpstats } +: Dumps [player stats](../stats_system) to a file (`addons/sourcemod/get5_matchstats.cfg` if no file parameter is provided). + ####`get5_test` : Runs get5 tests. **This should not be used on a live match server since it will reload a match config to test**. diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 3affd2889..bedf27779 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -59,8 +59,8 @@ disconnect even in warmup with the intention to reconnect!). **`Default: 0`** : Whether to wait for map vetoes to be printed to GOTV before changing map. **`Default: 0`** ####`get5_check_auths` -: Whether the Steam IDs from a "players" section are used to force players onto teams, and will kick -users if they are not in the auth list. **`Default: 1`** +: Whether the Steam IDs from a `players` of a [match configuration](../match_schema/#schema) section are used to +force players onto teams, kicking everyone else. **`Default: 1`** ####`get5_print_update_notice` : Whether to print to chat when the game goes live if a new version of Get5 is available. This only works if @@ -154,9 +154,8 @@ if [get5_fixed_pause_time](#get5_fixed_pause_time) is set to a non-zero value. **`Default: 300 (5 minutes)`** ####`get5_fixed_pause_time` -: If non-zero, the fixed length in seconds all [`tactical`](../pausing/#tactical) pauses will be. Adjusting this to -non-zero will use the in-game timeout counter, and the [get5_max_pause_time](#get5_max_pause_time) -parameter is ignored. **`Default: 0`** +: If non-zero, the fixed length in seconds of all [`tactical`](../pausing/#tactical) pauses. This takes precedence +over the [get5_max_pause_time](#get5_max_pause_time) parameter, which will be ignored. **`Default: 0`** ####`get5_allow_technical_pause` : Whether [technical pauses](../pausing/#technical) are available to clients or not. **`Default: 1`** @@ -253,7 +252,7 @@ placeholder strings that will be replaced by meaningful values when printed. ### Colour Substitutes {: #color-substitutes } These variables can be used to color text in the chat. You must return to `{NORMAL}` (white) -after using a color variable. Note that a color prefix cannot be _followed by a space_. +after using a color variable. Example: `This text becomes {DARK_RED}red{NORMAL}, while {YELLOW}all of this will be yellow`. diff --git a/documentation/docs/index.md b/documentation/docs/index.md index 47cde4efd..630aa0099 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -11,18 +11,18 @@ functionality is built to work within how the CS:GO server normally operates, no Highlights of Get5 include: -- Locking players to their correct team and side by their Steam ID +- [Locking players to their correct team and side by their Steam ID](match_schema.md) - Automatically setting team names/logos/match text values for spectator/GOTV clients - In-game map-veto support from the match's list of maps - Support for multi-map series (Bo1, Bo2, Bo3, Bo5, etc.) - Warmup and [`!ready`](commands/#ready)-system for each team - Automatic GOTV demo recording -- Advanced backup system built on top of Valve's backup system +- [Advanced backup system](backup.md) built on top of Valve's backup system - Knifing for sides -- Pausing support +- [Advanced pausing](pausing.md) support - Coaching support -- Lightweight usage for scrims -- Event logging and SourceMod forwards you can interface with, allowing for collection of stats etc. +- Lightweight usage for [scrims](getting_started/#scrims) +- [Event logging and SourceMod forwards](events_and_forwards.md) you can interface with, allowing for collection of stats etc. - [Commands](commands/#serveradmin-commands) allow remote management of the plugin If you are installing this on your game server, head over to the [Installation](./installation.md) instructions. diff --git a/documentation/docs/installation.md b/documentation/docs/installation.md index a2b47d53a..c8c0c2265 100644 --- a/documentation/docs/installation.md +++ b/documentation/docs/installation.md @@ -8,7 +8,7 @@ You can get the latest versions here: [:material-download: Download MetaMod](https://www.sourcemm.net/downloads.php?branch=stable){ .md-button .md-button--primary } [:material-download: Download SourceMod](https://www.sourcemod.net/downloads.php?branch=stable){ .md-button .md-button--primary } -!!! tip +!!! info "OS is important" Remember to select the correct OS type (Windows/Linux/Mac) for **both** plugins. This should be the OS of the server. @@ -160,7 +160,7 @@ is just to indicate what the correct structure looks like. 1. SourceMod error logs can be found in here. This directory is empty by default. 2. This is the core Get5 plugin. 3. This is the MySQL extension for collecting stats. If you want to use this extension, please see - the [guide](../stats_system/#mysql-statistics). + the [guide](../stats_system/#mysql). 4. This is proof-of-concept integration called [get5 web panel](https://github.com/splewis/get5-web) that can be used to manage matches. **This is not supported and is probably very buggy. You should not use it.** 5. This folder contains all the language files and translations for all the plugins. diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 3b404eb9c..6c7796229 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -13,7 +13,7 @@ required to start a match. Reasonable defaults are used for the other values (Bo 5v5, empty strings for team names, etc.). We recommend using the JSON format whenever possible, as JSON has way better support in various programming languages than Valve's KeyValue format (which essentially has none). -## The schema +## The schema {: #schema } ```typescript title="TypeScript interface definition of a match configuration" type Get5PlayerSteamID = string; // (8) diff --git a/documentation/docs/stats_system.md b/documentation/docs/stats_system.md index e9105cdc8..003f93e0a 100644 --- a/documentation/docs/stats_system.md +++ b/documentation/docs/stats_system.md @@ -1,30 +1,31 @@ # Player Stats System -When a get5 match is live, the plugin will automatically record match stats for each player, across each map in the -match. These are recorded in an internal KeyValues structure, and are available at any time during the match (including -the postgame waiting period) via the `Get5_GetMatchStats` native and -the [`get5_dumpstats`](./commands.md#serveradmin-commands) command. +!!! warning -Note: the stats collection is not going to be reliable if -using [`get5_check_auths 0`](./configuration.md#server-setup). + None of the methods for collecting stats are going to be reliable if + [`get5_check_auths`](../configuration/#get5_check_auths) is set to `0`. -## SourceMod Forwards +## SourceMod Forwards {: #forwards } If you're writing your own plugin, you can collect stats from the game using the [forwards](./events_and_forwards.md) provided by Get5. -## Stats KeyValues structure +## KeyValue System {: #keyvalue } -The root level of the KV contains data for the full series: the series winner (if one exists yet) and the series type ( -bo1, bo2..., etc). +Get5 will automatically record basic stats for each player for each map in the match. These are stored in an internal +KeyValues structure, and are available at any time during the match (including the postgame waiting period) via the +`Get5_GetMatchStats` native and the [`get5_dumpstats`](../commands/#get5_dumpstats) command. -Under that root level, there is a level for each map ("map1", "map2"), which contains the map winner (if one exists yet) -, the mapname, and the demo file recording. +The root level contains data for the full series; the series winner (if one exists yet) and the series type ( +bo1, bo3, etc). -Under the map level, there is a section for each team ("team1" and "team2) which contains the current team score (on +Under the root level is a level for each map (`map0`, `map1` etc.), which contains the map winner (if one exists yet), +the map name and the demo file recording. + +Under the map level is a section for each team (`team1` and `team2`), which contains the current team score (on that map) and the team name. -Each player has a section under the team level under the section name of their steam64id. It contains all the personal +Each player has a section under the team level under the section name of their SteamID 64. It contains all the personal level stats: name, kills, deaths, assists, etc. Partial Example: @@ -32,7 +33,7 @@ Partial Example: ``` "Stats" { - "series_type" "bo1" + "series_type" "bo1" "team1_name" "EnvyUs" "team2_name" "Fnatic" "map0" @@ -46,35 +47,77 @@ Partial Example: { "name" "xyz" "kills" "0" - "deaths" "1" - "assists" "5" - "damage" "352" + "deaths" "1" + "assists" "5" + "damage" "352" } } } } ``` -## What Stats Are Collected +!!! question "What stats are collected?" -See the [get5 include](https://github.com/splewis/get5/blob/master/scripting/include/get5.inc#L1769) for what stats will -be recorded and what their key in the KeyValue structure is. + See the [get5.inc include file](https://github.com/splewis/get5/blob/master/scripting/include/get5.inc#L1769) for + what stats will be recorded and what their keys are in the KeyValue structure. ## MySQL Statistics {: #mysql } Get5 ships with a (disabled by default) plugin called `get5_mysqlstats` that will save many of the stats to a MySQL -database. To use this: +database. You can use the included plugin as a source of inspiration and build your own to collect even more stats, or +even wrap a website around it for managing matches. The included plugin is meant as a proof-of-concept of this +functionality, but can also be used as-is. + +!!! danger "Fixed Match IDs" + + If you use the MySQL extension, you should **not** set the `matchid` in your + [match configuration](../match_schema/#schema) (just leave it empty) or when creating scrims using the + [`get5_scrim`](../commands/#get5_scrim) command. The match ID will be set to the + [auto-incrementing integer](https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html) (cast to a string) + returned by inserting into the `get5_stats_matches` table. + +!!! tip "Advanced users only" -- Create the tables using this [schema](https://github.com/splewis/get5/blob/master/misc/import_stats.sql), raw text - link can be found [here](https://raw.githubusercontent.com/splewis/get5/master/misc/import_stats.sql). -- Configure a `"get5"` database section in `addons/sourcemod/configs/databases.cfg`. -- Make sure the `get5_mysqlstats` plugin is enabled (moved up a directory from `addons/sourcemod/plugins/disabled` - directory). + You should have a basic understanding of MySQL if you wish to use this plugin. It is assumed you know what the + commands below do. -**Note**: If you use this module, you can force the match ID used by setting it in your match config -(the [Match Schema](../match_schema/#optional-values) section). If you don't do this, the match ID will be set to the -auto-incrementing integer (cast to a string) returned by inserting into the `get5_stats_matches` table. It is strongly -recommended that you always leave the `matchid` blank, as MySQL will then manage the IDs for you. +1. Make sure the `get5_mysqlstats.smx` plugin is enabled (moved up a directory from `addons/sourcemod/plugins/disabled` + directory). -If you are using an external web panel, **this plugin is not needed** as most external applications record to their own -match tables. +2. Have a MySQL server reachable from the game server's network. These commands are for MySQL 8 but should also work on +MySQL 5.7. + +3. Create a schema/database for your tables: +```mysql +CREATE SCHEMA `get5` DEFAULT CHARACTER SET `utf8mb4` COLLATE `utf8mb4_0900_ai_ci`; +USE `get5`; +``` + :warning: The `utf8mb4` part ensures that your database can handle all kinds of emojis and unicode characters. This is + the default in MySQL 8 but must be explicitly defined for MySQL 5.7. + +4. Configure a database user and grant it access to the database: +```mysql +CREATE USER 'get5_db_user'@'%' IDENTIFIED WITH mysql_native_password BY 'super_secret_password'; +GRANT ALL ON `get5`.* TO 'get5_db_user'@'%'; +``` + :warning: You **can** use the `root` database user instead if you wish. `@'%'` means that the user can log in from any + network location, and you can replace this with `@'localhost'` if your database is running on the same host as the + game server. + +5. Create the required tables using [these commands](https://github.com/splewis/get5/blob/master/misc/import_stats.sql). +Raw text link can be found [here](https://raw.githubusercontent.com/splewis/get5/master/misc/import_stats.sql). + +6. Configure a `"get5"` database section in SourceMod and provide the parameters you used to configure your database: +!!! example ":material-file-cog: `addons/sourcemod/configs/databases.cfg`" + + ``` + "get5" + { + "driver" "mysql" + "host" "127.0.0.1" + "database" "get5" + "user" "get5_db_user" + "pass" "super_secret_password" + "port" "3306" + } + ``` diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index 84ee36105..e12db1a47 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -9,7 +9,7 @@ the [Discord](../community/#discord) and let us know. ## How to translate? Inside the `translations` folder you will find the base `get5.phrases.txt` file which is the English one and the -fallback in case a translation string cannot be found in a client's language. This is the "single source of truth" and +fallback in case a translation string cannot be found in a client's language. This is the _single source of truth_ and should be used when translating. Each language has a folder (for instance `fr` for french) within which there is another `get5.phrases.txt` file, but in French. @@ -22,7 +22,7 @@ entire language file**. "TeamPickedMapInfoMessage" { "#format" "{1:s},{2:s},{3:d}" # (1) - "en" "{1} picked {GREEN}{2} {NORMAL}as map {3}." # (2) + "en" "{1} picked {GREEN}{2}{NORMAL} as map {3}." # (2) } ``` @@ -32,7 +32,7 @@ what `{1}`, `{2}` and `{3}` are. In this case, the first and second arguments ar 2. Use the English strings and the [reference](#reference) below to determine how to translate the string. As the string implies, this example is used when a team picks a map, and the output is printed to chat and looks like -this: `Team A picked de_dust as map 2.` +this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. ## Types of strings @@ -131,9 +131,9 @@ this: `Team A picked de_dust as map 2.` | `CurrentScoreInfoMessage` | _Team A_ _12_ - _Team B_ _8_ | Chat | | `BackupLoadedInfoMessage` | Successfully loaded backup _backup_file_03.cfg_. | Chat | | `MatchBeginInSecondsInfoMessage` | The match will begin in _3_ seconds. | Chat | -| `MatchIsLiveInfoMessage` | Match is LIVE | Chat | +| `MatchIsLiveInfoMessage` | Match is LIVE
Match is LIVE
Match is LIVE
Match is LIVE
Match is LIVE | Chat | | `KnifeIn5SecInfoMessage` | The knife round will begin in 5 seconds. | Chat | -| `KnifeInfoMessage` | Knife! | Chat | +| `KnifeInfoMessage` | Knife!
Knife!
Knife!
Knife!
Knife! | Chat | | `TeamDecidedToStayInfoMessage` | _Team A_ has decided to stay. | Chat | | `TeamDecidedToSwapInfoMessage` | _Team A_ has decided to swap. | Chat | | `TeamLostTimeToDecideInfoMessage` | _Team A_ will stay since they did not make a decision in time. | Chat | diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 4634e99f6..b75bfe7a8 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -1014,16 +1014,17 @@ public Action Command_CreateMatch(int client, int args) { GetCleanMapName(matchMap, sizeof(matchMap)); if (args >= 1) { - GetCmdArg(1, matchid, sizeof(matchid)); - } - if (args >= 2) { - GetCmdArg(2, matchMap, sizeof(matchMap)); + GetCmdArg(1, matchMap, sizeof(matchMap)); if (!IsMapValid(matchMap)) { ReplyToCommand(client, "Invalid map: %s", matchMap); return Plugin_Handled; } } + if (args >= 2) { + GetCmdArg(2, matchid, sizeof(matchid)); + } + char path[PLATFORM_MAX_PATH]; Format(path, sizeof(path), "get5_%s.cfg", matchid); DeleteFileIfExists(path); From 8f6abbf044f2817fff9700cb76b7a8cfe936a9b5 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 1 Aug 2022 16:50:15 +0200 Subject: [PATCH 007/104] Reorder parameters of get5_creatematch to match get5_scrim (#806) --- scripting/get5/matchconfig.sp | 1 - 1 file changed, 1 deletion(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index b75bfe7a8..274aef06b 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -1020,7 +1020,6 @@ public Action Command_CreateMatch(int client, int args) { return Plugin_Handled; } } - if (args >= 2) { GetCmdArg(2, matchid, sizeof(matchid)); } From 853893016409bae84c9577bff865e09a468d28e3 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 1 Aug 2022 21:06:11 +0200 Subject: [PATCH 008/104] Init players stats to 0 when game starts (#814) Use LOOP_CLIENTS helper in more places Minor refactors in stats.sp --- documentation/docs/commands.md | 3 +- scripting/get5.sp | 6 ++ scripting/get5/stats.sp | 133 ++++++++++++++++++++++++--------- scripting/get5/util.sp | 10 +-- scripting/get5_apistats.sp | 7 +- scripting/include/get5.inc | 2 + 6 files changed, 118 insertions(+), 43 deletions(-) diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 674522261..07c69e0d5 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -228,7 +228,8 @@ to [`!ringer`](../commands/#ringer) : Dumps debug info to a file (`addons/sourcemod/logs/get5_debuginfo.txt` if no file parameter is provided). ####`get5_dumpstats [file]` {: #get5_dumpstats } -: Dumps [player stats](../stats_system) to a file (`addons/sourcemod/get5_matchstats.cfg` if no file parameter is provided). +: Dumps [player stats](../stats_system/#keyvalue) to a file (`addons/sourcemod/get5_matchstats.cfg` if no file +parameter is provided). ####`get5_test` : Runs get5 tests. **This should not be used on a live match server since it will reload a match config to test**. diff --git a/scripting/get5.sp b/scripting/get5.sp index 7ab43460c..6d2e0c829 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1460,6 +1460,12 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) Stats_RoundEnd(csTeamWinner); + if (g_DamagePrintCvar.BoolValue) { + LOOP_CLIENTS(i) { + PrintDamageInfo(i); // Checks valid client etc. on its own. + } + } + Get5RoundStatsUpdatedEvent statsEvent = new Get5RoundStatsUpdatedEvent(g_MatchID, g_MapNumber, g_RoundNumber); diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 057d62e11..e12fe5655 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -1,5 +1,3 @@ -const float kTimeGivenToTrade = 1.5; - public void Stats_PluginStart() { HookEvent("bomb_defused", Stats_BombDefusedEvent); HookEvent("bomb_exploded", Stats_BombExplodedEvent); @@ -147,7 +145,7 @@ public Get5Player GetPlayerObject(int client) { GetAuth(client, auth, sizeof(auth)); return new Get5Player(userId, auth, side, name, false); } else { - char botId[8]; + char botId[10]; Format(botId, sizeof(botId), "BOT-%d", userId); return new Get5Player(userId, botId, side, name, true); } @@ -183,7 +181,7 @@ public void Stats_ResetRoundValues() { g_TeamFirstDeathDone[CS_TEAM_CT] = false; g_TeamFirstDeathDone[CS_TEAM_T] = false; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { Stats_ResetClientRoundValues(i); } } @@ -196,7 +194,7 @@ public void Stats_ResetClientRoundValues(int client) { g_PlayerRoundKillOrAssistOrTradedDeath[client] = false; g_PlayerSurvived[client] = true; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { g_DamageDone[client][i] = 0; g_DamageDoneHits[client][i] = 0; g_DamageDoneKill[client][i] = false; @@ -252,17 +250,14 @@ public void Stats_ResetGrenadeContainers() { } public void Stats_RoundStart() { - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i)) { Get5Team team = GetClientMatchTeam(i); if (team == Get5Team_1 || team == Get5Team_2) { + // Ensures that each player has zero-filled stats on freeze-time end. + // Since joining the game after freeze-time will render you dead, you cannot obtain stats until next round. + InitPlayerStats(i); IncrementPlayerStat(i, STAT_ROUNDSPLAYED); - - GoToPlayer(i); - char name[MAX_NAME_LENGTH]; - GetClientName(i, name, sizeof(name)); - g_StatsKv.SetString(STAT_NAME, name); - GoBackFromPlayer(); } } } @@ -285,7 +280,7 @@ public void Stats_RoundEnd(int csTeamWinner) { GoBackFromTeam(); // Update player 1vx, x-kill, and KAST values. - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i)) { Get5Team team = GetClientMatchTeam(i); if (team == Get5Team_1 || team == Get5Team_2) { @@ -332,14 +327,6 @@ public void Stats_RoundEnd(int csTeamWinner) { } } } - - if (g_DamagePrintCvar.BoolValue) { - for (int i = 1; i <= MaxClients; i++) { - if (IsValidClient(i)) { - PrintDamageInfo(i); - } - } - } } public void Stats_UpdateMapScore(Get5Team winner) { @@ -804,12 +791,12 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr } static void UpdateTradeStat(int attacker, int victim) { + int attackerTeam = GetClientTeam(attacker); // Look to see if victim killed any of attacker's teammates recently. - for (int i = 1; i <= MaxClients; i++) { - if (IsPlayer(i) && g_PlayerKilledBy[i] == victim && - GetClientTeam(i) == GetClientTeam(attacker)) { + LOOP_CLIENTS(i) { + if (IsPlayer(i) && g_PlayerKilledBy[i] == victim && GetClientTeam(i) == attackerTeam) { float dt = GetGameTime() - g_PlayerKilledByTime[i]; - if (dt < kTimeGivenToTrade) { + if (dt < 1.5) { // "Time to trade" window fixed to 1.5 seconds. IncrementPlayerStat(attacker, STAT_TRADEKILL); // teammate (i) was traded g_PlayerRoundKillOrAssistOrTradedDeath[i] = true; @@ -980,16 +967,79 @@ static int SetPlayerStat(int client, const char[] field, int newValue) { return newValue; } +static void InitPlayerStats(int client) { + if (!GoToPlayer(client)) { + return; + } + + // Always update the name. + char name[MAX_NAME_LENGTH]; + GetClientName(client, name, sizeof(name)); + g_StatsKv.SetString(STAT_NAME, name); + + // If the player already had their stats set, don't override them. + if (g_StatsKv.GetNum(STAT_INIT, 0) > 0) { + GoBackFromPlayer(); + return; + } + + char keys[][] = { + STAT_KILLS, + STAT_DEATHS, + STAT_ASSISTS, + STAT_FLASHBANG_ASSISTS, + STAT_TEAMKILLS, + STAT_SUICIDES, + STAT_DAMAGE, + STAT_UTILITY_DAMAGE, + STAT_ENEMIES_FLASHED, + STAT_FRIENDLIES_FLASHED, + STAT_KNIFE_KILLS, + STAT_HEADSHOT_KILLS, + STAT_ROUNDSPLAYED, + STAT_BOMBDEFUSES, + STAT_BOMBPLANTS, + STAT_1K, + STAT_2K, + STAT_3K, + STAT_4K, + STAT_5K, + STAT_V1, + STAT_V2, + STAT_V3, + STAT_V4, + STAT_V5, + STAT_FIRSTKILL_T, + STAT_FIRSTKILL_CT, + STAT_FIRSTDEATH_T, + STAT_FIRSTDEATH_CT, + STAT_TRADEKILL, + STAT_KAST, + STAT_CONTRIBUTION_SCORE, + STAT_MVP + }; + + int length = sizeof(keys); + for (int i = 0; i < length; i++) + { + g_StatsKv.SetNum(keys[i], 0); + } + + g_StatsKv.SetNum(STAT_INIT, 1); + + GoBackFromPlayer(); +} + public int AddToPlayerStat(int client, const char[] field, int delta) { if (IsFakeClient(client)) { return 0; } + LogDebug("Updating player stat %s for %L", field, client); int value = GetPlayerStat(client, field); return SetPlayerStat(client, field, value + delta); } static int IncrementPlayerStat(int client, const char[] field) { - LogDebug("Incrementing player stat %s for %L", field, client); return AddToPlayerStat(client, field, 1); } @@ -1003,13 +1053,17 @@ static void GoBackFromMap() { g_StatsKv.GoBack(); } -static void GoToTeam(Get5Team team) { +static bool GoToTeam(Get5Team team) { GoToMap(); - if (team == Get5Team_1) + if (team == Get5Team_1) { g_StatsKv.JumpToKey("team1", true); - else + return true; + } else if (team == Get5Team_2) { g_StatsKv.JumpToKey("team2", true); + return true; + } + return false; } static void GoBackFromTeam() { @@ -1017,14 +1071,18 @@ static void GoBackFromTeam() { g_StatsKv.GoBack(); } -static void GoToPlayer(int client) { +static bool GoToPlayer(int client) { Get5Team team = GetClientMatchTeam(client); - GoToTeam(team); + if (!GoToTeam(team)) { + return false; + } char auth[AUTH_LENGTH]; if (GetAuth(client, auth, sizeof(auth))) { g_StatsKv.JumpToKey(auth, true); + return true; } + return false; } static void GoBackFromPlayer() { @@ -1043,7 +1101,7 @@ public int GetMapStatsNumber() { static int GetClutchingClient(int csTeam) { int client = -1; int count = 0; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i) && IsPlayerAlive(i) && GetClientTeam(i) == csTeam) { client = i; count++; @@ -1126,19 +1184,22 @@ JSON_Object EncodeKeyValue(KeyValues kv) { return json_kv; } -static void PrintDamageInfo(int client) { - if (!IsPlayer(client)) +public void PrintDamageInfo(int client) { + if (!IsPlayer(client)) { return; + } int team = GetClientTeam(client); - if (team != CS_TEAM_T && team != CS_TEAM_CT) + if (team != CS_TEAM_T && team != CS_TEAM_CT) { return; + } char message[256]; int msgSize = sizeof(message); int otherTeam = (team == CS_TEAM_T) ? CS_TEAM_CT : CS_TEAM_T; - for (int i = 1; i <= MaxClients; i++) { + + LOOP_CLIENTS(i) { if (IsValidClient(i) && IsClientInGame(i) && GetClientTeam(i) == otherTeam) { int health = IsPlayerAlive(i) ? GetClientHealth(i) : 0; char name[64]; diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 9ab377b33..0e0768c1c 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -16,7 +16,7 @@ static char _colorCodes[][] = {"\x01", "\x02", "\x03", "\x04", "\x05", "\x06", // Convenience macros. #define LOOP_TEAMS(%1) for (Get5Team %1 = Get5Team_1; %1 < Get5Team_Count; %1 ++) -#define LOOP_CLIENTS(%1) for (int %1 = 0; %1 <= MaxClients; %1 ++) +#define LOOP_CLIENTS(%1) for (int %1 = 1; %1 <= MaxClients; %1 ++) // These match CS:GO's m_gamePhase values. enum GamePhase { @@ -31,7 +31,7 @@ enum GamePhase { */ stock int GetNumHumansOnTeam(int team) { int count = 0; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i) && GetClientTeam(i) == team) { count++; } @@ -41,7 +41,7 @@ stock int GetNumHumansOnTeam(int team) { stock int CountAlivePlayersOnTeam(int csTeam) { int count = 0; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i) && IsPlayerAlive(i) && GetClientTeam(i) == csTeam) { count++; } @@ -51,7 +51,7 @@ stock int CountAlivePlayersOnTeam(int csTeam) { stock int SumHealthOfTeam(int team) { int sum = 0; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i) && IsPlayerAlive(i) && GetClientTeam(i) == team) { sum += GetClientHealth(i); } @@ -560,7 +560,7 @@ stock bool GetAuth(int client, char[] auth, int size) { // TODO: might want a auth->client adt-trie to speed this up, maintained during // client auth and disconnect forwards. stock int AuthToClient(const char[] auth) { - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { char clientAuth[AUTH_LENGTH]; if (GetAuth(i, clientAuth, sizeof(clientAuth)) && StrEqual(auth, clientAuth)) { diff --git a/scripting/get5_apistats.sp b/scripting/get5_apistats.sp index 6ec2b3132..ecae7a372 100644 --- a/scripting/get5_apistats.sp +++ b/scripting/get5_apistats.sp @@ -300,7 +300,7 @@ public void UpdatePlayerStats(const char[] matchId, KeyValues kv, Get5Team team) mapNumber, auth); if (req != INVALID_HANDLE) { AddStringParam(req, "team", teamString); - AddStringParam(req, "name", name); + AddStringParam(req, STAT_NAME, name); AddIntStat(req, kv, STAT_KILLS); AddIntStat(req, kv, STAT_DEATHS); AddIntStat(req, kv, STAT_ASSISTS); @@ -308,6 +308,10 @@ public void UpdatePlayerStats(const char[] matchId, KeyValues kv, Get5Team team) AddIntStat(req, kv, STAT_TEAMKILLS); AddIntStat(req, kv, STAT_SUICIDES); AddIntStat(req, kv, STAT_DAMAGE); + AddIntStat(req, kv, STAT_UTILITY_DAMAGE); + AddIntStat(req, kv, STAT_ENEMIES_FLASHED); + AddIntStat(req, kv, STAT_FRIENDLIES_FLASHED); + AddIntStat(req, kv, STAT_KNIFE_KILLS); AddIntStat(req, kv, STAT_HEADSHOT_KILLS); AddIntStat(req, kv, STAT_ROUNDSPLAYED); AddIntStat(req, kv, STAT_BOMBPLANTS); @@ -329,6 +333,7 @@ public void UpdatePlayerStats(const char[] matchId, KeyValues kv, Get5Team team) AddIntStat(req, kv, STAT_TRADEKILL); AddIntStat(req, kv, STAT_KAST); AddIntStat(req, kv, STAT_CONTRIBUTION_SCORE); + AddIntStat(req, kv, STAT_MVP); SteamWorks_SendHTTPRequest(req); } diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index d582bcd6d..56627740a 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -1798,6 +1798,8 @@ forward void Get5_OnBackupRestore(const Get5BackupRestoredEvent event); #define STAT_TEAMSCORE "score" // Player stats (under map section, then team section, then player's steam64) +// If adding stuff here, also add to the InitPlayerStats function! +#define STAT_INIT "init" // used to zero-fill stats only. Not a real stat. #define STAT_NAME "name" #define STAT_KILLS "kills" #define STAT_DEATHS "deaths" From b1a33c3d59079316817c0982ad1a2ab13bfdeaa3 Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Wed, 3 Aug 2022 06:23:53 -0600 Subject: [PATCH 009/104] Swap team logic to only check for warmup for CS coach changes. (#815) --- scripting/get5/teamlogic.sp | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index fc229f33f..8d4082861 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -18,21 +18,10 @@ public Action Timer_PlacePlayerOnJoin(Handle timer, int userId) { public void CheckClientTeam(int client) { Get5Team correctTeam = GetClientMatchTeam(client); - char auth[AUTH_LENGTH]; int csTeam = Get5TeamToCSTeam(correctTeam); int currentTeam = GetClientTeam(client); - if (csTeam != currentTeam) { - if (IsClientCoaching(client)) { - UpdateCoachTarget(client, csTeam); - } else if (GetAuth(client, auth, sizeof(auth))) { - char steam64[AUTH_LENGTH]; - ConvertAuthToSteam64(auth, steam64); - if (IsAuthOnTeamCoach(steam64, correctTeam)) { - UpdateCoachTarget(client, csTeam); - } - } - + if (!CheckIfClientCoachingAndMoveToCoach(client, correctTeam) && csTeam != currentTeam) { SwitchPlayerTeam(client, csTeam); } } @@ -111,8 +100,6 @@ public Action Command_JoinTeam(int client, const char[] command, int argc) { LogDebug("Forcing player %N onto %d", client, csTeam); FakeClientCommand(client, "jointeam %d", csTeam); } - - return Plugin_Stop; } return Plugin_Stop; @@ -126,7 +113,6 @@ public bool CheckIfClientCoachingAndMoveToCoach(int client, Get5Team team) { char clientAuth64[AUTH_LENGTH]; GetAuth(client, clientAuth64, AUTH_LENGTH); if (IsAuthOnTeamCoach(clientAuth64, team)) { - LogDebug("Forcing player %N to coach as they were previously.", client); MoveClientToCoach(client); return true; } @@ -169,17 +155,17 @@ public void MoveClientToCoach(int client) { // coaching command. Otherwise we manually move them to spec // and set the coaching target. // If in freeze time, we have to manually move as well. - if (!InWarmup() && InFreezeTime()) { + if (InWarmup()) { + LogDebug("Moving %L indirectly to coach slot via coach cmd", client); + g_MovingClientToCoach[client] = true; + FakeClientCommand(client, "coach %s", teamString); + g_MovingClientToCoach[client] = false; + } else { LogDebug("Moving %L directly to coach slot", client); SwitchPlayerTeam(client, CS_TEAM_SPECTATOR); UpdateCoachTarget(client, csTeam); // Need to set to avoid third person view bug. SetEntProp(client, Prop_Send, "m_iObserverMode", 4); - } else { - LogDebug("Moving %L indirectly to coach slot via coach cmd", client); - g_MovingClientToCoach[client] = true; - FakeClientCommand(client, "coach %s", teamString); - g_MovingClientToCoach[client] = false; } } From 072ac16c7b6193dff3232233bf83ab9d81bad1c2 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 3 Aug 2022 20:45:07 +0200 Subject: [PATCH 010/104] Added support for subfolders for backups (#793) Move round and map number inits to prestart Remove GetMapStatsNumber Reset g_ vars when loading match config --- documentation/docs/backup.md | 4 +- documentation/docs/commands.md | 8 ++- documentation/docs/configuration.md | 22 ++++++- documentation/docs/developer_api.md | 2 +- documentation/docs/pausing.md | 18 +++--- documentation/docs/stats_system.md | 2 +- scripting/get5.sp | 94 +++++++++++++++++++++-------- scripting/get5/backups.sp | 51 +++++++++++----- scripting/get5/matchconfig.sp | 2 + scripting/get5/stats.sp | 10 +-- 10 files changed, 148 insertions(+), 65 deletions(-) diff --git a/documentation/docs/backup.md b/documentation/docs/backup.md index 2e828b2a1..fa7e6cd2e 100644 --- a/documentation/docs/backup.md +++ b/documentation/docs/backup.md @@ -22,7 +22,7 @@ respectively. A special backup called `get5_backup_match%s_map%d_prelive.cfg` is When in a match, you can call [`get5_listbackups`](../commands/#get5_listbackups) to view all backups for the current match. Note that all rounds and map numbers start at 0. -They print in the format `filename date team1 team2 map team1_score team2_score`. +They print in the format `filepath date time team1 team2 map team1_score team2_score`. ``` > get5_listbackups @@ -37,7 +37,7 @@ get5_backup_match1844_map0_round17.cfg 2022-07-26 19:03:39 "Team A" "Team B" de_ ``` To load at the beginning of round 13 of the first map of match ID 1844, all players should be connected to the server, -and you can type: +and you use the [`get5_loadbackup`](../commands/#get5_loadbackup) command: `get5_loadbackup get5_backup_match1844_map0_round12.cfg`. diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 07c69e0d5..0faa9a9c4 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -74,8 +74,9 @@ Please note that these are meant to be used by *admins* in console. : Loads a [match configuration](../match_schema) file (JSON or KeyValue) relative from the `csgo` directory. ####`get5_loadbackup ` {: #get5_loadbackup } -: Loads a match backup file (JSON or KeyValue) relative from the `csgo` -directory. Only works if the [backup system is enabled](../configuration/#get5_backup_system_enabled). +: Loads a match backup, relative from the `csgo` +directory. Only works if the [backup system is enabled](../configuration/#get5_backup_system_enabled). If you define +[`get5_backup_path`](../configuration/#get5_backup_path), you must include the path in the filename. ####`get5_last_backup_file` : Prints the name of the last match backup file Get5 wrote in the current series, this is automatically updated each @@ -218,7 +219,8 @@ name. ``` ####`get5_listbackups [matchid]` {: #get5_listbackups } -: Lists backup files for the current match or a given match ID if provided. +: Lists backup files for the current match or a given match ID if provided. If you define +[`get5_backup_path`](../configuration/#get5_backup_path), it will only list backups found under that prefix. ####`get5_ringer ` : Adds/removes a ringer to/from the home scrim team. `player` is the name of the player. Similar diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index bedf27779..e848b83fd 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -200,7 +200,27 @@ disable. **`Default: get5_matchstats_{MATCHID}.cfg`** command as well as the [`get5_loadbackup`](../commands/#get5_loadbackup) command. **`Default: 1`** ####`get5_max_backup_age` -: Number of seconds before a Get5 backup file is automatically deleted. 0 to disable. **`Default: 160000`** +: Number of seconds before a Get5 backup file is automatically deleted. 0 to disable. If you define +[`get5_backup_path`](#get5_backup_path), only files in that path will be deleted. **`Default: 160000`** + +####`get5_backup_path` +: The folder of saved [backup files](../commands/#get5_loadbackup), relative to the `csgo` directory. You **can** use +the [`{MATCHID}`](#tag-matchid) variable, i.e. `backups/{MATCHID}/`. **`Default: ""`** + +!!! warning "Slash, slash, hundred yard dash :material-slash-forward:" + + It is very important that your backup path does **not** start with a slash but instead **ends with a slash**. If + not, the last part of the path will be considered a prefix of the filename and things will not work correctly. Also + note that if you use the [`{MATCHID}`](#tag-matchid) variable, [automatic deletion of backups](#get5_max_backup_age) + does not work. + + :white_check_mark: `backups/` + + :white_check_mark: `backups/{MATCHID}/` + + :no_entry: `/backups/` + + :no_entry: `/backups/{MATCHID}` ## Config Files diff --git a/documentation/docs/developer_api.md b/documentation/docs/developer_api.md index f194cf942..6b009be6f 100644 --- a/documentation/docs/developer_api.md +++ b/documentation/docs/developer_api.md @@ -1,4 +1,4 @@ -# Developer API +# :material-code-braces: Developer API Get5 can be interacted with in several ways. At a glance: diff --git a/documentation/docs/pausing.md b/documentation/docs/pausing.md index 5acb12ce8..841e9f306 100644 --- a/documentation/docs/pausing.md +++ b/documentation/docs/pausing.md @@ -35,9 +35,15 @@ Administrators cannot call technical pauses, as an administrative pause will be triggered instead. You can set [the maximum number of technical pauses](../configuration/#get5_max_tech_pauses). -## :material-account-hard-hat-outline: Administrative {: #dministrative } +## :material-backup-restore: Backup {: #backup } -As a [server admin](../installation/#administrators), you can pause the match at any time and with no time +If the game is [restored from a backup](backup.md), it will be so in a paused state. Both teams must +[`!unpause`](../commands/#unpause) before the match can continue. Administrators can also unpause backup pauses, or even +override them to an [administrative pause](#administrative). + +## :material-account-hard-hat-outline: Administrative {: #administrative } + +As a server admin, you can pause the match at any time and with no time restrictions, but you **cannot** use [`mp_pause_match`](https://totalcsgo.com/command/mppausematch) (or its unpause equivalent) at any stage. Due to the way Get5 handles pausing, you must use `sm_pause` in the console, since this will track all details and configurations related to pausing in the system. Similarly, `sm_unpause` must be used to unpause. @@ -45,13 +51,7 @@ Pauses initiated by administrators via console **cannot** be [`!unpause`'ed](../ that an [`admin` pause event](events_and_forwards.md) is fired when the game is paused during veto (only if [`get5_pause_on_veto`](../configuration/#get5_pause_on_veto) is enabled). -!!! fail "I'm an admin on my server, but I cannot call admin pauses!" +!!! question "I'm an admin on my server, but I cannot call admin pause?" Only console/RCON is considered an administrator in pause-context. Having an admin flag as a user/player does not allow you to call administrative pauses. - -!!! help "But the [event system](events_and_forwards.md) also hints at the existence of a `backup` pause type?" - - Internally, Get5 uses the `backup` pause type when pausing due to loading backup configurations. To not confuse - any program reaction to pauses, we added a special pause type in these cases, and you should probably just ignore - these events. They still fire because *technically* the game is pausing. diff --git a/documentation/docs/stats_system.md b/documentation/docs/stats_system.md index 003f93e0a..eb77525f3 100644 --- a/documentation/docs/stats_system.md +++ b/documentation/docs/stats_system.md @@ -1,4 +1,4 @@ -# Player Stats System +# :material-chart-bar: Player Stats System !!! warning diff --git a/scripting/get5.sp b/scripting/get5.sp index 6d2e0c829..1902a0fc6 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -99,6 +99,7 @@ ConVar g_VetoConfirmationTimeCvar; ConVar g_VetoCountdownCvar; ConVar g_WarmupCfgCvar; ConVar g_PrintUpdateNoticeCvar; +ConVar g_RoundBackupPathCvar; // Autoset convars (not meant for users to set) ConVar g_GameStateCvar; @@ -432,6 +433,9 @@ public void OnPluginStart() { g_WarmupCfgCvar = CreateConVar("get5_warmup_cfg", "get5/warmup.cfg", "Config file to exec in warmup periods"); g_PrintUpdateNoticeCvar = CreateConVar("get5_print_update_notice", "1", "Whether to print to chat when the game goes live if a new version of Get5 is available."); + g_RoundBackupPathCvar = CreateConVar( + "get5_backup_path", "", + "The folder to save backup files in, relative to the csgo directory. If defined, it must not start with a slash and must end with a slash."); /** Create and exec plugin's configuration file **/ AutoExecConfig(true, "get5"); @@ -1328,6 +1332,15 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad Stats_ResetRoundValues(); + // We need this for events that fire after the map ends, such as grenades detonating (or someone + // dying in fire), to be correct. It's sort of an edge-case, but due to how Get5_GetMapNumber + // works, it will return +1 if called after a map has been decided, but before the game actually + // stops, which could lead to events having the wrong map number, so we set both of these here and not + // in round_end + g_MapNumber = Get5_GetMapNumber(); + // Round number always -1 if not live. + g_RoundNumber = g_GameState != Get5State_Live ? -1 : GetRoundsPlayed(); + if (g_GameState >= Get5State_Warmup && !g_DoingBackupRestoreNow) { WriteBackup(); } @@ -1349,38 +1362,76 @@ public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast } } + +static bool CreateDirectoryWithPermissions(const char[] directory) { + LogDebug("Creating directory: %s", directory); + return CreateDirectory(directory, // sets 777 permissions. + FPERM_U_READ | FPERM_U_WRITE | FPERM_U_EXEC | + FPERM_G_READ | FPERM_G_WRITE | FPERM_G_EXEC | + FPERM_O_READ | FPERM_O_WRITE | FPERM_O_EXEC + ); +} + +static bool CreateBackupFolderStructure(const char[] path) { + if (strlen(path) == 0 || DirExists(path)) { + return true; + } + + LogDebug("Creating backup directory %s because it does not exist.", path); + char folders[16][PLATFORM_MAX_PATH]; // {folder1, folder2, etc} + char fullFolderPath[PLATFORM_MAX_PATH] = ""; // initially empty, but we append every time a folder is created/verified + char currentFolder[PLATFORM_MAX_PATH]; // shorthand for folders[i] + + ExplodeString(path, "/", folders, sizeof(folders), PLATFORM_MAX_PATH, true); + for (int i = 0; i < sizeof(folders); i++) { + currentFolder = folders[i]; + if (strlen(currentFolder) == 0) { // as the loop is a fixed size, we stop when there are no more pieces. + break; + } + // Append the current folder to the full path + Format(fullFolderPath, sizeof(fullFolderPath), "%s%s/", fullFolderPath, currentFolder); + if (!DirExists(fullFolderPath) && !CreateDirectoryWithPermissions(fullFolderPath)) { + LogError("Failed to create or verify existence of directory: %s", fullFolderPath); + return false; + } + } + return true; +} + public void WriteBackup() { - if (!g_BackupSystemEnabledCvar.BoolValue) { + if (!g_BackupSystemEnabledCvar.BoolValue || g_DoingBackupRestoreNow) { return; } - char path[PLATFORM_MAX_PATH]; - if (g_GameState == Get5State_Live) { - Format(path, sizeof(path), "get5_backup_match%s_map%d_round%d.cfg", g_MatchID, - GetMapStatsNumber(), GetRoundsPlayed()); + char folder[PLATFORM_MAX_PATH]; + g_RoundBackupPathCvar.GetString(folder, sizeof(folder)); + ReplaceString(folder, sizeof(folder), "{MATCHID}", g_MatchID); + + int backupFolderLength = strlen(folder); + if (backupFolderLength > 0 && (folder[0] == '/' || folder[0] == '.' || folder[backupFolderLength-1] != '/' || StrContains(folder, "//") != -1)) { + LogError("get5_backup_path must end with a slash and must not start with a slash or dot. It will be reset to an empty string! Current value: %s", folder); + folder = ""; + g_RoundBackupPathCvar.SetString(folder, false, false); } else { - Format(path, sizeof(path), "get5_backup_match%s_map%d_prelive.cfg", g_MatchID, - GetMapStatsNumber()); + CreateBackupFolderStructure(folder); } - LogDebug("created path %s", path); - - if (!g_DoingBackupRestoreNow) { - LogDebug("writing to %s", path); - WriteBackStructure(path); - g_LastGet5BackupCvar.SetString(path); + char path[PLATFORM_MAX_PATH]; + if (g_GameState == Get5State_Live) { + Format(path, sizeof(path), "%sget5_backup_match%s_map%d_round%d.cfg", folder, g_MatchID, + g_MapNumber, g_RoundNumber); + } else { + Format(path, sizeof(path), "%sget5_backup_match%s_map%d_prelive.cfg", folder, g_MatchID, + g_MapNumber); } + LogDebug("Writing backup to %s", path); + WriteBackStructure(path); + g_LastGet5BackupCvar.SetString(path); } public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundStart"); - // We need this for events that fire after the map ends, such as grenades detonating (or someone - // dying in fire), to be correct. It's sort of an edge-case, but due to how Get5_GetMapNumber - // works, it will return +1 if called after a map has been decided, but before the game actually - // stops, which could lead to events having the wrong map number. - g_MapNumber = Get5_GetMapNumber(); - // Always reset these on round start, regardless of game state. // This ensures that the functions that rely on these don't get messed up. g_RoundStartedTime = 0.0; @@ -1388,14 +1439,9 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas g_BombSiteLastPlanted = Get5BombSite_Unknown; if (g_GameState != Get5State_Live) { - g_RoundNumber = -1; // Round number always -1 if not yet live. return; } - // The same logic for tracking after-round-end events apply to g_RoundNumber, so that's set here - // as well. - g_RoundNumber = GetRoundsPlayed(); - Get5RoundStartedEvent startEvent = new Get5RoundStartedEvent(g_MatchID, g_MapNumber, g_RoundNumber); diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 18a703d80..c646f9aba 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -34,20 +34,25 @@ public Action Command_ListBackups(int client, int args) { strcopy(matchID, sizeof(matchID), g_MatchID); } - char pattern[PLATFORM_MAX_PATH]; - Format(pattern, sizeof(pattern), "get5_backup_match%s", matchID); + char path[PLATFORM_MAX_PATH]; + g_RoundBackupPathCvar.GetString(path, sizeof(path)); + ReplaceString(path, sizeof(path), "{MATCHID}", matchID); - DirectoryListing files = OpenDirectory("."); + DirectoryListing files = OpenDirectory(strlen(path) > 0 ? path : "."); if (files != null) { - char path[PLATFORM_MAX_PATH]; - char backupInfo[256]; - while (files.GetNext(path, sizeof(path))) { - if (StrContains(path, pattern) == 0) { - if (GetBackupInfo(path, backupInfo, sizeof(backupInfo))) { + char backupInfo[256]; + char pattern[PLATFORM_MAX_PATH]; + Format(pattern, sizeof(pattern), "get5_backup_match%s", matchID); + + char filename[PLATFORM_MAX_PATH]; + while (files.GetNext(filename, sizeof(filename))) { + if (StrContains(filename, pattern) == 0) { + Format(filename, sizeof(filename), "%s%s", path, filename); + if (GetBackupInfo(filename, backupInfo, sizeof(backupInfo))) { ReplyToCommand(client, backupInfo); } else { - ReplyToCommand(client, path); + ReplyToCommand(client, filename); } } } @@ -401,18 +406,34 @@ public Action Timer_FinishBackup(Handle timer) { public void DeleteOldBackups() { int maxTimeDifference = g_MaxBackupAgeCvar.IntValue; if (maxTimeDifference <= 0) { + LogDebug("Backups are not being deleted as get5_max_backup_age is 0."); + return; + } + + char path[PLATFORM_MAX_PATH]; + g_RoundBackupPathCvar.GetString(path, sizeof(path)); + + if (StrContains(path, "{MATCHID}") != -1) { + LogError("Automatic backup deletion cannot be performed when get5_backup_path contains the {MATCHID} variable."); return; } - DirectoryListing files = OpenDirectory("."); + DirectoryListing files = OpenDirectory(strlen(path) > 0 ? path : "."); if (files != null) { - char path[PLATFORM_MAX_PATH]; - while (files.GetNext(path, sizeof(path))) { - if (StrContains(path, "get5_backup_") == 0 && - GetTime() - GetFileTime(path, FileTime_LastChange) >= maxTimeDifference) { - DeleteFile(path); + LogDebug("Searching '%s' for expired backups...", path); + char filename[PLATFORM_MAX_PATH]; + while (files.GetNext(filename, sizeof(filename))) { + if (StrContains(filename, "get5_backup_") == 0) { + Format(filename, sizeof(filename), "%s%s", path, filename); + if (GetTime() - GetFileTime(filename, FileTime_LastChange) >= maxTimeDifference) { + if (DeleteFileIfExists(filename)) { + LogDebug("Deleted '%s' as it was older than %d seconds.", filename, maxTimeDifference); + } + } } } delete files; + } else { + LogError("Failed to list contents of directory '%s' for backup deletion.", path); } } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 274aef06b..e128274e1 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -39,6 +39,8 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { g_ForceWinnerSignal = false; g_ForcedWinner = Get5Team_None; + g_MapNumber = 0; + g_RoundNumber = -1; g_LastVetoTeam = Get5Team_2; g_MapPoolList.Clear(); g_MapsLeftInVetoPool.Clear(); diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index e12fe5655..a9a167f3b 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -1045,7 +1045,7 @@ static int IncrementPlayerStat(int client, const char[] field) { static void GoToMap() { char mapNumberString[32]; - Format(mapNumberString, sizeof(mapNumberString), "map%d", GetMapStatsNumber()); + Format(mapNumberString, sizeof(mapNumberString), "map%d", g_MapNumber); g_StatsKv.JumpToKey(mapNumberString, true); } @@ -1090,14 +1090,6 @@ static void GoBackFromPlayer() { g_StatsKv.GoBack(); } -public int GetMapStatsNumber() { - int x = Get5_GetMapNumber(); - if (g_MapChangePending) { - x--; - } - return x; -} - static int GetClutchingClient(int csTeam) { int client = -1; int count = 0; From b1b2b6e2c80d67086e11234cdb39c0b50c94d2fc Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 3 Aug 2022 20:55:15 +0200 Subject: [PATCH 011/104] Only allow one first kill per round, not per team (#818) --- scripting/get5.sp | 2 +- scripting/get5/stats.sp | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 1902a0fc6..69f3a9c9c 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -178,11 +178,11 @@ int g_LatestUserIdToDetonateMolotov = 0; // Molotov detonate and start-burning/extinguish are two separate events always fired right // after each other. We need this to bind them together as detonate does not have client id. int g_LatestMolotovToExtinguishBySmoke = 0; // Attributes extinguish booleans to smoke grenades. +bool g_FirstKillDone = false; bool g_SetTeamClutching[4]; int g_RoundKills[MAXPLAYERS + 1]; // kills per round each client has gotten int g_RoundClutchingEnemyCount[MAXPLAYERS + 1]; // number of enemies left alive when last alive on your team -bool g_TeamFirstKillDone[MATCHTEAM_COUNT]; bool g_TeamFirstDeathDone[MATCHTEAM_COUNT]; int g_PlayerKilledBy[MAXPLAYERS + 1]; float g_PlayerKilledByTime[MAXPLAYERS + 1]; diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index a9a167f3b..9cad02410 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -174,10 +174,9 @@ public void Stats_InitSeries() { } public void Stats_ResetRoundValues() { + g_FirstKillDone = false; g_SetTeamClutching[CS_TEAM_CT] = false; g_SetTeamClutching[CS_TEAM_T] = false; - g_TeamFirstKillDone[CS_TEAM_CT] = false; - g_TeamFirstKillDone[CS_TEAM_T] = false; g_TeamFirstDeathDone[CS_TEAM_CT] = false; g_TeamFirstDeathDone[CS_TEAM_T] = false; @@ -718,10 +717,9 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr if (attackerTeam == victimTeam) { IncrementPlayerStat(attacker, STAT_TEAMKILLS); } else { - if (!g_TeamFirstKillDone[attackerTeam]) { - g_TeamFirstKillDone[attackerTeam] = true; - IncrementPlayerStat(attacker, - (attackerTeam == CS_TEAM_CT) ? STAT_FIRSTKILL_CT : STAT_FIRSTKILL_T); + if (!g_FirstKillDone) { + g_FirstKillDone = true; + IncrementPlayerStat(attacker, (attackerTeam == CS_TEAM_CT) ? STAT_FIRSTKILL_CT : STAT_FIRSTKILL_T); } g_RoundKills[attacker]++; From a5e647b45bf0006823bbb40da80e038aee51b36a Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 4 Aug 2022 00:18:44 +0200 Subject: [PATCH 012/104] Also don't allow first death more than once per round (#819) --- scripting/get5.sp | 2 +- scripting/get5/stats.sp | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 69f3a9c9c..ed1104f05 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -179,11 +179,11 @@ int g_LatestUserIdToDetonateMolotov = // after each other. We need this to bind them together as detonate does not have client id. int g_LatestMolotovToExtinguishBySmoke = 0; // Attributes extinguish booleans to smoke grenades. bool g_FirstKillDone = false; +bool g_FirstDeathDone = false; bool g_SetTeamClutching[4]; int g_RoundKills[MAXPLAYERS + 1]; // kills per round each client has gotten int g_RoundClutchingEnemyCount[MAXPLAYERS + 1]; // number of enemies left alive when last alive on your team -bool g_TeamFirstDeathDone[MATCHTEAM_COUNT]; int g_PlayerKilledBy[MAXPLAYERS + 1]; float g_PlayerKilledByTime[MAXPLAYERS + 1]; int g_DamageDone[MAXPLAYERS + 1][MAXPLAYERS + 1]; diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 9cad02410..6b94be7d6 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -175,10 +175,9 @@ public void Stats_InitSeries() { public void Stats_ResetRoundValues() { g_FirstKillDone = false; + g_FirstDeathDone = false; g_SetTeamClutching[CS_TEAM_CT] = false; g_SetTeamClutching[CS_TEAM_T] = false; - g_TeamFirstDeathDone[CS_TEAM_CT] = false; - g_TeamFirstDeathDone[CS_TEAM_T] = false; LOOP_CLIENTS(i) { Stats_ResetClientRoundValues(i); @@ -705,10 +704,9 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr // used for calculating round KAST g_PlayerSurvived[victim] = false; - if (!g_TeamFirstDeathDone[victimTeam]) { - g_TeamFirstDeathDone[victimTeam] = true; - IncrementPlayerStat(victim, - (victimTeam == CS_TEAM_CT) ? STAT_FIRSTDEATH_CT : STAT_FIRSTDEATH_T); + if (!g_FirstDeathDone) { + g_FirstDeathDone = true; + IncrementPlayerStat(victim, (victimTeam == CS_TEAM_CT) ? STAT_FIRSTDEATH_CT : STAT_FIRSTDEATH_T); } if (isSuicide) { From 8e5c1e6a1c64b959479d51a8a0d59c7feb0203b2 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 4 Aug 2022 00:49:28 +0200 Subject: [PATCH 013/104] Use LOOP_CLIENTS(i) everywhere applicable (#820) --- scripting/get5.sp | 2 +- scripting/get5/backups.sp | 7 ++++--- scripting/get5/get5menu.sp | 2 +- scripting/get5/kniferounds.sp | 2 +- scripting/get5/matchconfig.sp | 6 +++--- scripting/get5/natives.sp | 6 ++++-- scripting/get5/teamlogic.sp | 6 +++--- scripting/get5/util.sp | 2 +- 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index ed1104f05..1d61f46b7 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1267,7 +1267,7 @@ public Action Timer_NextMatchMap(Handle timer) { public void KickClientsOnEnd() { if (g_KickClientsWithNoMatchCvar.BoolValue) { - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i) && !(g_KickClientImmunityCvar.BoolValue && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) { KickClient(i, "%t", "MatchFinishedInfoMessage"); diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index c646f9aba..0ff31ee3f 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -351,9 +351,10 @@ public void RestoreGet5Backup() { SetStartingTeams(); SetMatchTeamCvars(); ExecuteMatchConfigCvars(); - for (int i = 1; i <= MaxClients; i++) { - if (IsPlayer(i)) + LOOP_CLIENTS(i) { + if (IsPlayer(i)) { CheckClientTeam(i); + } } if (g_GameState == Get5State_Live) { @@ -373,7 +374,7 @@ public void RestoreGet5Backup() { } public Action Timer_SwapCoaches(Handle timer) { - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { CheckIfClientCoachingAndMoveToCoach(i, Get5Team_1); CheckIfClientCoachingAndMoveToCoach(i, Get5Team_2); diff --git a/scripting/get5/get5menu.sp b/scripting/get5/get5menu.sp index 61ed8a359..2f78db77b 100644 --- a/scripting/get5/get5menu.sp +++ b/scripting/get5/get5menu.sp @@ -58,7 +58,7 @@ public void GiveRingerMenu(int client) { menu.ExitButton = true; menu.ExitBackButton = true; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i)) { char infoString[64]; IntToString(GetClientSerial(i), infoString, sizeof(infoString)); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index 2276dd177..984815f1f 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -38,7 +38,7 @@ static void PerformSideSwap(bool swap) { g_TeamSide[Get5Team_2] = g_TeamSide[Get5Team_1]; g_TeamSide[Get5Team_1] = tmp; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsValidClient(i)) { int team = GetClientTeam(i); if (team == CS_TEAM_T) { diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index e128274e1..6acf173d8 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -141,7 +141,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { EventLogger_LogAndDeleteEvent(startEvent); } - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { if (GetClientMatchTeam(i) == Get5Team_None) { RememberAndKickClient(i, "%t", "YouAreNotAPlayerInfoMessage"); @@ -1179,7 +1179,7 @@ static int AddPlayersToAuthKv(KeyValues kv, Get5Team team, char teamName[MAX_CVA kv.JumpToKey("players", true); bool gotClientName = false; char auth[AUTH_LENGTH]; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { int csTeam = GetClientTeam(i); Get5Team t = Get5Team_None; @@ -1250,7 +1250,7 @@ static void AddTeamLogoToDownloadTable(const char[] logoName) { public void CheckTeamNameStatus(Get5Team team) { if (StrEqual(g_TeamNames[team], "") && team != Get5Team_Spec) { - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { if (GetClientMatchTeam(i) == team) { char clientName[MAX_NAME_LENGTH]; diff --git a/scripting/get5/natives.sp b/scripting/get5/natives.sp index 683a29f2f..4f2271748 100644 --- a/scripting/get5/natives.sp +++ b/scripting/get5/natives.sp @@ -65,7 +65,7 @@ public int Native_MessageToTeam(Handle plugin, int numParams) { char buffer[1024]; int bytesWritten = 0; - for (int i = 0; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (!IsPlayer(i) || GetClientMatchTeam(i) != team) { continue; } @@ -90,9 +90,11 @@ public int Native_MessageToAll(Handle plugin, int numParams) { char buffer[1024]; int bytesWritten = 0; + // Don't use LOOP_CLIENTS(i) because we need client 0 here. for (int i = 0; i <= MaxClients; i++) { - if (i != 0 && (!IsClientConnected(i) || !IsClientInGame(i))) + if (i != 0 && (!IsClientConnected(i) || !IsClientInGame(i))) { continue; + } SetGlobalTransTarget(i); FormatNativeString(0, 1, 2, sizeof(buffer), bytesWritten, buffer); diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index 8d4082861..fc25432e8 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -301,7 +301,7 @@ public Get5Team GetAuthMatchTeamCoach(const char[] steam64) { stock int CountPlayersOnCSTeam(int team, int exclude = -1) { int count = 0; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (i != exclude && IsAuthedPlayer(i) && GetClientTeam(i) == team) { count++; } @@ -311,7 +311,7 @@ stock int CountPlayersOnCSTeam(int team, int exclude = -1) { stock int CountPlayersOnMatchTeam(Get5Team team, int exclude = -1) { int count = 0; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team) { count++; } @@ -333,7 +333,7 @@ public Get5Team GetCaptainTeam(int client) { public int GetTeamCaptain(Get5Team team) { // If not forcing auths, take the 1st client on the team. if (!g_CheckAuthsCvar.BoolValue) { - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsAuthedPlayer(i) && GetClientMatchTeam(i) == team) { return i; } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 0e0768c1c..9833ee940 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -171,7 +171,7 @@ stock void ConvertGameStateToStringInJson(const JSON_Object obj, const char[] ke */ stock int GetRealClientCount() { int clients = 0; - for (int i = 1; i <= MaxClients; i++) { + LOOP_CLIENTS(i) { if (IsPlayer(i)) { clients++; } From 468fb774b4d9c31f4c9fed813fe9bb60aaa6ef4e Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Thu, 4 Aug 2022 08:33:16 -0600 Subject: [PATCH 014/104] Move Team Logos and Match Cvars above series init event to allow files to be downloaded. (#816) --- scripting/get5/matchconfig.sp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 6acf173d8..04c8bed5d 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -116,6 +116,13 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { ChangeState(Get5State_PreVeto); } + + // We need to ensure our match team CVARs are set + // before calling the event so we can grab values + // that are set in the OnSeriesInit event. + // Add to download table after setting. + SetMatchTeamCvars(); + if (!restoreBackup) { SetStartingTeams(); ExecCfg(g_WarmupCfgCvar); @@ -152,7 +159,6 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } AddTeamLogosToDownloadTable(); - SetMatchTeamCvars(); ExecuteMatchConfigCvars(); LoadPlayerNames(); strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); From 3a4e55a821a9fd409430fdab6a8e03d931aeb585 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 8 Aug 2022 22:50:58 +0200 Subject: [PATCH 015/104] Prevent stats forwards when doing backups (#823) --- scripting/get5.sp | 4 ++-- scripting/get5/stats.sp | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 1d61f46b7..26722290d 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1357,7 +1357,7 @@ public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast // We always want this to be correct, regardless of game state. g_RoundStartedTime = GetEngineTime(); - if (g_GameState == Get5State_Live) { + if (g_GameState == Get5State_Live && !g_DoingBackupRestoreNow && !g_WaitingForRoundBackup) { Stats_RoundStart(); } } @@ -1456,7 +1456,7 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundEnd"); - if (g_DoingBackupRestoreNow) { + if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return; } diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 6b94be7d6..81c982029 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -18,7 +18,7 @@ public void Stats_PluginStart() { public Action HandlePlayerDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } LogDebug("HandlePlayerDamage(victim=%d, attacker=%d, inflictor=%d, damage=%f, damageType=%d)", @@ -411,7 +411,7 @@ public void EndFlashbangEvent(const char[] flashKey) { } public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -436,7 +436,7 @@ public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontB } public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -464,7 +464,7 @@ public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bo } public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -492,7 +492,7 @@ public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool d } public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -508,7 +508,7 @@ public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, boo } public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -525,7 +525,7 @@ public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontB } public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -545,7 +545,7 @@ public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool do } public Action Stats_FlashbangDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -579,7 +579,7 @@ public Action Timer_HandleFlashbang(Handle timer, int entityId) { } public Action Stats_HEGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -613,7 +613,7 @@ public Action Timer_HandleHEGrenade(Handle timer, int entityId) { } public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -644,7 +644,7 @@ public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dont public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBroadcast) { int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { if (g_AutoReadyActivePlayersCvar.BoolValue && IsAuthedPlayer(attacker)) { // HandleReadyCommand checks for game state, so we don't need to do that here as well. HandleReadyCommand(attacker, true); @@ -802,7 +802,7 @@ static void UpdateTradeStat(int attacker, int victim) { } public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -831,7 +831,7 @@ public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBr } public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -864,7 +864,7 @@ public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBr } public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -883,7 +883,7 @@ public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontB } public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } @@ -925,7 +925,7 @@ public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBr } public Action Stats_RoundMVPEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return Plugin_Continue; } From 5c4add46be63b3d2da8d0859464b487998e2167e Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 8 Aug 2022 23:54:08 +0200 Subject: [PATCH 016/104] Adjust Danish --- translations/da/get5.phrases.txt | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 2978c6fa5..49f7b5e92 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -10,15 +10,15 @@ } "ReadyToRestoreBackupInfoMessage" { - "da" "Skriv {GREEN}!ready {NORMAL}når du er klar til at genoprette match backup." + "da" "Skriv {GREEN}!ready{NORMAL} når du er klar til at genoprette match backup." } "ReadyToKnifeInfoMessage" { - "da" "Skriv {GREEN}!ready {NORMAL}når du er klar til at knife." + "da" "Skriv {GREEN}!ready{NORMAL} når du er klar til at knife." } "ReadyToStartInfoMessage" { - "da" "Skriv {GREEN}!ready {NORMAL}når du er klar til at spille." + "da" "Skriv {GREEN}!ready{NORMAL} når du er klar til at spille." } "YouAreReady" { @@ -26,7 +26,7 @@ } "YouAreReadyAuto" { - "da" "NOTE: Du er blevet markeret som klar grundet din spilaktivitet. Skriv !unready hvis du ikke er klar." + "da" "NOTE: Du er blevet markeret som klar grundet spilaktivitet. Skriv !unready hvis du ikke er klar." } "YouAreNotReady" { @@ -218,15 +218,15 @@ } "TeamWinningSeriesInfoMessage" { - "da" "{1}{NORMAL} vinder serien {2}-{3}" + "da" "{1}{NORMAL} fører serien {2}-{3}." } "SeriesTiedInfoMessage" { - "da" "Serien er uafgjort {1}-{2}" + "da" "Serien er uafgjort {1}-{2}." } "NextSeriesMapInfoMessage" { - "da" "Det næste map i serien er{GREEN}{1}" + "da" "Det næste map i serien er {GREEN}{1}." } "TeamWonMatchInfoMessage" { @@ -234,7 +234,7 @@ } "TeamTiedMatchInfoMessage" { - "da" "{1} og {2} har spillet kampen uafgjort." + "da" "{1} og {2} har spillet uafgjort." } "TeamsSplitSeriesBO2InfoMessage" { @@ -302,11 +302,11 @@ } "TeamPickedMapInfoMessage" { - "da" "{1} valgte {GREEN}{2} {NORMAL}som deres map {3}" + "da" "{1} valgte {GREEN}{2} {NORMAL}som deres {3}. map." } "TeamSelectSideInfoMessage" { - "da" "{1} har valgt at start som {GREEN}{2} {NORMAL}på {3}" + "da" "{1} har valgt at start som {GREEN}{2} {NORMAL}på {3}." } "TeamVetoedMapInfoMessage" { @@ -376,4 +376,12 @@ { "da" "Veto begynder om {GREEN}{1} {NORMAL}sekunder." } + "NewVersionAvailable" + { + "da" "En nyere version af Get5 er tilgængelig. Opdatering kan findes på {1}." + } + "PrereleaseVersionWarning" + { + "da" "Serveren kører en uofficiel version af Get5 ({1}) designet til test og udvikling. Denne besked kan fjernes via {2}." + } } From e56d5d00972372a2d165e6bf306510d19dfcde5d Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 10 Aug 2022 01:14:14 +0200 Subject: [PATCH 017/104] Document map_sides (#824) --- documentation/docs/match_schema.md | 107 +++++++++++++++-------------- scripting/get5/readysystem.sp | 3 +- scripting/get5/util.sp | 8 +-- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 6c7796229..19c005a70 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -35,7 +35,7 @@ interface Get5MatchTeamFromFile { } interface Get5Match { - "match_title": string // (28) + "match_title": string // (25) "matchid": string, // (1) "num_maps": number, // (2) "players_per_team": number, // (3) @@ -45,6 +45,7 @@ interface Get5Match { "skip_veto": boolean, // (7), "veto_first": "team1" | "team2", // (11) "side_type": "standard" | "always_knife" | "never_knife", // (12) + "map_sides": ["team1_ct" | "team1_t" | "knife"], // (31) "spectators": { // (10) "name": string // (29) "players": Get5PlayerSet // (30) @@ -58,65 +59,71 @@ interface Get5Match { } ``` -1. _Optional_ - The ID of the match. This determines the `matchid` parameter in all the forwards and events. If you use -the [MySQL extension](../stats_system/#mysql), you should leave this field blank (or omit it), as match IDs will be -assigned automatically. If you do want to assign match IDs from another source, they **must** be integers (in a string) -and must increment between matches. **`Default: ""`** -2. _Optional_ - The number of maps to play in the series. **`Default: 3`** -3. _Optional_ - The number of players per team. **`Default: 5`** -4. _Optional_ - The maximum number of coaches per team. **`Default: 2`** -5. _Optional_ - The minimum number of players of each team that must type [`!ready`](../commands/#ready) for the game to - begin. **`Default: 1`** -6. _Optional_ - The minimum number of spectators that must be [`!ready`](../commands/#ready) for the game to begin. - **`Default: 0`** -7. _Optional_ - Whether to skip the veto phase. If set to `true`, `team1` will start on CT. If `false`, sides are - determined by `side_type`. **`Default: false`** +1. _Optional_
The ID of the match. This determines the `matchid` parameter in all + [forwards and events](events_and_forwards.md). If you use the [MySQL extension](../stats_system/#mysql), you + should leave this field blank (or omit it), as match IDs will be assigned automatically. If you do want to assign + match IDs from another source, they **must** be integers (in a string) and must increment between + matches.

**`Default: ""`** +2. _Optional_
The number of maps to play in the series.

**`Default: 3`** +3. _Optional_
The number of players per team.

**`Default: 5`** +4. _Optional_
The maximum number of coaches per team.

**`Default: 2`** +5. _Optional_
The minimum number of players of each team that must type [`!ready`](../commands/#ready) for the game + to begin.

**`Default: 1`** +6. _Optional_
The minimum number of spectators that must be [`!ready`](../commands/#ready) for the game to + begin.

**`Default: 0`** +7. _Optional_
Whether to skip the veto phase. When skipping veto, `map_sides` determines sides, and if `map_sides` is + not set, sides are determined by `side_type`.

**`Default: false`** 8. A player's :material-steam: Steam ID. This can be in any format, but we recommend a string representation of SteamID 64, i.e. `"76561197987713664"`. 9. Players are represented each with a mapping of `Get5PlayerSteamID -> PlayerName` as a key-value dictionary. The name is optional and should be set to an empty string to let players decide their own name. -10. _Optional_ - The spectators to allow into the game. If not defined, spectators cannot join the - game. **`Default: undefined`** -11. _Optional_ - The team that vetoes first. **`Default: team1`** -12. _Optional_ - The method used to determine sides. `standard` means that the team that doesn't pick a map gets the - side choice. `always_knife` means that sides are always determined by a knife-round and `never_kninfe` means that - `team1` always starts on CT. **`Default: standard`** -13. _Required_ - The map pool to pick from, as an array of strings (`["de_dust2", "de_nuke"]` etc.), or if `skip_veto` +10. _Optional_
The spectators to allow into the game. If not defined, spectators cannot join the + game.

**`Default: undefined`** +11. _Optional_
The team that vetoes first.

**`Default: "team1"`** +12. _Optional_
The method used to determine sides when vetoing **or** if veto is disabled and `map_sides` are not + set.

`standard` means that the team that doesn't pick a map gets the side choice (only if `skip_veto` + is `false`).

`always_knife` means that sides are always determined by a knife-round.

`never_knife` + means that `team1` always starts on CT.

This parameter is ignored if `map_sides` is set for all + maps. `standard` and `always_knife` behave similarly when `skip_veto` is `true`.

**`Default: "standard"`** +13. _Required_
The map pool to pick from, as an array of strings (`["de_dust2", "de_nuke"]` etc.), or if `skip_veto` is `true`, the order of maps played (limited by `num_maps`). **This should always be odd-sized if using the in-game veto system.** -14. _Optional_ - Wrapper for the server's `mp_teamprediction_pct`. This determines the chances of `team1` - winning. **`Default: 0`** -15. _Optional_ - Wrapper for the server's `mp_teamprediction_txt`. **`Default: ""`** -16. _Required_ - The team's name. Sets `mp_teamname_1` or `mp_teamname_2`. Printed frequently in chat. -17. _Optional_ - A short version of the team name, used in clan tags in-game ( - if [`get5_set_client_clan_tags`](../configuration#get5_set_client_clan_tags) is disabled). **`Default: ""`** -18. _Optional_ - The ISO-code to use for the in-game flag of the team. Must be a supported country, i.e. `FR`, `UK` - , `SE` etc. **`Default: ""`** -19. _Optional_ - The team logo (wraps `mp_teamlogo_1` or `mp_teamlogo_2`), which requires to be on a FastDL in order for - clients to see. **`Default: ""`** -20. _Required_ - The data for the first team. -21. _Required_ - The data for the second team. -22. _Optional_ - Various commands to execute on the server when loading the match configuration. This can be both +14. _Optional_
Wrapper for the server's `mp_teamprediction_pct`. This determines the chances of `team1` + winning.

**`Default: 0`** +15. _Optional_
Wrapper for the server's `mp_teamprediction_txt`.

**`Default: ""`** +16. _Required_
The team's name. Sets `mp_teamname_1` or `mp_teamname_2`. Printed frequently in chat. +17. _Optional_
A short version of the team name, used in clan tags in-game (requires + that [`get5_set_client_clan_tags`](../configuration#get5_set_client_clan_tags) is disabled). +

**`Default: ""`** +18. _Optional_
The ISO-code to use for the in-game flag of the team. Must be a supported country, i.e. `FR`,`UK`,`SE` + etc.

**`Default: ""`** +19. _Optional_
The team logo (wraps `mp_teamlogo_1` or `mp_teamlogo_2`), which requires to be on a FastDL in order + for clients to see.

**`Default: ""`** +20. _Required_
The data for the first team. +21. _Required_
The data for the second team. +22. _Optional_
Various commands to execute on the server when loading the match configuration. This can be both regular server-commands and any [`Get5 configuration parameter`](configuration.md), - i.e. `{"hostname": "Match #3123 - Astralis vs. NaVi"}`. **`Default: undefined`** -23. _Optional_ - Similarly to `players`, this object maps coaches using their Steam ID and - name. **`Default: undefined`** -24. _Required_ - The players on the team. -25. _Optional_ - Wrapper of the server's `mp_teammatchstat_txt` cvar, but can use `{MAPNUMBER}` and `{MAXMAPS}` as + i.e. `{"hostname": "Match #3123 - Astralis vs. NaVi"}`.

**`Default: undefined`** +23. _Optional_
Similarly to `players`, this object maps coaches using their Steam ID and + name.

**`Default: undefined`** +24. _Required_
The players on the team. +25. _Optional_
Wrapper of the server's `mp_teammatchstat_txt` cvar, but can use `{MAPNUMBER}` and `{MAXMAPS}` as variables that get replaced with their integer values. In a BoX series, you probably don't want to set this since Get5 automatically sets `mp_teamscore` cvars for the current series score, and take the place of - the `mp_teammatchstat` cvars. **`Default: Map {MAPNUMBER} of {MAXMAPS}`** -26. _Optional_ - The current score in the series, this can be used to give a team a map advantage or used as a manual - backup method. **`Default: 0`** -27. _Optional_ - Wraps `mp_teammatchstat_1` and `mp_teammatchstat_2`. You probably don't want to set this, in BoX + the `mp_teammatchstat` cvars.

**`Default: "Map {MAPNUMBER} of {MAXMAPS}"`** +26. _Optional_
The current score in the series, this can be used to give a team a map advantage or used as a manual + backup method.

**`Default: 0`** +27. _Optional_
Wraps `mp_teammatchstat_1` and `mp_teammatchstat_2`. You probably don't want to set this, in BoX series, `mp_teamscore` cvars are automatically set and take the place of the `mp_teammatchstat_x` - cvars. **`Default: ""`** + cvars.

**`Default: ""`** 28. Match teams can also be loaded from a separate file, allowing you to easily re-use a match configuration for - different sets of teams. - A `fromfile` value could be `"addons/sourcemod/configs/get5/team_nip.json"`, and that file should contain a valid - `Get5MatchTeam` object. -29. _Optional_ - The name of the spectator team. **`Default: casters`** -30. _Optional_ - The spectator/caster Steam IDs and names. + different sets of teams. A `fromfile` value could be `"addons/sourcemod/configs/get5/team_nip.json"`, and that file + should contain a valid `Get5MatchTeam` object. +29. _Optional_
The name of the spectator team.

**`Default: "casters"`** +30. _Optional_
The spectator/caster Steam IDs and names. +31. _Optional_
Determines the starting sides for each map. If this array is shorter than `num_maps`, `side_type` will + determine the side-behavior of the remaining maps. Ignored if `skip_veto` is `false`. +

**`Default: undefined`** !!! warning "SteamID64 in `.cfg` files" @@ -139,7 +146,7 @@ const match_schema: Match = { "min_spectators_to_ready": 0, "skip_veto": false, "veto_first": "team1", - "side_type": "always_knife", + "side_type": "standard", "spectators": { "name": "Blast PRO 2021", "players": { diff --git a/scripting/get5/readysystem.sp b/scripting/get5/readysystem.sp index 0d1786e76..8b0941ba8 100644 --- a/scripting/get5/readysystem.sp +++ b/scripting/get5/readysystem.sp @@ -242,10 +242,9 @@ static void HandleReadyMessage(Get5Team team) { if (g_GameState == Get5State_PreVeto) { Get5_MessageToAll("%t", "TeamReadyToVetoInfoMessage", g_FormattedTeamNames[team]); } else if (g_GameState == Get5State_Warmup) { - SideChoice sides = view_as(g_MapSides.Get(Get5_GetMapNumber())); if (g_WaitingForRoundBackup) { Get5_MessageToAll("%t", "TeamReadyToRestoreBackupInfoMessage", g_FormattedTeamNames[team]); - } else if (sides == SideChoice_KnifeRound) { + } else if (view_as(g_MapSides.Get(Get5_GetMapNumber())) == SideChoice_KnifeRound) { Get5_MessageToAll("%t", "TeamReadyToKnifeInfoMessage", g_FormattedTeamNames[team]); } else { Get5_MessageToAll("%t", "TeamReadyToBeginInfoMessage", g_FormattedTeamNames[team]); diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 9833ee940..e4c5d2935 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -733,14 +733,10 @@ stock bool HelpfulAttack(int attacker, int victim) { } stock SideChoice SideTypeFromString(const char[] input) { - if (StrEqual(input, "team1_ct", false)) { + if (StrEqual(input, "team1_ct", false) || StrEqual(input, "team2_t", false)) { return SideChoice_Team1CT; - } else if (StrEqual(input, "team1_t", false)) { + } else if (StrEqual(input, "team1_t", false) || StrEqual(input, "team2_ct", false)) { return SideChoice_Team1T; - } else if (StrEqual(input, "team2_ct", false)) { - return SideChoice_Team1T; - } else if (StrEqual(input, "team2_t", false)) { - return SideChoice_Team1CT; } else if (StrEqual(input, "knife", false)) { return SideChoice_KnifeRound; } else { From c5b49167aa44dafa353591ecf87eba4581a14b0c Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 10 Aug 2022 02:13:20 +0200 Subject: [PATCH 018/104] Clean up include formatting --- scripting/get5.sp | 4 +- scripting/include/get5.inc | 1532 +++++++++++++++++------------------- 2 files changed, 730 insertions(+), 806 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 26722290d..54cc2a7b2 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -172,8 +172,8 @@ bool g_DoingBackupRestoreNow = false; // Stats values StringMap g_FlashbangContainer; // Stores flashbang-entity-id -> Get5FlashbangDetonatedEvent. -StringMap g_HEGrenadeContainer; // Stores flashbang-entity-id -> Get5HEDetonatedEvent. -StringMap g_MolotovContainer; // Stores flashbang-entity-id -> Get5MolotovDetonatedEvent. +StringMap g_HEGrenadeContainer; // Stores he-entity-id -> Get5HEDetonatedEvent. +StringMap g_MolotovContainer; // Stores molotov-entity-id -> Get5MolotovDetonatedEvent. int g_LatestUserIdToDetonateMolotov = 0; // Molotov detonate and start-burning/extinguish are two separate events always fired right // after each other. We need this to bind them together as detonate does not have client id. diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index 56627740a..a9b84ee54 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -135,254 +135,251 @@ native int Get5_IncreasePlayerStat(int client, const char[] statName, int amount methodmap Get5StatusTeam < JSON_Object { - public bool SetTeamName(const char[] event) { - return this.SetString("name", event); - } - - property int SeriesScore { - public set(int score) { - this.SetInt("series_score", score); - } - } + public bool SetTeamName(const char[] event) { + return this.SetString("name", event); + } - property int MapScore { - public set(int score) { - this.SetInt("current_map_score", score); - } + property int SeriesScore { + public set(int score) { + this.SetInt("series_score", score); } + } - property int ConnectedClients { - public set(int clients) { - this.SetInt("connected_clients", clients); - } + property int MapScore { + public set(int score) { + this.SetInt("current_map_score", score); } + } - property bool Ready { - public set(bool ready) { - this.SetBool("ready", ready); - } + property int ConnectedClients { + public set(int clients) { + this.SetInt("connected_clients", clients); } + } - property Get5Side Side { - public set(Get5Side side) { - this.SetInt("side_int", view_as(side)); - this.SetHidden("side_int", true); - ConvertGet5SideToStringInJson(this, "side", side); - } + property bool Ready { + public set(bool ready) { + this.SetBool("ready", ready); } + } - public Get5StatusTeam(const char[] teamName, - const int seriesScore, const int mapScore, const bool ready, const Get5Side side, const int connectedClients) { - Get5StatusTeam self = view_as(new JSON_Object()); - self.SetTeamName(teamName); - self.SeriesScore = seriesScore; - self.MapScore = mapScore; - self.Ready = ready; - self.Side = side; - self.ConnectedClients = connectedClients; - return self; + property Get5Side Side { + public set(Get5Side side) { + this.SetInt("side_int", view_as(side)); + this.SetHidden("side_int", true); + ConvertGet5SideToStringInJson(this, "side", side); } + } + public Get5StatusTeam(const char[] teamName, + const int seriesScore, const int mapScore, const bool ready, const Get5Side side, const int connectedClients) { + Get5StatusTeam self = view_as(new JSON_Object()); + self.SetTeamName(teamName); + self.SeriesScore = seriesScore; + self.MapScore = mapScore; + self.Ready = ready; + self.Side = side; + self.ConnectedClients = connectedClients; + return self; + } } methodmap Get5Status < JSON_Object { - public bool SetPluginVersion(const char[] event) { - return this.SetString("plugin_version", event); - } + public bool SetPluginVersion(const char[] event) { + return this.SetString("plugin_version", event); + } - property Get5State GameState { - public set(Get5State state) { - ConvertGameStateToStringInJson(this, "gamestate", state); - } + property Get5State GameState { + public set(Get5State state) { + ConvertGameStateToStringInJson(this, "gamestate", state); } + } - property bool IsPaused { - public set(bool paused) { - this.SetBool("paused", paused); - } + property bool IsPaused { + public set(bool paused) { + this.SetBool("paused", paused); } + } - public bool SetConfigFile(const char[] file) { - return this.SetString("loaded_config_file", file); - } + public bool SetConfigFile(const char[] file) { + return this.SetString("loaded_config_file", file); + } - public bool SetMatchId(const char[] matchId) { - return this.SetString("matchid", matchId); - } + public bool SetMatchId(const char[] matchId) { + return this.SetString("matchid", matchId); + } - property int MapNumber { - public set(int mapNumber) { - this.SetInt("map_number", mapNumber); - } + property int MapNumber { + public set(int mapNumber) { + this.SetInt("map_number", mapNumber); } + } - property int RoundNumber { - public set(int roundNumber) { - this.SetInt("round_number", roundNumber); - } + property int RoundNumber { + public set(int roundNumber) { + this.SetInt("round_number", roundNumber); } + } - property int RoundTime { - public set(int roundTime) { - this.SetInt("round_time", roundTime); - } + property int RoundTime { + public set(int roundTime) { + this.SetInt("round_time", roundTime); } + } - property Get5StatusTeam Team1 { - public set(Get5StatusTeam team) { - this.SetObject("team1", team); - } + property Get5StatusTeam Team1 { + public set(Get5StatusTeam team) { + this.SetObject("team1", team); } + } - property Get5StatusTeam Team2 { - public set(Get5StatusTeam team) { - this.SetObject("team2", team); - } + property Get5StatusTeam Team2 { + public set(Get5StatusTeam team) { + this.SetObject("team2", team); } + } - public bool AddMap(const char[] map) { - if (!this.HasKey("maps")) { - this.SetObject("maps", new JSON_Array()); - } - JSON_Array maps = view_as(this.GetObject("maps")); - maps.PushString(map); + public bool AddMap(const char[] map) { + if (!this.HasKey("maps")) { + this.SetObject("maps", new JSON_Array()); } + JSON_Array maps = view_as(this.GetObject("maps")); + maps.PushString(map); + } - public Get5Status(const char[] pluginVersion, const Get5State gamestate, const bool isPaused) { - Get5Status self = view_as(new JSON_Object()); - self.SetPluginVersion(pluginVersion); - self.GameState = gamestate; - self.IsPaused = isPaused; - return self; - } + public Get5Status(const char[] pluginVersion, const Get5State gamestate, const bool isPaused) { + Get5Status self = view_as(new JSON_Object()); + self.SetPluginVersion(pluginVersion); + self.GameState = gamestate; + self.IsPaused = isPaused; + return self; + } } methodmap Get5Weapon < JSON_Object { - public bool SetWeaponName(const char[] value) { - return this.SetString("name", value); - } + public bool SetWeaponName(const char[] value) { + return this.SetString("name", value); + } - public bool GetWeaponName(char[] buffer, const int maxSize) { - return this.GetString("name", buffer, maxSize); - } + public bool GetWeaponName(char[] buffer, const int maxSize) { + return this.GetString("name", buffer, maxSize); + } - property CSWeaponID Id { - public get() { - return view_as(this.GetInt("id")); - } - public set(CSWeaponID id) { - this.SetInt("id", view_as(id)); - } + property CSWeaponID Id { + public get() { + return view_as(this.GetInt("id")); } - - public Get5Weapon(const char[] weapon, CSWeaponID weaponId) { - Get5Weapon self = view_as(new JSON_Object()); - self.SetWeaponName(weapon); - self.Id = weaponId; - return self; + public set(CSWeaponID id) { + this.SetInt("id", view_as(id)); } + } + + public Get5Weapon(const char[] weapon, CSWeaponID weaponId) { + Get5Weapon self = view_as(new JSON_Object()); + self.SetWeaponName(weapon); + self.Id = weaponId; + return self; + } } methodmap Get5Winner < JSON_Object { - property Get5Side Side { - public get() { - return view_as(this.GetInt("side_int")); - } - public set(Get5Side side) { - this.SetInt("side_int", view_as(side)); - this.SetHidden("side_int", true); - ConvertGet5SideToStringInJson(this, "side", side); - } - } - - property Get5Team Team { - public get() { - return view_as(this.GetInt("team_int")); - } - public set(Get5Team team) { - this.SetInt("team_int", view_as(team)); - this.SetHidden("team_int", true); - ConvertGet5TeamToStringInJson(this, "team", team); - } - } - - public Get5Winner(Get5Team team, Get5Side side) { - Get5Winner self = view_as(new JSON_Object()); - self.Team = team; - self.Side = side; - return self; + property Get5Side Side { + public get() { + return view_as(this.GetInt("side_int")); + } + public set(Get5Side side) { + this.SetInt("side_int", view_as(side)); + this.SetHidden("side_int", true); + ConvertGet5SideToStringInJson(this, "side", side); + } + } + + property Get5Team Team { + public get() { + return view_as(this.GetInt("team_int")); + } + public set(Get5Team team) { + this.SetInt("team_int", view_as(team)); + this.SetHidden("team_int", true); + ConvertGet5TeamToStringInJson(this, "team", team); } + } + + public Get5Winner(Get5Team team, Get5Side side) { + Get5Winner self = view_as(new JSON_Object()); + self.Team = team; + self.Side = side; + return self; + } } methodmap Get5Player < JSON_Object { - property Get5Side Side { - public get() { - return view_as(this.GetInt("side_int")); - } - public set(Get5Side side) { - this.SetInt("side_int", view_as(side)); - this.SetHidden("side_int", true); - ConvertGet5SideToStringInJson(this, "side", side); - } + property Get5Side Side { + public get() { + return view_as(this.GetInt("side_int")); } - - public bool SetSteamId(const char[] value) { - return this.SetString("steamid", value); + public set(Get5Side side) { + this.SetInt("side_int", view_as(side)); + this.SetHidden("side_int", true); + ConvertGet5SideToStringInJson(this, "side", side); } + } - public bool GetSteamId(char[] buffer, const int maxSize) { - return this.GetString("steamid", buffer, maxSize); - } + public bool SetSteamId(const char[] value) { + return this.SetString("steamid", value); + } + public bool GetSteamId(char[] buffer, const int maxSize) { + return this.GetString("steamid", buffer, maxSize); + } - public bool SetName(const char[] value) { - return this.SetString("name", value); - } + public bool SetName(const char[] value) { + return this.SetString("name", value); + } + public bool GetName(char[] buffer, const int maxSize) { + return this.GetString("name", buffer, maxSize); + } - public bool GetName(char[] buffer, const int maxSize) { - return this.GetString("name", buffer, maxSize); + property bool IsBot { + public get() { + return this.GetBool("is_bot"); } - - property bool IsBot { - public get() { - return this.GetBool("is_bot"); - } - public set(bool bot) { - this.SetBool("is_bot", bot); - } + public set(bool bot) { + this.SetBool("is_bot", bot); } + } - property int UserId { - public get() { - return this.GetInt("user_id"); - } - public set(int id) { - this.SetInt("user_id", id); - } + property int UserId { + public get() { + return this.GetInt("user_id"); } - - public Get5Player(const int userId, const char[] steamId, const Get5Side side, const char[] name, const bool isBot) { - Get5Player self = view_as(new JSON_Object()); - self.UserId = userId; - self.SetSteamId(steamId); - self.Side = side; - self.SetName(name); - self.IsBot = isBot; - return self; + public set(int id) { + this.SetInt("user_id", id); } + } + + public Get5Player(const int userId, const char[] steamId, const Get5Side side, const char[] name, const bool isBot) { + Get5Player self = view_as(new JSON_Object()); + self.UserId = userId; + self.SetSteamId(steamId); + self.Side = side; + self.SetName(name); + self.IsBot = isBot; + return self; + } } methodmap Get5Event < JSON_Object { - public bool SetEvent(const char[] event) { - return this.SetString("event", event); - } - public bool GetEvent(char[] buffer, const int maxSize) { - return this.GetString("event", buffer, maxSize); - } + public bool SetEvent(const char[] event) { + return this.SetString("event", event); + } + public bool GetEvent(char[] buffer, const int maxSize) { + return this.GetString("event", buffer, maxSize); + } } methodmap Get5PlayerConnectedEvent < Get5Event { @@ -391,7 +388,6 @@ methodmap Get5PlayerConnectedEvent < Get5Event { public get() { return view_as(this.GetObject("player")); } - public set(Get5Player player) { this.SetObject("player", player); } @@ -426,15 +422,12 @@ methodmap Get5PlayerDisconnectedEvent < Get5PlayerConnectedEvent { methodmap Get5MatchEvent < Get5Event { - public bool SetMatchId(const char[] matchId) - { - return this.SetString("matchid", matchId); - } - - public bool GetMatchId(char[] buffer, const int maxSize) { - return this.GetString("matchid", buffer, maxSize); - } - + public bool SetMatchId(const char[] matchId) { + return this.SetString("matchid", matchId); + } + public bool GetMatchId(char[] buffer, const int maxSize) { + return this.GetString("matchid", buffer, maxSize); + } } methodmap Get5MatchTeamEvent < Get5MatchEvent { @@ -443,7 +436,6 @@ methodmap Get5MatchTeamEvent < Get5MatchEvent { public get() { return view_as(this.GetInt("team_int")); } - public set(Get5Team team) { this.SetInt("team_int", view_as(team)); this.SetHidden("team_int", true); @@ -454,23 +446,21 @@ methodmap Get5MatchTeamEvent < Get5MatchEvent { methodmap Get5MapEvent < Get5MatchEvent { - public bool SetMapName(const char[] map) { - return this.SetString("map_name", map); - } + public bool SetMapName(const char[] map) { + return this.SetString("map_name", map); + } + public bool GetMapName(char[] buffer, const int maxSize) { + return this.GetString("map_name", buffer, maxSize); + } - public bool GetMapName(char[] buffer, const int maxSize) { - return this.GetString("map_name", buffer, maxSize); + property int MapNumber { + public get() { + return this.GetInt("map_number"); } - - property int MapNumber { - public get() { - return this.GetInt("map_number"); - } - - public set(int mapNumber) { - this.SetInt("map_number", mapNumber); - } + public set(int mapNumber) { + this.SetInt("map_number", mapNumber); } + } } methodmap Get5MapTeamEvent < Get5MapEvent { @@ -479,7 +469,6 @@ methodmap Get5MapTeamEvent < Get5MapEvent { public get() { return view_as(this.GetInt("team_int")); } - public set(Get5Team team) { this.SetInt("team_int", view_as(team)); this.SetHidden("team_int", true); @@ -490,67 +479,62 @@ methodmap Get5MapTeamEvent < Get5MapEvent { methodmap Get5RoundEvent < Get5MapEvent { - property int RoundNumber { - public get() { - return this.GetInt("round_number"); - } - - public set(int roundNumber) { - this.SetInt("round_number", roundNumber); - } + property int RoundNumber { + public get() { + return this.GetInt("round_number"); + } + public set(int roundNumber) { + this.SetInt("round_number", roundNumber); } + } } methodmap Get5TimedRoundEvent < Get5RoundEvent { - property int RoundTime { - public get() { - return this.GetInt("round_time"); - } - - public set(int roundTime) { - this.SetInt("round_time", roundTime); - } + property int RoundTime { + public get() { + return this.GetInt("round_time"); + } + public set(int roundTime) { + this.SetInt("round_time", roundTime); } + } } methodmap Get5PlayerMapEvent < Get5MapEvent { - property Get5Player Player { - public get() { - return view_as(this.GetObject("player")); - } - - public set(Get5Player player) { - this.SetObject("player", player); - } + property Get5Player Player { + public get() { + return view_as(this.GetObject("player")); + } + public set(Get5Player player) { + this.SetObject("player", player); } + } } methodmap Get5PlayerRoundEvent < Get5RoundEvent { - property Get5Player Player { - public get() { - return view_as(this.GetObject("player")); - } - - public set(Get5Player player) { - this.SetObject("player", player); - } + property Get5Player Player { + public get() { + return view_as(this.GetObject("player")); + } + public set(Get5Player player) { + this.SetObject("player", player); } + } } methodmap Get5PlayerTimedRoundEvent < Get5TimedRoundEvent { - property Get5Player Player { - public get() { - return view_as(this.GetObject("player")); - } - - public set(Get5Player player) { - this.SetObject("player", player); - } + property Get5Player Player { + public get() { + return view_as(this.GetObject("player")); + } + public set(Get5Player player) { + this.SetObject("player", player); } + } } // MATCH CONFIG @@ -561,30 +545,27 @@ methodmap Get5SeriesResultEvent < Get5MatchEvent { public get() { return view_as(this.GetObject("winner")); } - public set(Get5Winner winner) { this.SetObject("winner", winner); } } property int Team1SeriesScore { - public get() { - return this.GetInt("team1_series_score"); - } - - public set(int score) { - this.SetInt("team1_series_score", score); - } + public get() { + return this.GetInt("team1_series_score"); + } + public set(int score) { + this.SetInt("team1_series_score", score); + } } property int Team2SeriesScore { - public get() { - return this.GetInt("team2_series_score"); - } - - public set(int score) { - this.SetInt("team2_series_score", score); - } + public get() { + return this.GetInt("team2_series_score"); + } + public set(int score) { + this.SetInt("team2_series_score", score); + } } public Get5SeriesResultEvent(const char[] matchId, const Get5Winner winner, const int team1Score, const int team2Score) { @@ -667,13 +648,12 @@ methodmap Get5TeamReadyStatusChangedEvent < Get5MatchTeamEvent { methodmap Get5MapSelectionEvent < Get5MatchTeamEvent { - public bool SetMapName(const char[] map) - { - return this.SetString("map_name", map); - } - public bool GetMapName(char[] buffer, const int maxSize) { - return this.GetString("map_name", buffer, maxSize); - } + public bool SetMapName(const char[] map) { + return this.SetString("map_name", map); + } + public bool GetMapName(char[] buffer, const int maxSize) { + return this.GetString("map_name", buffer, maxSize); + } } methodmap Get5MapPickedEvent < Get5MapSelectionEvent { @@ -833,14 +813,12 @@ methodmap Get5RoundStatsUpdatedEvent < Get5RoundEvent { methodmap Get5DemoFinishedEvent < Get5MapEvent { - public bool SetFileName(const char[] filename) - { - return this.SetString("filename", filename); - } - - public bool GetFileName(char[] buffer, const int maxSize) { - return this.GetString("filename", buffer, maxSize); - } + public bool SetFileName(const char[] filename) { + return this.SetString("filename", filename); + } + public bool GetFileName(char[] buffer, const int maxSize) { + return this.GetString("filename", buffer, maxSize); + } public Get5DemoFinishedEvent(const char[] matchId, const int mapNumber, const char[] filename) { Get5DemoFinishedEvent self = view_as(new JSON_Object()); @@ -850,72 +828,66 @@ methodmap Get5DemoFinishedEvent < Get5MapEvent { self.SetFileName(filename); return self; } - } methodmap Get5KnifeRoundStartedEvent < Get5MapEvent { - public Get5KnifeRoundStartedEvent(const char[] matchId, int mapNumber) { - Get5KnifeRoundStartedEvent self = view_as(new JSON_Object()); - self.SetEvent("knife_start"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - return self; + public Get5KnifeRoundStartedEvent(const char[] matchId, int mapNumber) { + Get5KnifeRoundStartedEvent self = view_as(new JSON_Object()); + self.SetEvent("knife_start"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + return self; } - } methodmap Get5KnifeRoundWonEvent < Get5MapTeamEvent { - // We don't use Get5Winner here as the side represents the picked side. - // Team already represents the winning team. Winning side is irrelevant in knife. - property Get5Side Side { - public get() { - return view_as(this.GetInt("side_int")); - } - - public set(Get5Side side) { - this.SetInt("side_int", view_as(side)); - this.SetHidden("side_int", true); - ConvertGet5SideToStringInJson(this, "side", side); - } + // We don't use Get5Winner here as the side represents the picked side. + // Team already represents the winning team. Winning side is irrelevant in knife. + property Get5Side Side { + public get() { + return view_as(this.GetInt("side_int")); } - - property bool Swapped { - public get() { - return this.GetBool("swapped"); - } - - public set(bool swapped) { - this.SetBool("swapped", swapped); - } + public set(Get5Side side) { + this.SetInt("side_int", view_as(side)); + this.SetHidden("side_int", true); + ConvertGet5SideToStringInJson(this, "side", side); } + } - public Get5KnifeRoundWonEvent(const char[] matchId, int mapNumber, const Get5Team winner, const Get5Side side, const bool swapped) { - Get5KnifeRoundWonEvent self = view_as(new JSON_Object()); - self.SetEvent("knife_won"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.Team = winner; - self.Side = side; - self.Swapped = swapped; - return self; + property bool Swapped { + public get() { + return this.GetBool("swapped"); + } + public set(bool swapped) { + this.SetBool("swapped", swapped); + } } + public Get5KnifeRoundWonEvent(const char[] matchId, int mapNumber, const Get5Team winner, const Get5Side side, const bool swapped) { + Get5KnifeRoundWonEvent self = view_as(new JSON_Object()); + self.SetEvent("knife_won"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.Team = winner; + self.Side = side; + self.Swapped = swapped; + return self; + } } methodmap Get5MatchPauseEvent < Get5MapTeamEvent { property Get5PauseType PauseType { - public get() { - return view_as(this.GetInt("pause_type_int")); - } - - public set(Get5PauseType type) { - this.SetInt("pause_type_int", view_as(type)); - this.SetHidden("pause_type_int", true); - ConvertGet5PauseTypeToStringInJson(this, "pause_type", type); - } + public get() { + return view_as(this.GetInt("pause_type_int")); + } + public set(Get5PauseType type) { + this.SetInt("pause_type_int", view_as(type)); + this.SetHidden("pause_type_int", true); + ConvertGet5PauseTypeToStringInJson(this, "pause_type", type); + } } } @@ -930,7 +902,6 @@ methodmap Get5MatchPausedEvent < Get5MatchPauseEvent { self.PauseType = pauseType; return self; } - } methodmap Get5MatchUnpausedEvent < Get5MatchPauseEvent { @@ -944,81 +915,74 @@ methodmap Get5MatchUnpausedEvent < Get5MatchPauseEvent { self.PauseType = pauseType; return self; } - } methodmap Get5SeriesStartedEvent < Get5MatchEvent { - public bool SetTeam1Name(const char[] value) - { - return this.SetString("team1_name", value); - } - - public bool GetTeam1Name(char[] buffer, const int maxSize) { - return this.GetString("team1_name", buffer, maxSize); - } - - public bool SetTeam2Name(const char[] value) - { - return this.SetString("team2_name", value); - } + public bool SetTeam1Name(const char[] value) { + return this.SetString("team1_name", value); + } + public bool GetTeam1Name(char[] buffer, const int maxSize) { + return this.GetString("team1_name", buffer, maxSize); + } - public bool GetTeam2Name(char[] buffer, const int maxSize) { - return this.GetString("team2_name", buffer, maxSize); - } + public bool SetTeam2Name(const char[] value) { + return this.SetString("team2_name", value); + } + public bool GetTeam2Name(char[] buffer, const int maxSize) { + return this.GetString("team2_name", buffer, maxSize); + } public Get5SeriesStartedEvent(const char[] matchId, const char[] team1Name, const char[] team2Name) { - Get5SeriesStartedEvent self = view_as(new JSON_Object()); - self.SetEvent("series_start"); - self.SetMatchId(matchId); - self.SetTeam1Name(team1Name); - self.SetTeam2Name(team2Name); - return self; - } + Get5SeriesStartedEvent self = view_as(new JSON_Object()); + self.SetEvent("series_start"); + self.SetMatchId(matchId); + self.SetTeam1Name(team1Name); + self.SetTeam2Name(team2Name); + return self; + } } methodmap Get5BackupRestoredEvent < Get5MapEvent { - public bool SetFileName(const char[] file) - { - return this.SetString("filename", file); - } - - public bool GetFileName(char[] buffer, const int maxSize) { - return this.GetString("filename", buffer, maxSize); - } + public bool SetFileName(const char[] file) { + return this.SetString("filename", file); + } + public bool GetFileName(char[] buffer, const int maxSize) { + return this.GetString("filename", buffer, maxSize); + } public Get5BackupRestoredEvent(const char[] matchId, const int mapNumber, const char[] file) { - Get5BackupRestoredEvent self = view_as(new JSON_Object()); - self.SetEvent("backup_loaded"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.SetFileName(file); - return self; - } + Get5BackupRestoredEvent self = view_as(new JSON_Object()); + self.SetEvent("backup_loaded"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.SetFileName(file); + return self; + } } methodmap Get5RoundStartedEvent < Get5RoundEvent { public Get5RoundStartedEvent(const char[] matchId, const int mapNumber, const int roundNumber) { - Get5RoundStartedEvent self = view_as(new JSON_Object()); - self.SetEvent("round_start"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - return self; - } + Get5RoundStartedEvent self = view_as(new JSON_Object()); + self.SetEvent("round_start"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + return self; + } } methodmap Get5GoingLiveEvent < Get5MapEvent { public Get5GoingLiveEvent(const char[] matchId, const int mapNumber) { - Get5GoingLiveEvent self = view_as(new JSON_Object()); - self.SetEvent("going_live"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - return self; - } + Get5GoingLiveEvent self = view_as(new JSON_Object()); + self.SetEvent("going_live"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + return self; + } } methodmap Get5RoundEndedEvent < Get5TimedRoundEvent { @@ -1026,152 +990,138 @@ methodmap Get5RoundEndedEvent < Get5TimedRoundEvent { // Note that reason is decremented by 1 to match the values defined at https://github.com/alliedmodders/sourcemod/blob/master/plugins/include/cstrike.inc // CSGO increments these by 1 for some reason. property CSRoundEndReason Reason { - public get() { - return view_as(this.GetInt("reason")); - } - - public set(CSRoundEndReason reason) { - this.SetInt("reason", view_as(reason)); - } + public get() { + return view_as(this.GetInt("reason")); + } + public set(CSRoundEndReason reason) { + this.SetInt("reason", view_as(reason)); + } } property Get5Winner Winner { - public get() { - return view_as(this.GetObject("winner")); - } - - public set(Get5Winner winner) { - this.SetObject("winner", winner); - } + public get() { + return view_as(this.GetObject("winner")); + } + public set(Get5Winner winner) { + this.SetObject("winner", winner); + } } property int Team1Score { - public get() { - return this.GetInt("team1_score"); - } - - public set(int score) { - this.SetInt("team1_score", score); - } + public get() { + return this.GetInt("team1_score"); + } + public set(int score) { + this.SetInt("team1_score", score); + } } property int Team2Score { - public get() { - return this.GetInt("team2_score"); - } - - public set(int score) { - this.SetInt("team2_score", score); - } + public get() { + return this.GetInt("team2_score"); + } + public set(int score) { + this.SetInt("team2_score", score); + } } public Get5RoundEndedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const CSRoundEndReason reason, const Get5Winner winner, const int team1Score, const int team2Score) { - Get5RoundEndedEvent self = view_as(new JSON_Object()); - self.SetEvent("round_end"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Reason = reason; - self.Winner = winner; - self.Team1Score = team1Score; - self.Team2Score = team2Score; - return self; - } + Get5RoundEndedEvent self = view_as(new JSON_Object()); + self.SetEvent("round_end"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Reason = reason; + self.Winner = winner; + self.Team1Score = team1Score; + self.Team2Score = team2Score; + return self; + } } // All other events methodmap Get5PlayerSayEvent < Get5PlayerTimedRoundEvent { - public bool SetCommand(const char[] command) - { + public bool SetCommand(const char[] command) { return this.SetString("command", command); } - public bool GetCommand(char[] buffer, const int maxSize) { return this.GetString("command", buffer, maxSize); } - public bool SetMessage(const char[] message) - { + public bool SetMessage(const char[] message) { return this.SetString("message", message); } - public bool GetMessage(char[] buffer, const int maxSize) { return this.GetString("message", buffer, maxSize); } public Get5PlayerSayEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player, const char[] command, const char[] message) { - Get5PlayerSayEvent self = view_as(new JSON_Object()); - self.SetEvent("player_say"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.SetCommand(command); - self.SetMessage(message); - return self; - } + Get5PlayerSayEvent self = view_as(new JSON_Object()); + self.SetEvent("player_say"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.SetCommand(command); + self.SetMessage(message); + return self; + } } methodmap Get5RoundMVPEvent < Get5PlayerRoundEvent { // There doesn't seem to be an enum for MVP reason, so we go with the plain integer. property int Reason { - public get() { - return this.GetInt("reason"); - } - - public set(int reason) { - this.SetInt("reason", reason); - } + public get() { + return this.GetInt("reason"); + } + public set(int reason) { + this.SetInt("reason", reason); + } } public Get5RoundMVPEvent(const char[] matchId, const int mapNumber, const int roundNumber, const Get5Player player, const int reason) { - Get5RoundMVPEvent self = view_as(new JSON_Object()); - self.SetEvent("round_mvp"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.Player = player; - self.Reason = reason; - return self; - } + Get5RoundMVPEvent self = view_as(new JSON_Object()); + self.SetEvent("round_mvp"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.Player = player; + self.Reason = reason; + return self; + } } methodmap Get5AssisterObject < JSON_Object { property Get5Player Player { - public get() { - return view_as(this.GetObject("player")); - } - - public set(Get5Player player) { - this.SetObject("player", player); - } + public get() { + return view_as(this.GetObject("player")); + } + public set(Get5Player player) { + this.SetObject("player", player); + } } property bool FriendlyFire { - public get() { - return this.GetBool("friendly_fire"); - } - - public set(bool friendlyFire) { - this.SetBool("friendly_fire", friendlyFire); - } - + public get() { + return this.GetBool("friendly_fire"); + } + public set(bool friendlyFire) { + this.SetBool("friendly_fire", friendlyFire); + } } property bool FlashAssist { - public get() { - return this.GetBool("flash_assist"); - } - - public set(bool flashAssist) { - this.SetBool("flash_assist", flashAssist); - } - + public get() { + return this.GetBool("flash_assist"); + } + public set(bool flashAssist) { + this.SetBool("flash_assist", flashAssist); + } } public Get5AssisterObject(const Get5Player player, bool flashAssist, bool friendlyFire) { @@ -1181,122 +1131,110 @@ methodmap Get5AssisterObject < JSON_Object { self.FriendlyFire = friendlyFire; return self; } - } methodmap Get5PlayerWeaponEvent < Get5PlayerTimedRoundEvent { - property Get5Weapon Weapon { - public get() { - return view_as(this.GetObject("weapon")); - } - - public set(Get5Weapon weapon) { - this.SetObject("weapon", weapon); - } + property Get5Weapon Weapon { + public get() { + return view_as(this.GetObject("weapon")); } + public set(Get5Weapon weapon) { + this.SetObject("weapon", weapon); + } + } } methodmap Get5PlayerDeathEvent < Get5PlayerWeaponEvent { property bool Bomb { - public get() { - return this.GetBool("bomb"); - } - - public set(bool bomb) { - this.SetBool("bomb", bomb); - } + public get() { + return this.GetBool("bomb"); + } + public set(bool bomb) { + this.SetBool("bomb", bomb); + } } property bool Headshot { - public get() { - return this.GetBool("headshot"); - } - - public set(bool headshot) { - this.SetBool("headshot", headshot); - } + public get() { + return this.GetBool("headshot"); + } + public set(bool headshot) { + this.SetBool("headshot", headshot); + } } property bool ThruSmoke { - public get() { - return this.GetBool("thru_smoke"); - } - - public set(bool thruSmoke) { - this.SetBool("thru_smoke", thruSmoke); - } + public get() { + return this.GetBool("thru_smoke"); + } + public set(bool thruSmoke) { + this.SetBool("thru_smoke", thruSmoke); + } } property int Penetrated { - public get() { - return this.GetInt("penetrated"); - } - - public set(int penetrated) { - this.SetInt("penetrated", penetrated); - } + public get() { + return this.GetInt("penetrated"); + } + public set(int penetrated) { + this.SetInt("penetrated", penetrated); + } } property bool AttackerBlind { - public get() { - return this.GetBool("attacker_blind"); - } - - public set(bool blind) { - this.SetBool("attacker_blind", blind); - } + public get() { + return this.GetBool("attacker_blind"); + } + public set(bool blind) { + this.SetBool("attacker_blind", blind); + } } property bool NoScope { - public get() { - return this.GetBool("no_scope"); - } - - public set(bool noScope) { - this.SetBool("no_scope", noScope); - } + public get() { + return this.GetBool("no_scope"); + } + public set(bool noScope) { + this.SetBool("no_scope", noScope); + } } property bool Suicide { - public get() { - return this.GetBool("suicide"); - } - - public set(bool suicide) { - this.SetBool("suicide", suicide); - } + public get() { + return this.GetBool("suicide"); + } + public set(bool suicide) { + this.SetBool("suicide", suicide); + } } property bool FriendlyFire { - public get() { - return this.GetBool("friendly_fire"); - } - - public set(bool friendlyFire) { - this.SetBool("friendly_fire", friendlyFire); - } + public get() { + return this.GetBool("friendly_fire"); + } + public set(bool friendlyFire) { + this.SetBool("friendly_fire", friendlyFire); + } } property Get5Player Attacker { - public get() { - return view_as(this.GetObject("attacker")); - } - - public set(Get5Player attacker) { - this.SetObject("attacker", attacker); - } + public get() { + return view_as(this.GetObject("attacker")); + } + public set(Get5Player attacker) { + this.SetObject("attacker", attacker); + } } property Get5AssisterObject Assist { - public get() { - return view_as(this.GetObject("assist")); - } - - public set(Get5AssisterObject assister) { - this.SetObject("assist", assister); - } + public get() { + return view_as(this.GetObject("assist")); + } + public set(Get5AssisterObject assister) { + this.SetObject("assist", assister); + } } // Use before accessing "Assist", as its getter will raise an exception if null. @@ -1323,270 +1261,256 @@ methodmap Get5PlayerDeathEvent < Get5PlayerWeaponEvent { const bool attackerBlind, const bool suicide, const int penetrated, - const bool bomb - ) { - - Get5PlayerDeathEvent self = view_as(new JSON_Object()); - self.SetEvent("player_death"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = victim; - self.Weapon = weapon; - self.Headshot = headshot; - self.FriendlyFire = friendlyFire; - self.ThruSmoke = thruSmoke; - self.NoScope = noScope; - self.AttackerBlind = attackerBlind; - self.Suicide = suicide; - self.Penetrated = penetrated; - self.Bomb = bomb; - - // set nullables to null initially - self.SetObject("assist", null); - self.SetObject("attacker", null); - return self; - - } + const bool bomb) { + Get5PlayerDeathEvent self = view_as(new JSON_Object()); + self.SetEvent("player_death"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = victim; + self.Weapon = weapon; + self.Headshot = headshot; + self.FriendlyFire = friendlyFire; + self.ThruSmoke = thruSmoke; + self.NoScope = noScope; + self.AttackerBlind = attackerBlind; + self.Suicide = suicide; + self.Penetrated = penetrated; + self.Bomb = bomb; + + // set nullables to null initially + self.SetObject("assist", null); + self.SetObject("attacker", null); + return self; + } } // GRENADES methodmap Get5GrenadeThrownEvent < Get5PlayerWeaponEvent { - public Get5GrenadeThrownEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player, const Get5Weapon weapon) { - Get5GrenadeThrownEvent self = view_as(new JSON_Object()); - self.SetEvent("grenade_thrown"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Weapon = weapon; - return self; - } + public Get5GrenadeThrownEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player, const Get5Weapon weapon) { + Get5GrenadeThrownEvent self = view_as(new JSON_Object()); + self.SetEvent("grenade_thrown"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Weapon = weapon; + return self; + } } methodmap Get5VictimGrenadeEvent < Get5PlayerWeaponEvent { - // Array of either Get5DamageGrenadeVictim or Get5BlindedGrenadeVictim - property JSON_Array Victims { - public get() { - return view_as(this.GetObject("victims")); - } - - public set(JSON_Array victims) { - this.SetObject("victims", victims); - } + // Array of either Get5DamageGrenadeVictim or Get5BlindedGrenadeVictim + property JSON_Array Victims { + public get() { + return view_as(this.GetObject("victims")); + } + public set(JSON_Array victims) { + this.SetObject("victims", victims); } + } } methodmap Get5VictimWithDamageGrenadeEvent < Get5VictimGrenadeEvent { - property int DamageEnemies { - public get() { - return this.GetInt("damage_enemies"); - } - - public set(int damage) { - this.SetInt("damage_enemies", damage); - } + property int DamageEnemies { + public get() { + return this.GetInt("damage_enemies"); } + public set(int damage) { + this.SetInt("damage_enemies", damage); + } + } - property int DamageFriendlies { - public get() { - return this.GetInt("damage_friendlies"); - } - - public set(int damage) { - this.SetInt("damage_friendlies", damage); - } + property int DamageFriendlies { + public get() { + return this.GetInt("damage_friendlies"); + } + public set(int damage) { + this.SetInt("damage_friendlies", damage); } + } } methodmap Get5SmokeDetonatedEvent < Get5PlayerWeaponEvent { - property bool ExtinguishedMolotov { - public get() { - return this.GetBool("extinguished_molotov"); - } - - public set(bool extinguishedMolotov) { - this.SetBool("extinguished_molotov", extinguishedMolotov); - } + property bool ExtinguishedMolotov { + public get() { + return this.GetBool("extinguished_molotov"); } - - public Get5SmokeDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player, bool extinguishedMolotov) { - Get5SmokeDetonatedEvent self = view_as(new JSON_Object()); - self.SetEvent("smokegrenade_detonated"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Weapon = new Get5Weapon("smokegrenade", CSWeapon_SMOKEGRENADE); - self.ExtinguishedMolotov = extinguishedMolotov; - return self; + public set(bool extinguishedMolotov) { + this.SetBool("extinguished_molotov", extinguishedMolotov); } + } + + public Get5SmokeDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player, bool extinguishedMolotov) { + Get5SmokeDetonatedEvent self = view_as(new JSON_Object()); + self.SetEvent("smokegrenade_detonated"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Weapon = new Get5Weapon("smokegrenade", CSWeapon_SMOKEGRENADE); + self.ExtinguishedMolotov = extinguishedMolotov; + return self; + } } methodmap Get5HEDetonatedEvent < Get5VictimWithDamageGrenadeEvent { - public Get5HEDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { - Get5HEDetonatedEvent self = view_as(new JSON_Object()); - self.SetEvent("hegrenade_detonated"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Weapon = new Get5Weapon("hegrenade", CSWeapon_HEGRENADE); - self.Victims = new JSON_Array(); - self.DamageEnemies = 0; - self.DamageFriendlies = 0; - return self; - } + public Get5HEDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { + Get5HEDetonatedEvent self = view_as(new JSON_Object()); + self.SetEvent("hegrenade_detonated"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Weapon = new Get5Weapon("hegrenade", CSWeapon_HEGRENADE); + self.Victims = new JSON_Array(); + self.DamageEnemies = 0; + self.DamageFriendlies = 0; + return self; + } } methodmap Get5FlashbangDetonatedEvent < Get5VictimGrenadeEvent { - public Get5FlashbangDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { - Get5FlashbangDetonatedEvent self = view_as(new JSON_Object()); - self.SetEvent("flashbang_detonated"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Weapon = new Get5Weapon("flashbang", CSWeapon_FLASHBANG); - self.Victims = new JSON_Array(); - return self; - } + public Get5FlashbangDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { + Get5FlashbangDetonatedEvent self = view_as(new JSON_Object()); + self.SetEvent("flashbang_detonated"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Weapon = new Get5Weapon("flashbang", CSWeapon_FLASHBANG); + self.Victims = new JSON_Array(); + return self; + } } methodmap Get5DecoyStartedEvent < Get5PlayerWeaponEvent { - public Get5DecoyStartedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { - Get5DecoyStartedEvent self = view_as(new JSON_Object()); - self.SetEvent("decoygrenade_started"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Weapon = new Get5Weapon("decoy", CSWeapon_DECOY); - return self; - } + public Get5DecoyStartedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { + Get5DecoyStartedEvent self = view_as(new JSON_Object()); + self.SetEvent("decoygrenade_started"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Weapon = new Get5Weapon("decoy", CSWeapon_DECOY); + return self; + } } // This event fires when the molotov ends, but its RoundTime parameter is when it started burning. // Note that this event does *not* fire if the molotov was thrown directly at a smoke and did not start burning. methodmap Get5MolotovDetonatedEvent < Get5VictimWithDamageGrenadeEvent { - // RoundTime is when the molotov detonated. - property int EndTime { - public get() { - return this.GetInt("round_time_ended"); - } - - public set(int endTime) { - this.SetInt("round_time_ended", endTime); - this.SetInt("duration", endTime - this.RoundTime); - } - } - - public Get5MolotovDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { - Get5MolotovDetonatedEvent self = view_as(new JSON_Object()); - self.SetEvent("molotov_detonated"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Weapon = new Get5Weapon("molotov", CSWeapon_MOLOTOV); // Sourcemod does not give us the info required to distinguish between molly and firebomb - self.Victims = new JSON_Array(); - self.EndTime = 0; // Set after the molotov stops burning (either by expiration, extinguish or new round start). - self.DamageEnemies = 0; - self.DamageFriendlies = 0; - return self; + // RoundTime is when the molotov detonated. + property int EndTime { + public get() { + return this.GetInt("round_time_ended"); } + public set(int endTime) { + this.SetInt("round_time_ended", endTime); + this.SetInt("duration", endTime - this.RoundTime); + } + } + + public Get5MolotovDetonatedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player) { + Get5MolotovDetonatedEvent self = view_as(new JSON_Object()); + self.SetEvent("molotov_detonated"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Weapon = new Get5Weapon("molotov", CSWeapon_MOLOTOV); // SourceMod does not give us the info required to distinguish between molly and firebomb + self.Victims = new JSON_Array(); + self.EndTime = 0; // Set after the molotov stops burning (either by expiration, extinguish or new round start). + self.DamageEnemies = 0; + self.DamageFriendlies = 0; + return self; + } } methodmap Get5GrenadeVictim < JSON_Object { - property Get5Player Player { - public get() { - return view_as(this.GetObject("player")); - } - - public set(Get5Player player) { - this.SetObject("player", player); - } + property Get5Player Player { + public get() { + return view_as(this.GetObject("player")); } - - property bool FriendlyFire { - public get() { - return this.GetBool("friendly_fire"); - } - - public set(bool friendlyFire) { - this.SetBool("friendly_fire", friendlyFire); - } + public set(Get5Player player) { + this.SetObject("player", player); } + } + property bool FriendlyFire { + public get() { + return this.GetBool("friendly_fire"); + } + public set(bool friendlyFire) { + this.SetBool("friendly_fire", friendlyFire); + } + } } methodmap Get5DamageGrenadeVictim < Get5GrenadeVictim { - property int Damage { - public get() { - return this.GetInt("damage"); - } - - public set(int damage) { - this.SetInt("damage", damage); - } + property int Damage { + public get() { + return this.GetInt("damage"); } - - property bool Killed { - public get() { - return this.GetBool("killed"); - } - - public set(bool killed) { - this.SetBool("killed", killed); - } + public set(int damage) { + this.SetInt("damage", damage); } + } - public Get5DamageGrenadeVictim(const Get5Player player, const bool friendlyFire, bool killed, const int damage) { - Get5DamageGrenadeVictim self = view_as(new JSON_Object()); - self.Player = player - self.FriendlyFire = friendlyFire; - self.Killed = killed; - self.Damage = damage; - return self; + property bool Killed { + public get() { + return this.GetBool("killed"); + } + public set(bool killed) { + this.SetBool("killed", killed); } + } + + public Get5DamageGrenadeVictim(const Get5Player player, const bool friendlyFire, bool killed, const int damage) { + Get5DamageGrenadeVictim self = view_as(new JSON_Object()); + self.Player = player + self.FriendlyFire = friendlyFire; + self.Killed = killed; + self.Damage = damage; + return self; + } } methodmap Get5BlindedGrenadeVictim < Get5GrenadeVictim { - property float BlindDuration { - public get() { - return this.GetFloat("blind_duration"); - } - - public set(float blindDuration) { - this.SetFloat("blind_duration", blindDuration); - } + property float BlindDuration { + public get() { + return this.GetFloat("blind_duration"); } - - public Get5BlindedGrenadeVictim(const Get5Player player, const bool friendlyFire, const float blindDuration) { - Get5BlindedGrenadeVictim self = view_as(new JSON_Object()); - self.Player = player - self.FriendlyFire = friendlyFire; - self.BlindDuration = blindDuration; - return self; + public set(float blindDuration) { + this.SetFloat("blind_duration", blindDuration); } + } + + public Get5BlindedGrenadeVictim(const Get5Player player, const bool friendlyFire, const float blindDuration) { + Get5BlindedGrenadeVictim self = view_as(new JSON_Object()); + self.Player = player + self.FriendlyFire = friendlyFire; + self.BlindDuration = blindDuration; + return self; + } } // BOMB @@ -1607,71 +1531,71 @@ methodmap Get5BombEvent < Get5TimedRoundEvent { methodmap Get5PlayerBombEvent < Get5PlayerTimedRoundEvent { - property Get5BombSite Site { - public get() { - return view_as(this.GetInt("site_int")); - } - public set(Get5BombSite site) { - this.SetInt("site_int", view_as(site)); - this.SetHidden("site_int", true); - ConvertBombSiteToStringInJson(this, "site", site); - } + property Get5BombSite Site { + public get() { + return view_as(this.GetInt("site_int")); + } + public set(Get5BombSite site) { + this.SetInt("site_int", view_as(site)); + this.SetHidden("site_int", true); + ConvertBombSiteToStringInJson(this, "site", site); } + } } methodmap Get5BombPlantedEvent < Get5PlayerBombEvent { - public Get5BombPlantedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player, const Get5BombSite site) { - Get5BombPlantedEvent self = view_as(new JSON_Object()); - self.SetEvent("bomb_planted"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Site = site; - return self; - } + public Get5BombPlantedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5Player player, const Get5BombSite site) { + Get5BombPlantedEvent self = view_as(new JSON_Object()); + self.SetEvent("bomb_planted"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Site = site; + return self; + } } methodmap Get5BombExplodedEvent < Get5BombEvent { - public Get5BombExplodedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5BombSite site) { - Get5BombExplodedEvent self = view_as(new JSON_Object()); - self.SetEvent("bomb_exploded"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Site = site; - return self; - } + public Get5BombExplodedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, const Get5BombSite site) { + Get5BombExplodedEvent self = view_as(new JSON_Object()); + self.SetEvent("bomb_exploded"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Site = site; + return self; + } } methodmap Get5BombDefusedEvent < Get5PlayerBombEvent { - property int TimeRemaining { - public get() { - return this.GetInt("bomb_time_remaining"); - } - public set(int time) { - this.SetInt("bomb_time_remaining", time); - } + property int TimeRemaining { + public get() { + return this.GetInt("bomb_time_remaining"); } - - public Get5BombDefusedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, - const Get5Player player, const Get5BombSite site, const int timeRemaining) { - Get5BombDefusedEvent self = view_as(new JSON_Object()); - self.SetEvent("bomb_defused"); - self.SetMatchId(matchId); - self.MapNumber = mapNumber; - self.RoundNumber = roundNumber; - self.RoundTime = roundTime; - self.Player = player; - self.Site = site; - self.TimeRemaining = timeRemaining; - return self; + public set(int time) { + this.SetInt("bomb_time_remaining", time); } + } + + public Get5BombDefusedEvent(const char[] matchId, const int mapNumber, const int roundNumber, const int roundTime, + const Get5Player player, const Get5BombSite site, const int timeRemaining) { + Get5BombDefusedEvent self = view_as(new JSON_Object()); + self.SetEvent("bomb_defused"); + self.SetMatchId(matchId); + self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; + self.RoundTime = roundTime; + self.Player = player; + self.Site = site; + self.TimeRemaining = timeRemaining; + return self; + } } // Called each get5-event with JSON formatted event text. From c8729abfdcd0044b624e7ecb6db8e67ca0078633 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 10 Aug 2022 03:42:46 +0200 Subject: [PATCH 019/104] Remove CurrentScoreInfoMessage translations as it never varies per language (#826) --- documentation/docs/translations.md | 1 - scripting/get5.sp | 2 +- translations/chi/get5.phrases.txt | 4 ---- translations/da/get5.phrases.txt | 4 ---- translations/de/get5.phrases.txt | 4 ---- translations/es/get5.phrases.txt | 4 ---- translations/fr/get5.phrases.txt | 4 ---- translations/get5.phrases.txt | 5 ----- translations/hu/get5.phrases.txt | 4 ---- translations/pl/get5.phrases.txt | 4 ---- translations/pt/get5.phrases.txt | 4 ---- translations/ru/get5.phrases.txt | 4 ---- 12 files changed, 1 insertion(+), 43 deletions(-) diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index e12db1a47..795850846 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -128,7 +128,6 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | `TeamsSplitSeriesBO2InfoMessage` | _Team A_ and _Team B_ have split the series 1-1. | Chat | | `TeamWonSeriesInfoMessage` | _Team A_ has won the series _2_-_1_. | Chat | | `MatchFinishedInfoMessage` | The match is over | KickedNote | -| `CurrentScoreInfoMessage` | _Team A_ _12_ - _Team B_ _8_ | Chat | | `BackupLoadedInfoMessage` | Successfully loaded backup _backup_file_03.cfg_. | Chat | | `MatchBeginInSecondsInfoMessage` | The match will begin in _3_ seconds. | Chat | | `MatchIsLiveInfoMessage` | Match is LIVE
Match is LIVE
Match is LIVE
Match is LIVE
Match is LIVE | Chat | diff --git a/scripting/get5.sp b/scripting/get5.sp index 54cc2a7b2..3e67dac77 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1500,7 +1500,7 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) if (g_GameState == Get5State_Live) { int csTeamWinner = event.GetInt("winner"); - Get5_MessageToAll("%t", "CurrentScoreInfoMessage", g_TeamNames[Get5Team_1], + Get5_MessageToAll("{LIGHT_GREEN}%s {GREEN}%d {NORMAL}- {GREEN}%d {LIGHT_GREEN}%s", g_TeamNames[Get5Team_1], CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)), CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)), g_TeamNames[Get5Team_2]); diff --git a/translations/chi/get5.phrases.txt b/translations/chi/get5.phrases.txt index 98e1d73cf..2d071febf 100644 --- a/translations/chi/get5.phrases.txt +++ b/translations/chi/get5.phrases.txt @@ -180,10 +180,6 @@ { "chi" "这场比赛已完结。" } - "CurrentScoreInfoMessage" - { - "chi" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "BackupLoadedInfoMessage" { "chi" "成功加载备份档案 {1}" diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 49f7b5e92..260f61f26 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -248,10 +248,6 @@ { "da" "Kampen er afsluttet" } - "CurrentScoreInfoMessage" - { - "da" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "StopCommandNotEnabled" { "da" "Stop-kommandoen er ikke aktiveret." diff --git a/translations/de/get5.phrases.txt b/translations/de/get5.phrases.txt index 4937241e0..d60af0bc0 100644 --- a/translations/de/get5.phrases.txt +++ b/translations/de/get5.phrases.txt @@ -152,10 +152,6 @@ { "de" "Das Match ist beendet" } - "CurrentScoreInfoMessage" - { - "de" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "BackupLoadedInfoMessage" { "de" "Backup {1} erfolgreich geladen" diff --git a/translations/es/get5.phrases.txt b/translations/es/get5.phrases.txt index f502ac12c..21f33fb0e 100644 --- a/translations/es/get5.phrases.txt +++ b/translations/es/get5.phrases.txt @@ -184,10 +184,6 @@ { "es" "La partida está terminada" } - "CurrentScoreInfoMessage" - { - "es" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "BackupLoadedInfoMessage" { "es" "Backup cargado exitosamente {1}" diff --git a/translations/fr/get5.phrases.txt b/translations/fr/get5.phrases.txt index 793d623ad..c6a67fb19 100644 --- a/translations/fr/get5.phrases.txt +++ b/translations/fr/get5.phrases.txt @@ -184,10 +184,6 @@ { "fr" "Le match est terminé" } - "CurrentScoreInfoMessage" - { - "fr" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "BackupLoadedInfoMessage" { "fr" "Sauvegarde chargée avec succès {1}" diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 8fb79412b..5c377357c 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -291,11 +291,6 @@ { "en" "The match is over" } - "CurrentScoreInfoMessage" - { - "#format" "{1:s},{2:d},{3:d},{4:s}" - "en" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "StopCommandNotEnabled" { "en" "The stop command is not enabled." diff --git a/translations/hu/get5.phrases.txt b/translations/hu/get5.phrases.txt index 0d439b917..2540746a7 100644 --- a/translations/hu/get5.phrases.txt +++ b/translations/hu/get5.phrases.txt @@ -256,10 +256,6 @@ { "hu" "A mérkőzés befejeződött" } - "CurrentScoreInfoMessage" - { - "hu" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "StopCommandNotEnabled" { "hu" "A stop parancs nincs engedélyezve." diff --git a/translations/pl/get5.phrases.txt b/translations/pl/get5.phrases.txt index 4fda81f07..3c7e91c1a 100644 --- a/translations/pl/get5.phrases.txt +++ b/translations/pl/get5.phrases.txt @@ -156,10 +156,6 @@ { "pl" "Mecz został zakończony" } - "CurrentScoreInfoMessage" - { - "pl" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "BackupLoadedInfoMessage" { "pl" "Pomyślnie wczytano kopię meczu {1}" diff --git a/translations/pt/get5.phrases.txt b/translations/pt/get5.phrases.txt index 1f00fe1fc..e78033e93 100644 --- a/translations/pt/get5.phrases.txt +++ b/translations/pt/get5.phrases.txt @@ -172,10 +172,6 @@ { "pt" "A partida foi fnializada." } - "CurrentScoreInfoMessage" - { - "pt" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "BackupLoadedInfoMessage" { "pt" "Backup carregado com sucesso {1}" diff --git a/translations/ru/get5.phrases.txt b/translations/ru/get5.phrases.txt index 94925465d..4d03099b3 100644 --- a/translations/ru/get5.phrases.txt +++ b/translations/ru/get5.phrases.txt @@ -148,10 +148,6 @@ { "ru" "Матч уже завершен" } - "CurrentScoreInfoMessage" - { - "ru" "{LIGHT_GREEN}{1} {GREEN}{2} {NORMAL}- {GREEN}{3} {LIGHT_GREEN}{4}" - } "BackupLoadedInfoMessage" { "ru" "Удачно загружен бэкап {1}" From 376bde6764700c44eb1a66c6ae9406aa991ec22d Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 10 Aug 2022 23:04:58 +0200 Subject: [PATCH 020/104] Remove AddMapScore, refactor its logic to use g_MapNumber --- scripting/get5.sp | 15 ++++++++------- scripting/get5/teamlogic.sp | 10 ---------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 3e67dac77..6bd5f98d6 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1155,17 +1155,18 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast // Update series scores Stats_UpdateMapScore(winningTeam); - AddMapScore(); g_TeamSeriesScores[winningTeam]++; - // Handle map end - int team1score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)); - int team2score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)); + g_TeamScoresPerMap.Set(g_MapNumber, t1score, view_as(Get5Team_1)); + g_TeamScoresPerMap.Set(g_MapNumber, t2score, view_as(Get5Team_2)); Get5MapResultEvent mapResultEvent = new Get5MapResultEvent( - g_MatchID, g_MapNumber, - new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), team1score, - team2score); + g_MatchID, + g_MapNumber, + new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), + t1score, + t2score + ); LogDebug("Calling Get5_OnMapResult()"); diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index fc25432e8..f650d97d4 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -397,16 +397,6 @@ public void SetStartingTeams() { g_TeamStartingSide[Get5Team_2] = g_TeamSide[Get5Team_2]; } -public void AddMapScore() { - int currentMapNumber = Get5_GetMapNumber(); - - g_TeamScoresPerMap.Set(currentMapNumber, CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)), - view_as(Get5Team_1)); - - g_TeamScoresPerMap.Set(currentMapNumber, CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)), - view_as(Get5Team_2)); -} - public int GetMapScore(int mapNumber, Get5Team team) { return g_TeamScoresPerMap.Get(mapNumber, view_as(team)); } From 901d48d92ffd7b4f8f85d8a4d997091b7b2f6a47 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 12 Aug 2022 04:26:46 +0200 Subject: [PATCH 021/104] Minor doc adjustment --- documentation/docs/installation.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/docs/installation.md b/documentation/docs/installation.md index c8c0c2265..3ad110347 100644 --- a/documentation/docs/installation.md +++ b/documentation/docs/installation.md @@ -14,10 +14,10 @@ You can get the latest versions here: ## Download Get5 -The latest release of Get5 can be found [here](https://github.com/splewis/get5/releases/latest). Older Releases of +The latest release of Get5 can be found [here](https://github.com/splewis/get5/releases/latest). Older Releases of Get5 can be found in the [Releases](https://github.com/splewis/get5/releases) section of the repo. Anything *not* -marked as "Nightly" in the title are known to be stable, but may be lacking features that are currently in development. -If you would like to test new features, or be on the "bleeding edge", you can also download any of the latest +marked as "Nightly" in the title are known to be stable, but may be lacking features that are currently in development. +If you would like to test new features, or be on the "bleeding edge", you can also download any of the latest pre-releases found at the same link above that are marked in the title with "Nightly" or are marked as "Pre-release". !!! info @@ -170,10 +170,10 @@ is just to indicate what the correct structure looks like. 8. Don't change anything in here. There are no editable files in the `metamod` folder. It's here because SourceMod depends on it. 9. SourceMod binaries. - 10. This a JSON-example of a [match configuration]. You should use this as a template for your own match configuration. - All JSON match configurations **must** end with `.json`. - 11. The server's default scrim match configuration. This is loaded when using - the [`get5_scrim`](../commands/#get5_scrim) command. + 10. This a JSON-example of a [match configuration](match_schema.md). You should use this as a template for your own + match configuration. All JSON match configurations **must** end with `.json`. + 11. The server's default scrim [match configuration](match_schema.md). This is loaded when using the + [`get5_scrim`](../commands/#get5_scrim) command. 12. Match configurations can be created in both JSON and SourceMod's [KeyValue](https://wiki.alliedmods.net/KeyValues_(SourceMod_Scripting)) format. We recommend JSON for all new users, but Get5 will continue to support reading `.cfg` files as well. From e418e91227527d5d84bbf42db165e71765456925 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 12 Aug 2022 23:42:31 +0200 Subject: [PATCH 022/104] Unpause on match end (#828) --- scripting/get5.sp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripting/get5.sp b/scripting/get5.sp index 6bd5f98d6..1d16f1a0f 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1135,6 +1135,11 @@ public Action Timer_ReplenishMoney(Handle timer, int client) { public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_MatchOver"); if (g_GameState == Get5State_Live) { + + // If someone called for a pause in the last round; cancel it. + if (IsPaused()) { + UnpauseGame(Get5Team_None); + } // Figure out who won int t1score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)); int t2score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)); From 62295701646700f494c090aafd8dca6975bd344a Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 13 Aug 2022 20:24:20 +0200 Subject: [PATCH 023/104] Set scrim matchid to "scrim"; this was fixed in mysql extension instead (#829) --- documentation/docs/commands.md | 2 +- documentation/docs/stats_system.md | 5 +++-- scripting/get5/matchconfig.sp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 0faa9a9c4..945589f27 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -97,7 +97,7 @@ You should put the `url` argument inside quotation marks (`""`). ####`get5_endmatch` : Force ends the current match. No winner is set (draw). -####`get5_creatematch [map name] [matchid]` +####`get5_creatematch [map name] [matchid]` {: #get5_creatematch } : Creates a BO1 match with the current players on the server. `map name` defaults to the current map and `matchid` defaults to `manual`. You should **not** provide a match ID if you use the [MySQL extension](../stats_system/#mysql). diff --git a/documentation/docs/stats_system.md b/documentation/docs/stats_system.md index eb77525f3..17ce86ca0 100644 --- a/documentation/docs/stats_system.md +++ b/documentation/docs/stats_system.md @@ -71,8 +71,9 @@ functionality, but can also be used as-is. !!! danger "Fixed Match IDs" If you use the MySQL extension, you should **not** set the `matchid` in your - [match configuration](../match_schema/#schema) (just leave it empty) or when creating scrims using the - [`get5_scrim`](../commands/#get5_scrim) command. The match ID will be set to the + [match configuration](../match_schema/#schema) (just leave it empty) or when creating scrims or matches using the + [`get5_scrim`](../commands/#get5_scrim) or [`get5_creatematch`](../commands/#get5_creatematch) commands. The match + ID will be set to the [auto-incrementing integer](https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html) (cast to a string) returned by inserting into the `get5_stats_matches` table. diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 04c8bed5d..a9b837123 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -1081,7 +1081,7 @@ public Action Command_CreateScrim(int client, int args) { return Plugin_Handled; } - char matchid[MATCH_ID_LENGTH] = ""; + char matchid[MATCH_ID_LENGTH] = "scrim"; char matchMap[PLATFORM_MAX_PATH]; GetCleanMapName(matchMap, sizeof(matchMap)); char otherTeamName[MAX_CVAR_LENGTH] = "Away"; From b50025b5639ab40deb5156a31cd8b43a615bb34a Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 7 Aug 2022 22:39:10 +0200 Subject: [PATCH 024/104] Ensure correct MapNumber logic when restoring from backup in case of draws Read round number from valve backup on restore event Use g_MapNumber in more places and reduce use of Get5_GetMapNumber where not needed Properly calculate series results when ties are included Remove bo2 tie message (it was used when not bo2) Remove call to WriteBackup on match end Remove unused translation from docs --- documentation/docs/event_schema.yml | 5 +- documentation/docs/translations.md | 1 - scripting/get5.sp | 94 +++++++++++++---------------- scripting/get5/backups.sp | 16 ++++- scripting/get5/goinglive.sp | 2 +- scripting/get5/kniferounds.sp | 4 +- scripting/get5/matchconfig.sp | 15 ++--- scripting/get5/readysystem.sp | 2 +- scripting/get5_apistats.sp | 11 ++-- scripting/get5_mysqlstats.sp | 11 ++-- scripting/include/get5.inc | 8 ++- translations/chi/get5.phrases.txt | 4 -- translations/da/get5.phrases.txt | 4 -- translations/de/get5.phrases.txt | 4 -- translations/es/get5.phrases.txt | 4 -- translations/fr/get5.phrases.txt | 4 -- translations/get5.phrases.txt | 5 -- translations/hu/get5.phrases.txt | 4 -- translations/pl/get5.phrases.txt | 4 -- translations/pt/get5.phrases.txt | 4 -- translations/ru/get5.phrases.txt | 4 -- 21 files changed, 84 insertions(+), 126 deletions(-) diff --git a/documentation/docs/event_schema.yml b/documentation/docs/event_schema.yml index ab03123e0..c6ec1149e 100644 --- a/documentation/docs/event_schema.yml +++ b/documentation/docs/event_schema.yml @@ -489,14 +489,15 @@ paths: tags: - Series Flow description: | - Fired when a round is restored from a backup. + Fired when a round is restored from a backup. Note that the map and round numbers indicate the round being + restored **to**, not the round the backup was requested during. requestBody: content: application/json: schema: title: Get5BackupRestoredEvent allOf: - - "$ref": "#/components/schemas/Get5MapEvent" + - "$ref": "#/components/schemas/Get5RoundEvent" properties: event: enum: diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index 795850846..01036eb48 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -125,7 +125,6 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | `NextSeriesMapInfoMessage` | The next map in the series is _de_nuke_. | Chat | | `TeamWonMatchInfoMessage` | _Team A_ has won the match. | Chat | | `TeamTiedMatchInfoMessage` | _Team A_ and _Team B_ have tied the match. | Chat | -| `TeamsSplitSeriesBO2InfoMessage` | _Team A_ and _Team B_ have split the series 1-1. | Chat | | `TeamWonSeriesInfoMessage` | _Team A_ has won the series _2_-_1_. | Chat | | `MatchFinishedInfoMessage` | The match is over | KickedNote | | `BackupLoadedInfoMessage` | Successfully loaded backup _backup_file_03.cfg_. | Chat | diff --git a/scripting/get5.sp b/scripting/get5.sp index 1d16f1a0f..86757a327 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1155,9 +1155,6 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast // trigger it either. Stats_ResetGrenadeContainers(); - // Write backup before series score increments - WriteBackup(); - // Update series scores Stats_UpdateMapScore(winningTeam); g_TeamSeriesScores[winningTeam]++; @@ -1187,70 +1184,61 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast float minDelay = float(GetTvDelay()) + MATCH_END_DELAY_AFTER_TV; - if (t1maps == g_MapsToWin) { - // Team 1 won - SeriesEndMessage(Get5Team_1); - DelayFunction(minDelay, EndSeries); - - } else if (t2maps == g_MapsToWin) { - // Team 2 won - SeriesEndMessage(Get5Team_2); - DelayFunction(minDelay, EndSeries); + if (t1maps == t2maps) { + // As long as team scores are equal, we play until there are no maps left. + if (t1maps + t2maps + tiedMaps == g_MapsToPlay.Length) { + SeriesEndMessage(Get5Team_None); + DelayFunction(minDelay, EndSeries); + return Plugin_Continue; + } + } else { + // This adjusts for ties! + int actualMapsToWin = ((g_MapsToPlay.Length - tiedMaps) / 2) + 1; + if (t1maps == actualMapsToWin) { + // Team 1 won + SeriesEndMessage(Get5Team_1); + DelayFunction(minDelay, EndSeries); + return Plugin_Continue; + } else if (t2maps == actualMapsToWin) { + // Team 2 won + SeriesEndMessage(Get5Team_2); + DelayFunction(minDelay, EndSeries); + return Plugin_Continue; + } + } - } else if (t1maps == t2maps && t1maps + tiedMaps == g_MapsToWin) { - // The whole series was a tie - SeriesEndMessage(Get5Team_None); - DelayFunction(minDelay, EndSeries); + if (t1maps > t2maps) { + Get5_MessageToAll("%t", "TeamWinningSeriesInfoMessage", g_FormattedTeamNames[Get5Team_1], + t1maps, t2maps); - } else if (g_BO2Match && Get5_GetMapNumber() == 2) { - // It was a bo2, and none of the teams got to 2 - SeriesEndMessage(Get5Team_None); - DelayFunction(minDelay, EndSeries); + } else if (t2maps > t1maps) { + Get5_MessageToAll("%t", "TeamWinningSeriesInfoMessage", g_FormattedTeamNames[Get5Team_2], + t2maps, t1maps); } else { - if (t1maps > t2maps) { - Get5_MessageToAll("%t", "TeamWinningSeriesInfoMessage", g_FormattedTeamNames[Get5Team_1], - t1maps, t2maps); - - } else if (t2maps > t1maps) { - Get5_MessageToAll("%t", "TeamWinningSeriesInfoMessage", g_FormattedTeamNames[Get5Team_2], - t2maps, t1maps); - - } else { - Get5_MessageToAll("%t", "SeriesTiedInfoMessage", t1maps, t2maps); - } + Get5_MessageToAll("%t", "SeriesTiedInfoMessage", t1maps, t2maps); + } - int index = Get5_GetMapNumber(); - char nextMap[PLATFORM_MAX_PATH]; - g_MapsToPlay.GetString(index, nextMap, sizeof(nextMap)); + char nextMap[PLATFORM_MAX_PATH]; + g_MapsToPlay.GetString(Get5_GetMapNumber(), nextMap, sizeof(nextMap)); - g_MapChangePending = true; - Get5_MessageToAll("%t", "NextSeriesMapInfoMessage", nextMap); - ChangeState(Get5State_PostGame); - CreateTimer(minDelay, Timer_NextMatchMap); - } + g_MapChangePending = true; + Get5_MessageToAll("%t", "NextSeriesMapInfoMessage", nextMap); + ChangeState(Get5State_PostGame); + CreateTimer(minDelay, Timer_NextMatchMap); } return Plugin_Continue; } static void SeriesEndMessage(Get5Team team) { - if (g_MapsToWin == 1) { - if (team == Get5Team_None) { - Get5_MessageToAll("%t", "TeamTiedMatchInfoMessage", g_FormattedTeamNames[Get5Team_1], - g_FormattedTeamNames[Get5Team_2]); - } else { - Get5_MessageToAll("%t", "TeamWonMatchInfoMessage", g_FormattedTeamNames[team]); - } + if (team == Get5Team_None) { + Get5_MessageToAll("%t", "TeamTiedMatchInfoMessage", g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2]); } else { - if (team == Get5Team_None) { - // BO2 split. - Get5_MessageToAll("%t", "TeamsSplitSeriesBO2InfoMessage", g_FormattedTeamNames[Get5Team_1], - g_FormattedTeamNames[Get5Team_2]); - + if (g_MapsToPlay.Length == 1) { + Get5_MessageToAll("%t", "TeamWonMatchInfoMessage", g_FormattedTeamNames[team]); } else { - Get5_MessageToAll("%t", "TeamWonSeriesInfoMessage", g_FormattedTeamNames[team], - g_TeamSeriesScores[team], g_TeamSeriesScores[OtherMatchTeam(team)]); + Get5_MessageToAll("%t", "TeamWonSeriesInfoMessage", g_FormattedTeamNames[team], g_TeamSeriesScores[team], g_TeamSeriesScores[OtherMatchTeam(team)]); } } } diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 0ff31ee3f..8c8c14ace 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -169,6 +169,8 @@ public void WriteBackStructure(const char[] path) { kv.SetNum("team1_series_score", g_TeamSeriesScores[Get5Team_1]); kv.SetNum("team2_series_score", g_TeamSeriesScores[Get5Team_2]); + kv.SetNum("series_draw", g_TeamSeriesScores[Get5Team_None]); + // Write original maplist. kv.JumpToKey("maps", true); for (int i = 0; i < g_MapsToPlay.Length; i++) { @@ -251,6 +253,12 @@ public bool RestoreFromBackup(const char[] path) { g_TeamSeriesScores[Get5Team_1] = kv.GetNum("team1_series_score"); g_TeamSeriesScores[Get5Team_2] = kv.GetNum("team2_series_score"); + // This ensures that the MapNumber logic correctly calculates the map number when there have been draws. + g_TeamSeriesScores[Get5Team_None] = kv.GetNum("series_draw", 0); + + // Immediately set map number global var to ensure anything below doesn't break. + g_MapNumber = Get5_GetMapNumber(); + char mapName[PLATFORM_MAX_PATH]; if (g_GameState > Get5State_Veto) { if (kv.JumpToKey("maps")) { @@ -292,11 +300,14 @@ public bool RestoreFromBackup(const char[] path) { kv.GoBack(); } + // When loading round 0, there is no valve backup, so we assume round 0 if the game is live, otherwise -1 + int roundNumberRestoredTo = g_GameState == Get5State_Live ? 0 : -1; char tempValveBackup[PLATFORM_MAX_PATH]; GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); if (kv.JumpToKey("valve_backup")) { g_SavedValveBackup = true; kv.ExportToFile(tempValveBackup); + roundNumberRestoredTo = kv.GetNum("round", 0); kv.GoBack(); } else { g_SavedValveBackup = false; @@ -306,7 +317,7 @@ public bool RestoreFromBackup(const char[] path) { GetCurrentMap(currentMap, sizeof(currentMap)); char currentSeriesMap[PLATFORM_MAX_PATH]; - g_MapsToPlay.GetString(Get5_GetMapNumber(), currentSeriesMap, sizeof(currentSeriesMap)); + g_MapsToPlay.GetString(g_MapNumber, currentSeriesMap, sizeof(currentSeriesMap)); if (!StrEqual(currentMap, currentSeriesMap)) { ChangeMap(currentSeriesMap, 1.0); @@ -320,8 +331,7 @@ public bool RestoreFromBackup(const char[] path) { LogDebug("Calling Get5_OnBackupRestore()"); - Get5BackupRestoredEvent backupEvent = - new Get5BackupRestoredEvent(g_MatchID, Get5_GetMapNumber(), path); + Get5BackupRestoredEvent backupEvent = new Get5BackupRestoredEvent(g_MatchID, g_MapNumber, roundNumberRestoredTo, path); Call_StartForward(g_OnBackupRestore); Call_PushCell(backupEvent); diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index b2742669b..763217967 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -21,7 +21,7 @@ public Action StartGoingLive(Handle timer) { float delay = float(5 + g_LiveCountdownTimeCvar.IntValue); CreateTimer(delay, MatchLive); - Get5GoingLiveEvent liveEvent = new Get5GoingLiveEvent(g_MatchID, Get5_GetMapNumber()); + Get5GoingLiveEvent liveEvent = new Get5GoingLiveEvent(g_MatchID, g_MapNumber); LogDebug("Calling Get5_OnGoingLive()"); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index 984815f1f..cc604d478 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -55,9 +55,9 @@ static void PerformSideSwap(bool swap) { // that way set starting teams won't swap on round 0, // since a temp valve backup does not exist. if (g_TeamSide[Get5Team_1] == CS_TEAM_CT) - g_MapSides.Set(Get5_GetMapNumber(), SideChoice_Team1CT); + g_MapSides.Set(g_MapNumber, SideChoice_Team1CT); else - g_MapSides.Set(Get5_GetMapNumber(), SideChoice_Team1T); + g_MapSides.Set(g_MapNumber, SideChoice_Team1T); } else { g_TeamSide[Get5Team_1] = TEAM1_STARTING_SIDE; g_TeamSide[Get5Team_2] = TEAM2_STARTING_SIDE; diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index a9b837123..057da6c19 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -104,19 +104,20 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } } - g_MapPoolList.GetString(Get5_GetMapNumber(), mapName, sizeof(mapName)); ChangeState(Get5State_Warmup); - - char currentMap[PLATFORM_MAX_PATH]; - GetCurrentMap(currentMap, sizeof(currentMap)); - if (!StrEqual(mapName, currentMap) && !restoreBackup) { - ChangeMap(mapName); + if (!restoreBackup) { + // When restoring from backup, changelevel is called after loading the match config. + g_MapPoolList.GetString(Get5_GetMapNumber(), mapName, sizeof(mapName)); + char currentMap[PLATFORM_MAX_PATH]; + GetCurrentMap(currentMap, sizeof(currentMap)); + if (!StrEqual(mapName, currentMap)) { + ChangeMap(mapName); + } } } else { ChangeState(Get5State_PreVeto); } - // We need to ensure our match team CVARs are set // before calling the event so we can grab values // that are set in the OnSeriesInit event. diff --git a/scripting/get5/readysystem.sp b/scripting/get5/readysystem.sp index 8b0941ba8..608047fc7 100644 --- a/scripting/get5/readysystem.sp +++ b/scripting/get5/readysystem.sp @@ -244,7 +244,7 @@ static void HandleReadyMessage(Get5Team team) { } else if (g_GameState == Get5State_Warmup) { if (g_WaitingForRoundBackup) { Get5_MessageToAll("%t", "TeamReadyToRestoreBackupInfoMessage", g_FormattedTeamNames[team]); - } else if (view_as(g_MapSides.Get(Get5_GetMapNumber())) == SideChoice_KnifeRound) { + } else if (view_as(g_MapSides.Get(g_MapNumber)) == SideChoice_KnifeRound) { Get5_MessageToAll("%t", "TeamReadyToKnifeInfoMessage", g_FormattedTeamNames[team]); } else { Get5_MessageToAll("%t", "TeamReadyToBeginInfoMessage", g_FormattedTeamNames[team]); diff --git a/scripting/get5_apistats.sp b/scripting/get5_apistats.sp index ecae7a372..0b59b3a20 100644 --- a/scripting/get5_apistats.sp +++ b/scripting/get5_apistats.sp @@ -235,7 +235,7 @@ public void Get5_OnGoingLive(const Get5GoingLiveEvent event) { Get5_AddLiveCvar("get5_web_api_url", g_APIURL); } -public void UpdateRoundStats(const char[] matchId, int mapNumber) { +public void UpdateRoundStats(const char[] matchId, const int mapNumber) { int t1score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_1)); int t2score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_2)); @@ -252,11 +252,11 @@ public void UpdateRoundStats(const char[] matchId, int mapNumber) { Format(mapKey, sizeof(mapKey), "map%d", mapNumber); if (kv.JumpToKey(mapKey)) { if (kv.JumpToKey("team1")) { - UpdatePlayerStats(matchId, kv, Get5Team_1); + UpdatePlayerStats(matchId, mapNumber, kv, Get5Team_1); kv.GoBack(); } if (kv.JumpToKey("team2")) { - UpdatePlayerStats(matchId, kv, Get5Team_2); + UpdatePlayerStats(matchId, mapNumber, kv, Get5Team_2); kv.GoBack(); } kv.GoBack(); @@ -284,10 +284,9 @@ static void AddIntStat(Handle req, KeyValues kv, const char[] field) { AddIntParam(req, field, kv.GetNum(field)); } -public void UpdatePlayerStats(const char[] matchId, KeyValues kv, Get5Team team) { +public void UpdatePlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; - int mapNumber = Get5_GetMapNumber(); if (kv.GotoFirstSubKey()) { do { @@ -382,6 +381,6 @@ public void Get5_OnRoundStatsUpdated(const Get5RoundStatsUpdatedEvent event) { if (Get5_GetGameState() == Get5State_Live) { char matchId[64]; event.GetMatchId(matchId, sizeof(matchId)); - UpdateRoundStats(matchId, Get5_GetMapNumber()); + UpdateRoundStats(matchId, event.MapNumber); } } diff --git a/scripting/get5_mysqlstats.sp b/scripting/get5_mysqlstats.sp index 03c970c91..d9fc51129 100644 --- a/scripting/get5_mysqlstats.sp +++ b/scripting/get5_mysqlstats.sp @@ -157,7 +157,7 @@ public void Get5_OnGoingLive(const Get5GoingLiveEvent event) { db.Query(SQLErrorCheckCallback, queryBuffer); } -public void UpdateRoundStats(const char[] matchId, int mapNumber) { +public void UpdateRoundStats(const char[] matchId, const int mapNumber) { // Update team scores int t1score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_1)); int t2score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_2)); @@ -178,11 +178,11 @@ public void UpdateRoundStats(const char[] matchId, int mapNumber) { Format(mapKey, sizeof(mapKey), "map%d", mapNumber); if (kv.JumpToKey(mapKey)) { if (kv.JumpToKey("team1")) { - AddPlayerStats(matchId, kv, Get5Team_1); + AddPlayerStats(matchId, mapNumber, kv, Get5Team_1); kv.GoBack(); } if (kv.JumpToKey("team2")) { - AddPlayerStats(matchId, kv, Get5Team_2); + AddPlayerStats(matchId, mapNumber, kv, Get5Team_2); kv.GoBack(); } kv.GoBack(); @@ -223,12 +223,11 @@ public void Get5_OnMapResult(const Get5MapResultEvent event) { db.Query(SQLErrorCheckCallback, queryBuffer); } -public void AddPlayerStats(const char[] matchId, KeyValues kv, Get5Team team) { +public void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; char nameSz[MAX_NAME_LENGTH * 2 + 1]; char authSz[AUTH_LENGTH * 2 + 1]; - int mapNumber = Get5_GetMapNumber(); char matchIdSz[64]; db.Escape(matchId, matchIdSz, sizeof(matchIdSz)); @@ -381,6 +380,6 @@ public void Get5_OnRoundStatsUpdated(const Get5RoundStatsUpdatedEvent event) { if (Get5_GetGameState() == Get5State_Live && !g_DisableStats) { char matchId[64]; event.GetMatchId(matchId, sizeof(matchId)); - UpdateRoundStats(matchId, Get5_GetMapNumber()); + UpdateRoundStats(matchId, event.MapNumber); } } diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index a9b84ee54..ce2a8840d 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -943,7 +943,7 @@ methodmap Get5SeriesStartedEvent < Get5MatchEvent { } } -methodmap Get5BackupRestoredEvent < Get5MapEvent { +methodmap Get5BackupRestoredEvent < Get5RoundEvent { public bool SetFileName(const char[] file) { return this.SetString("filename", file); @@ -952,11 +952,12 @@ methodmap Get5BackupRestoredEvent < Get5MapEvent { return this.GetString("filename", buffer, maxSize); } - public Get5BackupRestoredEvent(const char[] matchId, const int mapNumber, const char[] file) { + public Get5BackupRestoredEvent(const char[] matchId, const int mapNumber, const int roundNumber, const char[] file) { Get5BackupRestoredEvent self = view_as(new JSON_Object()); self.SetEvent("backup_loaded"); self.SetMatchId(matchId); self.MapNumber = mapNumber; + self.RoundNumber = roundNumber; self.SetFileName(file); return self; } @@ -1703,7 +1704,8 @@ forward void Get5_OnMatchPaused(const Get5MatchPausedEvent event); forward void Get5_OnMatchUnpaused(const Get5MatchUnpausedEvent event); // Called when a match backup is restored. -// Note that the match ID and map number is the one being restored *to*, not the current game state and the time the backup is loaded. +// Note that the match ID, map number and round number is the one being restored *to*, not the current game state at the +// time the backup is loaded. forward void Get5_OnBackupRestore(const Get5BackupRestoredEvent event); // Series stats (root section) diff --git a/translations/chi/get5.phrases.txt b/translations/chi/get5.phrases.txt index 2d071febf..bfc28263e 100644 --- a/translations/chi/get5.phrases.txt +++ b/translations/chi/get5.phrases.txt @@ -168,10 +168,6 @@ { "chi" "{1}与{2}打成了平局。" } - "TeamsSplitSeriesBO2InfoMessage" - { - "chi" "{1}和{2}在此系列比赛中平局 1-1。" - } "TeamWonSeriesInfoMessage" { "chi" "{1}赢得了此系列比赛 {2}-{3}." diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 260f61f26..02f798e30 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -236,10 +236,6 @@ { "da" "{1} og {2} har spillet uafgjort." } - "TeamsSplitSeriesBO2InfoMessage" - { - "da" "{1} og {2} har splittet serien 1-1." - } "TeamWonSeriesInfoMessage" { "da" "{1} har vundet serien {2}-{3}." diff --git a/translations/de/get5.phrases.txt b/translations/de/get5.phrases.txt index d60af0bc0..b1212599f 100644 --- a/translations/de/get5.phrases.txt +++ b/translations/de/get5.phrases.txt @@ -140,10 +140,6 @@ { "de" "{1} hat das Match gewonnen." } - "TeamsSplitSeriesBO2InfoMessage" - { - "de" "{1} und {2} haben die Serie 1-1 geteilt." - } "TeamWonSeriesInfoMessage" { "de" "{1} hat die Serie gewonnen {2}-{3}." diff --git a/translations/es/get5.phrases.txt b/translations/es/get5.phrases.txt index 21f33fb0e..c4116f47c 100644 --- a/translations/es/get5.phrases.txt +++ b/translations/es/get5.phrases.txt @@ -172,10 +172,6 @@ { "es" "{1} y {2} empataron." } - "TeamsSplitSeriesBO2InfoMessage" - { - "es" "{1} y {2} han ganado la misma cantidad de partidas (1-1)." - } "TeamWonSeriesInfoMessage" { "es" "{1} ganó la serie {2}-{3}." diff --git a/translations/fr/get5.phrases.txt b/translations/fr/get5.phrases.txt index c6a67fb19..8cdbd2ac2 100644 --- a/translations/fr/get5.phrases.txt +++ b/translations/fr/get5.phrases.txt @@ -172,10 +172,6 @@ { "fr" "{1} et {2} ont fait match nul." } - "TeamsSplitSeriesBO2InfoMessage" - { - "fr" "{1} et {2} ont gagné autant de matchs (1-1)." - } "TeamWonSeriesInfoMessage" { "fr" "{1} a remporté la série {2}-{3}." diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 5c377357c..01096c945 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -277,11 +277,6 @@ "#format" "{1:s},{2:s}" "en" "{1} and {2} have tied the match." } - "TeamsSplitSeriesBO2InfoMessage" - { - "#format" "{1:s},{2:s}" - "en" "{1} and {2} have split the series 1-1." - } "TeamWonSeriesInfoMessage" { "#format" "{1:s},{2:d},{3:d}" diff --git a/translations/hu/get5.phrases.txt b/translations/hu/get5.phrases.txt index 2540746a7..308109ddf 100644 --- a/translations/hu/get5.phrases.txt +++ b/translations/hu/get5.phrases.txt @@ -244,10 +244,6 @@ { "hu" "{1} és a {2} döntetlent játszott." } - "TeamsSplitSeriesBO2InfoMessage" - { - "hu" "{1} és a {2} kiegyenlítettek. 1-1-re." - } "TeamWonSeriesInfoMessage" { "hu" "{1} nyerte a szériát. {2}-{3}" diff --git a/translations/pl/get5.phrases.txt b/translations/pl/get5.phrases.txt index 3c7e91c1a..342ad8c3b 100644 --- a/translations/pl/get5.phrases.txt +++ b/translations/pl/get5.phrases.txt @@ -144,10 +144,6 @@ { "pl" "{1} wygrało mapę." } - "TeamsSplitSeriesBO2InfoMessage" - { - "pl" "{1} i {2} zremisowali spotkanie 1-1." - } "TeamWonSeriesInfoMessage" { "pl" "{1} wygrało spotkanie {2}-{3}." diff --git a/translations/pt/get5.phrases.txt b/translations/pt/get5.phrases.txt index e78033e93..82b029517 100644 --- a/translations/pt/get5.phrases.txt +++ b/translations/pt/get5.phrases.txt @@ -160,10 +160,6 @@ { "pt" "{1} é a equipe vencedora da partida." } - "TeamsSplitSeriesBO2InfoMessage" - { - "pt" "{1} e {2} empataram a série em 1-1." - } "TeamWonSeriesInfoMessage" { "pt" "{1} é a equipe vencedora da série {2}-{3}." diff --git a/translations/ru/get5.phrases.txt b/translations/ru/get5.phrases.txt index 4d03099b3..688c85f60 100644 --- a/translations/ru/get5.phrases.txt +++ b/translations/ru/get5.phrases.txt @@ -136,10 +136,6 @@ { "ru" "{1} выиграл матч." } - "TeamsSplitSeriesBO2InfoMessage" - { - "ru" "{1} и {2} закончили серию со счетом 1-1." - } "TeamWonSeriesInfoMessage" { "ru" "{1} выиграли серию {2}-{3}." From 2d9d5c15c48ba2693b1495b0f89255612360fe70 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 9 Aug 2022 20:27:26 +0200 Subject: [PATCH 025/104] Add series clinch logic --- documentation/docs/match_schema.md | 5 ++++- scripting/get5.sp | 12 +++++++++--- scripting/get5/debug.sp | 1 + scripting/get5/matchconfig.sp | 5 +++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 19c005a70..106566955 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -55,7 +55,8 @@ interface Get5Match { "favored_percentage_text": string, // (15) "team1": Get5MatchTeam | Get5MatchTeamFromFile, // (20) "team2": Get5MatchTeam | Get5MatchTeamFromFile, // (21) - "cvars": { [key: string]: string } // (22) + "cvars": { [key: string]: string }, // (22) + "clinch_series": boolean // (32) } ``` @@ -124,6 +125,8 @@ interface Get5Match { 31. _Optional_
Determines the starting sides for each map. If this array is shorter than `num_maps`, `side_type` will determine the side-behavior of the remaining maps. Ignored if `skip_veto` is `false`.

**`Default: undefined`** +32. _Optional_
If `false`, the entire map list will be played, regardless of score. If `true`, a series will be won + when the series score for a team exceeds the number of maps divided by two.

**`Default: true`** !!! warning "SteamID64 in `.cfg` files" diff --git a/scripting/get5.sp b/scripting/get5.sp index 86757a327..4786cbc31 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -112,6 +112,7 @@ ConVar g_CoachingEnabledCvar; /** Series config game-state **/ int g_MapsToWin = 1; // Maps needed to win the series. bool g_BO2Match = false; +bool g_SeriesCanClinch = true; int g_RoundNumber = -1; // The round number, 0-indexed. -1 if the match is not live. // The active map number, used by stats. Required as the calculated round number changes immediately // as a map ends, but before the map changes to the next. @@ -1181,17 +1182,18 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast int t1maps = g_TeamSeriesScores[Get5Team_1]; int t2maps = g_TeamSeriesScores[Get5Team_2]; int tiedMaps = g_TeamSeriesScores[Get5Team_None]; + int remainingMaps = g_MapsToPlay.Length - t1maps - t2maps - tiedMaps; float minDelay = float(GetTvDelay()) + MATCH_END_DELAY_AFTER_TV; if (t1maps == t2maps) { - // As long as team scores are equal, we play until there are no maps left. - if (t1maps + t2maps + tiedMaps == g_MapsToPlay.Length) { + // As long as team scores are equal, we play until there are no maps left, regardless of clinch config. + if (remainingMaps <= 0) { SeriesEndMessage(Get5Team_None); DelayFunction(minDelay, EndSeries); return Plugin_Continue; } - } else { + } else if (g_SeriesCanClinch) { // This adjusts for ties! int actualMapsToWin = ((g_MapsToPlay.Length - tiedMaps) / 2) + 1; if (t1maps == actualMapsToWin) { @@ -1205,6 +1207,10 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast DelayFunction(minDelay, EndSeries); return Plugin_Continue; } + } else if (remainingMaps <= 0) { + SeriesEndMessage(t1maps > t2maps ? Get5Team_1 : Get5Team_2); // Tie handled in first if-block + DelayFunction(minDelay, EndSeries); + return Plugin_Continue; } if (t1maps > t2maps) { diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index 59dc1f1fb..81e86dc90 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -109,6 +109,7 @@ static void AddGlobalStateInfo(File f) { f.WriteLine("g_SkipVeto = %d", g_SkipVeto); f.WriteLine("g_MatchSideType = %d", g_MatchSideType); f.WriteLine("g_InScrimMode = %d", g_InScrimMode); + f.WriteLine("g_SeriesCanClinch = %d", g_SeriesCanClinch); f.WriteLine("g_HasKnifeRoundStarted = %d", g_HasKnifeRoundStarted); f.WriteLine("g_MapChangePending = %d", g_MapChangePending); diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 057da6c19..9dab987c6 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -10,6 +10,7 @@ #define CONFIG_SPECTATORSNAME_DEFAULT "casters" #define CONFIG_NUM_MAPSDEFAULT 3 #define CONFIG_SKIPVETO_DEFAULT false +#define CONFIG_CLINCH_SERIES_DEFAULT true #define CONFIG_VETOFIRST_DEFAULT "team1" #define CONFIG_SIDETYPE_DEFAULT "standard" @@ -305,6 +306,7 @@ public void WriteMatchToKv(KeyValues kv) { kv.SetNum("min_players_to_ready", g_MinPlayersToReady); kv.SetNum("min_spectators_to_ready", g_MinSpectatorsToReady); kv.SetString("match_title", g_MatchTitle); + kv.SetNum("clinch_series", g_SeriesCanClinch); kv.SetNum("favored_percentage_team1", g_FavoredTeamPercentage); kv.SetString("favored_percentage_text", g_FavoredTeamText); @@ -380,6 +382,7 @@ static bool LoadMatchFromKv(KeyValues kv) { g_InScrimMode = kv.GetNum("scrim") != 0; kv.GetString("match_title", g_MatchTitle, sizeof(g_MatchTitle), CONFIG_MATCHTITLE_DEFAULT); g_PlayersPerTeam = kv.GetNum("players_per_team", CONFIG_PLAYERSPERTEAM_DEFAULT); + g_SeriesCanClinch = kv.GetNum("clinch_series", CONFIG_CLINCH_SERIES_DEFAULT) != 0; g_CoachesPerTeam = kv.GetNum("coaches_per_team", CONFIG_COACHESPERTEAM_DEFAULT); g_MinPlayersToReady = kv.GetNum("min_players_to_ready", CONFIG_MINPLAYERSTOREADY_DEFAULT); g_MinSpectatorsToReady = @@ -487,6 +490,7 @@ static bool LoadMatchFromJson(JSON_Object json) { json_object_get_string_safe(json, "matchid", g_MatchID, sizeof(g_MatchID), CONFIG_MATCHID_DEFAULT); g_InScrimMode = json_object_get_bool_safe(json, "scrim", false); + g_SeriesCanClinch = json_object_get_bool_safe(json, "clinch_series", true); json_object_get_string_safe(json, "match_title", g_MatchTitle, sizeof(g_MatchTitle), CONFIG_MATCHTITLE_DEFAULT); @@ -1042,6 +1046,7 @@ public Action Command_CreateMatch(int client, int args) { kv.SetNum("maps_to_win", 1); kv.SetNum("skip_veto", 1); kv.SetNum("players_per_team", 5); + kv.SetNum("clinch_series", 1); kv.JumpToKey("maplist", true); kv.SetString(matchMap, KEYVALUE_STRING_PLACEHOLDER); From aa19b4244f9ecc363c26b36c64e5ab70847c971c Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 10 Aug 2022 03:09:57 +0200 Subject: [PATCH 026/104] Only extend mp_match_restart_delay if required - never shorten it Cleaned up EndSeries call Fixed match restart delay to GOTV delay Remove unnecessary g_ForceWinnerSignal and g_ForcedWinner vars Remove duplicate call to Stats_SeriesEnd when forfeiting Ensure Get5_OnDemoFinished is detached from global vars and on a delay for file flush Adjust translation to include time-to-map-change Ensure SetMatchRestartDelay is called after cvars are set, using timers Use datapacks for all backup timer chains, ensuring nothing messes with the parameters Update translations doc + danish Fix mp_match_restart_delay problems related to restoring cvars Cleaned up duplicate code used to start recording, moved all code to recording.sp prevent invalid handle if RestoreCvars is called twice Always reset g_DemoFileName on map start --- documentation/docs/translations.md | 2 +- scripting/get5.sp | 237 +++++++++++++---------------- scripting/get5/backups.sp | 1 - scripting/get5/goinglive.sp | 10 -- scripting/get5/mapveto.sp | 8 +- scripting/get5/matchconfig.sp | 2 - scripting/get5/recording.sp | 124 +++++++++++++++ scripting/get5/stats.sp | 9 +- scripting/get5/util.sp | 50 ------ scripting/include/restorecvars.inc | 4 + translations/da/get5.phrases.txt | 2 +- translations/get5.phrases.txt | 4 +- 12 files changed, 249 insertions(+), 204 deletions(-) create mode 100644 scripting/get5/recording.sp diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index 01036eb48..42ddd94d0 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -122,7 +122,7 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | `TeamWantsToReloadLastRoundInfoMessage` | _Team A_ wants to stop and reload last round, need _Team B_ to confirm with !stop. | Chat | | `TeamWinningSeriesInfoMessage` | _Team A_ is winning the series _2_-_1_. | Chat | | `SeriesTiedInfoMessage` | The series is tied at _1_-_1_. | Chat | -| `NextSeriesMapInfoMessage` | The next map in the series is _de_nuke_. | Chat | +| `NextSeriesMapInfoMessage` | The next map in the series is _de_nuke_ and it will start in _1:30_. | Chat | | `TeamWonMatchInfoMessage` | _Team A_ has won the match. | Chat | | `TeamTiedMatchInfoMessage` | _Team A_ and _Team B_ have tied the match. | Chat | | `TeamWonSeriesInfoMessage` | _Team A_ has won the series _2_-_1_. | Chat | diff --git a/scripting/get5.sp b/scripting/get5.sp index 4786cbc31..22b323294 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -215,9 +215,6 @@ char g_DefaultTeamColors[][] = { char g_LastKickedPlayerAuth[64]; -bool g_ForceWinnerSignal = false; -Get5Team g_ForcedWinner = Get5Team_None; - /** Chat aliases loaded **/ #define ALIAS_LENGTH 64 #define COMMAND_LENGTH 64 @@ -293,6 +290,7 @@ Handle g_OnSidePicked = INVALID_HANDLE; #include "get5/natives.sp" #include "get5/pausing.sp" #include "get5/readysystem.sp" +#include "get5/recording.sp" #include "get5/stats.sp" #include "get5/teamlogic.sp" #include "get5/tests.sp" @@ -625,10 +623,9 @@ public Action Timer_InfoMessages(Handle timer) { Get5_MessageToAll("%t", "ReadyToVetoInfoMessage"); } MissingPlayerInfoMessage(); - } + } else if (g_GameState == Get5State_Warmup && !g_MapChangePending) { - // Handle warmup state, provided we're not waiting for a map change - if (g_GameState == Get5State_Warmup && !g_MapChangePending) { + // Handle warmup state, provided we're not waiting for a map change // Backups take priority if (!IsTeamsReady() && g_WaitingForRoundBackup) { Get5_MessageToAll("%t", "ReadyToRestoreBackupInfoMessage"); @@ -648,18 +645,13 @@ public Action Timer_InfoMessages(Handle timer) { } MissingPlayerInfoMessage(); } else if (g_DisplayGotvVetoCvar.BoolValue && g_GameState == Get5State_Warmup && - g_MapChangePending) { + g_MapChangePending && GetTvDelay() > 0) { Get5_MessageToAll("%t", "WaitingForGOTVVetoInfoMessage"); - } - - // Handle waiting for knife decision - if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { - Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", - g_FormattedTeamNames[g_KnifeWinnerTeam]); - } - - // Handle postgame - if (g_GameState == Get5State_PostGame) { + } else if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { + // Handle waiting for knife decision + Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); + } else if (g_GameState == Get5State_PostGame && GetTvDelay() > 0) { + // Handle postgame Get5_MessageToAll("%t", "WaitingForGOTVBrodcastEndingInfoMessage"); } @@ -787,12 +779,17 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr g_GameState < Get5State_PostGame && GetRealClientCount() == 0 && !g_MapChangePending) { g_TeamSeriesScores[Get5Team_1] = 0; g_TeamSeriesScores[Get5Team_2] = 0; - EndSeries(); + StopRecording(true); + EndSeries(Get5Team_None, false); } } public void OnMapStart() { g_MapChangePending = false; + // Recording is always automatically stopped on map change, and + // since there are no hooks to detect tv_stoprecord, we reset + // our recording var if a map change is performed unexpectedly. + g_DemoFileName = ""; DeleteOldBackups(); ResetReadyStatus(); @@ -801,10 +798,11 @@ public void OnMapStart() { g_TeamReadyForUnpause[team] = false; g_TacticalPauseTimeUsed[team] = 0; g_TacticalPausesUsed[team] = 0; - g_ReadyTimeWaitingUsed = 0; g_TechnicalPausesUsed[team] = 0; } + g_ReadyTimeWaitingUsed = 0; + if (g_WaitingForRoundBackup) { ChangeState(Get5State_Warmup); ExecCfg(g_LiveCfgCvar); @@ -892,21 +890,18 @@ static void CheckReadyWaitingTimes() { bool team1Forfeited = CheckReadyWaitingTime(Get5Team_1); bool team2Forfeited = CheckReadyWaitingTime(Get5Team_2); + if (team1Forfeited || team2Forfeited) { + Stats_Forfeit(); + StopRecording(true); + } + + // False for printing in all these cases, as CheckReadyWaitingTime prints forfeit messages. if (team1Forfeited && team2Forfeited) { - g_ForcedWinner = Get5Team_None; - Stats_Forfeit(Get5Team_None); + EndSeries(Get5Team_None, false); } else if (team1Forfeited) { - g_ForcedWinner = Get5Team_2; - Stats_Forfeit(Get5Team_1); + EndSeries(Get5Team_2, false); } else if (team2Forfeited) { - g_ForcedWinner = Get5Team_1; - Stats_Forfeit(Get5Team_2); - } - - if (team1Forfeited || team2Forfeited) { - g_ForceWinnerSignal = true; - ChangeState(Get5State_None); - EndSeries(); + EndSeries(Get5Team_1, false); } } } @@ -958,32 +953,19 @@ public Action Command_EndMatch(int client, int args) { Get5MapResultEvent mapResultEvent = new Get5MapResultEvent( g_MatchID, g_MapNumber, new Get5Winner(Get5Team_None, Get5Side_None), team1score, team2score); - LogDebug("Calling Get5_OnMapResult()"); - Call_StartForward(g_OnMapResult); Call_PushCell(mapResultEvent); Call_Finish(); - EventLogger_LogAndDeleteEvent(mapResultEvent); - Get5SeriesResultEvent resultEvent = - new Get5SeriesResultEvent(g_MatchID, new Get5Winner(Get5Team_None, Get5Side_None), - g_TeamSeriesScores[Get5Team_1], g_TeamSeriesScores[Get5Team_2]); + // Don't print series result here as admin force-end is printed below. + StopRecording(true); + EndSeries(Get5Team_None, false); - LogDebug("Calling Get5_OnSeriesResult()"); - Call_StartForward(g_OnSeriesResult); - Call_PushCell(resultEvent); - Call_Finish(); - - EventLogger_LogAndDeleteEvent(resultEvent); - - ChangeState(Get5State_None); UpdateClanTags(); Get5_MessageToAll("%t", "AdminForceEndInfoMessage"); - RestoreCvars(g_MatchConfigChangedCvars); - StopRecording(); if (g_ActiveVetoMenu != null) { g_ActiveVetoMenu.Cancel(); @@ -1135,6 +1117,22 @@ public Action Timer_ReplenishMoney(Handle timer, int client) { public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_MatchOver"); + if (g_GameState == Get5State_None) { + return Plugin_Continue; + } + + // This ensures that the mp_match_restart_delay is not shorter + // than what is required for the GOTV recording to finish. + ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay"); + if (mp_match_restart_delay != INVALID_HANDLE) { + int requiredDelay = GetTvDelay() + MATCH_END_DELAY_AFTER_TV; + if (requiredDelay > mp_match_restart_delay.IntValue) { + LogDebug("Extended mp_match_restart_delay from %d to %d to ensure GOTV can finish recording.", + mp_match_restart_delay.IntValue, requiredDelay); + mp_match_restart_delay.IntValue = requiredDelay; + } + } + if (g_GameState == Get5State_Live) { // If someone called for a pause in the last round; cancel it. @@ -1184,13 +1182,16 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast int tiedMaps = g_TeamSeriesScores[Get5Team_None]; int remainingMaps = g_MapsToPlay.Length - t1maps - t2maps - tiedMaps; - float minDelay = float(GetTvDelay()) + MATCH_END_DELAY_AFTER_TV; + // Stops recording after GOTV has ended. + // mp_match_restart_delay is always of a longer duration than GOTV delay, so the recording + // **will** finish before the map changes, regardless if a next map is pending or if the + // series ends here. + StopRecording(); if (t1maps == t2maps) { // As long as team scores are equal, we play until there are no maps left, regardless of clinch config. if (remainingMaps <= 0) { - SeriesEndMessage(Get5Team_None); - DelayFunction(minDelay, EndSeries); + EndSeries(Get5Team_None, true); return Plugin_Continue; } } else if (g_SeriesCanClinch) { @@ -1198,18 +1199,15 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast int actualMapsToWin = ((g_MapsToPlay.Length - tiedMaps) / 2) + 1; if (t1maps == actualMapsToWin) { // Team 1 won - SeriesEndMessage(Get5Team_1); - DelayFunction(minDelay, EndSeries); + EndSeries(Get5Team_1, true); return Plugin_Continue; } else if (t2maps == actualMapsToWin) { // Team 2 won - SeriesEndMessage(Get5Team_2); - DelayFunction(minDelay, EndSeries); + EndSeries(Get5Team_2, true); return Plugin_Continue; } } else if (remainingMaps <= 0) { - SeriesEndMessage(t1maps > t2maps ? Get5Team_1 : Get5Team_2); // Tie handled in first if-block - DelayFunction(minDelay, EndSeries); + EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true); // Tie handled in first if-block return Plugin_Continue; } @@ -1228,78 +1226,62 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast char nextMap[PLATFORM_MAX_PATH]; g_MapsToPlay.GetString(Get5_GetMapNumber(), nextMap, sizeof(nextMap)); + float restartDelay = FindConVar("mp_match_restart_delay").FloatValue; + + char timeToMapChangeFormatted[8]; + convertSecondsToMinutesAndSeconds(RoundToFloor(restartDelay), timeToMapChangeFormatted, sizeof(timeToMapChangeFormatted)); + g_MapChangePending = true; - Get5_MessageToAll("%t", "NextSeriesMapInfoMessage", nextMap); + Get5_MessageToAll("%t", "NextSeriesMapInfoMessage", nextMap, timeToMapChangeFormatted); ChangeState(Get5State_PostGame); - CreateTimer(minDelay, Timer_NextMatchMap); + // Subtracting 4 seconds makes the map change 1 second before the timer expires, as there is a 3 second built-in + // delay in the ChangeMap function called by Timer_NextMatchMap. + CreateTimer(restartDelay - 4, Timer_NextMatchMap); } return Plugin_Continue; } -static void SeriesEndMessage(Get5Team team) { - if (team == Get5Team_None) { - Get5_MessageToAll("%t", "TeamTiedMatchInfoMessage", g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2]); - } else { - if (g_MapsToPlay.Length == 1) { - Get5_MessageToAll("%t", "TeamWonMatchInfoMessage", g_FormattedTeamNames[team]); - } else { - Get5_MessageToAll("%t", "TeamWonSeriesInfoMessage", g_FormattedTeamNames[team], g_TeamSeriesScores[team], g_TeamSeriesScores[OtherMatchTeam(team)]); - } - } -} - public Action Timer_NextMatchMap(Handle timer) { - if (g_GameState >= Get5State_Live) - StopRecording(); - - int index = Get5_GetMapNumber(); char map[PLATFORM_MAX_PATH]; - g_MapsToPlay.GetString(index, map, sizeof(map)); - - if (!g_SkipVeto && g_DisplayGotvVetoCvar.BoolValue && index == 0) { - float minDelay = float(GetTvDelay()) + MATCH_END_DELAY_AFTER_TV; - ChangeMap(map, minDelay); - } else { - ChangeMap(map); - } + g_MapsToPlay.GetString(Get5_GetMapNumber(), map, sizeof(map)); + // If you change these 3 seconds for whatever reason, you must adjust the counter-offset in Event_MatchOver. + ChangeMap(map, 3.0); } public void KickClientsOnEnd() { - if (g_KickClientsWithNoMatchCvar.BoolValue) { - LOOP_CLIENTS(i) { - if (IsPlayer(i) && !(g_KickClientImmunityCvar.BoolValue && - CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) { - KickClient(i, "%t", "MatchFinishedInfoMessage"); - } + LOOP_CLIENTS(i) { + if (IsPlayer(i) && !(g_KickClientImmunityCvar.BoolValue && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) { + KickClient(i, "%t", "MatchFinishedInfoMessage"); } } } -public void EndSeries() { - DelayFunction(10.0, KickClientsOnEnd); - StopRecording(); - - // Figure out who won - int t1maps = g_TeamSeriesScores[Get5Team_1]; - int t2maps = g_TeamSeriesScores[Get5Team_2]; - - Get5Team winningTeam = Get5Team_None; - if (t1maps > t2maps) { - winningTeam = Get5Team_1; - } else if (t2maps > t1maps) { - winningTeam = Get5Team_2; - } - - if (g_ForceWinnerSignal) { - winningTeam = g_ForcedWinner; +public void EndSeries(Get5Team winningTeam, bool printWinnerMessage) { + if (g_KickClientsWithNoMatchCvar.BoolValue) { + DelayFunction(10.0, KickClientsOnEnd); } Stats_SeriesEnd(winningTeam); + if (printWinnerMessage) { + if (winningTeam == Get5Team_None) { + Get5_MessageToAll("%t", "TeamTiedMatchInfoMessage", g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2]); + } else { + if (g_MapsToPlay.Length == 1) { + Get5_MessageToAll("%t", "TeamWonMatchInfoMessage", g_FormattedTeamNames[winningTeam]); + } else { + Get5_MessageToAll("%t", "TeamWonSeriesInfoMessage", g_FormattedTeamNames[winningTeam], g_TeamSeriesScores[winningTeam], g_TeamSeriesScores[OtherMatchTeam(winningTeam)]); + } + } + } + Get5SeriesResultEvent event = new Get5SeriesResultEvent( - g_MatchID, new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), - t1maps, t2maps); + g_MatchID, + new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), + g_TeamSeriesScores[Get5Team_1], + g_TeamSeriesScores[Get5Team_2] + ); LogDebug("Calling Get5_OnSeriesResult()"); @@ -1308,9 +1290,24 @@ public void EndSeries() { Call_Finish(); EventLogger_LogAndDeleteEvent(event); - - RestoreCvars(g_MatchConfigChangedCvars); ChangeState(Get5State_None); + + // We need to restore cvars on a timer if GOTV is recording, as it might otherwise set mp_match_restart_delay + // "back" to something that's shorter than the GOTV delay, which is a problem. + int goTvDelay = GetTvDelay(); + if (goTvDelay > 0) { + CreateTimer(float(goTvDelay) + MATCH_END_DELAY_AFTER_TV, Timer_RestoreCvars); + } else { + RestoreCvars(g_MatchConfigChangedCvars); + } +} + +public Action Timer_RestoreCvars(Handle timer) { + if (g_GameState == Get5State_None) { + // Only reset if no game is running, otherwise a game started before the GOTV for another ends will mess this up. + RestoreCvars(g_MatchConfigChangedCvars); + } + return Plugin_Handled; } public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroadcast) { @@ -1341,7 +1338,7 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad // Round number always -1 if not live. g_RoundNumber = g_GameState != Get5State_Live ? -1 : GetRoundsPlayed(); - if (g_GameState >= Get5State_Warmup && !g_DoingBackupRestoreNow) { + if (g_GameState >= Get5State_Warmup) { WriteBackup(); } } @@ -1399,7 +1396,7 @@ static bool CreateBackupFolderStructure(const char[] path) { } public void WriteBackup() { - if (!g_BackupSystemEnabledCvar.BoolValue || g_DoingBackupRestoreNow) { + if (!g_BackupSystemEnabledCvar.BoolValue || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { return; } @@ -1610,20 +1607,9 @@ public Action Event_CvarChanged(Event event, const char[] name, bool dontBroadca public void StartGame(bool knifeRound) { LogDebug("StartGame"); - if (!IsTVEnabled()) { - LogMessage("GOTV demo could not be recorded since tv_enable is not set to 1"); - g_DemoFileName = ""; - } else { - char demoName[PLATFORM_MAX_PATH + 1]; - if (FormatCvarString(g_DemoNameFormatCvar, demoName, sizeof(demoName)) && Record(demoName)) { - Format(g_DemoFileName, sizeof(g_DemoFileName), "%s.dem", demoName); - LogMessage("Recording to %s", g_DemoFileName); - } else { - g_DemoFileName = ""; - } - } ExecCfg(g_LiveCfgCvar); + StartRecording(); if (knifeRound) { LogDebug("StartGame: about to begin knife round"); @@ -1649,11 +1635,6 @@ public Action Timer_PostKnife(Handle timer) { EnsureIndefiniteWarmup(); } -public Action StopDemo(Handle timer) { - StopRecording(); - return Plugin_Handled; -} - public void ChangeState(Get5State state) { g_GameStateCvar.IntValue = view_as(state); diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 8c8c14ace..323bf23dc 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -352,7 +352,6 @@ public void RestoreGet5Backup() { ChangeState(Get5State_Live); SetMatchTeamCvars(); ExecuteMatchConfigCvars(); - SetMatchRestartDelay(); // There are some timing issues leading to incorrect score when restoring matches in second // half. Doing the restore on a timer diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index 763217967..172c9bfdd 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -1,4 +1,3 @@ -/** Begins the LO3 process. **/ public Action StartGoingLive(Handle timer) { LogDebug("StartGoingLive"); ExecCfg(g_LiveCfgCvar); @@ -46,10 +45,7 @@ public Action MatchLive(Handle timer) { // to be sure. SetMatchTeamCvars(); ExecuteMatchConfigCvars(); - - // We force the match end-delay to extend for the duration of the GOTV broadcast here. g_PendingSideSwap = false; - SetMatchRestartDelay(); for (int i = 0; i < 5; i++) { Get5_MessageToAll("%t", "MatchIsLiveInfoMessage"); @@ -75,9 +71,3 @@ public Action MatchLive(Handle timer) { return Plugin_Handled; } - -public void SetMatchRestartDelay() { - ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay"); - int delay = GetTvDelay() + MATCH_END_DELAY_AFTER_TV + 5; - SetConVarInt(mp_match_restart_delay, delay); -} diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index d86800f69..be043e73a 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -58,7 +58,13 @@ public void VetoFinished() { } g_MapChangePending = true; - CreateTimer(10.0, Timer_NextMatchMap); + if (!g_SkipVeto && g_DisplayGotvVetoCvar.BoolValue) { + float minDelay = float(GetTvDelay()) + MATCH_END_DELAY_AFTER_TV; + StopRecording(); // stops recording after GetTvDelay() seconds. + CreateTimer(minDelay, Timer_NextMatchMap); + } else { + CreateTimer(10.0, Timer_NextMatchMap); + } } // Main Veto Controller diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 9dab987c6..e2ad068d8 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -37,8 +37,6 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } g_ReadyTimeWaitingUsed = 0; - g_ForceWinnerSignal = false; - g_ForcedWinner = Get5Team_None; g_MapNumber = 0; g_RoundNumber = -1; diff --git a/scripting/get5/recording.sp b/scripting/get5/recording.sp new file mode 100644 index 000000000..1b7d07061 --- /dev/null +++ b/scripting/get5/recording.sp @@ -0,0 +1,124 @@ +stock bool StartRecording() { + if (!IsTVEnabled()) { + LogError("Demo recording will not work with \"tv_enable 0\". Set \"tv_enable 1\" and restart the map to fix this."); + g_DemoFileName = ""; + return false; + } + + char demoName[PLATFORM_MAX_PATH + 1]; + if (!FormatCvarString(g_DemoNameFormatCvar, demoName, sizeof(demoName))) { + LogError("Failed to format demo filename. Please check your demo file format convar."); + g_DemoFileName = ""; + return false; + } + + Format(g_DemoFileName, sizeof(g_DemoFileName), "%s.dem", demoName); + LogMessage("Recording to %s", g_DemoFileName); + + // Escape unsafe characters and start recording. + char szDemoName[PLATFORM_MAX_PATH + 1]; + strcopy(szDemoName, sizeof(szDemoName), demoName); + ReplaceString(szDemoName, sizeof(szDemoName), "\"", "\\\""); + ServerCommand("tv_record \"%s\"", szDemoName); + return true; +} + +stock void StopRecording(bool forceStop = false) { + if (!IsTVEnabled()) { + LogDebug("Cannot stop recording as GOTV is not enabled."); + return; + } + if (forceStop) { + LogDebug("Ending GOTV recording immediately by force."); + StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName); + return; + } + int tvDelay = GetTvDelay(); + if (tvDelay > 0) { + LogDebug("Starting timer that will end GOTV recording in %d seconds.", tvDelay); + DataPack pack = CreateDataPack(); + pack.WriteString(g_MatchID); + pack.WriteCell(g_MapNumber); + pack.WriteString(g_DemoFileName); + CreateTimer(float(tvDelay), Timer_StopGoTVRecording, pack, TIMER_FLAG_NO_MAPCHANGE); // changemap ends recording, so the timer cannot carry over. + } else { + LogDebug("Ending GOTV recording immediately as tv_delay is 0."); + StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName); + } +} + +static void StopRecordingCallback(char[] matchId, int mapNumber, char[] demoFileName) { + ServerCommand("tv_stoprecord"); + if (StrEqual("", demoFileName)) { + LogDebug("Demo was not recorded by Get5; not firing Get5_OnDemoFinished()"); + return; + } + + // We delay this by 3 seconds to allow the server to flush to the file before firing the event. + // This requires a pack with the data, as the map might change and stuff might happen after the + // tv_delay has expired. This would also allow us to extend this delay later without breaking anything. + DataPack pack = CreateDataPack(); + pack.WriteString(matchId); + pack.WriteCell(mapNumber); + pack.WriteString(demoFileName); + + CreateTimer(3.0, Timer_FireStopRecordingEvent, pack); +} + +public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { + + char matchId[MATCH_ID_LENGTH]; + char demoFileName[PLATFORM_MAX_PATH]; + pack.Reset(); + pack.ReadString(matchId, sizeof(matchId)); + int mapNumber = pack.ReadCell(); + pack.ReadString(demoFileName, sizeof(demoFileName)); + delete pack; + + StopRecordingCallback(matchId, mapNumber, demoFileName); + return Plugin_Handled; +} + +public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) { + + char matchId[MATCH_ID_LENGTH]; + char demoFileName[PLATFORM_MAX_PATH]; + pack.Reset(); + pack.ReadString(matchId, sizeof(matchId)); + int mapNumber = pack.ReadCell(); + pack.ReadString(demoFileName, sizeof(demoFileName)); + delete pack; + + Get5DemoFinishedEvent event = new Get5DemoFinishedEvent(matchId, mapNumber, demoFileName); + LogDebug("Calling Get5_OnDemoFinished()"); + Call_StartForward(g_OnDemoFinished); + Call_PushCell(event); + Call_Finish(); + EventLogger_LogAndDeleteEvent(event); + return Plugin_Handled; +} + +stock bool IsTVEnabled() { + ConVar tvEnabledCvar = FindConVar("tv_enable"); + if (tvEnabledCvar == null) { + LogError("Failed to get tv_enable cvar"); + return false; + } + if (tvEnabledCvar.BoolValue) { + // GOTV can be enabled without the bot actually running; map restart is + // required, so it might be disabled in edge-cases. + LOOP_CLIENTS(i) { + if (IsClientConnected(i) && IsClientSourceTV(i)) { + return true; + } + } + } + return false; +} + +stock int GetTvDelay() { + if (IsTVEnabled()) { + return GetCvarIntSafe("tv_delay"); + } + return 0; +} diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 81c982029..56240492c 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -341,15 +341,8 @@ public void Stats_UpdateMapScore(Get5Team winner) { DumpToFile(); } -public void Stats_Forfeit(Get5Team team) { +public void Stats_Forfeit() { g_StatsKv.SetNum(STAT_SERIES_FORFEIT, 1); - if (team == Get5Team_1) { - Stats_SeriesEnd(Get5Team_2); - } else if (team == Get5Team_2) { - Stats_SeriesEnd(Get5Team_1); - } else { - Stats_SeriesEnd(Get5Team_None); - } } public void Stats_SeriesEnd(Get5Team winner) { diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index e4c5d2935..efd5d705c 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -196,56 +196,6 @@ stock void ReplaceStringWithInt(char[] buffer, int len, const char[] replace, in ReplaceString(buffer, len, replace, intString, caseSensitive); } -stock bool IsTVEnabled() { - ConVar tvEnabledCvar = FindConVar("tv_enable"); - if (tvEnabledCvar == null) { - LogError("Failed to get tv_enable cvar"); - return false; - } - return tvEnabledCvar.BoolValue; -} - -stock int GetTvDelay() { - if (IsTVEnabled()) { - return GetCvarIntSafe("tv_delay"); - } - return 0; -} - -stock bool Record(const char[] demoName) { - char szDemoName[256]; - strcopy(szDemoName, sizeof(szDemoName), demoName); - ReplaceString(szDemoName, sizeof(szDemoName), "\"", "\\\""); - ServerCommand("tv_record \"%s\"", szDemoName); - - if (!IsTVEnabled()) { - LogError( - "Autorecording will not work with current cvar \"tv_enable\"=0. Set \"tv_enable 1\" in server.cfg (or another config file) to fix this."); - return false; - } - - return true; -} - -stock void StopRecording() { - ServerCommand("tv_stoprecord"); - - if (StrEqual("", g_DemoFileName, true)) { - // Demo not recorded; don't fire demo finish event. - return; - } - - Get5DemoFinishedEvent event = new Get5DemoFinishedEvent(g_MatchID, g_MapNumber, g_DemoFileName); - - LogDebug("Calling Get5_OnDemoFinished()"); - - Call_StartForward(g_OnDemoFinished); - Call_PushCell(event); - Call_Finish(); - - EventLogger_LogAndDeleteEvent(event); -} - stock bool InWarmup() { return GameRules_GetProp("m_bWarmupPeriod") != 0; } diff --git a/scripting/include/restorecvars.inc b/scripting/include/restorecvars.inc index ae3e16e5c..d197e2024 100644 --- a/scripting/include/restorecvars.inc +++ b/scripting/include/restorecvars.inc @@ -27,6 +27,10 @@ stock Handle SaveCvars(ArrayList cvarNames) { // Restores cvars to their previous value using a return value of SaveCvars. stock void RestoreCvars(Handle& cvarStorage, bool close = true) { + LogDebug("Restoring match cvars."); + if (cvarStorage == INVALID_HANDLE) { + return; + } ArrayList cvarNameList = view_as(GetArrayCell(cvarStorage, 0)); ArrayList cvarValueList = view_as(GetArrayCell(cvarStorage, 1)); diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 02f798e30..08c58d0cd 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -226,7 +226,7 @@ } "NextSeriesMapInfoMessage" { - "da" "Det næste map i serien er {GREEN}{1}." + "da" "Det næste map i serien er {GREEN}{1}{NORMAL} og det starter om {2}." } "TeamWonMatchInfoMessage" { diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 01096c945..285b88558 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -264,8 +264,8 @@ } "NextSeriesMapInfoMessage" { - "#format" "{1:s}" - "en" "The next map in the series is {GREEN}{1}{NORMAL}." + "#format" "{1:s},{2:s}" + "en" "The next map in the series is {GREEN}{1}{NORMAL} and it will start in {2}." } "TeamWonMatchInfoMessage" { From 33b8729d3502b83f4c62c0383429bde29d1fbf1b Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 12 Aug 2022 15:23:44 +0200 Subject: [PATCH 027/104] Add option to force-win for a team using get5_endmatch Don't wait for gotv when restoring cvars when force-ending a match ensure endmatch unpauses the game --- documentation/docs/commands.md | 5 +-- documentation/docs/translations.md | 11 +++--- scripting/get5.sp | 58 +++++++++++++++++++++++------- translations/chi/get5.phrases.txt | 2 +- translations/da/get5.phrases.txt | 2 +- translations/de/get5.phrases.txt | 2 +- translations/es/get5.phrases.txt | 2 +- translations/fr/get5.phrases.txt | 2 +- translations/get5.phrases.txt | 25 +++++++------ translations/pl/get5.phrases.txt | 2 +- translations/pt/get5.phrases.txt | 2 +- translations/ru/get5.phrases.txt | 2 +- 12 files changed, 77 insertions(+), 38 deletions(-) diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 945589f27..528abc076 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -94,8 +94,9 @@ You should put the `url` argument inside quotation marks (`""`). Loading remote matches requires the [SteamWorks](../installation/#steamworks) extension. -####`get5_endmatch` -: Force ends the current match. No winner is set (draw). +####`get5_endmatch [team1|team2]` +: Force-ends the current match. The team argument will force the winner of the series and the current map to be set +to that team. Omitting the team argument sets no winner (tie). ####`get5_creatematch [map name] [matchid]` {: #get5_creatematch } : Creates a BO1 match with the current players on the server. `map name` defaults to the current map and `matchid` diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index 42ddd94d0..960a9ab38 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -113,13 +113,14 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | `TeamReadyToKnifeInfoMessage` | _Team A_ is ready to knife for sides. | Chat | | `TeamReadyToBeginInfoMessage` | _Team A_ is ready to begin the match. | Chat | | `TeamNotReadyInfoMessage` | _Team A_ is no longer ready. | Chat | -| `ForceReadyInfoMessage` | You may type !forceready to force ready your team if you have less than _5_ players. | Chat | +| `ForceReadyInfoMessage` | You may type !forceready to force-ready your team if you have less than _5_ players. | Chat | | `TeammateForceReadied` | Your team was force-readied by _PlayerName_. | Chat | | `AdminForceReadyInfoMessage` | An admin has force-readied all teams. | Chat | -| `AdminForceEndInfoMessage` | An admin force ended the match. | Chat | -| `AdminForcePauseInfoMessage` | An admin force paused the match. | Chat | -| `AdminForceUnPauseInfoMessage` | An admin force unpaused the match. | Chat | -| `TeamWantsToReloadLastRoundInfoMessage` | _Team A_ wants to stop and reload last round, need _Team B_ to confirm with !stop. | Chat | +| `AdminForceEndInfoMessage` | An admin force-ended the match. | Chat | +| `AdminForceEndWithWinnerInfoMessage` | An admin force-ended the match, setting _Team 1_ as the winner. | Chat | +| `AdminForcePauseInfoMessage` | An admin force-paused the match. | Chat | +| `AdminForceUnPauseInfoMessage` | An admin unpaused the match. | Chat | +| `TeamWantsToReloadLastRoundInfoMessage` | _Team A_ wants to stop and reload last round. _Team B_ must confirm with !stop. | Chat | | `TeamWinningSeriesInfoMessage` | _Team A_ is winning the series _2_-_1_. | Chat | | `SeriesTiedInfoMessage` | The series is tied at _1_-_1_. | Chat | | `NextSeriesMapInfoMessage` | The next map in the series is _de_nuke_ and it will start in _1:30_. | Chat | diff --git a/scripting/get5.sp b/scripting/get5.sp index 22b323294..22674bd88 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -780,7 +780,7 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr g_TeamSeriesScores[Get5Team_1] = 0; g_TeamSeriesScores[Get5Team_2] = 0; StopRecording(true); - EndSeries(Get5Team_None, false); + EndSeries(Get5Team_None, false, false); } } @@ -897,11 +897,11 @@ static void CheckReadyWaitingTimes() { // False for printing in all these cases, as CheckReadyWaitingTime prints forfeit messages. if (team1Forfeited && team2Forfeited) { - EndSeries(Get5Team_None, false); + EndSeries(Get5Team_None, false, false); } else if (team1Forfeited) { - EndSeries(Get5Team_2, false); + EndSeries(Get5Team_2, false, false); } else if (team2Forfeited) { - EndSeries(Get5Team_1, false); + EndSeries(Get5Team_1, false, false); } } } @@ -943,16 +943,41 @@ static void CheckAutoLoadConfig() { public Action Command_EndMatch(int client, int args) { if (g_GameState == Get5State_None) { + ReplyToCommand(client, "No match is configured; nothing to end."); return Plugin_Handled; } + Get5Team winningTeam = Get5Team_None; // defaults to tie + if (args >= 1) { + char forcedWinningTeam[8]; + GetCmdArg(1, forcedWinningTeam, sizeof(forcedWinningTeam)); + if (StrEqual("team1", forcedWinningTeam, false)) { + winningTeam = Get5Team_1; + } else if (StrEqual("team2", forcedWinningTeam, false)) { + winningTeam = Get5Team_2; + } else { + ReplyToCommand(client, "Usage: get5_endmatch (omit team for tie)"); + return Plugin_Handled; + } + } + + if (IsPaused()) { + UnpauseGame(Get5Team_None); + } + // Call game-ending forwards. g_MapChangePending = false; int team1score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)); int team2score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)); Get5MapResultEvent mapResultEvent = new Get5MapResultEvent( - g_MatchID, g_MapNumber, new Get5Winner(Get5Team_None, Get5Side_None), team1score, team2score); + g_MatchID, + g_MapNumber, + new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), + team1score, + team2score + ); + LogDebug("Calling Get5_OnMapResult()"); Call_StartForward(g_OnMapResult); Call_PushCell(mapResultEvent); @@ -960,17 +985,24 @@ public Action Command_EndMatch(int client, int args) { EventLogger_LogAndDeleteEvent(mapResultEvent); // Don't print series result here as admin force-end is printed below. + // We force-end the recording as the actual game is not ended, so there is no round restart to match the recording time to. StopRecording(true); - EndSeries(Get5Team_None, false); + EndSeries(winningTeam, false, false); UpdateClanTags(); - Get5_MessageToAll("%t", "AdminForceEndInfoMessage"); + if (winningTeam == Get5Team_None) { + Get5_MessageToAll("%t", "AdminForceEndInfoMessage"); + } else { + Get5_MessageToAll("%t", "AdminForceEndWithWinnerInfoMessage", g_FormattedTeamNames[winningTeam]); + } if (g_ActiveVetoMenu != null) { g_ActiveVetoMenu.Cancel(); } + ServerCommand("mp_restartgame 1"); + return Plugin_Handled; } @@ -1191,7 +1223,7 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast if (t1maps == t2maps) { // As long as team scores are equal, we play until there are no maps left, regardless of clinch config. if (remainingMaps <= 0) { - EndSeries(Get5Team_None, true); + EndSeries(Get5Team_None, true, true); return Plugin_Continue; } } else if (g_SeriesCanClinch) { @@ -1199,15 +1231,15 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast int actualMapsToWin = ((g_MapsToPlay.Length - tiedMaps) / 2) + 1; if (t1maps == actualMapsToWin) { // Team 1 won - EndSeries(Get5Team_1, true); + EndSeries(Get5Team_1, true, true); return Plugin_Continue; } else if (t2maps == actualMapsToWin) { // Team 2 won - EndSeries(Get5Team_2, true); + EndSeries(Get5Team_2, true, true); return Plugin_Continue; } } else if (remainingMaps <= 0) { - EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true); // Tie handled in first if-block + EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, true); // Tie handled in first if-block return Plugin_Continue; } @@ -1257,7 +1289,7 @@ public void KickClientsOnEnd() { } } -public void EndSeries(Get5Team winningTeam, bool printWinnerMessage) { +public void EndSeries(Get5Team winningTeam, bool printWinnerMessage, bool waitForGoTv) { if (g_KickClientsWithNoMatchCvar.BoolValue) { DelayFunction(10.0, KickClientsOnEnd); } @@ -1295,7 +1327,7 @@ public void EndSeries(Get5Team winningTeam, bool printWinnerMessage) { // We need to restore cvars on a timer if GOTV is recording, as it might otherwise set mp_match_restart_delay // "back" to something that's shorter than the GOTV delay, which is a problem. int goTvDelay = GetTvDelay(); - if (goTvDelay > 0) { + if (goTvDelay > 0 && waitForGoTv) { CreateTimer(float(goTvDelay) + MATCH_END_DELAY_AFTER_TV, Timer_RestoreCvars); } else { RestoreCvars(g_MatchConfigChangedCvars); diff --git a/translations/chi/get5.phrases.txt b/translations/chi/get5.phrases.txt index bfc28263e..07d962338 100644 --- a/translations/chi/get5.phrases.txt +++ b/translations/chi/get5.phrases.txt @@ -150,7 +150,7 @@ } "TeamWinningSeriesInfoMessage" { - "chi" "{1}{NORMAL}在这系列比赛中正处于领先状态 {2}-{3}" + "chi" "{1}在这系列比赛中正处于领先状态 {2}-{3}" } "SeriesTiedInfoMessage" { diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 08c58d0cd..0d75f6c8b 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -218,7 +218,7 @@ } "TeamWinningSeriesInfoMessage" { - "da" "{1}{NORMAL} fører serien {2}-{3}." + "da" "{1} fører serien {2}-{3}." } "SeriesTiedInfoMessage" { diff --git a/translations/de/get5.phrases.txt b/translations/de/get5.phrases.txt index b1212599f..cb9a9e687 100644 --- a/translations/de/get5.phrases.txt +++ b/translations/de/get5.phrases.txt @@ -126,7 +126,7 @@ } "TeamWinningSeriesInfoMessage" { - "de" "{1}{NORMAL} gewinnt die Serie {2}-{3}" + "de" "{1} gewinnt die Serie {2}-{3}" } "SeriesTiedInfoMessage" { diff --git a/translations/es/get5.phrases.txt b/translations/es/get5.phrases.txt index c4116f47c..c484c13fe 100644 --- a/translations/es/get5.phrases.txt +++ b/translations/es/get5.phrases.txt @@ -154,7 +154,7 @@ } "TeamWinningSeriesInfoMessage" { - "es" "{1}{NORMAL} ganó las series {2}-{3}" + "es" "{1} ganó las series {2}-{3}" } "SeriesTiedInfoMessage" { diff --git a/translations/fr/get5.phrases.txt b/translations/fr/get5.phrases.txt index 8cdbd2ac2..28f76234d 100644 --- a/translations/fr/get5.phrases.txt +++ b/translations/fr/get5.phrases.txt @@ -154,7 +154,7 @@ } "TeamWinningSeriesInfoMessage" { - "fr" "{1}{NORMAL} gagne la série avec {2}-{3}" + "fr" "{1} gagne la série avec {2}-{3}" } "SeriesTiedInfoMessage" { diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 285b88558..87bdc863a 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -224,7 +224,7 @@ "ForceReadyInfoMessage" { "#format" "{1:d}" - "en" "You may type {GREEN}!forceready {NORMAL}to force ready your team if you have less than {GREEN}{1}{NORMAL} players." + "en" "You may type {GREEN}!forceready{NORMAL} to force-ready your team if you have less than {GREEN}{1}{NORMAL} players." } "TeammateForceReadied" { @@ -237,25 +237,30 @@ } "AdminForceEndInfoMessage" { - "en" "An admin force ended the match." + "en" "An admin force-ended the match." } "AdminForcePauseInfoMessage" { - "en" "An admin force paused the match." + "en" "An admin paused the match." + } + "AdminForceEndWithWinnerInfoMessage" + { + "#format" "{1:s}" + "en" "An admin force-ended the match, setting {1} as the winner." } "AdminForceUnPauseInfoMessage" { - "en" "An admin force unpaused the match." + "en" "An admin unpaused the match." } "TeamWantsToReloadLastRoundInfoMessage" { "#format" "{1:s},{2:s}" - "en" "{1} wants to stop and reload last round, need {2} to confirm with !stop." + "en" "{1} wants to stop and reload last round. {2} must confirm with !stop." } "TeamWinningSeriesInfoMessage" { "#format" "{1:s},{2:d},{3:d}" - "en" "{1}{NORMAL} is winning the series {2}-{3}." + "en" "{1} is winning the series {2}-{3}." } "SeriesTiedInfoMessage" { @@ -344,12 +349,12 @@ "TeamPickedMapInfoMessage" { "#format" "{1:s},{2:s},{3:d}" - "en" "{1} picked {GREEN}{2} {NORMAL}as map {3}." + "en" "{1} picked {GREEN}{2}{NORMAL} as map {3}." } "TeamSelectSideInfoMessage" { "#format" "{1:s},{2:s},{3:s}" - "en" "{1} has selected to start on {GREEN}{2} {NORMAL}on {3}." + "en" "{1} has selected to start on {GREEN}{2}{NORMAL} on {3}." } "TeamVetoedMapInfoMessage" { @@ -362,7 +367,7 @@ } "ReadyToResumeVetoInfoMessage" { - "en" "Type {GREEN}!ready {NORMAL}when you are ready to resume the veto." + "en" "Type {GREEN}!ready{NORMAL} when you are ready to resume the veto." } "MatchConfigLoadedInfoMessage" { @@ -423,6 +428,6 @@ "VetoCountdown" { "#format" "{1:i}" - "en" "Veto commencing in {GREEN}{1} {NORMAL}seconds." + "en" "Veto commencing in {GREEN}{1}{NORMAL} seconds." } } diff --git a/translations/pl/get5.phrases.txt b/translations/pl/get5.phrases.txt index 342ad8c3b..c1f25ee41 100644 --- a/translations/pl/get5.phrases.txt +++ b/translations/pl/get5.phrases.txt @@ -130,7 +130,7 @@ } "TeamWinningSeriesInfoMessage" { - "pl" "{1}{NORMAL} wygrywa {2}-{3}" + "pl" "{1} wygrywa {2}-{3}" } "SeriesTiedInfoMessage" { diff --git a/translations/pt/get5.phrases.txt b/translations/pt/get5.phrases.txt index 82b029517..dd6c33638 100644 --- a/translations/pt/get5.phrases.txt +++ b/translations/pt/get5.phrases.txt @@ -146,7 +146,7 @@ } "TeamWinningSeriesInfoMessage" { - "pt" "{1}{NORMAL} está vencendo a série {2}-{3}" + "pt" "{1} está vencendo a série {2}-{3}" } "SeriesTiedInfoMessage" { diff --git a/translations/ru/get5.phrases.txt b/translations/ru/get5.phrases.txt index 688c85f60..61645a7a3 100644 --- a/translations/ru/get5.phrases.txt +++ b/translations/ru/get5.phrases.txt @@ -122,7 +122,7 @@ } "TeamWinningSeriesInfoMessage" { - "ru" "{1}{NORMAL} выигрывает со счетом {2}-{3}" + "ru" "{1} выигрывает со счетом {2}-{3}" } "SeriesTiedInfoMessage" { From b1c1e8778ab6bff0f755b3ef2da6a5ebee37102b Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 12 Aug 2022 21:00:14 +0200 Subject: [PATCH 028/104] Inject colors into translation variables Add gold color; color player names based on their current side Fixed some translations consistently color team names --- documentation/docs/translations.md | 72 +++++++++++++++++------------- scripting/get5.sp | 47 +++++++++++++------ scripting/get5/kniferounds.sp | 2 +- scripting/get5/maps.sp | 4 +- scripting/get5/mapveto.sp | 15 +++++-- scripting/get5/pausing.sp | 22 ++++++--- scripting/get5/readysystem.sp | 16 ++++--- scripting/get5/util.sp | 21 ++++++++- translations/chi/get5.phrases.txt | 38 ++++++++-------- translations/da/get5.phrases.txt | 44 +++++++++--------- translations/de/get5.phrases.txt | 36 +++++++-------- translations/es/get5.phrases.txt | 44 +++++++++--------- translations/fr/get5.phrases.txt | 42 ++++++++--------- translations/get5.phrases.txt | 67 ++++++++++++++------------- translations/hu/get5.phrases.txt | 46 +++++++++---------- translations/pl/get5.phrases.txt | 36 +++++++-------- translations/pt/get5.phrases.txt | 48 ++++++++++---------- translations/ru/get5.phrases.txt | 38 ++++++++-------- 18 files changed, 362 insertions(+), 276 deletions(-) diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index 960a9ab38..c0a4ecde7 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -2,9 +2,9 @@ Get5 has been translated into a few languages, but some a are still incomplete or could use a grammatical hand. If you are proficient in a language other than English, you are welcome to open a pull request on GitHub with adjustments or -even entirely new languages. Note that you should be **good** at the language; machine-translations or sloppy linguistics -are worse than defaulting Get5 to English. If you cannot code and have found errors in translations, feel free to join -the [Discord](../community/#discord) and let us know. +even entirely new languages. Note that you should be **good** at the language; machine-translations or sloppy +linguistics are worse than defaulting Get5 to English. If you cannot code and have found errors in translations, feel +free to join the [Discord](../community/#discord) and let us know. ## How to translate? @@ -18,44 +18,56 @@ entire language file**. ## Example -```yaml -"TeamPickedMapInfoMessage" -{ - "#format" "{1:s},{2:s},{3:d}" # (1) - "en" "{1} picked {GREEN}{2}{NORMAL} as map {3}." # (2) -} -``` +!!! example "translations/get5.phrases.txt" -1. The `#format` parameter indicates the order and types of parameters. These will *not* be defined in other languages, and -you should only provide the language string itself (with its language prefix, i.e. `en`). The original file indicates -what `{1}`, `{2}` and `{3}` are. In this case, the first and second arguments are strings and the third is a number. -2. Use the English strings and the [reference](#reference) below to determine how to translate the string. + ```yaml + "TeamPickedMapInfoMessage" + { + "#format" "{1:s},{2:s},{3:d}" # (1) + "en" "{1} picked {2} as map {3}." # (2) + } + ``` + + 1. The `#format` parameter indicates the order and types of parameters. These will *not* be defined in other + and you should only provide the language string itself (with its language prefix, i.e. `en`). The original file + indicates what `{1}`, `{2}` and `{3}` are. In this case, the first and second arguments are strings and the third + is a number. + 2. Use the English strings and the [reference](#reference) below to determine how to translate the string. As the string implies, this example is used when a team picks a map, and the output is printed to chat and looks like -this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. +this: `Team A picked de_dust2 as map 2.` The French translation file for this string looks like this: + +!!! example "translations/fr/get5.phrases.txt" + + ```yaml + "TeamPickedMapInfoMessage" + { + "fr" "{1} a choisi {2} comme map {3}." + } + ``` ## Types of strings ####`Chat` : Displayed in the regular game chat. This is the only type that supports - [color modifiers](../configuration#color-substitutes). +[color modifiers](../configuration#color-substitutes). You should use the same colors in the same lexical context as the +English translation. All injected variables are colored automatically if required. ####`HintText` : Displayed as a ["hint"](https://sourcemod.dev/#/halflife/function.PrintHintText) in the lower center of the screen, - where you would also see the pause or restart alert. +where you would also see the pause or restart alert. ####`KickedNote` : Displayed as a modal in the middle of the CS:GO menu after you have been removed from the server. These must **not** - end with a full stop as this is added automatically. +end with a full stop as this is added automatically. ####`Menu` : Displayed as an in-game menu where you select/browse using the numbers on your keyboard. - ## String Reference {: #reference } !!! warning @@ -66,15 +78,15 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | String | Example | Type | |---------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| -| `WaitingForCastersReadyInfoMessage` | Waiting for _Team A_ to type !ready to being. | Chat | -| `ReadyToVetoInfoMessage` | Type !ready when your team is ready to veto. | Chat | -| `ReadyToRestoreBackupInfoMessage` | Type !ready when you are ready to restore the match backup. | Chat | -| `ReadyToKnifeInfoMessage` | Type !ready when you are ready to knife. | Chat | -| `ReadyToStartInfoMessage` | Type !ready when you are ready to begin. | Chat | +| `WaitingForCastersReadyInfoMessage` | Waiting for _Team A_ to type _!ready_ to begin. | Chat | +| `ReadyToVetoInfoMessage` | Type _!ready_ when your team is ready to veto. | Chat | +| `ReadyToRestoreBackupInfoMessage` | Type _!ready_ when you are ready to restore the match backup. | Chat | +| `ReadyToKnifeInfoMessage` | Type _!ready_ when you are ready to knife. | Chat | +| `ReadyToStartInfoMessage` | Type _!ready_ when you are ready to begin. | Chat | | `YouAreReady` | You have been marked as ready. | Chat | -| `YouAreReadyAuto` | NOTE: You have been marked as ready due to game activity. Type !unready if you are not ready. | HintText | +| `YouAreReadyAuto` | NOTE: You have been marked as ready due to game activity. Type _!unready_ if you are not ready. | HintText | | `YouAreNotReady` | You have been marked as NOT ready. | Chat | -| `WaitingForEnemySwapInfoMessage` | _Team A_ won the knife round. Waiting for them to type !stay or !swap. | Chat | +| `WaitingForEnemySwapInfoMessage` | _Team A_ won the knife round. Waiting for them to type _!stay_ or _!swap_. | Chat | | `WaitingForGOTVBrodcastEndingInfoMessage` | The map will change once the GOTV broadcast has ended. | Chat | | `WaitingForGOTVVetoInfoMessage` | The map will change once the GOTV broadcast has displayed the map vetoes. | Chat | | `NoMatchSetupInfoMessage` | No match was set up | KickedNote | @@ -100,12 +112,12 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | `UserCannotUnpauseAdmin` | As an admin has called for this pause, it must also be unpaused by an admin. | Chat | | `PausingTeamCannotUnpauseUntilFreezeTime` | You cannot unpause before your pause has started. Pause requests cannot be canceled. | Chat | | `PauseRunoutInfoMessage` | _Team A_ has run out of pause time. Unpausing the match. | Chat | -| `TechPauseRunoutInfoMessage` | Maximum technical pause length has been reached. Anyone may unpause now. | Chat | +| `TechPauseRunoutInfoMessage` | Maximum technical pause length has been reached. Anyone may _!unpause_ now. | Chat | | `TechPauseNoTimeRemaining` | _Team A_ has no more tech pause time. Please use tactical pauses. | Chat | | `TechPauseNoPausesRemaining` | _Team B_ has no more tech pauses. Please use tactical pauses. | Chat | | `TechPausePausesRemaining` | Technical pauses remaining for _Team A_: _2_ | Chat | | `MatchUnpauseInfoMessage` | _PlayerName_ unpaused the match. | Chat | -| `WaitingForUnpauseInfoMessage` | _Team A_ wants to unpause, waiting for Team B to type !unpause. | Chat | +| `WaitingForUnpauseInfoMessage` | _Team A_ wants to unpause, waiting for Team B to type _!unpause_. | Chat | | `PausesLeftInfoMessage` | Tactical pauses remaining for _Team A_: _3_ | Chat | | `TeamFailToReadyMinPlayerCheck` | You must have at least _3_ player(s) on the server to ready up. | Chat | | `TeamReadyToVetoInfoMessage` | _Team A_ is ready to veto. | Chat | @@ -120,7 +132,7 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | `AdminForceEndWithWinnerInfoMessage` | An admin force-ended the match, setting _Team 1_ as the winner. | Chat | | `AdminForcePauseInfoMessage` | An admin force-paused the match. | Chat | | `AdminForceUnPauseInfoMessage` | An admin unpaused the match. | Chat | -| `TeamWantsToReloadLastRoundInfoMessage` | _Team A_ wants to stop and reload last round. _Team B_ must confirm with !stop. | Chat | +| `TeamWantsToReloadLastRoundInfoMessage` | _Team A_ wants to stop and reload last round. _Team B_ must confirm with _!stop_. | Chat | | `TeamWinningSeriesInfoMessage` | _Team A_ is winning the series _2_-_1_. | Chat | | `SeriesTiedInfoMessage` | The series is tied at _1_-_1_. | Chat | | `NextSeriesMapInfoMessage` | The next map in the series is _de_nuke_ and it will start in _1:30_. | Chat | @@ -143,7 +155,7 @@ this: `Team A picked de_dust2 as map 2.` - with `de_dust2` being colored green. | `TeamSelectSideInfoMessage` | _Team A_ has selected to start on _CT_ on _de_nuke_. | Chat | | `TeamVetoedMapInfoMessage` | _Team A_ vetoed _de_nuke_. | Chat | | `CaptainLeftOnVetoInfoMessage` | A captain left during the veto, pausing the veto. | Chat | -| `ReadyToResumeVetoInfoMessage` | Type !ready when you are ready to resume the veto. | Chat | +| `ReadyToResumeVetoInfoMessage` | Type _!ready_ when you are ready to resume the veto. | Chat | | `MatchConfigLoadedInfoMessage` | Loaded match config. | Chat | | `MoveToCoachInfoMessage` | You were moved to the coach position because your team is full. | Chat | | `ReadyTag` | **[READY]** PlayerName: Hey, I'm ready... | Chat | diff --git a/scripting/get5.sp b/scripting/get5.sp index 22674bd88..2a0ef8e9e 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -614,13 +614,20 @@ public void OnPluginStart() { } public Action Timer_InfoMessages(Handle timer) { + if (g_GameState == Get5State_Live || g_GameState == Get5State_None) { + return Plugin_Continue; + } + + char readyCommandFormatted[64]; + FormatChatCommand(readyCommandFormatted, sizeof(readyCommandFormatted), "!ready"); + // Handle pre-veto messages if (g_GameState == Get5State_PreVeto) { if (IsTeamsReady() && !IsSpectatorsReady()) { Get5_MessageToAll("%t", "WaitingForCastersReadyInfoMessage", - g_FormattedTeamNames[Get5Team_Spec]); + g_FormattedTeamNames[Get5Team_Spec], readyCommandFormatted); } else { - Get5_MessageToAll("%t", "ReadyToVetoInfoMessage"); + Get5_MessageToAll("%t", "ReadyToVetoInfoMessage", readyCommandFormatted); } MissingPlayerInfoMessage(); } else if (g_GameState == Get5State_Warmup && !g_MapChangePending) { @@ -628,19 +635,19 @@ public Action Timer_InfoMessages(Handle timer) { // Handle warmup state, provided we're not waiting for a map change // Backups take priority if (!IsTeamsReady() && g_WaitingForRoundBackup) { - Get5_MessageToAll("%t", "ReadyToRestoreBackupInfoMessage"); + Get5_MessageToAll("%t", "ReadyToRestoreBackupInfoMessage", readyCommandFormatted); return Plugin_Continue; } // Find out what we're waiting for if (IsTeamsReady() && !IsSpectatorsReady()) { Get5_MessageToAll("%t", "WaitingForCastersReadyInfoMessage", - g_FormattedTeamNames[Get5Team_Spec]); + g_FormattedTeamNames[Get5Team_Spec], readyCommandFormatted); } else { if (g_MapSides.Get(Get5_GetMapNumber()) == SideChoice_KnifeRound) { - Get5_MessageToAll("%t", "ReadyToKnifeInfoMessage"); + Get5_MessageToAll("%t", "ReadyToKnifeInfoMessage", readyCommandFormatted); } else { - Get5_MessageToAll("%t", "ReadyToStartInfoMessage"); + Get5_MessageToAll("%t", "ReadyToStartInfoMessage", readyCommandFormatted); } } MissingPlayerInfoMessage(); @@ -649,7 +656,11 @@ public Action Timer_InfoMessages(Handle timer) { Get5_MessageToAll("%t", "WaitingForGOTVVetoInfoMessage"); } else if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { // Handle waiting for knife decision - Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); + char formattedStayCommand[64]; + FormatChatCommand(formattedStayCommand, sizeof(formattedStayCommand), "!stay"); + char formattedSwapCommand[64]; + FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); + Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, formattedSwapCommand); } else if (g_GameState == Get5State_PostGame && GetTvDelay() > 0) { // Handle postgame Get5_MessageToAll("%t", "WaitingForGOTVBrodcastEndingInfoMessage"); @@ -1097,12 +1108,14 @@ public Action Command_Stop(int client, int args) { Get5Team team = GetClientMatchTeam(client); g_TeamGivenStopCommand[team] = true; + char stopCommandFormatted[64]; + FormatChatCommand(stopCommandFormatted, sizeof(stopCommandFormatted), "!stop"); if (g_TeamGivenStopCommand[Get5Team_1] && !g_TeamGivenStopCommand[Get5Team_2]) { Get5_MessageToAll("%t", "TeamWantsToReloadLastRoundInfoMessage", - g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2]); + g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2], stopCommandFormatted); } else if (!g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { Get5_MessageToAll("%t", "TeamWantsToReloadLastRoundInfoMessage", - g_FormattedTeamNames[Get5Team_2], g_FormattedTeamNames[Get5Team_1]); + g_FormattedTeamNames[Get5Team_2], g_FormattedTeamNames[Get5Team_1], stopCommandFormatted); } else if (g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { RestoreLastRound(client); } @@ -1264,6 +1277,7 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast convertSecondsToMinutesAndSeconds(RoundToFloor(restartDelay), timeToMapChangeFormatted, sizeof(timeToMapChangeFormatted)); g_MapChangePending = true; + Format(nextMap, sizeof(nextMap), "{GREEN}%s{NORMAL}", nextMap); Get5_MessageToAll("%t", "NextSeriesMapInfoMessage", nextMap, timeToMapChangeFormatted); ChangeState(Get5State_PostGame); // Subtracting 4 seconds makes the map change 1 second before the timer expires, as there is a 3 second built-in @@ -1519,8 +1533,11 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) } g_KnifeWinnerTeam = CSTeamToGet5Team(winningCSTeam); - Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", - g_FormattedTeamNames[g_KnifeWinnerTeam]); + char formattedStayCommand[64]; + FormatChatCommand(formattedStayCommand, sizeof(formattedStayCommand), "!stay"); + char formattedSwapCommand[64]; + FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); + Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, formattedSwapCommand); if (g_TeamTimeToKnifeDecisionCvar.FloatValue > 0) CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision); @@ -1529,9 +1546,11 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) if (g_GameState == Get5State_Live) { int csTeamWinner = event.GetInt("winner"); - Get5_MessageToAll("{LIGHT_GREEN}%s {GREEN}%d {NORMAL}- {GREEN}%d {LIGHT_GREEN}%s", g_TeamNames[Get5Team_1], - CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)), - CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)), g_TeamNames[Get5Team_2]); + Get5_MessageToAll("%s {GREEN}%d {NORMAL}- {GREEN}%d %s", g_FormattedTeamNames[Get5Team_1], + CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)), + CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)), + g_FormattedTeamNames[Get5Team_2] + ); Stats_RoundEnd(csTeamWinner); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index cc604d478..304756d39 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -15,7 +15,7 @@ public Action StartKnifeRound(Handle timer) { public Action Timer_AnnounceKnife(Handle timer) { for (int i = 0; i < 5; i++) { - Get5_MessageToAll("%t", "KnifeInfoMessage"); + Get5_MessageToAll("{GREEN}%t", "KnifeInfoMessage"); } Get5KnifeRoundStartedEvent knifeEvent = new Get5KnifeRoundStartedEvent(g_MatchID, g_MapNumber); diff --git a/scripting/get5/maps.sp b/scripting/get5/maps.sp index efad54842..0c857e89f 100644 --- a/scripting/get5/maps.sp +++ b/scripting/get5/maps.sp @@ -1,5 +1,7 @@ stock void ChangeMap(const char[] map, float delay = 3.0) { - Get5_MessageToAll("%t", "ChangingMapInfoMessage", map); + char formattedMapName[32]; + Format(formattedMapName, sizeof(formattedMapName), "{GREEN}%s{NORMAL}", map); + Get5_MessageToAll("%t", "ChangingMapInfoMessage", formattedMapName); // pass the "true" name to a timer to changelevel Handle data = CreateDataPack(); diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index be043e73a..3cae0bdd0 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -30,14 +30,18 @@ public Action Timer_VetoCountdown(Handle timer) { } else { warningsPrinted++; int secondsRemaining = g_VetoCountdownCvar.IntValue - warningsPrinted + 1; - Get5_MessageToAll("%t", "VetoCountdown", secondsRemaining); + char secondsFormatted[8]; + Format(secondsFormatted, sizeof(secondsFormatted), "{GREEN}%d{NORMAL}", secondsRemaining); + Get5_MessageToAll("%t", "VetoCountdown", secondsFormatted); return Plugin_Continue; } } static void AbortVeto() { Get5_MessageToAll("%t", "CaptainLeftOnVetoInfoMessage"); - Get5_MessageToAll("%t", "ReadyToResumeVetoInfoMessage"); + char readyCommandFormatted[64]; + FormatChatCommand(readyCommandFormatted, sizeof(readyCommandFormatted), "!ready"); + Get5_MessageToAll("%t", "ReadyToResumeVetoInfoMessage", readyCommandFormatted); ChangeState(Get5State_PreVeto); } @@ -54,6 +58,7 @@ public void VetoFinished() { for (int i = seriesScore; i < g_MapsToPlay.Length; i++) { char map[PLATFORM_MAX_PATH]; g_MapsToPlay.GetString(i, map, sizeof(map)); + Format(map, sizeof(map), "{GREEN}%s{NORMAL}", map); Get5_MessageToAll("%t", "MapIsInfoMessage", i + 1 - seriesScore, map); } @@ -288,6 +293,7 @@ public int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int para RemoveStringFromArray(g_MapsLeftInVetoPool, mapName); + Format(mapName, sizeof(mapName), "{LIGHT_RED}%s{NORMAL}", mapName); Get5_MessageToAll("%t", "TeamVetoedMapInfoMessage", g_FormattedTeamNames[team], mapName); Get5MapVetoedEvent event = new Get5MapVetoedEvent(g_MatchID, team, mapName); @@ -355,7 +361,9 @@ public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int para g_MapsToPlay.PushString(mapName); RemoveStringFromArray(g_MapsLeftInVetoPool, mapName); - Get5_MessageToAll("%t", "TeamPickedMapInfoMessage", g_FormattedTeamNames[team], mapName, + char mapNameFormatted[PLATFORM_MAX_PATH]; + Format(mapNameFormatted, sizeof(mapNameFormatted), "{GREEN}%s{NORMAL}", mapName); + Get5_MessageToAll("%t", "TeamPickedMapInfoMessage", g_FormattedTeamNames[team], mapNameFormatted, g_MapsToPlay.Length); g_LastVetoTeam = team; @@ -435,6 +443,7 @@ public int SidePickMenuHandler(Menu menu, MenuAction action, int param1, int par char mapName[PLATFORM_MAX_PATH]; g_MapsToPlay.GetString(mapNumber, mapName, sizeof(mapName)); + Format(choice, sizeof(choice), "{GREEN}%s{NORMAL}", choice); Get5_MessageToAll("%t", "TeamSelectSideInfoMessage", g_FormattedTeamNames[team], choice, mapName); diff --git a/scripting/get5/pausing.sp b/scripting/get5/pausing.sp index 88a17d029..9fae990e2 100644 --- a/scripting/get5/pausing.sp +++ b/scripting/get5/pausing.sp @@ -121,7 +121,9 @@ public Action Command_TechPause(int client, int args) { g_TechnicalPausesUsed[team]++; PauseGame(team, Get5PauseType_Tech); - Get5_MessageToAll("%t", "MatchTechPausedByTeamMessage", client); + char formattedClientName[MAX_NAME_LENGTH]; + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + Get5_MessageToAll("%t", "MatchTechPausedByTeamMessage", formattedClientName); if (maxTechPauses > 0) { Get5_MessageToAll("%t", "TechPausePausesRemaining", g_FormattedTeamNames[team], maxTechPauses - g_TechnicalPausesUsed[team]); } @@ -176,7 +178,9 @@ public Action Command_Pause(int client, int args) { PauseGame(team, Get5PauseType_Tactical); if (IsPlayer(client)) { - Get5_MessageToAll("%t", "MatchPausedByTeamMessage", client); + char formattedClientName[MAX_NAME_LENGTH]; + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + Get5_MessageToAll("%t", "MatchPausedByTeamMessage", formattedClientName); } if (maxPauses > 0) { @@ -225,12 +229,16 @@ public Action Command_Unpause(int client, int args) { ) { UnpauseGame(team); if (IsPlayer(client)) { - Get5_MessageToAll("%t", "MatchUnpauseInfoMessage", client); + char formattedClientName[MAX_NAME_LENGTH]; + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + Get5_MessageToAll("%t", "MatchUnpauseInfoMessage", formattedClientName); } return Plugin_Handled; } } + char formattedUnpauseCommand[64]; + FormatChatCommand(formattedUnpauseCommand, sizeof(formattedUnpauseCommand), "!unpause"); if (g_TeamReadyForUnpause[Get5Team_1] && g_TeamReadyForUnpause[Get5Team_2]) { UnpauseGame(team); if (IsPlayer(client)) { @@ -238,10 +246,10 @@ public Action Command_Unpause(int client, int args) { } } else if (!g_TeamReadyForUnpause[Get5Team_2]) { Get5_MessageToAll("%t", "WaitingForUnpauseInfoMessage", g_FormattedTeamNames[Get5Team_1], - g_FormattedTeamNames[Get5Team_2]); + g_FormattedTeamNames[Get5Team_2], formattedUnpauseCommand); } else if (!g_TeamReadyForUnpause[Get5Team_1]) { Get5_MessageToAll("%t", "WaitingForUnpauseInfoMessage", g_FormattedTeamNames[Get5Team_2], - g_FormattedTeamNames[Get5Team_1]); + g_FormattedTeamNames[Get5Team_1], formattedUnpauseCommand); } return Plugin_Handled; @@ -372,7 +380,9 @@ public Action Timer_PauseTimeCheck(Handle timer) { if (timeLeft == 0) { // Only print to chat when hitting 0, but keep the timer going as tech pauses don't unpause on their own. // The PrintHintText below will inform users that they can now unpause. - Get5_MessageToAll("%t", "TechPauseRunoutInfoMessage"); + char formattedUnpauseCommand[64]; + FormatChatCommand(formattedUnpauseCommand, sizeof(formattedUnpauseCommand), "!unpause"); + Get5_MessageToAll("%t", "TechPauseRunoutInfoMessage", formattedUnpauseCommand); } } } diff --git a/scripting/get5/readysystem.sp b/scripting/get5/readysystem.sp index 608047fc7..3fea5ee67 100644 --- a/scripting/get5/readysystem.sp +++ b/scripting/get5/readysystem.sp @@ -153,7 +153,8 @@ public void HandleReadyCommand(int client, bool autoReady) { Get5_Message(client, "%t", "YouAreReady"); if (autoReady) { - PrintHintText(client, "%t", "YouAreReadyAuto"); + // We cannot color text in hints, so no formatting the command. + PrintHintText(client, "%t", "YouAreReadyAuto", "!unready"); } SetClientReady(client, true); @@ -209,11 +210,12 @@ public Action Command_ForceReadyClient(int client, int args) { Get5_Message(client, "%t", "TeamFailToReadyMinPlayerCheck", minReady); return Plugin_Handled; } - + char formattedClientName[MAX_NAME_LENGTH]; + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); LOOP_CLIENTS(i) { if (IsPlayer(i) && GetClientMatchTeam(i) == team) { SetClientReady(i, true); - Get5_Message(i, "%t", "TeammateForceReadied", client); + Get5_Message(i, "%t", "TeammateForceReadied", formattedClientName); } } SetTeamForcedReady(team, true); @@ -268,8 +270,12 @@ public void MissingPlayerInfoMessageTeam(Get5Team team) { int playerCount = GetTeamPlayerCount(team); int readyCount = GetTeamReadyCount(team); - if (playerCount == readyCount && playerCount < minPlayers && readyCount >= minReady) { - Get5_MessageToTeam(team, "%t", "ForceReadyInfoMessage", minPlayers); + if (playerCount == readyCount && playerCount < minPlayers && readyCount >= minReady && minPlayers > 1) { + char minPlayersFormatted[32]; + Format(minPlayersFormatted, sizeof(minPlayersFormatted), "{GREEN}%d{NORMAL}", minPlayers); + char forceReadyFormatted[64]; + FormatChatCommand(forceReadyFormatted, sizeof(forceReadyFormatted), "!forceready"); + Get5_MessageToTeam(team, "%t", "ForceReadyInfoMessage", forceReadyFormatted, minPlayersFormatted); } } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index efd5d705c..a6ae30b9e 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -10,9 +10,11 @@ static char _colorNames[][] = {"{NORMAL}", "{DARK_RED}", "{PINK}", "{GREEN}", "{YELLOW}", "{LIGHT_GREEN}", "{LIGHT_RED}", "{GRAY}", - "{ORANGE}", "{LIGHT_BLUE}", "{DARK_BLUE}", "{PURPLE}"}; + "{ORANGE}", "{LIGHT_BLUE}", "{DARK_BLUE}", "{PURPLE}", + "{GOLD}"}; static char _colorCodes[][] = {"\x01", "\x02", "\x03", "\x04", "\x05", "\x06", - "\x07", "\x08", "\x09", "\x0B", "\x0C", "\x0E"}; + "\x07", "\x08", "\x09", "\x0B", "\x0C", "\x0E", + "\x10"}; // Convenience macros. #define LOOP_TEAMS(%1) for (Get5Team %1 = Get5Team_1; %1 < Get5Team_Count; %1 ++) @@ -189,6 +191,21 @@ stock void Colorize(char[] msg, int size, bool stripColor = false) { } } +stock void FormatChatCommand(char[] buffer, const int bufferLength, const char[] command) { + Format(buffer, bufferLength, "{GREEN}%s{NORMAL}", command); +} + +stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client) { + Get5Side side = view_as(IsClientInGame(client) ? GetClientTeam(client) : CS_TEAM_NONE); + if (side == Get5Side_CT) { + Format(buffer, bufferLength, "{LIGHT_BLUE}%N{NORMAL}", client); + } else if (side == Get5Side_T) { + Format(buffer, bufferLength, "{GOLD}%N{NORMAL}", client); + } else { + Format(buffer, bufferLength, "{PURPLE}%N{NORMAL}", client); + } +} + stock void ReplaceStringWithInt(char[] buffer, int len, const char[] replace, int value, bool caseSensitive = false) { char intString[MAX_INTEGER_STRING_LENGTH]; diff --git a/translations/chi/get5.phrases.txt b/translations/chi/get5.phrases.txt index 07d962338..cc734a984 100644 --- a/translations/chi/get5.phrases.txt +++ b/translations/chi/get5.phrases.txt @@ -2,23 +2,23 @@ { "ReadyToVetoInfoMessage" { - "chi" "当您的队伍准备好进行Veto(Ban图)时,请输入{GREEN}!ready{NORMAL}。" + "chi" "当您的队伍准备好进行Veto(Ban图)时,请输入{1}。" } "WaitingForCastersReadyInfoMessage" { - "chi" "正在等待裁判输入{GREEN}!ready{NORMAL}来开始比赛。" + "chi" "正在等待裁判输入{2}来开始比赛。" } "ReadyToRestoreBackupInfoMessage" { - "chi" "当您准备好恢复比赛至备份档案时,请输入{GREEN}!ready{NORMAL}。" + "chi" "当您准备好恢复比赛至备份档案时,请输入{1}。" } "ReadyToKnifeInfoMessage" { - "chi" "当您准备好进行刀局时,请输入{GREEN}!ready{NORMAL}。" + "chi" "当您准备好进行刀局时,请输入{1}。" } "ReadyToStartInfoMessage" { - "chi" "当您准备好开始比赛时,请输入{GREEN}!ready{NORMAL}。" + "chi" "当您准备好开始比赛时,请输入{1}。" } "YouAreReady" { @@ -30,7 +30,7 @@ } "WaitingForEnemySwapInfoMessage" { - "chi" "{1}赢得刀局。等待他们输入!stay或是!swapwon来选择留下还是交换队伍。" + "chi" "{1}赢得刀局。等待他们输入{2}或是{3}来选择留下还是交换队伍。" } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -90,7 +90,7 @@ } "WaitingForUnpauseInfoMessage" { - "chi" "{1}想要取消暂停,正在等待{2}输入!unpause来取消暂停。" + "chi" "{1}想要取消暂停,正在等待{2}输入{3}来取消暂停。" } "PausesLeftInfoMessage" { @@ -122,11 +122,11 @@ } "ForceReadyInfoMessage" { - "chi" "如果您有不到{1}名玩家,您可以输入{GREEN}!forceready{NORMAL}来强制准备您的队伍。" + "chi" "如果您有不到{1}名玩家,您可以输入{2}来强制准备您的队伍。" } "TeammateForceReadied" { - "chi" "您的队伍已被{GREEN}{1}{NORMAL}强制准备就绪。" + "chi" "您的队伍已被{1}强制准备就绪。" } "AdminForceReadyInfoMessage" { @@ -146,7 +146,7 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "chi" "{1}想停止比赛并重新加载比赛至上一回合,需要{2}输入!stop来确认。" + "chi" "{1}想停止比赛并重新加载比赛至上一回合,需要{2}输入{3}来确认。" } "TeamWinningSeriesInfoMessage" { @@ -158,7 +158,7 @@ } "NextSeriesMapInfoMessage" { - "chi" "下一张地图在此系列比赛中是{GREEN}{1}" + "chi" "下一张地图在此系列比赛中是{1}" } "TeamWonMatchInfoMessage" { @@ -170,7 +170,7 @@ } "TeamWonSeriesInfoMessage" { - "chi" "{1}赢得了此系列比赛 {2}-{3}." + "chi" "{1}赢得了此系列比赛 {2}-{3}" } "MatchFinishedInfoMessage" { @@ -210,7 +210,7 @@ } "ChangingMapInfoMessage" { - "chi" "切换地图至{GREEN}{1}......" + "chi" "切换地图至{1}..." } "MapDecidedInfoMessage" { @@ -218,19 +218,19 @@ } "MapIsInfoMessage" { - "chi" "地图 {1}: {GREEN}{2}" + "chi" "地图 {1}: {2}" } "TeamPickedMapInfoMessage" { - "chi" "{1}挑选了{GREEN}{2}{NORMAL}作为地图 {3}" + "chi" "{1}挑选了{2}作为地图 {3}" } "TeamSelectSideInfoMessage" { - "chi" "{1}已选择在{GREEN}{2}{NORMAL}开始 {3}" + "chi" "{1}已选择在{2}开始 {3}" } "TeamVetoedMapInfoMessage" { - "chi" "{1}vetoed(Ban掉)了{LIGHT_RED}{2}" + "chi" "{1}vetoed(Ban掉)了{2}" } "CaptainLeftOnVetoInfoMessage" { @@ -238,7 +238,7 @@ } "ReadyToResumeVetoInfoMessage" { - "chi" "当您准备好恢复Veto(Ban图)时,请输入{GREEN}!ready{NORMAL}。" + "chi" "当您准备好恢复Veto(Ban图)时,请输入{1}。" } "MatchConfigLoadedInfoMessage" { @@ -294,6 +294,6 @@ } "VetoCountdown" { - "chi" "Veto(Ban图)还剩{GREEN}{1}秒。" + "chi" "Veto(Ban图)还剩{1}秒。" } } diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 0d75f6c8b..495262b80 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -2,23 +2,23 @@ { "ReadyToVetoInfoMessage" { - "da" "Skriv {GREEN}!ready{NORMAL} når dit hold er klar til at veto." + "da" "Skriv {1} når dit hold er klar til at veto." } "WaitingForCastersReadyInfoMessage" { - "da" "Venter på, at {1} skriver {GREEN}!ready {NORMAL}." + "da" "Venter på, at {1} skriver {2}." } "ReadyToRestoreBackupInfoMessage" { - "da" "Skriv {GREEN}!ready{NORMAL} når du er klar til at genoprette match backup." + "da" "Skriv {1} når du er klar til at genoprette match backup." } "ReadyToKnifeInfoMessage" { - "da" "Skriv {GREEN}!ready{NORMAL} når du er klar til at knife." + "da" "Skriv {1} når du er klar til at knife." } "ReadyToStartInfoMessage" { - "da" "Skriv {GREEN}!ready{NORMAL} når du er klar til at spille." + "da" "Skriv {1} når du er klar til at spille." } "YouAreReady" { @@ -26,7 +26,7 @@ } "YouAreReadyAuto" { - "da" "NOTE: Du er blevet markeret som klar grundet spilaktivitet. Skriv !unready hvis du ikke er klar." + "da" "NOTE: Du er blevet markeret som klar grundet spilaktivitet. Skriv {1} hvis du ikke er klar." } "YouAreNotReady" { @@ -34,7 +34,7 @@ } "WaitingForEnemySwapInfoMessage" { - "da" "{1} vandt knife-runden. Venter på, at de skriver !stay eller !swap." + "da" "{1} vandt knife-runden. Venter på, at de skriver {2} eller {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -138,7 +138,7 @@ } "TechPauseRunoutInfoMessage" { - "da" "Maksimal teknisk pauselængde er nået, og alle kan nu !unpause." + "da" "Maksimal teknisk pauselængde er nået, og alle kan nu {1}." } "TechPauseNoTimeRemaining" { @@ -158,7 +158,7 @@ } "WaitingForUnpauseInfoMessage" { - "da" "{1} vil gerne fortsætte kampen. Afventer !unpause fra {2}." + "da" "{1} vil gerne fortsætte kampen. Afventer {3} fra {2}." } "PausesLeftInfoMessage" { @@ -190,11 +190,11 @@ } "ForceReadyInfoMessage" { - "da" "Du kan skrive {GREEN}!forceready {NORMAL} for tvinge dit hold klar, hvis der er mindre end {GREEN}{1}{NORMAL} spillere." + "da" "Du kan skrive {1} for tvinge dit hold klar, hvis der er mindre end {2} spillere." } "TeammateForceReadied" { - "da" "Dit hold blev tvunget klar af {GREEN}{1}." + "da" "Dit hold blev tvunget klar af {1}." } "AdminForceReadyInfoMessage" { @@ -204,6 +204,10 @@ { "da" "En admin har tvunget kampen til at slutte." } + "AdminForceEndWithWinnerInfoMessage" + { + "da" "En admin har tvunget kampen til at slutte med {1} som vinder." + } "AdminForcePauseInfoMessage" { "da" "En admin har tvunget kampen til at pause." @@ -214,7 +218,7 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "da" "{1} vil stoppe og genindlæse sidste runde. Afventer !stop fra {2}." + "da" "{1} vil stoppe og genindlæse sidste runde. Afventer {3} fra {2}." } "TeamWinningSeriesInfoMessage" { @@ -226,7 +230,7 @@ } "NextSeriesMapInfoMessage" { - "da" "Det næste map i serien er {GREEN}{1}{NORMAL} og det starter om {2}." + "da" "Det næste map i serien er {1} og det starter om {2}." } "TeamWonMatchInfoMessage" { @@ -282,7 +286,7 @@ } "ChangingMapInfoMessage" { - "da" "Skifter map til {GREEN}{1}..." + "da" "Skifter map til {1}..." } "MapDecidedInfoMessage" { @@ -290,19 +294,19 @@ } "MapIsInfoMessage" { - "da" "Map {1}: {GREEN}{2}" + "da" "Map {1}: {2}" } "TeamPickedMapInfoMessage" { - "da" "{1} valgte {GREEN}{2} {NORMAL}som deres {3}. map." + "da" "{1} valgte {2} som deres {3}. map." } "TeamSelectSideInfoMessage" { - "da" "{1} har valgt at start som {GREEN}{2} {NORMAL}på {3}." + "da" "{1} har valgt at start som {2} på {3}." } "TeamVetoedMapInfoMessage" { - "da" "{1} vetoed {LIGHT_RED}{2}" + "da" "{1} vetoed {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -310,7 +314,7 @@ } "ReadyToResumeVetoInfoMessage" { - "da" "Skriv {GREEN}!ready {NORMAL}når du er klar til at genoptage veto." + "da" "Skriv {1}, når du er klar til at genoptage veto." } "MatchConfigLoadedInfoMessage" { @@ -366,7 +370,7 @@ } "VetoCountdown" { - "da" "Veto begynder om {GREEN}{1} {NORMAL}sekunder." + "da" "Veto begynder om {1} sekunder." } "NewVersionAvailable" { diff --git a/translations/de/get5.phrases.txt b/translations/de/get5.phrases.txt index cb9a9e687..7e3250d80 100644 --- a/translations/de/get5.phrases.txt +++ b/translations/de/get5.phrases.txt @@ -2,27 +2,27 @@ { "ReadyToVetoInfoMessage" { - "de" "Tippe {GREEN}!ready {NORMAL}wenn dein Team bereit zum Voten ist." + "de" "Tippe {1}wenn dein Team bereit zum Voten ist." } "WaitingForCastersReadyInfoMessage" { - "de" "Warten auf die Caster, tippe {GREEN}!ready {NORMAL}um zu beginnen." + "de" "Warten auf die Caster, tippe {2} um zu beginnen." } "ReadyToRestoreBackupInfoMessage" { - "de" "Tippe {GREEN}!ready {NORMAL}wenn dein Team bereit ist das Match Backup wieder einzuspielen." + "de" "Tippe {1}wenn dein Team bereit ist das Match Backup wieder einzuspielen." } "ReadyToKnifeInfoMessage" { - "de" "Tippe {GREEN}!ready {NORMAL}wenn dein Team bereit ist für die Messer-Runde." + "de" "Tippe {1}wenn dein Team bereit ist für die Messer-Runde." } "ReadyToStartInfoMessage" { - "de" "Tippe {GREEN}!ready {NORMAL}wenn dein Team bereit ist zu beginnen." + "de" "Tippe {1}wenn dein Team bereit ist zu beginnen." } "WaitingForEnemySwapInfoMessage" { - "de" "{1} gewinnt die Seitenwahl. Wartend auf !stay oder !swap ." + "de" "{1} gewinnt die Seitenwahl. Wartend auf {2} oder {3} ." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -78,7 +78,7 @@ } "WaitingForUnpauseInfoMessage" { - "de" "{1} möchte fortfahren, zum zuzustimmen muss {2} !unpause schreiben." + "de" "{1} möchte fortfahren, zum zuzustimmen muss {2} {3} schreiben." } "TeamFailToReadyMinPlayerCheck" { @@ -122,19 +122,19 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "de" "{1} möchte die letzte Runde wiederholen, {2} muss mit !stop bestätigen." + "de" "{1} möchte die letzte Runde wiederholen, {2} muss mit {3} bestätigen." } "TeamWinningSeriesInfoMessage" { - "de" "{1} gewinnt die Serie {2}-{3}" + "de" "{1} gewinnt die Serie {2}-{3}." } "SeriesTiedInfoMessage" { - "de" "Die Serie ist unentschieden mit {1}-{2}" + "de" "Die Serie ist unentschieden mit {1}-{2}." } "NextSeriesMapInfoMessage" { - "de" "Die nächste Map in der Serie ist {GREEN}{1}" + "de" "Die nächste Map in der Serie ist {1}." } "TeamWonMatchInfoMessage" { @@ -150,7 +150,7 @@ } "BackupLoadedInfoMessage" { - "de" "Backup {1} erfolgreich geladen" + "de" "Backup {1} erfolgreich geladen." } "MatchBeginInSecondsInfoMessage" { @@ -182,7 +182,7 @@ } "ChangingMapInfoMessage" { - "de" "Ändern der Map in {GREEN}{1}..." + "de" "Ändern der Map in {1}..." } "MapDecidedInfoMessage" { @@ -190,19 +190,19 @@ } "MapIsInfoMessage" { - "de" "Map {1}: {GREEN}{2}" + "de" "Map {1}: {2}" } "TeamPickedMapInfoMessage" { - "de" "{1} wählte {GREEN}{2} {NORMAL}als Map aus {3}" + "de" "{1} wählte {2} als Map aus {3}." } "TeamSelectSideInfoMessage" { - "de" "{1} möchte als {GREEN}{2} {NORMAL}starten {3}" + "de" "{1} möchte als {2} starten {3}." } "TeamVetoedMapInfoMessage" { - "de" "{1} verbietet {LIGHT_RED}{2}" + "de" "{1} verbietet {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -210,7 +210,7 @@ } "ReadyToResumeVetoInfoMessage" { - "de" "Tippe {GREEN}!ready {NORMAL}wenn ihr fertig seit mit dem veto fortzufahren." + "de" "Tippe {1} wenn ihr fertig seit mit dem veto fortzufahren." } "MatchConfigLoadedInfoMessage" { diff --git a/translations/es/get5.phrases.txt b/translations/es/get5.phrases.txt index c484c13fe..200406cef 100644 --- a/translations/es/get5.phrases.txt +++ b/translations/es/get5.phrases.txt @@ -2,23 +2,23 @@ { "ReadyToVetoInfoMessage" { - "es" "Teclee {GREEN}!ready {NORMAL}cuando su equipo este listo para la ronda veto." + "es" "Teclee {1} cuando su equipo este listo para la ronda veto." } "WaitingForCastersReadyInfoMessage" { - "es" "Esperando : los casters deben teclear {GREEN}!ready {NORMAL}para que la partida comience." + "es" "Esperando : los casters deben teclear {2} para que la partida comience." } "ReadyToRestoreBackupInfoMessage" { - "es" "Teclee {GREEN}!ready {NORMAL}cuando este listo para restaurar el backup de la partida." + "es" "Teclee {1} cuando este listo para restaurar el backup de la partida." } "ReadyToKnifeInfoMessage" { - "es" "Teclee {GREEN}!ready {NORMAL}cuando este listo para empezar la ronda Knife." + "es" "Teclee {1} cuando este listo para empezar la ronda Knife." } "ReadyToStartInfoMessage" { - "es" "Teclee {GREEN}!ready {NORMAL}cuando este listo para empezar la ronda." + "es" "Teclee {1} cuando este listo para empezar la ronda." } "YouAreReady" { @@ -30,7 +30,7 @@ } "WaitingForEnemySwapInfoMessage" { - "es" "{1} ganó la ronda Knife. Esperando la elección entre !stay o !swap." + "es" "{1} ganó la ronda Knife. Esperando la elección entre {2} o {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -94,7 +94,7 @@ } "WaitingForUnpauseInfoMessage" { - "es" "{1} quiere retomar la partida, esperando a que {2} teclee !unpause" + "es" "{1} quiere retomar la partida, esperando a que {2} teclee {3}." } "PausesLeftInfoMessage" { @@ -126,11 +126,11 @@ } "ForceReadyInfoMessage" { - "es" "Puede teclear {GREEN}!forceready {NORMAL}para forzar su equipo a estar listo si tienen {GREEN}{1}{NORMAL} jugadores" + "es" "Puede teclear {1} para forzar su equipo a estar listo si tienen {2} jugadores" } "TeammateForceReadied" { - "es" "Su equipo a sido forzado a estar listo por {GREEN}{1}" + "es" "Su equipo a sido forzado a estar listo por {1}." } "AdminForceReadyInfoMessage" { @@ -150,19 +150,19 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "es" "{1} quiere parar y reiniciar la partida, esto requiere {2} confirmaciones con !stop." + "es" "{1} quiere parar y reiniciar la partida, esto requiere {2} confirmaciones con {3}." } "TeamWinningSeriesInfoMessage" { - "es" "{1} ganó las series {2}-{3}" + "es" "{1} ganó las series {2}-{3}." } "SeriesTiedInfoMessage" { - "es" "La serie empató {1}-{2}" + "es" "La serie empató {1}-{2}." } "NextSeriesMapInfoMessage" { - "es" "El proximo mapa de la serie es {GREEN}{1}" + "es" "El proximo mapa de la serie es {1}." } "TeamWonMatchInfoMessage" { @@ -182,7 +182,7 @@ } "BackupLoadedInfoMessage" { - "es" "Backup cargado exitosamente {1}" + "es" "Backup cargado exitosamente {1}." } "MatchBeginInSecondsInfoMessage" { @@ -202,7 +202,7 @@ } "TeamDecidedToStayInfoMessage" { - "es" "{1} {1} decidiío quedarse.." + "es" "{1} {1} decidiío quedarse." } "TeamDecidedToSwapInfoMessage" { @@ -214,7 +214,7 @@ } "ChangingMapInfoMessage" { - "es" "Cambio de mapa por {GREEN}{1}…" + "es" "Cambio de mapa por {1}..." } "MapDecidedInfoMessage" { @@ -222,19 +222,19 @@ } "MapIsInfoMessage" { - "es" "Map {1}: {GREEN}{2}" + "es" "Map {1}: {2}" } "TeamPickedMapInfoMessage" { - "es" "{1} eligió {GREEN}{2} {NORMAL}de mapa {3}}" + "es" "{1} eligió {2} de mapa {3}." } "TeamSelectSideInfoMessage" { - "es" "{1} eligió empezar con {GREEN}{2} {NORMAL}sobre {3}" + "es" "{1} eligió empezar con {2} sobre {3}." } "TeamVetoedMapInfoMessage" { - "es" "{1} rechazó {LIGHT_RED}{2}" + "es" "{1} rechazó {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -242,7 +242,7 @@ } "ReadyToResumeVetoInfoMessage" { - "es" "Teclee {GREEN}!ready {NORMAL}cuando este listo para retomar la partida de veto." + "es" "Teclee {1} cuando este listo para retomar la partida de veto." } "MatchConfigLoadedInfoMessage" { @@ -298,6 +298,6 @@ } "VetoCountdown" { - "es" "El veto comenzará en {GREEN}{1} {NORMAL}segundos." + "es" "El veto comenzará en {1} segundos." } } diff --git a/translations/fr/get5.phrases.txt b/translations/fr/get5.phrases.txt index 28f76234d..d299f7cc9 100644 --- a/translations/fr/get5.phrases.txt +++ b/translations/fr/get5.phrases.txt @@ -2,23 +2,23 @@ { "ReadyToVetoInfoMessage" { - "fr" "Entrez {GREEN}!ready {NORMAL}quand votre équipe est prête pour le tour de veto." + "fr" "Entrez {1} quand votre équipe est prête pour le tour de veto." } "WaitingForCastersReadyInfoMessage" { - "fr" "En attente : les casters doivent entrer {GREEN}!ready {NORMAL}pour que le match commence." + "fr" "En attente : les casters doivent entrer {2} pour que le match commence." } "ReadyToRestoreBackupInfoMessage" { - "fr" "Entrez {GREEN}!ready {NORMAL}quand vous êtes prêt(e) à restorer la sauvegarde du match." + "fr" "Entrez {1} quand vous êtes prêt(e) à restorer la sauvegarde du match." } "ReadyToKnifeInfoMessage" { - "fr" "Entrez {GREEN}!ready {NORMAL}quand vous êtes prêt(e) à démarrer un round au couteau." + "fr" "Entrez {1} quand vous êtes prêt(e) à démarrer un round au couteau." } "ReadyToStartInfoMessage" { - "fr" "Entrez {GREEN}!ready {NORMAL}quand vous êtes prêt(e) à commencer." + "fr" "Entrez {1} quand vous êtes prêt(e) à commencer." } "YouAreReady" { @@ -30,7 +30,7 @@ } "WaitingForEnemySwapInfoMessage" { - "fr" "{1} a gagné le round au couteau. En attente de leur choix entre !stay et !swap." + "fr" "{1} a gagné le round au couteau. En attente de leur choix entre {2} et {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -94,7 +94,7 @@ } "WaitingForUnpauseInfoMessage" { - "fr" "{1} désire reprendre le match, en attente que {2} tape !unpause" + "fr" "{1} désire reprendre le match, en attente que {2} tape {3}." } "PausesLeftInfoMessage" { @@ -126,11 +126,11 @@ } "ForceReadyInfoMessage" { - "fr" "Vous pouvez entrer {GREEN}!forceready {NORMAL}pour forcer le statut de votre équipe si vous avez moins de {GREEN}{1}{NORMAL} joueurs." + "fr" "Vous pouvez entrer {1} pour forcer le statut de votre équipe si vous avez moins de {2} joueurs." } "TeammateForceReadied" { - "fr" "Votre équipe s'est déclaré prête (de force) par {GREEN}{1}." + "fr" "Votre équipe s'est déclaré prête (de force) par {1}." } "AdminForceReadyInfoMessage" { @@ -150,19 +150,19 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "fr" "{1} souhaite arrêter et recommencer au dernier round, cela nécessite que {2} confirme avec !stop." + "fr" "{1} souhaite arrêter et recommencer au dernier round, cela nécessite que {2} confirme avec {3}." } "TeamWinningSeriesInfoMessage" { - "fr" "{1} gagne la série avec {2}-{3}" + "fr" "{1} gagne la série avec {2}-{3}." } "SeriesTiedInfoMessage" { - "fr" "La série est ex-æquo avec {1}-{2}" + "fr" "La série est ex-æquo avec {1}-{2}." } "NextSeriesMapInfoMessage" { - "fr" "La prochaine map dans la série est {GREEN}{1}" + "fr" "La prochaine map dans la série est {1}." } "TeamWonMatchInfoMessage" { @@ -182,7 +182,7 @@ } "BackupLoadedInfoMessage" { - "fr" "Sauvegarde chargée avec succès {1}" + "fr" "Sauvegarde chargée avec succès {1}." } "MatchBeginInSecondsInfoMessage" { @@ -214,7 +214,7 @@ } "ChangingMapInfoMessage" { - "fr" "Changement de map pour {GREEN}{1}…" + "fr" "Changement de map pour {1}..." } "MapDecidedInfoMessage" { @@ -222,19 +222,19 @@ } "MapIsInfoMessage" { - "fr" "Map {1} : {GREEN}{2}" + "fr" "Map {1}: {2}" } "TeamPickedMapInfoMessage" { - "fr" "{1} a choisi {GREEN}{2} {NORMAL}comme map {3}}" + "fr" "{1} a choisi {2} comme map {3}." } "TeamSelectSideInfoMessage" { - "fr" "{1} a choisi de commencer en {GREEN}{2} {NORMAL}sur {3}" + "fr" "{1} a choisi de commencer en {2} sur {3}." } "TeamVetoedMapInfoMessage" { - "fr" "{1} a rejeté {LIGHT_RED}{2}" + "fr" "{1} a rejeté {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -242,7 +242,7 @@ } "ReadyToResumeVetoInfoMessage" { - "fr" "Entrez {GREEN}!ready {NORMAL}quand vous êtes prêt(e) à reprendre le tour de veto." + "fr" "Entrez {1} quand vous êtes prêt(e) à reprendre le tour de veto." } "MatchConfigLoadedInfoMessage" { @@ -298,6 +298,6 @@ } "VetoCountdown" { - "fr" "Le veto commencera dans {GREEN}{1} {NORMAL}secondes." + "fr" "Le veto commencera dans {1} secondes." } } diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 87bdc863a..680b0d121 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -2,24 +2,28 @@ { "ReadyToVetoInfoMessage" { - "en" "Type {GREEN}!ready {NORMAL}when your team is ready to veto." + "#format" "{1:s}" + "en" "Type {1} when your team is ready to veto." } "WaitingForCastersReadyInfoMessage" { - "#format" "{1:s}" - "en" "Waiting for {1} to type {GREEN}!ready {NORMAL}to begin." + "#format" "{1:s},{2:s}" + "en" "Waiting for {1} to type {2} to begin." } "ReadyToRestoreBackupInfoMessage" { - "en" "Type {GREEN}!ready {NORMAL}when you are ready to restore the match backup." + "#format" "{1:s}" + "en" "Type {1} when you are ready to restore the match backup." } "ReadyToKnifeInfoMessage" { - "en" "Type {GREEN}!ready {NORMAL}when you are ready to knife." + "#format" "{1:s}" + "en" "Type {1} when you are ready to knife." } "ReadyToStartInfoMessage" { - "en" "Type {GREEN}!ready {NORMAL}when you are ready to begin." + "#format" "{1:s}" + "en" "Type {1} when you are ready to begin." } "YouAreReady" { @@ -27,7 +31,8 @@ } "YouAreReadyAuto" { - "en" "NOTE: You have been marked as ready due to game activity. Type !unready if you are not ready." + "#format" "{1:s}" + "en" "NOTE: You have been marked as ready due to game activity. Type {1} if you are not ready." } "YouAreNotReady" { @@ -35,8 +40,8 @@ } "WaitingForEnemySwapInfoMessage" { - "#format" "{1:s}" - "en" "{1} won the knife round. Waiting for them to type !stay or !swap." + "#format" "{1:s},{2:s},{3:s}" + "en" "{1} won the knife round. Waiting for them to type {2} or {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -90,12 +95,12 @@ } "MatchPausedByTeamMessage" { - "#format" "{1:N}" + "#format" "{1:s}" "en" "{1} has called for a tactical pause." } "MatchTechPausedByTeamMessage" { - "#format" "{1:N}" + "#format" "{1:s}" "en" "{1} has called for a technical pause." } "PausesNotEnabled" @@ -149,7 +154,8 @@ } "TechPauseRunoutInfoMessage" { - "en" "Maximum technical pause length has been reached. Anyone may unpause now." + "#format" "{1:s}" + "en" "Maximum technical pause length has been reached. Anyone may {1} now." } "TechPauseNoTimeRemaining" { @@ -168,13 +174,13 @@ } "MatchUnpauseInfoMessage" { - "#format" "{1:N}" + "#format" "{1:s}" "en" "{1} unpaused the match." } "WaitingForUnpauseInfoMessage" { - "#format" "{1:s},{2:s}" - "en" "{1} wants to unpause, waiting for {2} to type !unpause." + "#format" "{1:s},{2:s},{3:s}" + "en" "{1} wants to unpause, waiting for {2} to type {3}." } "PausesLeftInfoMessage" { @@ -223,13 +229,13 @@ } "ForceReadyInfoMessage" { - "#format" "{1:d}" - "en" "You may type {GREEN}!forceready{NORMAL} to force-ready your team if you have less than {GREEN}{1}{NORMAL} players." + "#format" "{1:s},{2:s}" + "en" "You may type {1} to force-ready your team if you have less than {2} players." } "TeammateForceReadied" { - "#format" "{1:N}" - "en" "Your team was force-readied by {GREEN}{1}{NORMAL}." + "#format" "{1:s}" + "en" "Your team was force-readied by {1}." } "AdminForceReadyInfoMessage" { @@ -254,8 +260,8 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "#format" "{1:s},{2:s}" - "en" "{1} wants to stop and reload last round. {2} must confirm with !stop." + "#format" "{1:s},{2:s},{3:s}" + "en" "{1} wants to stop and reload last round. {2} must confirm with {3}." } "TeamWinningSeriesInfoMessage" { @@ -270,7 +276,7 @@ "NextSeriesMapInfoMessage" { "#format" "{1:s},{2:s}" - "en" "The next map in the series is {GREEN}{1}{NORMAL} and it will start in {2}." + "en" "The next map in the series is {1} and it will start in {2}." } "TeamWonMatchInfoMessage" { @@ -335,7 +341,7 @@ "ChangingMapInfoMessage" { "#format" "{1:s}" - "en" "Changing map to {GREEN}{1}..." + "en" "Changing map to {1}..." } "MapDecidedInfoMessage" { @@ -344,22 +350,22 @@ "MapIsInfoMessage" { "#format" "{1:d},{2:s}" - "en" "Map {1}: {GREEN}{2}{NORMAL}." + "en" "Map {1}: {2}" } "TeamPickedMapInfoMessage" { "#format" "{1:s},{2:s},{3:d}" - "en" "{1} picked {GREEN}{2}{NORMAL} as map {3}." + "en" "{1} picked {2} as map {3}." } "TeamSelectSideInfoMessage" { "#format" "{1:s},{2:s},{3:s}" - "en" "{1} has selected to start on {GREEN}{2}{NORMAL} on {3}." + "en" "{1} has selected to start on {2} on {3}." } "TeamVetoedMapInfoMessage" { "#format" "{1:s},{2:s}" - "en" "{1} vetoed {LIGHT_RED}{2}{NORMAL}." + "en" "{1} vetoed {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -367,7 +373,8 @@ } "ReadyToResumeVetoInfoMessage" { - "en" "Type {GREEN}!ready{NORMAL} when you are ready to resume the veto." + "#format" "{1:s}" + "en" "Type {1} when you are ready to resume the veto." } "MatchConfigLoadedInfoMessage" { @@ -427,7 +434,7 @@ } "VetoCountdown" { - "#format" "{1:i}" - "en" "Veto commencing in {GREEN}{1}{NORMAL} seconds." + "#format" "{1:s}" + "en" "Veto commencing in {1} seconds." } } diff --git a/translations/hu/get5.phrases.txt b/translations/hu/get5.phrases.txt index 308109ddf..877eb0282 100644 --- a/translations/hu/get5.phrases.txt +++ b/translations/hu/get5.phrases.txt @@ -2,23 +2,23 @@ { "ReadyToVetoInfoMessage" { - "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha a csapatod készen áll a vétóra." + "hu" "Írd be a {1} parancsot, ha a csapatod készen áll a vétóra." } "WaitingForCastersReadyInfoMessage" { - "hu" "A {1} várunk, hogy beírja a {GREEN}!ready{NORMAL} parancsot a kezdéshez." + "hu" "A {1} várunk, hogy beírja a {2} parancsot a kezdéshez." } "ReadyToRestoreBackupInfoMessage" { - "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha a készen állsz a mérkőzés visszatöltésre." + "hu" "Írd be a {1} parancsot, ha a készen állsz a mérkőzés visszatöltésre." } "ReadyToKnifeInfoMessage" { - "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha készen állsz a késelésre." + "hu" "Írd be a {1} parancsot, ha készen állsz a késelésre." } "ReadyToStartInfoMessage" { - "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot, ha készenállsz a kezdésre." + "hu" "Írd be a {1} parancsot, ha készenállsz a kezdésre." } "YouAreReady" { @@ -26,7 +26,7 @@ } "YouAreReadyAuto" { - "hu" "INFO: Felkészült állapotba kerültél a játék aktívitásod által. Írd be, hogy !unready ha nem állsz még készen." + "hu" "INFO: Felkészült állapotba kerültél a játék aktívitásod által. Írd be, hogy {1} ha nem állsz még készen." } "YouAreNotReady" { @@ -34,7 +34,7 @@ } "WaitingForEnemySwapInfoMessage" { - "hu" "{1} nyerte meg a kés kört. Várakozunk, hogy kiválasszák a megfelelő oldalt. {GREEN}!stay{NORMAL} vagy {GREEN}!swap{NORMAL}." + "hu" "{1} nyerte meg a kés kört. Várakozunk, hogy kiválasszák a megfelelő oldalt. {2} vagy {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -122,7 +122,7 @@ } "PausedForBackup" { - "hu" "A játék vissza lett töltve a kör mentésből. Mindkét csapatnak be kell írnia az {GREEN}!unpause{NORMAL} parancsot a folytatáshoz." + "hu" "A játék vissza lett töltve a kör mentésből. Mindkét csapatnak be kell írnia az {1} parancsot a folytatáshoz." } "UserCannotUnpauseAdmin" { @@ -158,7 +158,7 @@ } "WaitingForUnpauseInfoMessage" { - "hu" "{1} szeretné folytatni. Várakozunk a {2} csapatra, hogy beírják a {GREEN}!unpause{NORMAL} parancsot." + "hu" "{1} szeretné folytatni. Várakozunk a {2} csapatra, hogy beírják a {3} parancsot." } "PausesLeftInfoMessage" { @@ -198,11 +198,11 @@ } "ForceReadyInfoMessage" { - "hu" "Beírhatod a {GREEN}!forceready{NORMAL} parancsot, ha fel szeretnéd készíteni a csapatod, ha kevesebb mint {GREEN}{1}{NORMAL} játékosod van." + "hu" "Beírhatod a {1} parancsot, ha fel szeretnéd készíteni a csapatod, ha kevesebb mint {2} játékosod van." } "TeammateForceReadied" { - "hu" "A csapatod felkészült {GREEN}{1} által." + "hu" "A csapatod felkészült {1} által." } "AdminForceReadyInfoMessage" { @@ -222,19 +222,19 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "hu" "{1} szeretne megállni és visszatölteni az előző kört. {2} el kell fogadni a {GREEN}!stop{NORMAL} paranccsal." + "hu" "{1} szeretne megállni és visszatölteni az előző kört. {2} el kell fogadni a {3} paranccsal." } "TeamWinningSeriesInfoMessage" { - "hu" "{1} áll nyerésre {2}-{3}" + "hu" "{1} áll nyerésre {2}-{3}." } "SeriesTiedInfoMessage" { - "hu" "Az állás döntetlen. {1}-{2}" + "hu" "Az állás döntetlen. {1}-{2}." } "NextSeriesMapInfoMessage" { - "hu" "A következő pálya a szériában {GREEN}{1}" + "hu" "A következő pálya a szériában {1}." } "TeamWonMatchInfoMessage" { @@ -246,7 +246,7 @@ } "TeamWonSeriesInfoMessage" { - "hu" "{1} nyerte a szériát. {2}-{3}" + "hu" "{1} nyerte a szériát. {2}-{3}." } "MatchFinishedInfoMessage" { @@ -290,7 +290,7 @@ } "ChangingMapInfoMessage" { - "hu" "Pályaváltás a következőre {GREEN}{1}..." + "hu" "Pályaváltás a következőre {1}..." } "MapDecidedInfoMessage" { @@ -298,19 +298,19 @@ } "MapIsInfoMessage" { - "hu" "{1}. Pálya: {GREEN}{2}{NORMAL}" + "hu" "{1}. Pálya: {2}" } "TeamPickedMapInfoMessage" { - "hu" "{1} választotta a {GREEN}{2}{NORMAL} {3}. pályának." + "hu" "{1} választotta a {2} {3}. pályának." } "TeamSelectSideInfoMessage" { - "hu" "{1} választott, hogy kezd {GREEN}{2}{NORMAL} oldalon a(z) {3}. pályán." + "hu" "{1} választott, hogy kezd {2} oldalon a(z) {3}. pályán." } "TeamVetoedMapInfoMessage" { - "hu" "{1} szavazott {LIGHT_RED}{2}{NORMAL}." + "hu" "{1} szavazott {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -318,7 +318,7 @@ } "ReadyToResumeVetoInfoMessage" { - "hu" "Írd be a {GREEN}!ready{NORMAL} parancsot ha készenállsz a vétó folytatására." + "hu" "Írd be a {1} parancsot ha készenállsz a vétó folytatására." } "MatchConfigLoadedInfoMessage" { @@ -374,6 +374,6 @@ } "VetoCountdown" { - "hu" "Kezdődik a vétózás {GREEN}{1}{NORMAL} másodperc múlva." + "hu" "Kezdődik a vétózás {1} másodperc múlva." } } diff --git a/translations/pl/get5.phrases.txt b/translations/pl/get5.phrases.txt index c1f25ee41..2b6c09cc3 100644 --- a/translations/pl/get5.phrases.txt +++ b/translations/pl/get5.phrases.txt @@ -2,27 +2,27 @@ { "ReadyToVetoInfoMessage" { - "pl" "Napisz {GREEN}!ready {NORMAL}kiedy Twoja drużyna będzie gotowa do głosowania." + "pl" "Napisz {1} kiedy Twoja drużyna będzie gotowa do głosowania." } "WaitingForCastersReadyInfoMessage" { - "pl" "Czekamy na komentatorów na wpisanie {GREEN}!ready {NORMAL}aby zacząć rozgrywkę." + "pl" "Czekamy na komentatorów na wpisanie {2} aby zacząć rozgrywkę." } "ReadyToRestoreBackupInfoMessage" { - "pl" "Napisz {GREEN}!ready {NORMAL}kiedy Twoja drużyna będzie gotowa do wczytania meczu." + "pl" "Napisz {1} kiedy Twoja drużyna będzie gotowa do wczytania meczu." } "ReadyToKnifeInfoMessage" { - "pl" "Napisz {GREEN}!ready {NORMAL}kiedy Twoja drużyna będzie gotowa do rundy nożowej." + "pl" "Napisz {1} kiedy Twoja drużyna będzie gotowa do rundy nożowej." } "ReadyToStartInfoMessage" { - "pl" "Napisz {GREEN}!ready {NORMAL}kiedy Twoja drużyna będzie gotowa." + "pl" "Napisz {1} kiedy Twoja drużyna będzie gotowa." } "WaitingForEnemySwapInfoMessage" { - "pl" "{1} wygralo runde nożową. Oczekiwanie na !stay lub !swap." + "pl" "{1} wygralo runde nożową. Oczekiwanie na {2} lub {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -78,7 +78,7 @@ } "WaitingForUnpauseInfoMessage" { - "pl" "{1} chce wznowić rozgrywkę, oczekiwanie na {2} na wpisanie !unpause." + "pl" "{1} chce wznowić rozgrywkę, oczekiwanie na {2} na wpisanie {3}." } "PausesLeftInfoMessage" { @@ -126,19 +126,19 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "pl" "{1} chce zatrzymać mecz i wczytać poprzednią rundę, oczekiwanie na potwierdzenie przez {2} i wpisanie !stop." + "pl" "{1} chce zatrzymać mecz i wczytać poprzednią rundę, oczekiwanie na potwierdzenie przez {2} i wpisanie {3}." } "TeamWinningSeriesInfoMessage" { - "pl" "{1} wygrywa {2}-{3}" + "pl" "{1} wygrywa {2}-{3}." } "SeriesTiedInfoMessage" { - "pl" "Drużyny remisują {1}-{2}" + "pl" "Drużyny remisują {1}-{2}." } "NextSeriesMapInfoMessage" { - "pl" "Następna mapa w meczu to {GREEN}{1}" + "pl" "Następna mapa w meczu to {1}." } "TeamWonMatchInfoMessage" { @@ -154,7 +154,7 @@ } "BackupLoadedInfoMessage" { - "pl" "Pomyślnie wczytano kopię meczu {1}" + "pl" "Pomyślnie wczytano kopię meczu {1}." } "MatchBeginInSecondsInfoMessage" { @@ -186,7 +186,7 @@ } "ChangingMapInfoMessage" { - "pl" "Zmiana mapy na {GREEN}{1}..." + "pl" "Zmiana mapy na {1}..." } "MapDecidedInfoMessage" { @@ -194,19 +194,19 @@ } "MapIsInfoMessage" { - "pl" "Mapa {1}: {GREEN}{2}" + "pl" "Mapa {1}: {2}" } "TeamPickedMapInfoMessage" { - "pl" "{1} wybrało {GREEN}{2} {NORMAL}jako mapę {3}" + "pl" "{1} wybrało {2} jako mapę {3}." } "TeamSelectSideInfoMessage" { - "pl" "{1} wybrało stronę {GREEN}{2} {NORMAL}na {3}" + "pl" "{1} wybrało stronę {2} na {3}." } "TeamVetoedMapInfoMessage" { - "pl" "{1} odrzuciło {LIGHT_RED}{2}" + "pl" "{1} odrzuciło {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -214,7 +214,7 @@ } "ReadyToResumeVetoInfoMessage" { - "pl" "Napisz {GREEN}!ready {NORMAL}kiedy Twoja drużyna będzie gotowa do wznowienia głosowania." + "pl" "Napisz {1} kiedy Twoja drużyna będzie gotowa do wznowienia głosowania." } "MatchConfigLoadedInfoMessage" { diff --git a/translations/pt/get5.phrases.txt b/translations/pt/get5.phrases.txt index dd6c33638..49fdecb66 100644 --- a/translations/pt/get5.phrases.txt +++ b/translations/pt/get5.phrases.txt @@ -2,35 +2,35 @@ { "ReadyToVetoInfoMessage" { - "pt" "Digite {GREEN}!ready {NORMAL}quando seu time estiver pronto para o veto." + "pt" "Digite {1} quando seu time estiver pronto para o veto." } "WaitingForCastersReadyInfoMessage" { - "pt" "Esperando pelos streammers para digitar {GREEN}!ready {NORMAL}pra começar." + "pt" "Esperando pelos streammers para digitar {2} pra começar." } "ReadyToRestoreBackupInfoMessage" { - "pt" "Digite {GREEN}!ready {NORMAL}quando vocês estiver pronto para restaurar o backup da partida." + "pt" "Digite {1} quando vocês estiver pronto para restaurar o backup da partida." } "ReadyToKnifeInfoMessage" { - "pt" "Digite {GREEN}!ready {NORMAL}quando você estiver pronto pro Round Faca." + "pt" "Digite {1} quando você estiver pronto pro Round Faca." } "ReadyToStartInfoMessage" { - "pt" "Digite {GREEN}!ready {NORMAL}quando você estiver pronto para começar." + "pt" "Digite {1} quando você estiver pronto para começar." } "YouAreReady" { - "pt" "Você foi marcado como {GREEN}pronto{NORMAL}." + "pt" "Você foi marcado como pronto." } "YouAreNotReady" { - "pt" "Você foi desmarcado como {GREEN}pronto{NORMAL}." + "pt" "Você foi desmarcado como pronto." } "WaitingForEnemySwapInfoMessage" { - "pt" "{1} venceu o Round Faca. Esperando pelo time vencedor digitar !stay ou !swap." + "pt" "{1} venceu o Round Faca. Esperando pelo time vencedor digitar {2} ou {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -86,7 +86,7 @@ } "WaitingForUnpauseInfoMessage" { - "pt" "{1} quer resumir a partida, mas esperando por {2} digitar !unpause." + "pt" "{1} quer resumir a partida, mas esperando por {2} digitar {3}." } "PausesLeftInfoMessage" { @@ -118,11 +118,11 @@ } "ForceReadyInfoMessage" { - "pt" "Você pode digitar {GREEN}!forceready {NORMAL}para forçar seu time a ficar pronto se você tiver menos de {GREEN}{1}{NORMAL} jogador." + "pt" "Você pode digitar {1} para forçar seu time a ficar pronto se você tiver menos de {2} jogador." } "TeammateForceReadied" { - "pt" "Seu time foi forçado a ficar pronto pelo jogador {GREEN}{1}" + "pt" "Seu time foi forçado a ficar pronto pelo jogador {1}." } "AdminForceReadyInfoMessage" { @@ -142,19 +142,19 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "pt" "{1} quer parar e recomeçar o último round. Para isso precisa que outros {2} confirmem digitanto !stop." + "pt" "{1} quer parar e recomeçar o último round. Para isso precisa que outros {2} confirmem digitanto {3}." } "TeamWinningSeriesInfoMessage" { - "pt" "{1} está vencendo a série {2}-{3}" + "pt" "{1} está vencendo a série {2}-{3}." } "SeriesTiedInfoMessage" { - "pt" "A série está empatada em {1}-{2}" + "pt" "A série está empatada em {1}-{2}." } "NextSeriesMapInfoMessage" { - "pt" "O próximo mapa da série será {GREEN}{1}" + "pt" "O próximo mapa da série será {1}." } "TeamWonMatchInfoMessage" { @@ -166,11 +166,11 @@ } "MatchFinishedInfoMessage" { - "pt" "A partida foi fnializada." + "pt" "A partida foi fnializada" } "BackupLoadedInfoMessage" { - "pt" "Backup carregado com sucesso {1}" + "pt" "Backup carregado com sucesso {1}." } "MatchBeginInSecondsInfoMessage" { @@ -178,7 +178,7 @@ } "MatchIsLiveInfoMessage" { - "pt" "{GREEN}Começou a partida! Boa diversão e boa sorte!" + "pt" "A partida é {GREEN}LIVE" } "KnifeIn5SecInfoMessage" { @@ -202,7 +202,7 @@ } "ChangingMapInfoMessage" { - "pt" "Mudando o mapa para {GREEN}{1}..." + "pt" "Mudando o mapa para {1}..." } "MapDecidedInfoMessage" { @@ -210,19 +210,19 @@ } "MapIsInfoMessage" { - "pt" "Mapa {1}: {GREEN}{2}" + "pt" "Mapa {1}: {2}" } "TeamPickedMapInfoMessage" { - "pt" "{1} escolheu {GREEN}{2} {NORMAL}como mapa {3}" + "pt" "{1} escolheu {2} como mapa {3}." } "TeamSelectSideInfoMessage" { - "pt" "{1} escolheu começar no lado {GREEN}{2} {NORMAL}em {3}" + "pt" "{1} escolheu começar no lado {2} em {3}." } "TeamVetoedMapInfoMessage" { - "pt" "{1} vetou {LIGHT_RED}{2}" + "pt" "{1} vetou {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -230,7 +230,7 @@ } "ReadyToResumeVetoInfoMessage" { - "pt" "Digite {GREEN}!ready {NORMAL}quando você estiver pronto para resumir o veto." + "pt" "Digite {1} quando você estiver pronto para resumir o veto." } "MatchConfigLoadedInfoMessage" { diff --git a/translations/ru/get5.phrases.txt b/translations/ru/get5.phrases.txt index 61645a7a3..44d91619f 100644 --- a/translations/ru/get5.phrases.txt +++ b/translations/ru/get5.phrases.txt @@ -2,27 +2,27 @@ { "ReadyToVetoInfoMessage" { - "ru" "Напишите {GREEN}!ready {NORMAL}когда ваша команда будет готова к голосованию." + "ru" "Напишите {1} когда ваша команда будет готова к голосованию." } "WaitingForCastersReadyInfoMessage" { - "ru" "Ожидаем, пока все игроки напишут {GREEN}!ready {NORMAL}для начала." + "ru" "Ожидаем, пока все игроки напишут {2} для начала." } "ReadyToRestoreBackupInfoMessage" { - "ru" "Напиши {GREEN}!ready {NORMAL}когда твоя команда будет готова загрузить сохраненную игру." + "ru" "Напиши {1} когда твоя команда будет готова загрузить сохраненную игру." } "ReadyToKnifeInfoMessage" { - "ru" "Напиши {GREEN}!ready {NORMAL}когда твоя команда будет готова к ножевому раунду." + "ru" "Напиши {1} когда твоя команда будет готова к ножевому раунду." } "ReadyToStartInfoMessage" { - "ru" "Напиши {GREEN}!ready {NORMAL}когда твоя команда будет готова начать." + "ru" "Напиши {1} когда твоя команда будет готова начать." } "WaitingForEnemySwapInfoMessage" { - "ru" "{1} выиграла раунд. Ожидаем, пока они напишут !stay или !swap." + "ru" "{1} выиграла раунд. Ожидаем, пока они напишут {2} или {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -78,7 +78,7 @@ } "WaitingForUnpauseInfoMessage" { - "ru" "{1} желает снять паузу, ожидаем когда {2} напишут !unpause." + "ru" "{1} желает снять паузу, ожидаем когда {2} напишут {3}." } "TeamReadyToVetoInfoMessage" { @@ -118,19 +118,19 @@ } "TeamWantsToReloadLastRoundInfoMessage" { - "ru" "{1} желает остановить и перезагрузить последний раунд, ждем {2} чтобы написали !stop для подтверждения." + "ru" "{1} желает остановить и перезагрузить последний раунд, ждем {2} чтобы написали {3} для подтверждения." } "TeamWinningSeriesInfoMessage" { - "ru" "{1} выигрывает со счетом {2}-{3}" + "ru" "{1} выигрывает со счетом {2}-{3}." } "SeriesTiedInfoMessage" { - "ru" "Матч остановлен на счете {1}-{2}" + "ru" "Матч остановлен на счете {1}-{2}." } "NextSeriesMapInfoMessage" { - "ru" "Следующая карта в серии {GREEN}{1}" + "ru" "Следующая карта в серии {1}." } "TeamWonMatchInfoMessage" { @@ -146,7 +146,7 @@ } "BackupLoadedInfoMessage" { - "ru" "Удачно загружен бэкап {1}" + "ru" "Удачно загружен бэкап {1}." } "MatchBeginInSecondsInfoMessage" { @@ -154,7 +154,7 @@ } "MatchIsLiveInfoMessage" { - "ru" "Матч {GREEN}НАЧАЛСЯ!" + "ru" "Матч {GREEN}НАЧАЛСЯ" } "KnifeIn5SecInfoMessage" { @@ -178,7 +178,7 @@ } "ChangingMapInfoMessage" { - "ru" "Карта изменена на {GREEN}{1}..." + "ru" "Карта изменена на {1}..." } "MapDecidedInfoMessage" { @@ -186,19 +186,19 @@ } "MapIsInfoMessage" { - "ru" "Карта {1}: {GREEN}{2}" + "ru" "Карта {1}: {2}" } "TeamPickedMapInfoMessage" { - "ru" "{1} выбрал {GREEN}{2} {NORMAL}как карту {3}" + "ru" "{1} выбрал {2} как карту {3}." } "TeamSelectSideInfoMessage" { - "ru" "{1} выбрали начать за {GREEN}{2} {NORMAL}на {3}" + "ru" "{1} выбрали начать за {2} на {3}." } "TeamVetoedMapInfoMessage" { - "ru" "{1} вычеркнули {LIGHT_RED}{2}" + "ru" "{1} вычеркнули {2}." } "CaptainLeftOnVetoInfoMessage" { @@ -206,7 +206,7 @@ } "ReadyToResumeVetoInfoMessage" { - "ru" "Напишите {GREEN}!ready {NORMAL}, когда будете готовы продолжить голосование." + "ru" "Напишите {1}, когда будете готовы продолжить голосование." } "MatchConfigLoadedInfoMessage" { From 7f878393aa696faa2b1dcbe789f1a37d19aee289 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 13 Aug 2022 20:00:09 +0200 Subject: [PATCH 029/104] Add util for default round win reason Fixed correct callout and graphics for winning team when knife-round ends Get rid of Timer_PostKnife Always reset g_HasKnifeRoundStarted when loading configs or maps --- scripting/get5.sp | 72 +++++++++++++++++++++++------------ scripting/get5/kniferounds.sp | 8 ++-- scripting/get5/matchconfig.sp | 1 + scripting/get5/util.sp | 8 ++++ 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 2a0ef8e9e..94f8d8606 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -529,10 +529,11 @@ public void OnPluginStart() { /** Hooks **/ HookEvent("cs_win_panel_match", Event_MatchOver); + HookEvent("cs_win_panel_round", Event_RoundWinPanel, EventHookMode_Pre); HookEvent("player_connect_full", Event_PlayerConnectFull); HookEvent("player_disconnect", Event_PlayerDisconnect); HookEvent("player_spawn", Event_PlayerSpawn); - HookEvent("round_end", Event_RoundEnd); + HookEvent("round_end", Event_RoundEnd, EventHookMode_Pre); HookEvent("round_freeze_end", Event_FreezeEnd); HookEvent("round_prestart", Event_RoundPreStart); HookEvent("round_start", Event_RoundStart); @@ -813,6 +814,7 @@ public void OnMapStart() { } g_ReadyTimeWaitingUsed = 0; + g_HasKnifeRoundStarted = false; if (g_WaitingForRoundBackup) { ChangeState(Get5State_Warmup); @@ -1373,6 +1375,15 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad ChangeState(Get5State_Live); } + if (g_GameState == Get5State_WaitingForKnifeRoundDecision && !InWarmup()) { + // Ensures that round end after knife sends players directly into warmup. + // This immediately triggers another Event_RoundPreStart, so we can return here and avoid writing backup twice. + LogDebug("Changed to warmup post knife."); + ExecCfg(g_WarmupCfgCvar); + EnsureIndefiniteWarmup(); + return Plugin_Continue; + } + Stats_ResetRoundValues(); // We need this for events that fire after the map ends, such as grenades detonating (or someone @@ -1387,6 +1398,7 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad if (g_GameState >= Get5State_Warmup) { WriteBackup(); } + return Plugin_Continue; } public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast) { @@ -1497,21 +1509,19 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas EventLogger_LogAndDeleteEvent(startEvent); } -public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { - LogDebug("Event_RoundEnd"); - if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { - return; - } - +public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroadcast) { + LogDebug("Event_RoundWinPanel"); if (g_GameState == Get5State_KnifeRound && g_HasKnifeRoundStarted) { g_HasKnifeRoundStarted = false; ChangeState(Get5State_WaitingForKnifeRoundDecision); - CreateTimer(1.0, Timer_PostKnife); + if (g_KnifeChangedCvars != INVALID_HANDLE) { + RestoreCvars(g_KnifeChangedCvars, true); + } int ctAlive = CountAlivePlayersOnTeam(CS_TEAM_CT); int tAlive = CountAlivePlayersOnTeam(CS_TEAM_T); - int winningCSTeam = CS_TEAM_NONE; + int winningCSTeam; if (ctAlive > tAlive) { winningCSTeam = CS_TEAM_CT; } else if (tAlive > ctAlive) { @@ -1524,11 +1534,8 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) } else if (tHealth > ctHealth) { winningCSTeam = CS_TEAM_T; } else { - if (GetRandomFloat(0.0, 1.0) < 0.5) { - winningCSTeam = CS_TEAM_CT; - } else { - winningCSTeam = CS_TEAM_T; - } + winningCSTeam = GetRandomFloat(0.0, 1.0) < 0.5 ? CS_TEAM_CT : CS_TEAM_T; + LogDebug("Randomized knife winner to side %d", winningCSTeam); } } @@ -1539,8 +1546,31 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, formattedSwapCommand); - if (g_TeamTimeToKnifeDecisionCvar.FloatValue > 0) - CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision); + if (g_TeamTimeToKnifeDecisionCvar.FloatValue > 0) { + CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision, TIMER_FLAG_NO_MAPCHANGE); + } + + // This ensures that the correct graphic is displayed in-game for the winning team, as CTs will always win if the + // clock runs out. It also ensures that the reason displayed is correct, i.e. just "win" and no "won because clock + // ran down". + event.SetInt("final_event", ConvertCSTeamToDefaultWinReason(winningCSTeam)); + } + return Plugin_Continue; +} + +public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { + LogDebug("Event_RoundEnd"); + if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + return Plugin_Continue; + } + + if (g_GameState == Get5State_WaitingForKnifeRoundDecision && g_KnifeWinnerTeam != Get5Team_None) { + int winningCSTeam = Get5TeamToCSTeam(g_KnifeWinnerTeam); + // Event_RoundWinPanel is called before Event_RoundEnd, so that event handles knife winner. + // We override this event only to have the correct audio callout in the game. + event.SetInt("winner", winningCSTeam); + event.SetInt("reason", ConvertCSTeamToDefaultWinReason(winningCSTeam)); + return Plugin_Continue; } if (g_GameState == Get5State_Live) { @@ -1623,6 +1653,7 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) EventLogger_LogAndDeleteEvent(roundEndEvent); } + return Plugin_Continue; } public void SwapSides() { @@ -1677,15 +1708,6 @@ public void StartGame(bool knifeRound) { } } -public Action Timer_PostKnife(Handle timer) { - if (g_KnifeChangedCvars != INVALID_HANDLE) { - RestoreCvars(g_KnifeChangedCvars, true); - } - - ExecCfg(g_WarmupCfgCvar); - EnsureIndefiniteWarmup(); -} - public void ChangeState(Get5State state) { g_GameStateCvar.IntValue = view_as(state); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index 304756d39..6f1039ba5 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -85,6 +85,8 @@ public void EndKnifeRound(bool swap) { ChangeState(Get5State_GoingLive); CreateTimer(3.0, StartGoingLive, _, TIMER_FLAG_NO_MAPCHANGE); + + g_KnifeWinnerTeam = Get5Team_None; } static bool AwaitingKnifeDecision(int client) { @@ -96,18 +98,18 @@ static bool AwaitingKnifeDecision(int client) { public Action Command_Stay(int client, int args) { if (AwaitingKnifeDecision(client)) { - EndKnifeRound(false); Get5_MessageToAll("%t", "TeamDecidedToStayInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); + EndKnifeRound(false); } return Plugin_Handled; } public Action Command_Swap(int client, int args) { if (AwaitingKnifeDecision(client)) { - EndKnifeRound(true); Get5_MessageToAll("%t", "TeamDecidedToSwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); + EndKnifeRound(true); } else if (g_GameState == Get5State_Warmup && g_InScrimMode && GetClientMatchTeam(client) == Get5Team_1) { PerformSideSwap(true); @@ -142,8 +144,8 @@ public Action Command_T(int client, int args) { public Action Timer_ForceKnifeDecision(Handle timer) { if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { - EndKnifeRound(false); Get5_MessageToAll("%t", "TeamLostTimeToDecideInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); + EndKnifeRound(false); } } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index e2ad068d8..52e1c9e8f 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -37,6 +37,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } g_ReadyTimeWaitingUsed = 0; + g_HasKnifeRoundStarted = false; g_MapNumber = 0; g_RoundNumber = -1; diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index a6ae30b9e..28ce79189 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -61,6 +61,12 @@ stock int SumHealthOfTeam(int team) { return sum; } +stock int ConvertCSTeamToDefaultWinReason(int side) { + // This maps to https://github.com/VSES/SourceEngine2007/blob/master/se2007/game/shared/cstrike/cs_gamerules.h, which + // is the regular CSRoundEndReason + 1. + return view_as(side == CS_TEAM_CT ? CSRoundEnd_CTWin : CSRoundEnd_TerroristWin) + 1; +} + /** * Switches and respawns a player onto a new team. */ @@ -227,8 +233,10 @@ stock bool InFreezeTime() { stock void EnsureIndefiniteWarmup() { if (!InWarmup()) { + LogDebug("EnsureIndefiniteWarmup: Not in warmup; calling StartWarmup()"); StartWarmup(); } else { + LogDebug("EnsureIndefiniteWarmup: Already in warmup; setting indefinite"); ServerCommand("mp_warmup_pausetimer 1"); ServerCommand("mp_do_warmup_period 1"); ServerCommand("mp_warmup_pausetimer 1"); From 11aa8f6d951921e5ae1e6d3edab9a5fc58d8ff48 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 13 Aug 2022 22:08:43 +0200 Subject: [PATCH 030/104] Function visibility and line spaces --- scripting/get5/recording.sp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scripting/get5/recording.sp b/scripting/get5/recording.sp index 1b7d07061..765a55d11 100644 --- a/scripting/get5/recording.sp +++ b/scripting/get5/recording.sp @@ -1,4 +1,4 @@ -stock bool StartRecording() { +bool StartRecording() { if (!IsTVEnabled()) { LogError("Demo recording will not work with \"tv_enable 0\". Set \"tv_enable 1\" and restart the map to fix this."); g_DemoFileName = ""; @@ -23,7 +23,7 @@ stock bool StartRecording() { return true; } -stock void StopRecording(bool forceStop = false) { +void StopRecording(bool forceStop = false) { if (!IsTVEnabled()) { LogDebug("Cannot stop recording as GOTV is not enabled."); return; @@ -66,7 +66,6 @@ static void StopRecordingCallback(char[] matchId, int mapNumber, char[] demoFile } public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { - char matchId[MATCH_ID_LENGTH]; char demoFileName[PLATFORM_MAX_PATH]; pack.Reset(); @@ -80,7 +79,6 @@ public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { } public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) { - char matchId[MATCH_ID_LENGTH]; char demoFileName[PLATFORM_MAX_PATH]; pack.Reset(); @@ -98,7 +96,7 @@ public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) { return Plugin_Handled; } -stock bool IsTVEnabled() { +bool IsTVEnabled() { ConVar tvEnabledCvar = FindConVar("tv_enable"); if (tvEnabledCvar == null) { LogError("Failed to get tv_enable cvar"); @@ -116,7 +114,7 @@ stock bool IsTVEnabled() { return false; } -stock int GetTvDelay() { +int GetTvDelay() { if (IsTVEnabled()) { return GetCvarIntSafe("tv_delay"); } From 45f22355c7335c9387154a80f629a31f576141be Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 13 Aug 2022 22:10:05 +0200 Subject: [PATCH 031/104] Bump version --- scripting/get5/version.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripting/get5/version.sp b/scripting/get5/version.sp index 73a92935b..8b9e2220a 100644 --- a/scripting/get5/version.sp +++ b/scripting/get5/version.sp @@ -1,6 +1,6 @@ #tryinclude "manual_version.sp" #if !defined PLUGIN_VERSION -#define PLUGIN_VERSION "0.9.0-dev" +#define PLUGIN_VERSION "0.10.0-dev" #endif // This MUST be the latest version in x.y.z semver format followed by -dev. From b3126844637c5d02a3bed9df9087a8e82d5041ed Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 13 Aug 2022 22:43:18 +0200 Subject: [PATCH 032/104] Minor doc adjustment --- documentation/docs/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 528abc076..853cf1e35 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -94,7 +94,7 @@ You should put the `url` argument inside quotation marks (`""`). Loading remote matches requires the [SteamWorks](../installation/#steamworks) extension. -####`get5_endmatch [team1|team2]` +####`get5_endmatch [team1|team2]` {: #get5_endmatch } : Force-ends the current match. The team argument will force the winner of the series and the current map to be set to that team. Omitting the team argument sets no winner (tie). From 89c470a437ad594a361c3e4dc38d4d75aeb3b413 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 13 Aug 2022 22:53:24 +0200 Subject: [PATCH 033/104] Add GOLD to color substitute doc --- documentation/docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index e848b83fd..833abe2cb 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -288,3 +288,4 @@ Example: `This text becomes {DARK_RED}red{NORMAL}, while {YELLOW}all of this wil - `{LIGHT_BLUE}` - `{DARK_BLUE}` - `{PURPLE}` +- `{GOLD}` From 5a968bda7fddf9575c232731ed6f69714823d42f Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 16:13:06 +0200 Subject: [PATCH 034/104] Don't allow !ready command when map change is pending --- scripting/get5/matchconfig.sp | 2 +- scripting/get5/readysystem.sp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 52e1c9e8f..ed3c62052 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -38,7 +38,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { g_ReadyTimeWaitingUsed = 0; g_HasKnifeRoundStarted = false; - + g_MapChangePending = false; g_MapNumber = 0; g_RoundNumber = -1; g_LastVetoTeam = Get5Team_2; diff --git a/scripting/get5/readysystem.sp b/scripting/get5/readysystem.sp index 3fea5ee67..39042b28b 100644 --- a/scripting/get5/readysystem.sp +++ b/scripting/get5/readysystem.sp @@ -8,7 +8,7 @@ public void ResetReadyStatus() { } public bool IsReadyGameState() { - return g_GameState == Get5State_PreVeto || g_GameState == Get5State_Warmup; + return (g_GameState == Get5State_PreVeto || g_GameState == Get5State_Warmup) && !g_MapChangePending; } // Client ready status From e919ab2ff3cdf3a0bd1fe0a286313cd610bf0e5f Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 17:10:41 +0200 Subject: [PATCH 035/104] Add configuration parameter for number of knife/live phase announcements in chat (#833) Hardcode Powered by Get5 Add additional authors --- documentation/docs/configuration.md | 7 +++++-- documentation/docs/translations.md | 1 - scripting/get5.sp | 6 +++++- scripting/get5/goinglive.sp | 22 +++++++++++++--------- scripting/get5/kniferounds.sp | 4 +--- scripting/get5/util.sp | 11 +++++++++++ translations/chi/get5.phrases.txt | 4 ---- translations/da/get5.phrases.txt | 4 ---- translations/es/get5.phrases.txt | 4 ---- translations/fr/get5.phrases.txt | 4 ---- translations/get5.phrases.txt | 4 ---- translations/hu/get5.phrases.txt | 4 ---- translations/pt/get5.phrases.txt | 4 ---- 13 files changed, 35 insertions(+), 44 deletions(-) diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 833abe2cb..cd6f682a0 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -134,9 +134,12 @@ if [`get5_print_damage`](#get5_print_damage) is disabled. - [-] (30 in 1) to [-] (0 in 0) from Player5 (0 HP) # - dealt damage to this player, not enough for assist ``` +####`get5_phase_announcement_count` +: The number of times the "Knife" or "Match is LIVE" announcements will be printed in chat. Set to zero to disable. +**`Default: 5`** + ####`get5_message_prefix` -: The tag applied before plugin messages. If you change this variable, `Powered by Get5` will be printed when the game -goes live. **`Default: Get5`** +: The tag applied before plugin messages. **`Default: Get5`** ## Pausing diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index c0a4ecde7..9a25cb1ce 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -160,7 +160,6 @@ end with a full stop as this is added automatically. | `MoveToCoachInfoMessage` | You were moved to the coach position because your team is full. | Chat | | `ReadyTag` | **[READY]** PlayerName: Hey, I'm ready... | Chat | | `NotReadyTag` | **[NOT READY]** PlayerName: Hey, I'm not ready... | Chat | -| `MatchPoweredBy` | Powered by Get5 | Chat | | `MapVetoPickMenuText` | Select a map to PLAY: | Menu | | `MapVetoPickConfirmMenuText` | Confirm you want to PLAY _de_nuke_: | Menu | | `MapVetoBanMenuText` | Select a map to VETO: | Menu | diff --git a/scripting/get5.sp b/scripting/get5.sp index 94f8d8606..43e64219f 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -100,6 +100,7 @@ ConVar g_VetoCountdownCvar; ConVar g_WarmupCfgCvar; ConVar g_PrintUpdateNoticeCvar; ConVar g_RoundBackupPathCvar; +ConVar g_PhaseAnnouncementCountCvar; // Autoset convars (not meant for users to set) ConVar g_GameStateCvar; @@ -298,7 +299,7 @@ Handle g_OnSidePicked = INVALID_HANDLE; // clang-format off public Plugin myinfo = { name = "Get5", - author = "splewis", + author = "splewis, nickdnk & PhlexPlexico", description = "", version = PLUGIN_VERSION, url = "https://github.com/splewis/get5" @@ -435,6 +436,9 @@ public void OnPluginStart() { g_RoundBackupPathCvar = CreateConVar( "get5_backup_path", "", "The folder to save backup files in, relative to the csgo directory. If defined, it must not start with a slash and must end with a slash."); + g_PhaseAnnouncementCountCvar = CreateConVar( + "get5_phase_announcement_count", "5", + "The number of times Get5 will print 'Knife' or 'Match is LIVE' when the game starts. Set to 0 to disable."); /** Create and exec plugin's configuration file **/ AutoExecConfig(true, "get5"); diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index 172c9bfdd..afffe0f9a 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -47,15 +47,7 @@ public Action MatchLive(Handle timer) { ExecuteMatchConfigCvars(); g_PendingSideSwap = false; - for (int i = 0; i < 5; i++) { - Get5_MessageToAll("%t", "MatchIsLiveInfoMessage"); - } - - char tag[64]; - g_MessagePrefixCvar.GetString(tag, sizeof(tag)); - if (!StrEqual(tag, DEFAULT_TAG)) { - Get5_MessageToAll("%t", "MatchPoweredBy"); - } + announcePhaseChange("%t", "MatchIsLiveInfoMessage"); if (!g_PrintUpdateNoticeCvar.BoolValue) { return Plugin_Handled; @@ -69,5 +61,17 @@ public Action MatchLive(Handle timer) { Get5_MessageToAll("%t", "NewVersionAvailable", GET5_GITHUB_PAGE); } + /** + * Please do not change this. Thousands of uncompensated hours were poured into making this plugin. + * Claiming it as your own because you made slight modifications to it is not cool. If you have suggestions, + * bug reports or feature requests, please see GitHub or join our Discord: https://splewis.github.io/get5/community/ + * Thanks in advance! + */ + char tag[64]; + g_MessagePrefixCvar.GetString(tag, sizeof(tag)); + if (!StrEqual(tag, DEFAULT_TAG)) { + Get5_MessageToAll("Powered by {YELLOW}Get5"); + } + return Plugin_Handled; } diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index 6f1039ba5..ece9926b4 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -14,9 +14,7 @@ public Action StartKnifeRound(Handle timer) { } public Action Timer_AnnounceKnife(Handle timer) { - for (int i = 0; i < 5; i++) { - Get5_MessageToAll("{GREEN}%t", "KnifeInfoMessage"); - } + announcePhaseChange("{GREEN}%t", "KnifeInfoMessage"); Get5KnifeRoundStartedEvent knifeEvent = new Get5KnifeRoundStartedEvent(g_MatchID, g_MapNumber); diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 28ce79189..0bad6dab6 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -219,6 +219,17 @@ stock void ReplaceStringWithInt(char[] buffer, int len, const char[] replace, in ReplaceString(buffer, len, replace, intString, caseSensitive); } +stock void announcePhaseChange(const char[] format, const char[] message) { + int count = g_PhaseAnnouncementCountCvar.IntValue; + if (count > 10) { + count = 10; + } else if (count > 0) { + for (int i = 0; i < count; i++) { + Get5_MessageToAll(format, message); + } + } +} + stock bool InWarmup() { return GameRules_GetProp("m_bWarmupPeriod") != 0; } diff --git a/translations/chi/get5.phrases.txt b/translations/chi/get5.phrases.txt index cc734a984..76a183b35 100644 --- a/translations/chi/get5.phrases.txt +++ b/translations/chi/get5.phrases.txt @@ -256,10 +256,6 @@ { "chi" "[未准备]" } - "MatchPoweredBy" - { - "chi" "由{YELLOW}Get5{NORMAL}强力驱动" - } "MapVetoPickMenuText" { "chi" "请选择一张想玩的地图:" diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 495262b80..3f826317e 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -332,10 +332,6 @@ { "da" "[IKKE KLAR]" } - "MatchPoweredBy" - { - "da" "Drevet af {YELLOW}Get5" - } "MapVetoPickMenuText" { "da" "Vælg et map at spille:" diff --git a/translations/es/get5.phrases.txt b/translations/es/get5.phrases.txt index 200406cef..1f77e6164 100644 --- a/translations/es/get5.phrases.txt +++ b/translations/es/get5.phrases.txt @@ -260,10 +260,6 @@ { "es" "[NO LISTO]" } - "MatchPoweredBy" - { - "es" "Gestionado con {YELLOW}Get5" - } "MapVetoPickMenuText" { "es" "Seleccionar una mapa para el juego:" diff --git a/translations/fr/get5.phrases.txt b/translations/fr/get5.phrases.txt index d299f7cc9..db8355ea8 100644 --- a/translations/fr/get5.phrases.txt +++ b/translations/fr/get5.phrases.txt @@ -260,10 +260,6 @@ { "fr" "[PAS PRÊT]" } - "MatchPoweredBy" - { - "fr" "Powered by {YELLOW}Get5" - } "MapVetoPickMenuText" { "fr" "Selectionnez une map à JOUER :" diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 680b0d121..054cecb41 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -392,10 +392,6 @@ { "en" "[NOT READY]" } - "MatchPoweredBy" - { - "en" "Powered by {YELLOW}Get5" - } "MapVetoPickMenuText" { "en" "Select a map to PLAY:" diff --git a/translations/hu/get5.phrases.txt b/translations/hu/get5.phrases.txt index 877eb0282..6d472405d 100644 --- a/translations/hu/get5.phrases.txt +++ b/translations/hu/get5.phrases.txt @@ -336,10 +336,6 @@ { "hu" "[NEM ÁLL KÉSZEN]" } - "MatchPoweredBy" - { - "hu" "{YELLOW}Get5{NORMAL} által megvalósítva" - } "MapVetoPickMenuText" { "hu" "Válassz pályát, amin játszanál:" diff --git a/translations/pt/get5.phrases.txt b/translations/pt/get5.phrases.txt index 49fdecb66..cc7f2bc7a 100644 --- a/translations/pt/get5.phrases.txt +++ b/translations/pt/get5.phrases.txt @@ -248,8 +248,4 @@ { "pt" "[NOT READY]" } - "MatchPoweredBy" - { - "pt" "Oferecido por {YELLOW}Get5" - } } From 63fda31bbabc1595094787c1dd2c475f93132dcd Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 17:14:08 +0200 Subject: [PATCH 036/104] PrintHintText used for pausing should be visible to GOTV (#834) --- scripting/get5/pausing.sp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripting/get5/pausing.sp b/scripting/get5/pausing.sp index 9fae990e2..f5cc15503 100644 --- a/scripting/get5/pausing.sp +++ b/scripting/get5/pausing.sp @@ -330,7 +330,7 @@ public Action Timer_PauseTimeCheck(Handle timer) { } LOOP_CLIENTS(i) { - if (IsPlayer(i)) { + if (IsValidClient(i)) { if (fixedPauseTime) { // If fixed pause; takes precedence over total time and reuses timeLeft for simplicity if (maxTacticalPauses > 0) { // Team A (CT) tactical pause (2/4): 0:45 @@ -394,7 +394,7 @@ public Action Timer_PauseTimeCheck(Handle timer) { } LOOP_CLIENTS(i) { - if (IsPlayer(i)) { + if (IsValidClient(i)) { if (timeLeft >= 0) { if (maxTechPauses > 0) { // Team A (CT) technical pause (3/4): Time remaining before anyone can unpause: 1:30 @@ -418,7 +418,7 @@ public Action Timer_PauseTimeCheck(Handle timer) { } else if (g_PauseType == Get5PauseType_Admin) { LOOP_CLIENTS(i) { - if (IsPlayer(i)) { + if (IsValidClient(i)) { PrintHintText(i, "%t", "PausedByAdministrator"); } } @@ -426,7 +426,7 @@ public Action Timer_PauseTimeCheck(Handle timer) { } else if (g_PauseType == Get5PauseType_Backup) { LOOP_CLIENTS(i) { - if (IsPlayer(i)) { + if (IsValidClient(i)) { PrintHintText(i, "%t", "PausedForBackup"); } } From af5514f234b4933121d23901e15f8dfafc313ca4 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 17:29:36 +0200 Subject: [PATCH 037/104] Set team1/team2 team names if nobody is on a team when creating a match (#835) --- scripting/get5/matchconfig.sp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index ed3c62052..2d3b64645 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -1055,14 +1055,12 @@ public Action Command_CreateMatch(int client, int args) { kv.JumpToKey("team1", true); int count = AddPlayersToAuthKv(kv, Get5Team_1, teamName); - if (count > 0) - kv.SetString("name", teamName); + kv.SetString("name", count > 0 ? teamName : "Team 1"); kv.GoBack(); kv.JumpToKey("team2", true); count = AddPlayersToAuthKv(kv, Get5Team_2, teamName); - if (count > 0) - kv.SetString("name", teamName); + kv.SetString("name", count > 0 ? teamName : "Team 2"); kv.GoBack(); kv.JumpToKey("spectators", true); From f8df40eaa8f63218068e7aca3f7cddf1b2e04b2b Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 17:37:43 +0200 Subject: [PATCH 038/104] Adjust prefix docs --- documentation/docs/configuration.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index cd6f682a0..a0dd789bf 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -64,7 +64,7 @@ force players onto teams, kicking everyone else. **`Default: 1`** ####`get5_print_update_notice` : Whether to print to chat when the game goes live if a new version of Get5 is available. This only works if - [SteamWorks](../installation/#steamworks) has been installed. **`Default: 1`** +[SteamWorks](../installation/#steamworks) has been installed. **`Default: 1`** ####`get5_pretty_print_json` : Whether to pretty-print all JSON output. This also affects the output of JSON in the @@ -139,7 +139,8 @@ if [`get5_print_damage`](#get5_print_damage) is disabled. **`Default: 5`** ####`get5_message_prefix` -: The tag applied before plugin messages. **`Default: Get5`** +: The tag applied before plugin messages. Note that at least one character must come before +a [color modifier](#color-substitutes). **`Default: "[{YELLOW}Get5{NORMAL}]"`** ## Pausing @@ -211,7 +212,7 @@ command as well as the [`get5_loadbackup`](../commands/#get5_loadbackup) command the [`{MATCHID}`](#tag-matchid) variable, i.e. `backups/{MATCHID}/`. **`Default: ""`** !!! warning "Slash, slash, hundred yard dash :material-slash-forward:" - + It is very important that your backup path does **not** start with a slash but instead **ends with a slash**. If not, the last part of the path will be considered a prefix of the filename and things will not work correctly. Also note that if you use the [`{MATCHID}`](#tag-matchid) variable, [automatic deletion of backups](#get5_max_backup_age) From 0d0552751f17c2a2f902ffb5262b5b8ffb79ef25 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 18:21:16 +0200 Subject: [PATCH 039/104] Reset game state on backup load failure (#836) --- scripting/get5/backups.sp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 323bf23dc..1baa875df 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -236,6 +236,12 @@ public bool RestoreFromBackup(const char[] path) { if (!LoadMatchConfig(tempBackupFile, true)) { delete kv; LogError("Could not restore from match config \"%s\"", tempBackupFile); + if (g_GameState != Get5State_None) { + // If the backup load fails, all the game configs will have been reset by LoadMatchConfig, but the game state + // won't. This ensure we don't end up a in a "live" state with no get5 variables set, which would prevent a call + // to load a new match. + ChangeState(Get5State_None); + } return false; } kv.GoBack(); From e0bb93c5c0e58eaf5ef68cf40833f754ebad73bf Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 19:23:30 +0200 Subject: [PATCH 040/104] Knife timing issues (#837) Always send players to warmup if making a knife decision Reset knife-related timers if ending a match --- scripting/get5.sp | 22 +++++++++++++++++----- scripting/get5/kniferounds.sp | 5 ++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 43e64219f..c6e65384e 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -146,7 +146,13 @@ MatchSideType g_MatchSideType = MatchSideType_Standard; ArrayList g_CvarNames = null; ArrayList g_CvarValues = null; bool g_InScrimMode = false; + +/** Knife for sides **/ bool g_HasKnifeRoundStarted = false; +Get5Team g_KnifeWinnerTeam = Get5Team_None; +Handle g_KnifeChangedCvars = INVALID_HANDLE; +Handle g_KnifeDecisionTimer = INVALID_HANDLE; +Handle g_KnifeCountdownTimer = INVALID_HANDLE; /** Pausing **/ bool g_IsChangingPauseState = false; // Used to prevent mp_pause_match and mp_unpause_match from being called directly. @@ -222,9 +228,6 @@ char g_LastKickedPlayerAuth[64]; ArrayList g_ChatAliases; ArrayList g_ChatAliasesCommands; -/** Map game-state **/ -Get5Team g_KnifeWinnerTeam = Get5Team_None; - /** Map-game state not related to the actual gameplay. **/ char g_DemoFileName[PLATFORM_MAX_PATH]; bool g_MapChangePending = false; @@ -235,7 +238,6 @@ bool g_PendingSideSwap = false; bool g_RunningPrereleaseVersion = false; bool g_NewerVersionAvailable = false; -Handle g_KnifeChangedCvars = INVALID_HANDLE; Handle g_MatchConfigChangedCvars = INVALID_HANDLE; /** Forwards **/ @@ -1018,6 +1020,16 @@ public Action Command_EndMatch(int client, int args) { g_ActiveVetoMenu.Cancel(); } + if (g_KnifeCountdownTimer != INVALID_HANDLE) { + LogDebug("Killing knife announce countdown timer."); + delete g_KnifeCountdownTimer; + } + + if (g_KnifeDecisionTimer != INVALID_HANDLE) { + LogDebug("Killing knife decision timer."); + delete g_KnifeDecisionTimer; + } + ServerCommand("mp_restartgame 1"); return Plugin_Handled; @@ -1551,7 +1563,7 @@ public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, formattedSwapCommand); if (g_TeamTimeToKnifeDecisionCvar.FloatValue > 0) { - CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision, TIMER_FLAG_NO_MAPCHANGE); + g_KnifeDecisionTimer = CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision); } // This ensures that the correct graphic is displayed in-game for the winning team, as CTs will always win if the diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index ece9926b4..c40c62813 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -9,11 +9,12 @@ public Action StartKnifeRound(Handle timer) { RestartGame(5); } - CreateTimer(10.0, Timer_AnnounceKnife); + g_KnifeCountdownTimer = CreateTimer(10.0, Timer_AnnounceKnife); return Plugin_Handled; } public Action Timer_AnnounceKnife(Handle timer) { + g_KnifeCountdownTimer = INVALID_HANDLE; announcePhaseChange("{GREEN}%t", "KnifeInfoMessage"); Get5KnifeRoundStartedEvent knifeEvent = new Get5KnifeRoundStartedEvent(g_MatchID, g_MapNumber); @@ -85,6 +86,7 @@ public void EndKnifeRound(bool swap) { CreateTimer(3.0, StartGoingLive, _, TIMER_FLAG_NO_MAPCHANGE); g_KnifeWinnerTeam = Get5Team_None; + EnsureIndefiniteWarmup(); } static bool AwaitingKnifeDecision(int client) { @@ -141,6 +143,7 @@ public Action Command_T(int client, int args) { } public Action Timer_ForceKnifeDecision(Handle timer) { + g_KnifeDecisionTimer = INVALID_HANDLE; if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { Get5_MessageToAll("%t", "TeamLostTimeToDecideInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); From 781dca1a3117d1a302dd46ac9ee8a0df339dca40 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 14 Aug 2022 20:13:21 +0200 Subject: [PATCH 041/104] Fix knife fun fact reason (#838) --- scripting/get5.sp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index c6e65384e..c4006e0e6 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1567,8 +1567,27 @@ public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad } // This ensures that the correct graphic is displayed in-game for the winning team, as CTs will always win if the - // clock runs out. It also ensures that the reason displayed is correct, i.e. just "win" and no "won because clock + // clock runs out. It also ensures that the fun fact displayed is correct; overriding to number of players killed + // by knife and no "CT won by running down the clock". MVP can still be on the losing team though. // ran down". + int maxFrags = 0; + int topFragClient = 0; + int frags; + LOOP_CLIENTS(i) { + if (IsValidClient(i)) { + frags = GetClientFrags(i); + if (frags >= maxFrags) { + maxFrags = frags; + topFragClient = i; + } + } + } + if (topFragClient > 0) { + // Found here: https://github.com/SteamDatabase/GameTracking-CSGO/blob/master/csgo/bin/server_client_strings.txt + event.SetString("funfact_token", "#funfact_knife_kills"); + event.SetInt("funfact_player", topFragClient); + event.SetInt("funfact_data1", maxFrags); + } event.SetInt("final_event", ConvertCSTeamToDefaultWinReason(winningCSTeam)); } return Plugin_Continue; From 43a7db70ed121d8d953337612aa2ae38ec4d6622 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 15 Aug 2022 02:19:02 +0200 Subject: [PATCH 042/104] Capitalize function name --- scripting/get5/goinglive.sp | 2 +- scripting/get5/kniferounds.sp | 2 +- scripting/get5/util.sp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index afffe0f9a..4587e44cf 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -47,7 +47,7 @@ public Action MatchLive(Handle timer) { ExecuteMatchConfigCvars(); g_PendingSideSwap = false; - announcePhaseChange("%t", "MatchIsLiveInfoMessage"); + AnnouncePhaseChange("%t", "MatchIsLiveInfoMessage"); if (!g_PrintUpdateNoticeCvar.BoolValue) { return Plugin_Handled; diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index c40c62813..16e06a662 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -15,7 +15,7 @@ public Action StartKnifeRound(Handle timer) { public Action Timer_AnnounceKnife(Handle timer) { g_KnifeCountdownTimer = INVALID_HANDLE; - announcePhaseChange("{GREEN}%t", "KnifeInfoMessage"); + AnnouncePhaseChange("{GREEN}%t", "KnifeInfoMessage"); Get5KnifeRoundStartedEvent knifeEvent = new Get5KnifeRoundStartedEvent(g_MatchID, g_MapNumber); diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 0bad6dab6..07dda235e 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -219,7 +219,7 @@ stock void ReplaceStringWithInt(char[] buffer, int len, const char[] replace, in ReplaceString(buffer, len, replace, intString, caseSensitive); } -stock void announcePhaseChange(const char[] format, const char[] message) { +stock void AnnouncePhaseChange(const char[] format, const char[] message) { int count = g_PhaseAnnouncementCountCvar.IntValue; if (count > 10) { count = 10; From aecac87a8a2910bfe6579999067ce6f64607dca7 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 15 Aug 2022 02:27:22 +0200 Subject: [PATCH 043/104] Adjust GOTV and cvar restore logic (#841) --- cfg/get5/live.cfg | 2 - cfg/get5/warmup.cfg | 3 - documentation/docs/configuration.md | 7 +- documentation/docs/gotv.md | 22 +++++ documentation/docs/stats_system.md | 1 + documentation/mkdocs.yml | 3 +- scripting/get5.sp | 124 +++++++++++++++------------- scripting/get5/mapveto.sp | 10 ++- scripting/get5/recording.sp | 82 ++++++++++-------- scripting/get5/util.sp | 16 ---- 10 files changed, 148 insertions(+), 122 deletions(-) create mode 100644 documentation/docs/gotv.md diff --git a/cfg/get5/live.cfg b/cfg/get5/live.cfg index f63f70b17..cd1ee6e91 100644 --- a/cfg/get5/live.cfg +++ b/cfg/get5/live.cfg @@ -55,6 +55,4 @@ sv_holiday_mode 0 sv_talk_enemy_dead 0 sv_talk_enemy_living 0 sv_voiceenable 1 -tv_delay 105 -tv_delaymapchange 1 tv_relayvoice 0 diff --git a/cfg/get5/warmup.cfg b/cfg/get5/warmup.cfg index 32e2a166f..ac3275eab 100644 --- a/cfg/get5/warmup.cfg +++ b/cfg/get5/warmup.cfg @@ -27,8 +27,5 @@ sv_hibernate_when_empty 0 sv_infinite_ammo 0 sv_showimpacts 0 sv_voiceenable 1 -tv_delay 105 -tv_delaymapchange 1 tv_relayvoice 0 - sv_cheats 0 diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index a0dd789bf..fca1bcbfe 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -49,7 +49,8 @@ cfg/get5/live.cfg # (3) : Whether the [`!stop`](../commands/#stop) command is enabled. **`Default: 1`** ####`get5_kick_when_no_match_loaded` -: Whether to kick all clients if no match is loaded. **`Default: 0`** +: Whether to kick all clients if no match is loaded. Players will not be kicked if a match is forcefully ended +using [`get5_endmatch`](../commands/#get5_endmatch). **`Default: 0`** ####`get5_end_match_on_empty_server` : Whether the match is ended with no winner if all players leave (note: this will happen even if all players @@ -188,7 +189,9 @@ must confirm. **`Default: 0`** doing! Avoid using spaces or colons.** **`Default: %Y-%m-%d_%H`** ####`get5_demo_name_format` -: Format to name demo files. Set to empty string to disable. **`Default: {MATCHID}_map{MAPNUMBER}_{MAPNAME}`** +: Format to use for demo files when [recording matches](gotv.md). Do not include a file extension (`.dem` is added +automatically). Set to empty string to disable.
Note that the [`{MAPNUMBER}`](#tag-mapnumber) variable is not +zero-indexed!
**`Default: {MATCHID}_map{MAPNUMBER}_{MAPNAME}`** ####`get5_event_log_format` : Format to write event logs to. Set to empty string to disable. **`Default: ""`** diff --git a/documentation/docs/gotv.md b/documentation/docs/gotv.md new file mode 100644 index 000000000..519ad1f96 --- /dev/null +++ b/documentation/docs/gotv.md @@ -0,0 +1,22 @@ +# :material-filmstrip: GOTV & Demos {: #gotv } + +Get5 can be configured to automatically record matches. This is enabled by default based on the state +of [`get5_demo_name_format`](../configuration/#get5_demo_name_format) and can be disabled by setting that parameter to +an empty string. + +!!! warning "Don't mess too much with the delay!" + + Changing the `tv_delay` or `tv_enable` in `warmup.cfg`, `live.cfg` etc. is going to cause problems with your demos. + We recommend you set this variable either on your server in general or only once in the `cvar` section of your + [match configuration](../match_schema). You should also not set `tv_delaymapchange` as Get5 handles this + automatically. + +Demo recording starts once all teams have readied up and ends shortly following a map result. When a demo file is +written to disk, the [`Get5_OnDemoFinished`](events_and_forwards.md) forward is called, which you can use to move the +file or upload it somewhere. The filename can also be found in the map-section of the +[KeyValue stats system](../stats_system/#keyvalue). + +Get5 will automatically adjust the [`mp_match_restart_delay`](https://totalcsgo.com/command/mpmatchrestartdelay) when a +map ends if GOTV is enabled to assure that it won't be shorter than what is required for the GOTV broadcast to finish. +Players will also not be [kicked from the server](../configuration/#get5_kick_when_no_match_loaded) before this delay +has passed. diff --git a/documentation/docs/stats_system.md b/documentation/docs/stats_system.md index 17ce86ca0..46c662e8f 100644 --- a/documentation/docs/stats_system.md +++ b/documentation/docs/stats_system.md @@ -39,6 +39,7 @@ Partial Example: "map0" { "mapname" "de_mirage" + "demo_filename" "304_map1_de_mirage.dem" "winner" "team1" "team1" { diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 483d02a21..625b2740a 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -35,10 +35,11 @@ nav: - Configuration: configuration.md - Basics: - Getting Started: getting_started.md + - Match Schema: match_schema.md - Commands: commands.md - Pausing: pausing.md - Backup System: backup.md - - Match Schema: match_schema.md + - GOTV & Demos: gotv.md - Advanced: - Developer API: developer_api.md - Events & Forwards: events_and_forwards.md diff --git a/scripting/get5.sp b/scripting/get5.sp index c4006e0e6..b1a3f9be7 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -38,7 +38,7 @@ #define DEBUG_CVAR "get5_debug" #define MATCH_ID_LENGTH 64 #define MAX_CVAR_LENGTH 128 -#define MATCH_END_DELAY_AFTER_TV 10 +#define MATCH_END_DELAY_AFTER_TV 15 #define TEAM1_COLOR "{LIGHT_GREEN}" #define TEAM2_COLOR "{PINK}" @@ -797,8 +797,8 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr g_GameState < Get5State_PostGame && GetRealClientCount() == 0 && !g_MapChangePending) { g_TeamSeriesScores[Get5Team_1] = 0; g_TeamSeriesScores[Get5Team_2] = 0; - StopRecording(true); - EndSeries(Get5Team_None, false, false); + StopRecording(); + EndSeries(Get5Team_None, false, 0.0, false); } } @@ -911,16 +911,19 @@ static void CheckReadyWaitingTimes() { if (team1Forfeited || team2Forfeited) { Stats_Forfeit(); - StopRecording(true); - } - - // False for printing in all these cases, as CheckReadyWaitingTime prints forfeit messages. - if (team1Forfeited && team2Forfeited) { - EndSeries(Get5Team_None, false, false); - } else if (team1Forfeited) { - EndSeries(Get5Team_2, false, false); - } else if (team2Forfeited) { - EndSeries(Get5Team_1, false, false); + float minDelay = 5.0; + StopRecording(minDelay); + float endDelay = float(GetTvDelay()); + if (endDelay < minDelay) { + endDelay = minDelay; + } + if (team1Forfeited && team2Forfeited) { + EndSeries(Get5Team_None, false, endDelay); + } else if (team1Forfeited) { + EndSeries(Get5Team_2, false, endDelay); + } else { + EndSeries(Get5Team_1, false, endDelay); + } } } } @@ -1003,10 +1006,8 @@ public Action Command_EndMatch(int client, int args) { Call_Finish(); EventLogger_LogAndDeleteEvent(mapResultEvent); - // Don't print series result here as admin force-end is printed below. - // We force-end the recording as the actual game is not ended, so there is no round restart to match the recording time to. - StopRecording(true); - EndSeries(winningTeam, false, false); + // No delay required when not kicking players. + EndSeries(winningTeam, false, 0.0, false); UpdateClanTags(); @@ -1030,6 +1031,7 @@ public Action Command_EndMatch(int client, int args) { delete g_KnifeDecisionTimer; } + StopRecording(1.0); ServerCommand("mp_restartgame 1"); return Plugin_Handled; @@ -1186,15 +1188,14 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast // This ensures that the mp_match_restart_delay is not shorter // than what is required for the GOTV recording to finish. - ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay"); - if (mp_match_restart_delay != INVALID_HANDLE) { - int requiredDelay = GetTvDelay() + MATCH_END_DELAY_AFTER_TV; - if (requiredDelay > mp_match_restart_delay.IntValue) { - LogDebug("Extended mp_match_restart_delay from %d to %d to ensure GOTV can finish recording.", - mp_match_restart_delay.IntValue, requiredDelay); - mp_match_restart_delay.IntValue = requiredDelay; - } + float restartDelay = GetCurrentMatchRestartDelay(); + float requiredDelay = float(GetTvDelay() + MATCH_END_DELAY_AFTER_TV); + if (requiredDelay > restartDelay) { + LogDebug("Extended mp_match_restart_delay from %f to %f to ensure GOTV broadcast can finish.", restartDelay, requiredDelay); + SetCurrentMatchRestartDelay(requiredDelay); + restartDelay = requiredDelay; // reassigned because we reuse the variable below. } + StopRecording(float(MATCH_END_DELAY_AFTER_TV)); if (g_GameState == Get5State_Live) { @@ -1245,16 +1246,10 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast int tiedMaps = g_TeamSeriesScores[Get5Team_None]; int remainingMaps = g_MapsToPlay.Length - t1maps - t2maps - tiedMaps; - // Stops recording after GOTV has ended. - // mp_match_restart_delay is always of a longer duration than GOTV delay, so the recording - // **will** finish before the map changes, regardless if a next map is pending or if the - // series ends here. - StopRecording(); - if (t1maps == t2maps) { // As long as team scores are equal, we play until there are no maps left, regardless of clinch config. if (remainingMaps <= 0) { - EndSeries(Get5Team_None, true, true); + EndSeries(Get5Team_None, true, restartDelay); return Plugin_Continue; } } else if (g_SeriesCanClinch) { @@ -1262,15 +1257,15 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast int actualMapsToWin = ((g_MapsToPlay.Length - tiedMaps) / 2) + 1; if (t1maps == actualMapsToWin) { // Team 1 won - EndSeries(Get5Team_1, true, true); + EndSeries(Get5Team_1, true, restartDelay); return Plugin_Continue; } else if (t2maps == actualMapsToWin) { // Team 2 won - EndSeries(Get5Team_2, true, true); + EndSeries(Get5Team_2, true, restartDelay); return Plugin_Continue; } } else if (remainingMaps <= 0) { - EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, true); // Tie handled in first if-block + EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, restartDelay); // Tie handled in first if-block return Plugin_Continue; } @@ -1289,8 +1284,6 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast char nextMap[PLATFORM_MAX_PATH]; g_MapsToPlay.GetString(Get5_GetMapNumber(), nextMap, sizeof(nextMap)); - float restartDelay = FindConVar("mp_match_restart_delay").FloatValue; - char timeToMapChangeFormatted[8]; convertSecondsToMinutesAndSeconds(RoundToFloor(restartDelay), timeToMapChangeFormatted, sizeof(timeToMapChangeFormatted)); @@ -1313,19 +1306,7 @@ public Action Timer_NextMatchMap(Handle timer) { ChangeMap(map, 3.0); } -public void KickClientsOnEnd() { - LOOP_CLIENTS(i) { - if (IsPlayer(i) && !(g_KickClientImmunityCvar.BoolValue && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) { - KickClient(i, "%t", "MatchFinishedInfoMessage"); - } - } -} - -public void EndSeries(Get5Team winningTeam, bool printWinnerMessage, bool waitForGoTv) { - if (g_KickClientsWithNoMatchCvar.BoolValue) { - DelayFunction(10.0, KickClientsOnEnd); - } - +void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay, bool kickPlayers = true) { Stats_SeriesEnd(winningTeam); if (printWinnerMessage) { @@ -1356,19 +1337,46 @@ public void EndSeries(Get5Team winningTeam, bool printWinnerMessage, bool waitFo EventLogger_LogAndDeleteEvent(event); ChangeState(Get5State_None); - // We need to restore cvars on a timer if GOTV is recording, as it might otherwise set mp_match_restart_delay - // "back" to something that's shorter than the GOTV delay, which is a problem. - int goTvDelay = GetTvDelay(); - if (goTvDelay > 0 && waitForGoTv) { - CreateTimer(float(goTvDelay) + MATCH_END_DELAY_AFTER_TV, Timer_RestoreCvars); - } else { + // We don't want to kick players until after the specified delay, as it will kick casters potentially before GOTV ends. + if (kickPlayers && g_KickClientsWithNoMatchCvar.BoolValue) { + if (restoreDelay < 0.1) { + KickPlayers(); + } else { + CreateTimer(restoreDelay, Timer_KickOnEnd, _, TIMER_FLAG_NO_MAPCHANGE); + } + } + + if (restoreDelay < 0.1) { + // When force-ending the match there is no delay. RestoreCvars(g_MatchConfigChangedCvars); + } else { + // If we restore cvars immediately, it might change the tv_ params or set the mp_match_restart_delay to something + // lower, which is noticed by the game and may trigger a map change before GOTV broadcast ends, so we don't do this + // until the current match restart delay has passed. + CreateTimer(restoreDelay, Timer_RestoreMatchCvars, _, TIMER_FLAG_NO_MAPCHANGE); + } +} + +public Action Timer_KickOnEnd(Handle timer) { + if (g_GameState == Get5State_None) { + // If a match was started before this event is triggered, don't do anything. + KickPlayers(); + } + return Plugin_Handled; +} + +static void KickPlayers() { + bool kickImmunity = g_KickClientImmunityCvar.BoolValue; + LOOP_CLIENTS(i) { + if (IsPlayer(i) && !(kickImmunity && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) { + KickClient(i, "%t", "MatchFinishedInfoMessage"); + } } } -public Action Timer_RestoreCvars(Handle timer) { +public Action Timer_RestoreMatchCvars(Handle timer) { if (g_GameState == Get5State_None) { - // Only reset if no game is running, otherwise a game started before the GOTV for another ends will mess this up. + // Only reset if no game is running, otherwise a game started before the restart delay for another ends will mess this up. RestoreCvars(g_MatchConfigChangedCvars); } return Plugin_Handled; diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 3cae0bdd0..3e0512b24 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -62,14 +62,16 @@ public void VetoFinished() { Get5_MessageToAll("%t", "MapIsInfoMessage", i + 1 - seriesScore, map); } + float delay = 10.0; g_MapChangePending = true; if (!g_SkipVeto && g_DisplayGotvVetoCvar.BoolValue) { - float minDelay = float(GetTvDelay()) + MATCH_END_DELAY_AFTER_TV; - StopRecording(); // stops recording after GetTvDelay() seconds. - CreateTimer(minDelay, Timer_NextMatchMap); + // Players must wait for GOTV to end before we can change map, but we don't need to record that. + CreateTimer(float(GetTvDelay()) + delay, Timer_NextMatchMap); } else { - CreateTimer(10.0, Timer_NextMatchMap); + CreateTimer(delay, Timer_NextMatchMap); } + // Always end recording here; ensures that we can successfully start one after veto. + StopRecording(delay); } // Main Veto Controller diff --git a/scripting/get5/recording.sp b/scripting/get5/recording.sp index 765a55d11..60d25f381 100644 --- a/scripting/get5/recording.sp +++ b/scripting/get5/recording.sp @@ -1,4 +1,11 @@ bool StartRecording() { + char demoFormat[PLATFORM_MAX_PATH]; + g_DemoNameFormatCvar.GetString(demoFormat, sizeof(demoFormat)); + if (StrEqual("", demoFormat)) { + LogMessage("Demo recording is disabled via get5_demo_name_format."); + return false; + } + if (!IsTVEnabled()) { LogError("Demo recording will not work with \"tv_enable 0\". Set \"tv_enable 1\" and restart the map to fix this."); g_DemoFileName = ""; @@ -15,7 +22,7 @@ bool StartRecording() { Format(g_DemoFileName, sizeof(g_DemoFileName), "%s.dem", demoName); LogMessage("Recording to %s", g_DemoFileName); - // Escape unsafe characters and start recording. + // Escape unsafe characters and start recording. .dem is appended to the filename automatically. char szDemoName[PLATFORM_MAX_PATH + 1]; strcopy(szDemoName, sizeof(szDemoName), demoName); ReplaceString(szDemoName, sizeof(szDemoName), "\"", "\\\""); @@ -23,57 +30,48 @@ bool StartRecording() { return true; } -void StopRecording(bool forceStop = false) { - if (!IsTVEnabled()) { - LogDebug("Cannot stop recording as GOTV is not enabled."); - return; - } - if (forceStop) { - LogDebug("Ending GOTV recording immediately by force."); +void StopRecording(float delay = 0.0) { + if (delay < 0.1) { + LogDebug("Stopping GOTV recording immediately."); StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName); - return; - } - int tvDelay = GetTvDelay(); - if (tvDelay > 0) { - LogDebug("Starting timer that will end GOTV recording in %d seconds.", tvDelay); - DataPack pack = CreateDataPack(); - pack.WriteString(g_MatchID); - pack.WriteCell(g_MapNumber); - pack.WriteString(g_DemoFileName); - CreateTimer(float(tvDelay), Timer_StopGoTVRecording, pack, TIMER_FLAG_NO_MAPCHANGE); // changemap ends recording, so the timer cannot carry over. } else { - LogDebug("Ending GOTV recording immediately as tv_delay is 0."); - StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName); + LogDebug("Starting timer that will end GOTV recording in %f seconds.", delay); + CreateTimer(delay, Timer_StopGoTVRecording, GetDemoInfoDataPack(g_MatchID, g_MapNumber, g_DemoFileName)); } + g_DemoFileName = ""; } -static void StopRecordingCallback(char[] matchId, int mapNumber, char[] demoFileName) { +static void StopRecordingCallback(const char[] matchId, const int mapNumber, const char[] demoFileName) { ServerCommand("tv_stoprecord"); if (StrEqual("", demoFileName)) { LogDebug("Demo was not recorded by Get5; not firing Get5_OnDemoFinished()"); return; } - // We delay this by 3 seconds to allow the server to flush to the file before firing the event. - // This requires a pack with the data, as the map might change and stuff might happen after the - // tv_delay has expired. This would also allow us to extend this delay later without breaking anything. + CreateTimer(3.0, Timer_FireStopRecordingEvent, GetDemoInfoDataPack(matchId, mapNumber, demoFileName)); +} + +static DataPack GetDemoInfoDataPack(const char[] matchId, const int mapNumber, const char[] demoFileName) { DataPack pack = CreateDataPack(); pack.WriteString(matchId); pack.WriteCell(mapNumber); pack.WriteString(demoFileName); + return pack; +} - CreateTimer(3.0, Timer_FireStopRecordingEvent, pack); +static void ReadDemoDataPack(DataPack pack, char[] matchId, const int matchIdLength, int &mapNumber, char[] demoFileName, const int demoFileNameLength) { + pack.Reset(); + pack.ReadString(matchId, matchIdLength); + mapNumber = pack.ReadCell(); + pack.ReadString(demoFileName, demoFileNameLength); + delete pack; } public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { char matchId[MATCH_ID_LENGTH]; char demoFileName[PLATFORM_MAX_PATH]; - pack.Reset(); - pack.ReadString(matchId, sizeof(matchId)); - int mapNumber = pack.ReadCell(); - pack.ReadString(demoFileName, sizeof(demoFileName)); - delete pack; - + int mapNumber; + ReadDemoDataPack(pack, matchId, sizeof(matchId), mapNumber, demoFileName, sizeof(demoFileName)); StopRecordingCallback(matchId, mapNumber, demoFileName); return Plugin_Handled; } @@ -81,11 +79,8 @@ public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) { char matchId[MATCH_ID_LENGTH]; char demoFileName[PLATFORM_MAX_PATH]; - pack.Reset(); - pack.ReadString(matchId, sizeof(matchId)); - int mapNumber = pack.ReadCell(); - pack.ReadString(demoFileName, sizeof(demoFileName)); - delete pack; + int mapNumber; + ReadDemoDataPack(pack, matchId, sizeof(matchId), mapNumber, demoFileName, sizeof(demoFileName)); Get5DemoFinishedEvent event = new Get5DemoFinishedEvent(matchId, mapNumber, demoFileName); LogDebug("Calling Get5_OnDemoFinished()"); @@ -120,3 +115,18 @@ int GetTvDelay() { } return 0; } + +float GetCurrentMatchRestartDelay() { + ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay"); + if (mp_match_restart_delay == INVALID_HANDLE) { + return 1.0; // Shouldn't really be possible, but as a safeguard. + } + return mp_match_restart_delay.FloatValue; +} + +void SetCurrentMatchRestartDelay(float delay) { + ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay"); + if (mp_match_restart_delay != INVALID_HANDLE) { + mp_match_restart_delay.FloatValue = delay; + } +} diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 07dda235e..fb689c7b3 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -731,22 +731,6 @@ stock SideChoice SideTypeFromString(const char[] input) { } } -typedef VoidFunction = function void(); - -stock void DelayFunction(float delay, VoidFunction f) { - DataPack p = CreateDataPack(); - p.WriteFunction(f); - CreateTimer(delay, _DelayFunctionCallback, p); -} - -public Action _DelayFunctionCallback(Handle timer, DataPack data) { - data.Reset(); - Function func = data.ReadFunction(); - Call_StartFunction(INVALID_HANDLE, func); - Call_Finish(); - delete data; -} - // Deletes a file if it exists. Returns true if the // file existed AND there was an error deleting it. public bool DeleteFileIfExists(const char[] path) { From 4b2589f77fa9cb0a7d307ecb9eb914fee93db72c Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 15 Aug 2022 05:04:19 +0200 Subject: [PATCH 044/104] Fix wrong syntax in AnnouncePhaseChange --- scripting/get5/util.sp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index fb689c7b3..0a1d1abed 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -223,10 +223,9 @@ stock void AnnouncePhaseChange(const char[] format, const char[] message) { int count = g_PhaseAnnouncementCountCvar.IntValue; if (count > 10) { count = 10; - } else if (count > 0) { - for (int i = 0; i < count; i++) { - Get5_MessageToAll(format, message); - } + } + for (int i = 0; i < count; i++) { + Get5_MessageToAll(format, message); } } From 986fe9053ca4eaf3a9a119a73fbd25916e24075e Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Mon, 15 Aug 2022 16:48:05 -0600 Subject: [PATCH 045/104] Doc updates explaining disabling practice mode. (#842) Update templates to reflect changes. Update autoload description. --- configs/get5/example_match.cfg | 1 + configs/get5/example_match.json | 3 ++- configs/get5/scrim_template.cfg | 1 + documentation/docs/configuration.md | 5 ++--- documentation/docs/getting_started.md | 3 ++- documentation/docs/gotv.md | 2 +- documentation/docs/match_schema.md | 3 ++- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/configs/get5/example_match.cfg b/configs/get5/example_match.cfg index 794d71156..99fb40630 100644 --- a/configs/get5/example_match.cfg +++ b/configs/get5/example_match.cfg @@ -81,5 +81,6 @@ "cvars" { "hostname" "Match server #1" + "sm_practicemode_can_be_started" "0" // Disallow enabling practice mode when a match is loaded. } } diff --git a/configs/get5/example_match.json b/configs/get5/example_match.json index 2c7e779d3..7482e7035 100644 --- a/configs/get5/example_match.json +++ b/configs/get5/example_match.json @@ -63,6 +63,7 @@ }, "cvars": { - "hostname": "Match server #1" + "hostname": "Match server #1", + "sm_practicemode_can_be_started": "0" } } diff --git a/configs/get5/scrim_template.cfg b/configs/get5/scrim_template.cfg index 288a737d9..a39edc237 100644 --- a/configs/get5/scrim_template.cfg +++ b/configs/get5/scrim_template.cfg @@ -36,5 +36,6 @@ "get5_demo_name_format" "scrim_{TIME}_{MAPNAME}" // Set to "" to disable recording "get5_kick_when_no_match_loaded" "0" "get5_print_damage" "1" // Enabling will print damage on round-end. + "sm_practicemode_can_be_started" "0" // Disallow enabling practice mode when a match is loaded. } } diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index fca1bcbfe..463e9b87b 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -235,9 +235,8 @@ the [`{MATCHID}`](#tag-matchid) variable, i.e. `backups/{MATCHID}/`. **`Default: : Config file executed when the game goes live. **`Default: get5/live.cfg`** ####`get5_autoload_config` -: A config file to autoload on map starts if no match is loaded, relative to the `csgo` directory. Set to empty -string -to disable. **`Default: ""`** +: A [match configuration](../match_schema/#schema) file, relative to the `csgo` directory, to autoload when a player joins the server +if no match is loaded. Set to empty string to disable. **`Default: ""`** ####`get5_warmup_cfg` : Config file executed in warmup periods. **`Default: get5/warmup.cfg`** diff --git a/documentation/docs/getting_started.md b/documentation/docs/getting_started.md index b56e16d5f..484074cc1 100644 --- a/documentation/docs/getting_started.md +++ b/documentation/docs/getting_started.md @@ -53,7 +53,8 @@ Once you've done this, all that has to happen is teams to [ready up](../commands !!! danger "Practice Mode" If you have [practicemode](https://github.com/splewis/csgo-practice-mode) on your server as well, you may wish to - add `sm_practicemode_can_be_started 0` in your [live configuration](../configuration/#phase-configuration-files). + add `sm_practicemode_can_be_started 0` in the `cvars` section of your [match configuration](../match_schema/#schema). + This will remove the ability to start practice mode until the match is completed or cancelled. ### Changing Scrim Settings diff --git a/documentation/docs/gotv.md b/documentation/docs/gotv.md index 519ad1f96..e4ec458a0 100644 --- a/documentation/docs/gotv.md +++ b/documentation/docs/gotv.md @@ -17,6 +17,6 @@ file or upload it somewhere. The filename can also be found in the map-section o [KeyValue stats system](../stats_system/#keyvalue). Get5 will automatically adjust the [`mp_match_restart_delay`](https://totalcsgo.com/command/mpmatchrestartdelay) when a -map ends if GOTV is enabled to assure that it won't be shorter than what is required for the GOTV broadcast to finish. +map ends if GOTV is enabled to ensure that it won't be shorter than what is required for the GOTV broadcast to finish. Players will also not be [kicked from the server](../configuration/#get5_kick_when_no_match_loaded) before this delay has passed. diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 106566955..a4fef99e1 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -193,7 +193,8 @@ const match_schema: Match = { "hostname": "Get5 Match #3123", "mp_friendly_fire": "0", "get5_end_match_on_empty_server": "0", - "get5_stop_command_enabled": "0" + "get5_stop_command_enabled": "0", + "sm_practicemode_can_be_started": "0" } } From 1d3b4a9e5460718c458dfe50b86388a2676b460b Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 16 Aug 2022 14:22:38 +0200 Subject: [PATCH 046/104] Fix wrong parameters passed to translations (#845) --- scripting/get5/mapveto.sp | 2 +- scripting/get5/pausing.sp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 3e0512b24..e35f6b579 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -30,7 +30,7 @@ public Action Timer_VetoCountdown(Handle timer) { } else { warningsPrinted++; int secondsRemaining = g_VetoCountdownCvar.IntValue - warningsPrinted + 1; - char secondsFormatted[8]; + char secondsFormatted[32]; Format(secondsFormatted, sizeof(secondsFormatted), "{GREEN}%d{NORMAL}", secondsRemaining); Get5_MessageToAll("%t", "VetoCountdown", secondsFormatted); return Plugin_Continue; diff --git a/scripting/get5/pausing.sp b/scripting/get5/pausing.sp index f5cc15503..f24e2076a 100644 --- a/scripting/get5/pausing.sp +++ b/scripting/get5/pausing.sp @@ -242,7 +242,9 @@ public Action Command_Unpause(int client, int args) { if (g_TeamReadyForUnpause[Get5Team_1] && g_TeamReadyForUnpause[Get5Team_2]) { UnpauseGame(team); if (IsPlayer(client)) { - Get5_MessageToAll("%t", "MatchUnpauseInfoMessage", client); + char formattedClientName[MAX_NAME_LENGTH]; + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + Get5_MessageToAll("%t", "MatchUnpauseInfoMessage", formattedClientName); } } else if (!g_TeamReadyForUnpause[Get5Team_2]) { Get5_MessageToAll("%t", "WaitingForUnpauseInfoMessage", g_FormattedTeamNames[Get5Team_1], From dbf94ccce30d0180a3c25d7f7c3c87da83d23fb1 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 16 Aug 2022 18:08:53 +0200 Subject: [PATCH 047/104] Functions used in get5.inc should be included in that file (#843) --- scripting/get5/util.sp | 97 -------------------------------------- scripting/include/get5.inc | 80 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 97 deletions(-) diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 0a1d1abed..ac76c2fb4 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -100,80 +100,6 @@ stock bool IsAuthedPlayer(int client) { return IsPlayer(client) && IsClientAuthorized(client); } -/** - * Used to consistently set string keys on JSON objects that receive a Get5Side parameter, which - * should be output as ct, t, spec or null in JSON. - */ -stock void ConvertGet5SideToStringInJson(const JSON_Object obj, const char[] key, Get5Side side) { - if (side == Get5Side_T) { - obj.SetString(key, "t"); - } else if (side == Get5Side_CT) { - obj.SetString(key, "ct"); - } else if (side == Get5Side_Spec) { - obj.SetString(key, "spec"); - } else { - obj.SetObject(key, null); - } -} - -/** - * Used to consistently set string keys on JSON objects that receive a Get5Team parameter, which - * should be output as team1, team2, spec or null in JSON. - */ -stock void ConvertGet5TeamToStringInJson(const JSON_Object obj, const char[] key, Get5Team team) { - if (team == Get5Team_1) { - obj.SetString(key, "team1"); - } else if (team == Get5Team_2) { - obj.SetString(key, "team2"); - } else if (team == Get5Team_Spec) { - obj.SetString(key, "spec"); - } else { - obj.SetObject(key, null); - } -} - -/** - * Used to consistently map a Get5PauseType to string in JSON. - */ -stock void ConvertGet5PauseTypeToStringInJson(const JSON_Object obj, const char[] key, - Get5PauseType pauseType) { - if (pauseType == Get5PauseType_Admin) { - obj.SetString(key, "admin"); - } else if (pauseType == Get5PauseType_Tech) { - obj.SetString(key, "technical"); - } else if (pauseType == Get5PauseType_Tactical) { - obj.SetString(key, "tactical"); - } else if (pauseType == Get5PauseType_Backup) { - obj.SetString(key, "backup"); - } else { - obj.SetObject(key, null); - } -} - -/** - * Used to consistently convert Get5BombSite to 'a', 'b' or null. - */ -stock void ConvertBombSiteToStringInJson(const JSON_Object obj, const char[] key, - const Get5BombSite site) { - if (site == Get5BombSite_A) { - obj.SetString(key, "a"); - } else if (site == Get5BombSite_B) { - obj.SetString(key, "b"); - } else { - obj.SetObject(key, null); - } -} - -/** - * Used to consistently set string keys on JSON objects that receive a Get5State parameter. - */ -stock void ConvertGameStateToStringInJson(const JSON_Object obj, const char[] key, - const Get5State state) { - char gameStateString[64]; - GameStateString(state, gameStateString, sizeof(gameStateString)); - obj.SetString(key, gameStateString); -} - /** * Returns the number of clients that are actual players in the game. */ @@ -585,29 +511,6 @@ stock void GetTeamString(Get5Team team, char[] buffer, int len) { } } -stock void GameStateString(Get5State state, char[] buffer, int length) { - switch (state) { - case Get5State_None: - Format(buffer, length, "none"); - case Get5State_PreVeto: - Format(buffer, length, "pre_veto"); - case Get5State_Veto: - Format(buffer, length, "veto"); - case Get5State_Warmup: - Format(buffer, length, "warmup"); - case Get5State_KnifeRound: - Format(buffer, length, "knife"); - case Get5State_WaitingForKnifeRoundDecision: - Format(buffer, length, "waiting_for_knife_decision"); - case Get5State_GoingLive: - Format(buffer, length, "going_live"); - case Get5State_Live: - Format(buffer, length, "live"); - case Get5State_PostGame: - Format(buffer, length, "post_game"); - } -} - public MatchSideType MatchSideTypeFromString(const char[] str) { if (StrEqual(str, "normal", false) || StrEqual(str, "standard", false)) { return MatchSideType_Standard; diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index ce2a8840d..60e4af5f4 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -1599,6 +1599,86 @@ methodmap Get5BombDefusedEvent < Get5PlayerBombEvent { } } +void GameStateString(const Get5State state, char[] buffer, const int length) { + switch (state) { + case Get5State_None: + Format(buffer, length, "none"); + case Get5State_PreVeto: + Format(buffer, length, "pre_veto"); + case Get5State_Veto: + Format(buffer, length, "veto"); + case Get5State_Warmup: + Format(buffer, length, "warmup"); + case Get5State_KnifeRound: + Format(buffer, length, "knife"); + case Get5State_WaitingForKnifeRoundDecision: + Format(buffer, length, "waiting_for_knife_decision"); + case Get5State_GoingLive: + Format(buffer, length, "going_live"); + case Get5State_Live: + Format(buffer, length, "live"); + case Get5State_PostGame: + Format(buffer, length, "post_game"); + } +} + +void ConvertGameStateToStringInJson(const JSON_Object obj, const char[] key, + const Get5State state) { + char gameStateString[64]; + GameStateString(state, gameStateString, sizeof(gameStateString)); + obj.SetString(key, gameStateString); +} + +void ConvertGet5SideToStringInJson(const JSON_Object obj, const char[] key, Get5Side side) { + if (side == Get5Side_T) { + obj.SetString(key, "t"); + } else if (side == Get5Side_CT) { + obj.SetString(key, "ct"); + } else if (side == Get5Side_Spec) { + obj.SetString(key, "spec"); + } else { + obj.SetObject(key, null); + } +} + +void ConvertGet5TeamToStringInJson(const JSON_Object obj, const char[] key, Get5Team team) { + if (team == Get5Team_1) { + obj.SetString(key, "team1"); + } else if (team == Get5Team_2) { + obj.SetString(key, "team2"); + } else if (team == Get5Team_Spec) { + obj.SetString(key, "spec"); + } else { + obj.SetObject(key, null); + } +} + +void ConvertGet5PauseTypeToStringInJson(const JSON_Object obj, const char[] key, + Get5PauseType pauseType) { + if (pauseType == Get5PauseType_Admin) { + obj.SetString(key, "admin"); + } else if (pauseType == Get5PauseType_Tech) { + obj.SetString(key, "technical"); + } else if (pauseType == Get5PauseType_Tactical) { + obj.SetString(key, "tactical"); + } else if (pauseType == Get5PauseType_Backup) { + obj.SetString(key, "backup"); + } else { + obj.SetObject(key, null); + } +} + +void ConvertBombSiteToStringInJson(const JSON_Object obj, const char[] key, + const Get5BombSite site) { + if (site == Get5BombSite_A) { + obj.SetString(key, "a"); + } else if (site == Get5BombSite_B) { + obj.SetString(key, "b"); + } else { + obj.SetObject(key, null); + } +} + // Called each get5-event with JSON formatted event text. forward void Get5_OnEvent(const Get5Event, const char[] eventJson); From 1d8e126ad6aebb201f2def1a1cbd45ca8e96457e Mon Sep 17 00:00:00 2001 From: Sean Lewis Date: Tue, 16 Aug 2022 10:39:58 -0700 Subject: [PATCH 048/104] Fix formatting. --- scripting/get5.sp | 197 +++++++++++++++++++--------------- scripting/get5/backups.sp | 19 ++-- scripting/get5/goinglive.sp | 10 +- scripting/get5/mapveto.sp | 4 +- scripting/get5/matchconfig.sp | 32 ++++-- scripting/get5/pausing.sp | 117 ++++++++++++-------- scripting/get5/readysystem.sp | 9 +- scripting/get5/recording.sp | 20 ++-- scripting/get5/stats.sp | 108 +++++++++---------- scripting/get5/teamlogic.sp | 14 +-- scripting/get5/util.sp | 20 ++-- scripting/get5_apistats.sp | 3 +- scripting/get5_mysqlstats.sp | 3 +- 13 files changed, 314 insertions(+), 242 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index b1a3f9be7..243db6821 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -23,11 +23,11 @@ #include "include/restorecvars.inc" #include #include // github.com/clugg/sm-json +#include #include #include #include #include -#include #undef REQUIRE_EXTENSIONS #include @@ -48,7 +48,8 @@ #define DEFAULT_TAG "[{YELLOW}Get5{NORMAL}]" #if !defined LATEST_VERSION_URL -#define LATEST_VERSION_URL "https://raw.githubusercontent.com/splewis/get5/master/scripting/get5/version.sp" +#define LATEST_VERSION_URL \ + "https://raw.githubusercontent.com/splewis/get5/master/scripting/get5/version.sp" #endif #if !defined GET5_GITHUB_PAGE @@ -155,9 +156,10 @@ Handle g_KnifeDecisionTimer = INVALID_HANDLE; Handle g_KnifeCountdownTimer = INVALID_HANDLE; /** Pausing **/ -bool g_IsChangingPauseState = false; // Used to prevent mp_pause_match and mp_unpause_match from being called directly. -Get5Team g_PausingTeam = Get5Team_None; // The team that last called for a pause. -Get5PauseType g_PauseType = Get5PauseType_None; // The type of pause last initiated. +bool g_IsChangingPauseState = + false; // Used to prevent mp_pause_match and mp_unpause_match from being called directly. +Get5Team g_PausingTeam = Get5Team_None; // The team that last called for a pause. +Get5PauseType g_PauseType = Get5PauseType_None; // The type of pause last initiated. int g_LatestPauseDuration = 0; bool g_TeamReadyForUnpause[MATCHTEAM_COUNT]; bool g_TeamGivenStopCommand[MATCHTEAM_COUNT]; @@ -316,8 +318,8 @@ public void OnPluginStart() { InitDebugLog(DEBUG_CVAR, "get5"); LogDebug("OnPluginStart version=%s", PLUGIN_VERSION); - // Because we use SDKHooks for damage, we need to re-hook clients that are already on the server in case - // the plugin is reloaded. This includes bots. + // Because we use SDKHooks for damage, we need to re-hook clients that are already on the server + // in case the plugin is reloaded. This includes bots. LOOP_CLIENTS(i) { if (IsValidClient(i)) { Stats_HookDamageForClient(i); @@ -434,7 +436,9 @@ public void OnPluginStart() { "Seconds to countdown before veto process commences. Set to \"0\" to disable."); g_WarmupCfgCvar = CreateConVar("get5_warmup_cfg", "get5/warmup.cfg", "Config file to exec in warmup periods"); - g_PrintUpdateNoticeCvar = CreateConVar("get5_print_update_notice", "1", "Whether to print to chat when the game goes live if a new version of Get5 is available."); + g_PrintUpdateNoticeCvar = CreateConVar( + "get5_print_update_notice", "1", + "Whether to print to chat when the game goes live if a new version of Get5 is available."); g_RoundBackupPathCvar = CreateConVar( "get5_backup_path", "", "The folder to save backup files in, relative to the csgo directory. If defined, it must not start with a slash and must end with a slash."); @@ -638,7 +642,6 @@ public Action Timer_InfoMessages(Handle timer) { } MissingPlayerInfoMessage(); } else if (g_GameState == Get5State_Warmup && !g_MapChangePending) { - // Handle warmup state, provided we're not waiting for a map change // Backups take priority if (!IsTeamsReady() && g_WaitingForRoundBackup) { @@ -667,7 +670,9 @@ public Action Timer_InfoMessages(Handle timer) { FormatChatCommand(formattedStayCommand, sizeof(formattedStayCommand), "!stay"); char formattedSwapCommand[64]; FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); - Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, formattedSwapCommand); + Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", + g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, + formattedSwapCommand); } else if (g_GameState == Get5State_PostGame && GetTvDelay() > 0) { // Handle postgame Get5_MessageToAll("%t", "WaitingForGOTVBrodcastEndingInfoMessage"); @@ -969,7 +974,7 @@ public Action Command_EndMatch(int client, int args) { return Plugin_Handled; } - Get5Team winningTeam = Get5Team_None; // defaults to tie + Get5Team winningTeam = Get5Team_None; // defaults to tie if (args >= 1) { char forcedWinningTeam[8]; GetCmdArg(1, forcedWinningTeam, sizeof(forcedWinningTeam)); @@ -993,12 +998,9 @@ public Action Command_EndMatch(int client, int args) { int team2score = CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)); Get5MapResultEvent mapResultEvent = new Get5MapResultEvent( - g_MatchID, - g_MapNumber, - new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), - team1score, - team2score - ); + g_MatchID, g_MapNumber, + new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), team1score, + team2score); LogDebug("Calling Get5_OnMapResult()"); Call_StartForward(g_OnMapResult); @@ -1014,7 +1016,8 @@ public Action Command_EndMatch(int client, int args) { if (winningTeam == Get5Team_None) { Get5_MessageToAll("%t", "AdminForceEndInfoMessage"); } else { - Get5_MessageToAll("%t", "AdminForceEndWithWinnerInfoMessage", g_FormattedTeamNames[winningTeam]); + Get5_MessageToAll("%t", "AdminForceEndWithWinnerInfoMessage", + g_FormattedTeamNames[winningTeam]); } if (g_ActiveVetoMenu != null) { @@ -1132,10 +1135,12 @@ public Action Command_Stop(int client, int args) { FormatChatCommand(stopCommandFormatted, sizeof(stopCommandFormatted), "!stop"); if (g_TeamGivenStopCommand[Get5Team_1] && !g_TeamGivenStopCommand[Get5Team_2]) { Get5_MessageToAll("%t", "TeamWantsToReloadLastRoundInfoMessage", - g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2], stopCommandFormatted); + g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2], + stopCommandFormatted); } else if (!g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { Get5_MessageToAll("%t", "TeamWantsToReloadLastRoundInfoMessage", - g_FormattedTeamNames[Get5Team_2], g_FormattedTeamNames[Get5Team_1], stopCommandFormatted); + g_FormattedTeamNames[Get5Team_2], g_FormattedTeamNames[Get5Team_1], + stopCommandFormatted); } else if (g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { RestoreLastRound(client); } @@ -1191,14 +1196,14 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast float restartDelay = GetCurrentMatchRestartDelay(); float requiredDelay = float(GetTvDelay() + MATCH_END_DELAY_AFTER_TV); if (requiredDelay > restartDelay) { - LogDebug("Extended mp_match_restart_delay from %f to %f to ensure GOTV broadcast can finish.", restartDelay, requiredDelay); + LogDebug("Extended mp_match_restart_delay from %f to %f to ensure GOTV broadcast can finish.", + restartDelay, requiredDelay); SetCurrentMatchRestartDelay(requiredDelay); - restartDelay = requiredDelay; // reassigned because we reuse the variable below. + restartDelay = requiredDelay; // reassigned because we reuse the variable below. } StopRecording(float(MATCH_END_DELAY_AFTER_TV)); if (g_GameState == Get5State_Live) { - // If someone called for a pause in the last round; cancel it. if (IsPaused()) { UnpauseGame(Get5Team_None); @@ -1226,12 +1231,9 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast g_TeamScoresPerMap.Set(g_MapNumber, t2score, view_as(Get5Team_2)); Get5MapResultEvent mapResultEvent = new Get5MapResultEvent( - g_MatchID, - g_MapNumber, - new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), - t1score, - t2score - ); + g_MatchID, g_MapNumber, + new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), t1score, + t2score); LogDebug("Calling Get5_OnMapResult()"); @@ -1247,7 +1249,8 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast int remainingMaps = g_MapsToPlay.Length - t1maps - t2maps - tiedMaps; if (t1maps == t2maps) { - // As long as team scores are equal, we play until there are no maps left, regardless of clinch config. + // As long as team scores are equal, we play until there are no maps left, regardless of + // clinch config. if (remainingMaps <= 0) { EndSeries(Get5Team_None, true, restartDelay); return Plugin_Continue; @@ -1265,7 +1268,8 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast return Plugin_Continue; } } else if (remainingMaps <= 0) { - EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, restartDelay); // Tie handled in first if-block + EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, + restartDelay); // Tie handled in first if-block return Plugin_Continue; } @@ -1285,14 +1289,15 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast g_MapsToPlay.GetString(Get5_GetMapNumber(), nextMap, sizeof(nextMap)); char timeToMapChangeFormatted[8]; - convertSecondsToMinutesAndSeconds(RoundToFloor(restartDelay), timeToMapChangeFormatted, sizeof(timeToMapChangeFormatted)); + convertSecondsToMinutesAndSeconds(RoundToFloor(restartDelay), timeToMapChangeFormatted, + sizeof(timeToMapChangeFormatted)); g_MapChangePending = true; Format(nextMap, sizeof(nextMap), "{GREEN}%s{NORMAL}", nextMap); Get5_MessageToAll("%t", "NextSeriesMapInfoMessage", nextMap, timeToMapChangeFormatted); ChangeState(Get5State_PostGame); - // Subtracting 4 seconds makes the map change 1 second before the timer expires, as there is a 3 second built-in - // delay in the ChangeMap function called by Timer_NextMatchMap. + // Subtracting 4 seconds makes the map change 1 second before the timer expires, as there is a 3 + // second built-in delay in the ChangeMap function called by Timer_NextMatchMap. CreateTimer(restartDelay - 4, Timer_NextMatchMap); } @@ -1302,31 +1307,33 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast public Action Timer_NextMatchMap(Handle timer) { char map[PLATFORM_MAX_PATH]; g_MapsToPlay.GetString(Get5_GetMapNumber(), map, sizeof(map)); - // If you change these 3 seconds for whatever reason, you must adjust the counter-offset in Event_MatchOver. + // If you change these 3 seconds for whatever reason, you must adjust the counter-offset in + // Event_MatchOver. ChangeMap(map, 3.0); } -void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay, bool kickPlayers = true) { +void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay, + bool kickPlayers = true) { Stats_SeriesEnd(winningTeam); if (printWinnerMessage) { if (winningTeam == Get5Team_None) { - Get5_MessageToAll("%t", "TeamTiedMatchInfoMessage", g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2]); + Get5_MessageToAll("%t", "TeamTiedMatchInfoMessage", g_FormattedTeamNames[Get5Team_1], + g_FormattedTeamNames[Get5Team_2]); } else { if (g_MapsToPlay.Length == 1) { Get5_MessageToAll("%t", "TeamWonMatchInfoMessage", g_FormattedTeamNames[winningTeam]); } else { - Get5_MessageToAll("%t", "TeamWonSeriesInfoMessage", g_FormattedTeamNames[winningTeam], g_TeamSeriesScores[winningTeam], g_TeamSeriesScores[OtherMatchTeam(winningTeam)]); + Get5_MessageToAll("%t", "TeamWonSeriesInfoMessage", g_FormattedTeamNames[winningTeam], + g_TeamSeriesScores[winningTeam], + g_TeamSeriesScores[OtherMatchTeam(winningTeam)]); } } } Get5SeriesResultEvent event = new Get5SeriesResultEvent( - g_MatchID, - new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), - g_TeamSeriesScores[Get5Team_1], - g_TeamSeriesScores[Get5Team_2] - ); + g_MatchID, new Get5Winner(winningTeam, view_as(Get5TeamToCSTeam(winningTeam))), + g_TeamSeriesScores[Get5Team_1], g_TeamSeriesScores[Get5Team_2]); LogDebug("Calling Get5_OnSeriesResult()"); @@ -1337,7 +1344,8 @@ void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay EventLogger_LogAndDeleteEvent(event); ChangeState(Get5State_None); - // We don't want to kick players until after the specified delay, as it will kick casters potentially before GOTV ends. + // We don't want to kick players until after the specified delay, as it will kick casters + // potentially before GOTV ends. if (kickPlayers && g_KickClientsWithNoMatchCvar.BoolValue) { if (restoreDelay < 0.1) { KickPlayers(); @@ -1350,9 +1358,10 @@ void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay // When force-ending the match there is no delay. RestoreCvars(g_MatchConfigChangedCvars); } else { - // If we restore cvars immediately, it might change the tv_ params or set the mp_match_restart_delay to something - // lower, which is noticed by the game and may trigger a map change before GOTV broadcast ends, so we don't do this - // until the current match restart delay has passed. + // If we restore cvars immediately, it might change the tv_ params or set the + // mp_match_restart_delay to something lower, which is noticed by the game and may trigger a map + // change before GOTV broadcast ends, so we don't do this until the current match restart delay + // has passed. CreateTimer(restoreDelay, Timer_RestoreMatchCvars, _, TIMER_FLAG_NO_MAPCHANGE); } } @@ -1368,7 +1377,8 @@ public Action Timer_KickOnEnd(Handle timer) { static void KickPlayers() { bool kickImmunity = g_KickClientImmunityCvar.BoolValue; LOOP_CLIENTS(i) { - if (IsPlayer(i) && !(kickImmunity && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) { + if (IsPlayer(i) && + !(kickImmunity && CheckCommandAccess(i, "get5_kickcheck", ADMFLAG_CHANGEMAP))) { KickClient(i, "%t", "MatchFinishedInfoMessage"); } } @@ -1376,7 +1386,8 @@ static void KickPlayers() { public Action Timer_RestoreMatchCvars(Handle timer) { if (g_GameState == Get5State_None) { - // Only reset if no game is running, otherwise a game started before the restart delay for another ends will mess this up. + // Only reset if no game is running, otherwise a game started before the restart delay for + // another ends will mess this up. RestoreCvars(g_MatchConfigChangedCvars); } return Plugin_Handled; @@ -1401,7 +1412,8 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad if (g_GameState == Get5State_WaitingForKnifeRoundDecision && !InWarmup()) { // Ensures that round end after knife sends players directly into warmup. - // This immediately triggers another Event_RoundPreStart, so we can return here and avoid writing backup twice. + // This immediately triggers another Event_RoundPreStart, so we can return here and avoid + // writing backup twice. LogDebug("Changed to warmup post knife."); ExecCfg(g_WarmupCfgCvar); EnsureIndefiniteWarmup(); @@ -1413,8 +1425,8 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad // We need this for events that fire after the map ends, such as grenades detonating (or someone // dying in fire), to be correct. It's sort of an edge-case, but due to how Get5_GetMapNumber // works, it will return +1 if called after a map has been decided, but before the game actually - // stops, which could lead to events having the wrong map number, so we set both of these here and not - // in round_end + // stops, which could lead to events having the wrong map number, so we set both of these here and + // not in round_end g_MapNumber = Get5_GetMapNumber(); // Round number always -1 if not live. g_RoundNumber = g_GameState != Get5State_Live ? -1 : GetRoundsPlayed(); @@ -1428,8 +1440,9 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_FreezeEnd"); - // If someone changes the map while in a pause, we have to make sure we reset this state, as the UnpauseGame function - // will not be called to do it. FreezeTimeEnd is always called when the map initially loads. + // If someone changes the map while in a pause, we have to make sure we reset this state, as the + // UnpauseGame function will not be called to do it. FreezeTimeEnd is always called when the map + // initially loads. g_LatestPauseDuration = 0; g_PauseType = Get5PauseType_None; g_PausingTeam = Get5Team_None; @@ -1441,14 +1454,12 @@ public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast } } - static bool CreateDirectoryWithPermissions(const char[] directory) { LogDebug("Creating directory: %s", directory); - return CreateDirectory(directory, // sets 777 permissions. - FPERM_U_READ | FPERM_U_WRITE | FPERM_U_EXEC | - FPERM_G_READ | FPERM_G_WRITE | FPERM_G_EXEC | - FPERM_O_READ | FPERM_O_WRITE | FPERM_O_EXEC - ); + return CreateDirectory(directory, // sets 777 permissions. + FPERM_U_READ | FPERM_U_WRITE | FPERM_U_EXEC | FPERM_G_READ | + FPERM_G_WRITE | FPERM_G_EXEC | FPERM_O_READ | FPERM_O_WRITE | + FPERM_O_EXEC); } static bool CreateBackupFolderStructure(const char[] path) { @@ -1457,14 +1468,16 @@ static bool CreateBackupFolderStructure(const char[] path) { } LogDebug("Creating backup directory %s because it does not exist.", path); - char folders[16][PLATFORM_MAX_PATH]; // {folder1, folder2, etc} - char fullFolderPath[PLATFORM_MAX_PATH] = ""; // initially empty, but we append every time a folder is created/verified - char currentFolder[PLATFORM_MAX_PATH]; // shorthand for folders[i] + char folders[16][PLATFORM_MAX_PATH]; // {folder1, folder2, etc} + char fullFolderPath[PLATFORM_MAX_PATH] = + ""; // initially empty, but we append every time a folder is created/verified + char currentFolder[PLATFORM_MAX_PATH]; // shorthand for folders[i] ExplodeString(path, "/", folders, sizeof(folders), PLATFORM_MAX_PATH, true); for (int i = 0; i < sizeof(folders); i++) { currentFolder = folders[i]; - if (strlen(currentFolder) == 0) { // as the loop is a fixed size, we stop when there are no more pieces. + if (strlen(currentFolder) == + 0) { // as the loop is a fixed size, we stop when there are no more pieces. break; } // Append the current folder to the full path @@ -1487,8 +1500,12 @@ public void WriteBackup() { ReplaceString(folder, sizeof(folder), "{MATCHID}", g_MatchID); int backupFolderLength = strlen(folder); - if (backupFolderLength > 0 && (folder[0] == '/' || folder[0] == '.' || folder[backupFolderLength-1] != '/' || StrContains(folder, "//") != -1)) { - LogError("get5_backup_path must end with a slash and must not start with a slash or dot. It will be reset to an empty string! Current value: %s", folder); + if (backupFolderLength > 0 && + (folder[0] == '/' || folder[0] == '.' || folder[backupFolderLength - 1] != '/' || + StrContains(folder, "//") != -1)) { + LogError( + "get5_backup_path must end with a slash and must not start with a slash or dot. It will be reset to an empty string! Current value: %s", + folder); folder = ""; g_RoundBackupPathCvar.SetString(folder, false, false); } else { @@ -1500,7 +1517,7 @@ public void WriteBackup() { Format(path, sizeof(path), "%sget5_backup_match%s_map%d_round%d.cfg", folder, g_MatchID, g_MapNumber, g_RoundNumber); } else { - Format(path, sizeof(path), "%sget5_backup_match%s_map%d_prelive.cfg", folder, g_MatchID, + Format(path, sizeof(path), "%sget5_backup_match%s_map%d_prelive.cfg", folder, g_MatchID, g_MapNumber); } LogDebug("Writing backup to %s", path); @@ -1568,16 +1585,19 @@ public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad FormatChatCommand(formattedStayCommand, sizeof(formattedStayCommand), "!stay"); char formattedSwapCommand[64]; FormatChatCommand(formattedSwapCommand, sizeof(formattedSwapCommand), "!swap"); - Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, formattedSwapCommand); + Get5_MessageToAll("%t", "WaitingForEnemySwapInfoMessage", + g_FormattedTeamNames[g_KnifeWinnerTeam], formattedStayCommand, + formattedSwapCommand); if (g_TeamTimeToKnifeDecisionCvar.FloatValue > 0) { - g_KnifeDecisionTimer = CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision); + g_KnifeDecisionTimer = + CreateTimer(g_TeamTimeToKnifeDecisionCvar.FloatValue, Timer_ForceKnifeDecision); } - // This ensures that the correct graphic is displayed in-game for the winning team, as CTs will always win if the - // clock runs out. It also ensures that the fun fact displayed is correct; overriding to number of players killed - // by knife and no "CT won by running down the clock". MVP can still be on the losing team though. - // ran down". + // This ensures that the correct graphic is displayed in-game for the winning team, as CTs will + // always win if the clock runs out. It also ensures that the fun fact displayed is correct; + // overriding to number of players killed by knife and no "CT won by running down the clock". + // MVP can still be on the losing team though. ran down". int maxFrags = 0; int topFragClient = 0; int frags; @@ -1591,7 +1611,8 @@ public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad } } if (topFragClient > 0) { - // Found here: https://github.com/SteamDatabase/GameTracking-CSGO/blob/master/csgo/bin/server_client_strings.txt + // Found here: + // https://github.com/SteamDatabase/GameTracking-CSGO/blob/master/csgo/bin/server_client_strings.txt event.SetString("funfact_token", "#funfact_knife_kills"); event.SetInt("funfact_player", topFragClient); event.SetInt("funfact_data1", maxFrags); @@ -1620,16 +1641,15 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) int csTeamWinner = event.GetInt("winner"); Get5_MessageToAll("%s {GREEN}%d {NORMAL}- {GREEN}%d %s", g_FormattedTeamNames[Get5Team_1], - CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)), - CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)), - g_FormattedTeamNames[Get5Team_2] - ); + CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_1)), + CS_GetTeamScore(Get5TeamToCSTeam(Get5Team_2)), + g_FormattedTeamNames[Get5Team_2]); Stats_RoundEnd(csTeamWinner); if (g_DamagePrintCvar.BoolValue) { LOOP_CLIENTS(i) { - PrintDamageInfo(i); // Checks valid client etc. on its own. + PrintDamageInfo(i); // Checks valid client etc. on its own. } } @@ -1892,11 +1912,11 @@ public void EventLogger_LogAndDeleteEvent(Get5Event event) { } stock void CheckForLatestVersion() { - // both x.y.z-dev and x.y.z-abcdef contain a single dash, so we can look for that. g_RunningPrereleaseVersion = StrContains(PLUGIN_VERSION, "-", true) > -1; if (g_RunningPrereleaseVersion) { - LogMessage("Non-official Get5 version detected. Skipping update check. You may see this if you compiled Get5 \ + LogMessage( + "Non-official Get5 version detected. Skipping update check. You may see this if you compiled Get5 \ yourself or if you downloaded a pre-release for testing. If you are done testing, please download an official \ release version to remove this message."); return; @@ -1910,12 +1930,10 @@ release version to remove this message."); Handle req = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, LATEST_VERSION_URL); SteamWorks_SetHTTPCallbacks(req, VersionCheckRequestCallback); SteamWorks_SendHTTPRequest(req); - } stock int VersionCheckRequestCallback(Handle request, bool failure, bool requestSuccessful, - EHTTPStatusCode statusCode) { - + EHTTPStatusCode statusCode) { if (failure || !requestSuccessful) { LogError("Failed to check for Get5 update. HTTP error code: %d.", statusCode); delete request; @@ -1928,9 +1946,9 @@ stock int VersionCheckRequestCallback(Handle request, bool failure, bool request SteamWorks_GetHTTPResponseBodyData(request, response, responseSize); delete request; - // Since we're comparing against master, which always contains a -dev tag, we extract the version substring - // *before* that -dev tag (or whatever it might be). This *should* have been removed by the CI flow, so that official - // releases don't contain the -dev tag. + // Since we're comparing against master, which always contains a -dev tag, we extract the version + // substring *before* that -dev tag (or whatever it might be). This *should* have been removed by + // the CI flow, so that official releases don't contain the -dev tag. Regex versionRegex = new Regex("#define PLUGIN_VERSION \"(.+)-.+\""); RegexError rError; @@ -1954,12 +1972,13 @@ stock int VersionCheckRequestCallback(Handle request, bool failure, bool request LogDebug("Newest Get5 version from GitHub is: %s", newestVersionFound); g_NewerVersionAvailable = !StrEqual(PLUGIN_VERSION, newestVersionFound); if (g_NewerVersionAvailable) { - LogMessage("A newer version of Get5 is available. You are running %s while the latest version is %s.", PLUGIN_VERSION, newestVersionFound); + LogMessage( + "A newer version of Get5 is available. You are running %s while the latest version is %s.", + PLUGIN_VERSION, newestVersionFound); } else { LogMessage("Update check successful. Get5 is up-to-date (%s).", PLUGIN_VERSION); } } delete versionRegex; - } diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 1baa875df..5a61d995a 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -40,7 +40,6 @@ public Action Command_ListBackups(int client, int args) { DirectoryListing files = OpenDirectory(strlen(path) > 0 ? path : "."); if (files != null) { - char backupInfo[256]; char pattern[PLATFORM_MAX_PATH]; Format(pattern, sizeof(pattern), "get5_backup_match%s", matchID); @@ -237,9 +236,9 @@ public bool RestoreFromBackup(const char[] path) { delete kv; LogError("Could not restore from match config \"%s\"", tempBackupFile); if (g_GameState != Get5State_None) { - // If the backup load fails, all the game configs will have been reset by LoadMatchConfig, but the game state - // won't. This ensure we don't end up a in a "live" state with no get5 variables set, which would prevent a call - // to load a new match. + // If the backup load fails, all the game configs will have been reset by LoadMatchConfig, + // but the game state won't. This ensure we don't end up a in a "live" state with no get5 + // variables set, which would prevent a call to load a new match. ChangeState(Get5State_None); } return false; @@ -259,7 +258,8 @@ public bool RestoreFromBackup(const char[] path) { g_TeamSeriesScores[Get5Team_1] = kv.GetNum("team1_series_score"); g_TeamSeriesScores[Get5Team_2] = kv.GetNum("team2_series_score"); - // This ensures that the MapNumber logic correctly calculates the map number when there have been draws. + // This ensures that the MapNumber logic correctly calculates the map number when there have been + // draws. g_TeamSeriesScores[Get5Team_None] = kv.GetNum("series_draw", 0); // Immediately set map number global var to ensure anything below doesn't break. @@ -306,7 +306,8 @@ public bool RestoreFromBackup(const char[] path) { kv.GoBack(); } - // When loading round 0, there is no valve backup, so we assume round 0 if the game is live, otherwise -1 + // When loading round 0, there is no valve backup, so we assume round 0 if the game is live, + // otherwise -1 int roundNumberRestoredTo = g_GameState == Get5State_Live ? 0 : -1; char tempValveBackup[PLATFORM_MAX_PATH]; GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); @@ -337,7 +338,8 @@ public bool RestoreFromBackup(const char[] path) { LogDebug("Calling Get5_OnBackupRestore()"); - Get5BackupRestoredEvent backupEvent = new Get5BackupRestoredEvent(g_MatchID, g_MapNumber, roundNumberRestoredTo, path); + Get5BackupRestoredEvent backupEvent = + new Get5BackupRestoredEvent(g_MatchID, g_MapNumber, roundNumberRestoredTo, path); Call_StartForward(g_OnBackupRestore); Call_PushCell(backupEvent); @@ -430,7 +432,8 @@ public void DeleteOldBackups() { g_RoundBackupPathCvar.GetString(path, sizeof(path)); if (StrContains(path, "{MATCHID}") != -1) { - LogError("Automatic backup deletion cannot be performed when get5_backup_path contains the {MATCHID} variable."); + LogError( + "Automatic backup deletion cannot be performed when get5_backup_path contains the {MATCHID} variable."); return; } diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index 4587e44cf..bbba2decc 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -62,11 +62,11 @@ public Action MatchLive(Handle timer) { } /** - * Please do not change this. Thousands of uncompensated hours were poured into making this plugin. - * Claiming it as your own because you made slight modifications to it is not cool. If you have suggestions, - * bug reports or feature requests, please see GitHub or join our Discord: https://splewis.github.io/get5/community/ - * Thanks in advance! - */ + * Please do not change this. Thousands of uncompensated hours were poured into making this + * plugin. Claiming it as your own because you made slight modifications to it is not cool. If you + * have suggestions, bug reports or feature requests, please see GitHub or join our Discord: + * https://splewis.github.io/get5/community/ Thanks in advance! + */ char tag[64]; g_MessagePrefixCvar.GetString(tag, sizeof(tag)); if (!StrEqual(tag, DEFAULT_TAG)) { diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index e35f6b579..1109c983a 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -365,8 +365,8 @@ public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int para char mapNameFormatted[PLATFORM_MAX_PATH]; Format(mapNameFormatted, sizeof(mapNameFormatted), "{GREEN}%s{NORMAL}", mapName); - Get5_MessageToAll("%t", "TeamPickedMapInfoMessage", g_FormattedTeamNames[team], mapNameFormatted, - g_MapsToPlay.Length); + Get5_MessageToAll("%t", "TeamPickedMapInfoMessage", g_FormattedTeamNames[team], + mapNameFormatted, g_MapsToPlay.Length); g_LastVetoTeam = team; Get5MapPickedEvent event = diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 2d3b64645..fa5f47ee9 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -852,7 +852,10 @@ public Action Command_AddPlayer(int client, int args) { if (AddPlayerToTeam(auth, team, name)) { ReplyToCommand(client, "Successfully added player %s to %s.", auth, teamString); } else { - ReplyToCommand(client, "Failed to add player %s to team %. They may already be on a team or you provided an invalid Steam ID.", auth, teamString); + ReplyToCommand( + client, + "Failed to add player %s to team %. They may already be on a team or you provided an invalid Steam ID.", + auth, teamString); } } else { @@ -905,7 +908,10 @@ public Action Command_AddCoach(int client, int args) { WriteBackup(); ReplyToCommand(client, "Successfully added player %s as coach for %s.", auth, teamString); } else { - ReplyToCommand(client, "Failed to add player %s as coach for %s. They may already be coaching or you provided an invalid Steam ID.", auth, teamString); + ReplyToCommand( + client, + "Failed to add player %s as coach for %s. They may already be coaching or you provided an invalid Steam ID.", + auth, teamString); } } else { ReplyToCommand(client, "Usage: get5_addcoach [name]"); @@ -951,10 +957,13 @@ public Action Command_AddKickedPlayer(int client, int args) { } if (AddPlayerToTeam(g_LastKickedPlayerAuth, team, name)) { - ReplyToCommand(client, "Successfully added kicked player %s to %s.", - g_LastKickedPlayerAuth, teamString); + ReplyToCommand(client, "Successfully added kicked player %s to %s.", g_LastKickedPlayerAuth, + teamString); } else { - ReplyToCommand(client, "Failed to add player %s to %s. They may already be on a team or you provided an invalid Steam ID.", g_LastKickedPlayerAuth, teamString); + ReplyToCommand( + client, + "Failed to add player %s to %s. They may already be on a team or you provided an invalid Steam ID.", + g_LastKickedPlayerAuth, teamString); } } else { @@ -981,7 +990,8 @@ public Action Command_RemovePlayer(int client, int args) { if (RemovePlayerFromTeams(auth)) { ReplyToCommand(client, "Successfully removed player %s.", auth); } else { - ReplyToCommand(client, "Player %s not found in auth lists or the Steam ID was invalid.", auth); + ReplyToCommand(client, "Player %s not found in auth lists or the Steam ID was invalid.", + auth); } } else { ReplyToCommand(client, "Usage: get5_removeplayer "); @@ -1010,7 +1020,8 @@ public Action Command_RemoveKickedPlayer(int client, int args) { if (RemovePlayerFromTeams(g_LastKickedPlayerAuth)) { ReplyToCommand(client, "Successfully removed kicked player %s.", g_LastKickedPlayerAuth); } else { - ReplyToCommand(client, "Player %s not found in auth lists or the Steam ID was invalid.", g_LastKickedPlayerAuth); + ReplyToCommand(client, "Player %s not found in auth lists or the Steam ID was invalid.", + g_LastKickedPlayerAuth); } return Plugin_Handled; } @@ -1241,7 +1252,8 @@ static void AddTeamLogoToDownloadTable(const char[] logoName) { return; char logoPath[PLATFORM_MAX_PATH + 1]; - Format(logoPath, sizeof(logoPath), "materials/panorama/images/tournaments/teams/%s.svg", logoName); + Format(logoPath, sizeof(logoPath), "materials/panorama/images/tournaments/teams/%s.svg", + logoName); if (FileExists(logoPath)) { LogDebug("Adding file %s to download table", logoName); AddFileToDownloadsTable(logoPath); @@ -1251,10 +1263,10 @@ static void AddTeamLogoToDownloadTable(const char[] logoName) { LogDebug("Adding file %s to download table", logoName); AddFileToDownloadsTable(logoPath); } else { - LogError("Error in locating file %s. Please ensure the file exists on your game server.", logoPath); + LogError("Error in locating file %s. Please ensure the file exists on your game server.", + logoPath); } } - } public void CheckTeamNameStatus(Get5Team team) { diff --git a/scripting/get5/pausing.sp b/scripting/get5/pausing.sp index f24e2076a..ad0d9c7a0 100644 --- a/scripting/get5/pausing.sp +++ b/scripting/get5/pausing.sp @@ -1,8 +1,7 @@ public bool PauseableGameState() { return (g_GameState == Get5State_KnifeRound || - g_GameState == Get5State_WaitingForKnifeRoundDecision || - g_GameState == Get5State_Live || - g_GameState == Get5State_GoingLive); + g_GameState == Get5State_WaitingForKnifeRoundDecision || g_GameState == Get5State_Live || + g_GameState == Get5State_GoingLive); } public void PauseGame(Get5Team team, Get5PauseType type) { @@ -42,7 +41,8 @@ public Action Timer_ResetPauseRestriction(Handle timer, int data) { } stock void UnpauseGame(Get5Team team) { - Get5MatchUnpausedEvent event = new Get5MatchUnpausedEvent(g_MatchID, g_MapNumber, team, g_PauseType); + Get5MatchUnpausedEvent event = + new Get5MatchUnpausedEvent(g_MatchID, g_MapNumber, team, g_PauseType); LogDebug("Calling Get5_OnMatchUnpaused()"); @@ -64,7 +64,8 @@ public Action Command_PauseOrUnpauseMatch(int client, const char[] command, int if (g_GameState == Get5State_None || g_IsChangingPauseState) { return Plugin_Continue; } - ReplyToCommand(client, "Get5 prevents calls to %s. Administrators should use sm_pause/sm_unpause.", command); + ReplyToCommand( + client, "Get5 prevents calls to %s. Administrators should use sm_pause/sm_unpause.", command); return Plugin_Stop; } @@ -95,7 +96,8 @@ public Action Command_TechPause(int client, int args) { if (g_PauseType != Get5PauseType_None) { g_TeamReadyForUnpause[team] = false; - LogDebug("Ignoring technical pause request as game is already paused; setting team to not ready to unpause."); + LogDebug( + "Ignoring technical pause request as game is already paused; setting team to not ready to unpause."); return Plugin_Handled; } @@ -125,7 +127,8 @@ public Action Command_TechPause(int client, int args) { FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); Get5_MessageToAll("%t", "MatchTechPausedByTeamMessage", formattedClientName); if (maxTechPauses > 0) { - Get5_MessageToAll("%t", "TechPausePausesRemaining", g_FormattedTeamNames[team], maxTechPauses - g_TechnicalPausesUsed[team]); + Get5_MessageToAll("%t", "TechPausePausesRemaining", g_FormattedTeamNames[team], + maxTechPauses - g_TechnicalPausesUsed[team]); } return Plugin_Handled; } @@ -153,7 +156,8 @@ public Action Command_Pause(int client, int args) { if (g_PauseType != Get5PauseType_None) { g_TeamReadyForUnpause[team] = false; - LogDebug("Ignoring tactical pause request as game is already paused; setting team to not ready to unpause."); + LogDebug( + "Ignoring tactical pause request as game is already paused; setting team to not ready to unpause."); return Plugin_Handled; } @@ -163,8 +167,10 @@ public Action Command_Pause(int client, int args) { int maxPauseTime = g_MaxPauseTimeCvar.IntValue; if (maxPauseTime > 0 && g_TacticalPauseTimeUsed[team] >= maxPauseTime) { char maxPauseTimeFormatted[16]; - convertSecondsToMinutesAndSeconds(maxPauseTime, maxPauseTimeFormatted, sizeof(maxPauseTimeFormatted)); - Get5_Message(client, "%t", "MaxPausesTimeUsedInfoMessage", maxPauseTimeFormatted, g_FormattedTeamNames[team]); + convertSecondsToMinutesAndSeconds(maxPauseTime, maxPauseTimeFormatted, + sizeof(maxPauseTimeFormatted)); + Get5_Message(client, "%t", "MaxPausesTimeUsedInfoMessage", maxPauseTimeFormatted, + g_FormattedTeamNames[team]); return Plugin_Handled; } } @@ -225,8 +231,7 @@ public Action Command_Unpause(int client, int args) { int techPausesUsed = g_TechnicalPausesUsed[g_PausingTeam]; if ((maxTechPauseDuration > 0 && g_LatestPauseDuration >= maxTechPauseDuration) || - (maxTechPauses > 0 && techPausesUsed > maxTechPauses) - ) { + (maxTechPauses > 0 && techPausesUsed > maxTechPauses)) { UnpauseGame(team); if (IsPlayer(client)) { char formattedClientName[MAX_NAME_LENGTH]; @@ -279,14 +284,13 @@ public Action Timer_PauseTimeCheck(Handle timer) { CSTeamString(Get5TeamToCSTeam(team), teamString, sizeof(teamString)); if (g_PauseType == Get5PauseType_Tactical) { - int maxTacticalPauseTime = g_MaxPauseTimeCvar.IntValue; int maxTacticalPauses = g_MaxTacticalPausesCvar.IntValue; int tacticalPausesUsed = g_TacticalPausesUsed[team]; int fixedPauseTime = g_FixedPauseTimeCvar.IntValue; if (fixedPauseTime > 0 && fixedPauseTime < 15) { - fixedPauseTime = 15; // Don't allow less than 15 second fixed pauses. + fixedPauseTime = 15; // Don't allow less than 15 second fixed pauses. } // -1 assumes unlimited. @@ -299,23 +303,27 @@ public Action Timer_PauseTimeCheck(Handle timer) { return Plugin_Stop; } } else if (maxTacticalPauses > 0 && tacticalPausesUsed > maxTacticalPauses) { - // The game gets unpaused if the number of maximum pauses changes to below the number of used pauses while a - // pause is active. Kind of a weird edge-case, but it should be handled gracefully. - Get5_MessageToAll("%t", "MaxPausesUsedInfoMessage", maxTacticalPauses, g_FormattedTeamNames[g_PausingTeam]); + // The game gets unpaused if the number of maximum pauses changes to below the number of used + // pauses while a pause is active. Kind of a weird edge-case, but it should be handled + // gracefully. + Get5_MessageToAll("%t", "MaxPausesUsedInfoMessage", maxTacticalPauses, + g_FormattedTeamNames[g_PausingTeam]); UnpauseGame(team); return Plugin_Stop; } else if (!g_TeamReadyForUnpause[team]) { - // If the team that called the pause has indicated they are ready, no more time should be subtracted from their - // maximum pause time, but the timer must keep running as they could go back to not-ready-for-unpause before the - // other team unpauses, in which case we would keep counting their seconds used. + // If the team that called the pause has indicated they are ready, no more time should be + // subtracted from their maximum pause time, but the timer must keep running as they could go + // back to not-ready-for-unpause before the other team unpauses, in which case we would keep + // counting their seconds used. g_TacticalPauseTimeUsed[team]++; - LogDebug("Adding tactical pause time used for Get5Team %d. Now: %d", team, g_TacticalPauseTimeUsed[team]); + LogDebug("Adding tactical pause time used for Get5Team %d. Now: %d", team, + g_TacticalPauseTimeUsed[team]); if (maxTacticalPauseTime > 0) { timeLeft = maxTacticalPauseTime - g_TacticalPauseTimeUsed[team]; if (timeLeft <= 0) { - Get5_MessageToAll("%t", "PauseRunoutInfoMessage", g_FormattedTeamNames[team]); - UnpauseGame(team); - return Plugin_Stop; + Get5_MessageToAll("%t", "PauseRunoutInfoMessage", g_FormattedTeamNames[team]); + UnpauseGame(team); + return Plugin_Stop; } } } @@ -328,45 +336,56 @@ public Action Timer_PauseTimeCheck(Handle timer) { char pauseTimeMaxFormatted[16] = ""; if (timeLeft >= 0) { - convertSecondsToMinutesAndSeconds(maxTacticalPauseTime, pauseTimeMaxFormatted, sizeof(pauseTimeMaxFormatted)); + convertSecondsToMinutesAndSeconds(maxTacticalPauseTime, pauseTimeMaxFormatted, + sizeof(pauseTimeMaxFormatted)); } LOOP_CLIENTS(i) { if (IsValidClient(i)) { - if (fixedPauseTime) { // If fixed pause; takes precedence over total time and reuses timeLeft for simplicity + if (fixedPauseTime) { // If fixed pause; takes precedence over total time and reuses + // timeLeft for simplicity if (maxTacticalPauses > 0) { // Team A (CT) tactical pause (2/4): 0:45 - PrintHintText(i, "%s (%s) %t (%d/%d): %s", g_TeamNames[team], teamString, "TacticalPauseMidSentence", tacticalPausesUsed, maxTacticalPauses, timeLeftFormatted); + PrintHintText(i, "%s (%s) %t (%d/%d): %s", g_TeamNames[team], teamString, + "TacticalPauseMidSentence", tacticalPausesUsed, maxTacticalPauses, + timeLeftFormatted); } else { // Team A (CT) tactical pause: 0:45 - PrintHintText(i, "%s (%s) %t: %s", g_TeamNames[team], teamString, "TacticalPauseMidSentence", timeLeftFormatted); + PrintHintText(i, "%s (%s) %t: %s", g_TeamNames[team], teamString, + "TacticalPauseMidSentence", timeLeftFormatted); } - } else if (timeLeft >= 0) { // If total time restriction + } else if (timeLeft >= 0) { // If total time restriction if (maxTacticalPauses > 0) { // Team A (CT) tactical pause (2/4). // Remaining pause time: 0:45 / 3:00 - PrintHintText(i, "%s (%s) %t (%d/%d).\n%t: %s / %s", g_TeamNames[team], teamString, "TacticalPauseMidSentence", tacticalPausesUsed, maxTacticalPauses, "PauseTimeRemainingPrefix", timeLeftFormatted, pauseTimeMaxFormatted); + PrintHintText(i, "%s (%s) %t (%d/%d).\n%t: %s / %s", g_TeamNames[team], teamString, + "TacticalPauseMidSentence", tacticalPausesUsed, maxTacticalPauses, + "PauseTimeRemainingPrefix", timeLeftFormatted, pauseTimeMaxFormatted); } else { // Team A (CT) tactical pause. // Remaining pause time: 0:45 / 3:00 - PrintHintText(i, "%s (%s) %t.\n%t: %s / %s", g_TeamNames[team], teamString, "TacticalPauseMidSentence", "PauseTimeRemainingPrefix", timeLeftFormatted, pauseTimeMaxFormatted); + PrintHintText(i, "%s (%s) %t.\n%t: %s / %s", g_TeamNames[team], teamString, + "TacticalPauseMidSentence", "PauseTimeRemainingPrefix", timeLeftFormatted, + pauseTimeMaxFormatted); } - } else { // if no time restriction or awaiting unpause + } else { // if no time restriction or awaiting unpause if (maxTacticalPauses > 0) { // Team A (CT) tactical pause (2/4). // Awaiting unpause. - PrintHintText(i, "%s (%s) %t (%d/%d).\n%t.", g_TeamNames[team], teamString, "TacticalPauseMidSentence", tacticalPausesUsed, maxTacticalPauses, "AwaitingUnpause"); + PrintHintText(i, "%s (%s) %t (%d/%d).\n%t.", g_TeamNames[team], teamString, + "TacticalPauseMidSentence", tacticalPausesUsed, maxTacticalPauses, + "AwaitingUnpause"); } else { // Team A (CT) tactical pause. // Awaiting unpause. - PrintHintText(i, "%s (%s) %t.\n%t.", g_TeamNames[team], teamString, "TacticalPauseMidSentence", "AwaitingUnpause"); + PrintHintText(i, "%s (%s) %t.\n%t.", g_TeamNames[team], teamString, + "TacticalPauseMidSentence", "AwaitingUnpause"); } } } } } else if (g_PauseType == Get5PauseType_Tech) { - int maxTechPauseDuration = g_MaxTechPauseDurationCvar.IntValue; int maxTechPauses = g_MaxTechPausesCvar.IntValue; int techPausesUsed = g_TechnicalPausesUsed[team]; @@ -374,14 +393,16 @@ public Action Timer_PauseTimeCheck(Handle timer) { // -1 assumes unlimited. int timeLeft = -1; - // If tech pause max is reduced to below what is used, we don't want to print remaining time, as anyone can unpause. - // We achieve this by simply skipping the time calculation if max tech pauses have been exceeded. + // If tech pause max is reduced to below what is used, we don't want to print remaining time, as + // anyone can unpause. We achieve this by simply skipping the time calculation if max tech + // pauses have been exceeded. if (!g_TeamReadyForUnpause[team] && (maxTechPauses == 0 || techPausesUsed <= maxTechPauses)) { if (maxTechPauseDuration > 0) { timeLeft = maxTechPauseDuration - g_LatestPauseDuration; if (timeLeft == 0) { - // Only print to chat when hitting 0, but keep the timer going as tech pauses don't unpause on their own. - // The PrintHintText below will inform users that they can now unpause. + // Only print to chat when hitting 0, but keep the timer going as tech pauses don't + // unpause on their own. The PrintHintText below will inform users that they can now + // unpause. char formattedUnpauseCommand[64]; FormatChatCommand(formattedUnpauseCommand, sizeof(formattedUnpauseCommand), "!unpause"); Get5_MessageToAll("%t", "TechPauseRunoutInfoMessage", formattedUnpauseCommand); @@ -400,25 +421,31 @@ public Action Timer_PauseTimeCheck(Handle timer) { if (timeLeft >= 0) { if (maxTechPauses > 0) { // Team A (CT) technical pause (3/4): Time remaining before anyone can unpause: 1:30 - PrintHintText(i, "%s (%s) %t (%d/%d).\n%t: %s", g_TeamNames[team], teamString, "TechnicalPauseMidSentence", techPausesUsed, maxTechPauses, "TimeRemainingBeforeAnyoneCanUnpausePrefix", timeLeftFormatted); + PrintHintText(i, "%s (%s) %t (%d/%d).\n%t: %s", g_TeamNames[team], teamString, + "TechnicalPauseMidSentence", techPausesUsed, maxTechPauses, + "TimeRemainingBeforeAnyoneCanUnpausePrefix", timeLeftFormatted); } else { // Team A (CT) technical pause. Time remaining before anyone can unpause: 1:30 - PrintHintText(i, "%s (%s) %t.\n%t: %s", g_TeamNames[team], teamString, "TechnicalPauseMidSentence", "TimeRemainingBeforeAnyoneCanUnpausePrefix", timeLeftFormatted); + PrintHintText(i, "%s (%s) %t.\n%t: %s", g_TeamNames[team], teamString, + "TechnicalPauseMidSentence", "TimeRemainingBeforeAnyoneCanUnpausePrefix", + timeLeftFormatted); } } else { if (maxTechPauses > 0) { // Team A (CT) technical pause (3/4). Awaiting unpause. - PrintHintText(i, "%s (%s) %t (%d/%d).\n%t.", g_TeamNames[team], teamString, "TechnicalPauseMidSentence", techPausesUsed, maxTechPauses, "AwaitingUnpause"); + PrintHintText(i, "%s (%s) %t (%d/%d).\n%t.", g_TeamNames[team], teamString, + "TechnicalPauseMidSentence", techPausesUsed, maxTechPauses, + "AwaitingUnpause"); } else { // Team A (CT) technical pause. Awaiting unpause. - PrintHintText(i, "%s (%s) %t.\n%t.", g_TeamNames[team], teamString, "TechnicalPauseMidSentence", "AwaitingUnpause"); + PrintHintText(i, "%s (%s) %t.\n%t.", g_TeamNames[team], teamString, + "TechnicalPauseMidSentence", "AwaitingUnpause"); } } } } } else if (g_PauseType == Get5PauseType_Admin) { - LOOP_CLIENTS(i) { if (IsValidClient(i)) { PrintHintText(i, "%t", "PausedByAdministrator"); @@ -426,13 +453,11 @@ public Action Timer_PauseTimeCheck(Handle timer) { } } else if (g_PauseType == Get5PauseType_Backup) { - LOOP_CLIENTS(i) { if (IsValidClient(i)) { PrintHintText(i, "%t", "PausedForBackup"); } } - } return Plugin_Continue; } diff --git a/scripting/get5/readysystem.sp b/scripting/get5/readysystem.sp index 39042b28b..90d9bdd0c 100644 --- a/scripting/get5/readysystem.sp +++ b/scripting/get5/readysystem.sp @@ -8,7 +8,8 @@ public void ResetReadyStatus() { } public bool IsReadyGameState() { - return (g_GameState == Get5State_PreVeto || g_GameState == Get5State_Warmup) && !g_MapChangePending; + return (g_GameState == Get5State_PreVeto || g_GameState == Get5State_Warmup) && + !g_MapChangePending; } // Client ready status @@ -270,12 +271,14 @@ public void MissingPlayerInfoMessageTeam(Get5Team team) { int playerCount = GetTeamPlayerCount(team); int readyCount = GetTeamReadyCount(team); - if (playerCount == readyCount && playerCount < minPlayers && readyCount >= minReady && minPlayers > 1) { + if (playerCount == readyCount && playerCount < minPlayers && readyCount >= minReady && + minPlayers > 1) { char minPlayersFormatted[32]; Format(minPlayersFormatted, sizeof(minPlayersFormatted), "{GREEN}%d{NORMAL}", minPlayers); char forceReadyFormatted[64]; FormatChatCommand(forceReadyFormatted, sizeof(forceReadyFormatted), "!forceready"); - Get5_MessageToTeam(team, "%t", "ForceReadyInfoMessage", forceReadyFormatted, minPlayersFormatted); + Get5_MessageToTeam(team, "%t", "ForceReadyInfoMessage", forceReadyFormatted, + minPlayersFormatted); } } diff --git a/scripting/get5/recording.sp b/scripting/get5/recording.sp index 60d25f381..fc354e907 100644 --- a/scripting/get5/recording.sp +++ b/scripting/get5/recording.sp @@ -7,7 +7,8 @@ bool StartRecording() { } if (!IsTVEnabled()) { - LogError("Demo recording will not work with \"tv_enable 0\". Set \"tv_enable 1\" and restart the map to fix this."); + LogError( + "Demo recording will not work with \"tv_enable 0\". Set \"tv_enable 1\" and restart the map to fix this."); g_DemoFileName = ""; return false; } @@ -36,22 +37,26 @@ void StopRecording(float delay = 0.0) { StopRecordingCallback(g_MatchID, g_MapNumber, g_DemoFileName); } else { LogDebug("Starting timer that will end GOTV recording in %f seconds.", delay); - CreateTimer(delay, Timer_StopGoTVRecording, GetDemoInfoDataPack(g_MatchID, g_MapNumber, g_DemoFileName)); + CreateTimer(delay, Timer_StopGoTVRecording, + GetDemoInfoDataPack(g_MatchID, g_MapNumber, g_DemoFileName)); } g_DemoFileName = ""; } -static void StopRecordingCallback(const char[] matchId, const int mapNumber, const char[] demoFileName) { +static void StopRecordingCallback(const char[] matchId, const int mapNumber, + const char[] demoFileName) { ServerCommand("tv_stoprecord"); if (StrEqual("", demoFileName)) { LogDebug("Demo was not recorded by Get5; not firing Get5_OnDemoFinished()"); return; } // We delay this by 3 seconds to allow the server to flush to the file before firing the event. - CreateTimer(3.0, Timer_FireStopRecordingEvent, GetDemoInfoDataPack(matchId, mapNumber, demoFileName)); + CreateTimer(3.0, Timer_FireStopRecordingEvent, + GetDemoInfoDataPack(matchId, mapNumber, demoFileName)); } -static DataPack GetDemoInfoDataPack(const char[] matchId, const int mapNumber, const char[] demoFileName) { +static DataPack GetDemoInfoDataPack(const char[] matchId, const int mapNumber, + const char[] demoFileName) { DataPack pack = CreateDataPack(); pack.WriteString(matchId); pack.WriteCell(mapNumber); @@ -59,7 +64,8 @@ static DataPack GetDemoInfoDataPack(const char[] matchId, const int mapNumber, c return pack; } -static void ReadDemoDataPack(DataPack pack, char[] matchId, const int matchIdLength, int &mapNumber, char[] demoFileName, const int demoFileNameLength) { +static void ReadDemoDataPack(DataPack pack, char[] matchId, const int matchIdLength, int &mapNumber, + char[] demoFileName, const int demoFileNameLength) { pack.Reset(); pack.ReadString(matchId, matchIdLength); mapNumber = pack.ReadCell(); @@ -119,7 +125,7 @@ int GetTvDelay() { float GetCurrentMatchRestartDelay() { ConVar mp_match_restart_delay = FindConVar("mp_match_restart_delay"); if (mp_match_restart_delay == INVALID_HANDLE) { - return 1.0; // Shouldn't really be possible, but as a safeguard. + return 1.0; // Shouldn't really be possible, but as a safeguard. } return mp_match_restart_delay.FloatValue; } diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 56240492c..96650fda4 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -130,9 +130,9 @@ public Get5Player GetPlayerObject(int client) { return new Get5Player(0, "", view_as(CS_TEAM_NONE), "GOTV", false); } - // In cases where users disconnect (Get5PlayerDisconnectedEvent) without being on a team, they might error out - // on GetClientTeam(), so we check that they're in-game before we attempt to determine their team. - // Avoids "Client x is not in game" exception. + // In cases where users disconnect (Get5PlayerDisconnectedEvent) without being on a team, they + // might error out on GetClientTeam(), so we check that they're in-game before we attempt to + // determine their team. Avoids "Client x is not in game" exception. Get5Side side = view_as(IsClientInGame(client) ? GetClientTeam(client) : CS_TEAM_NONE); char name[MAX_NAME_LENGTH]; @@ -253,7 +253,8 @@ public void Stats_RoundStart() { Get5Team team = GetClientMatchTeam(i); if (team == Get5Team_1 || team == Get5Team_2) { // Ensures that each player has zero-filled stats on freeze-time end. - // Since joining the game after freeze-time will render you dead, you cannot obtain stats until next round. + // Since joining the game after freeze-time will render you dead, you cannot obtain stats + // until next round. InitPlayerStats(i); IncrementPlayerStat(i, STAT_ROUNDSPLAYED); } @@ -688,8 +689,9 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr // falling from vertigo is attacker 0, weapon id 0, weapon "trigger_hurt" // c4 is attacker 0, weapon id 0, weapon planted_c4 // killing self with weapons is attacker == victim - // some weapons, such as unsilenced USP or M4A1S and molotov fire are also weapon 0, so weapon ID is unreliable. - // with those in mind, we can determine that suicide must be true if attacker is 0 or attacker == victim and it was **not** the bomb. + // some weapons, such as unsilenced USP or M4A1S and molotov fire are also weapon 0, so weapon ID + // is unreliable. with those in mind, we can determine that suicide must be true if attacker is 0 + // or attacker == victim and it was **not** the bomb. bool killedByBomb = StrEqual("planted_c4", weapon); bool isSuicide = (!validAttacker || attacker == victim) && !killedByBomb; @@ -699,7 +701,8 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr if (!g_FirstDeathDone) { g_FirstDeathDone = true; - IncrementPlayerStat(victim, (victimTeam == CS_TEAM_CT) ? STAT_FIRSTDEATH_CT : STAT_FIRSTDEATH_T); + IncrementPlayerStat(victim, + (victimTeam == CS_TEAM_CT) ? STAT_FIRSTDEATH_CT : STAT_FIRSTDEATH_T); } if (isSuicide) { @@ -710,7 +713,8 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr } else { if (!g_FirstKillDone) { g_FirstKillDone = true; - IncrementPlayerStat(attacker, (attackerTeam == CS_TEAM_CT) ? STAT_FIRSTKILL_CT : STAT_FIRSTKILL_T); + IncrementPlayerStat(attacker, + (attackerTeam == CS_TEAM_CT) ? STAT_FIRSTKILL_CT : STAT_FIRSTKILL_T); } g_RoundKills[attacker]++; @@ -738,10 +742,11 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr } Get5PlayerDeathEvent playerDeathEvent = new Get5PlayerDeathEvent( - g_MatchID, g_MapNumber, g_RoundNumber, GetRoundTime(), - GetPlayerObject(victim), new Get5Weapon(weapon, weaponId), headshot, validAttacker ? attackerTeam == victimTeam : false, - event.GetBool("thrusmoke"), event.GetBool("noscope"), event.GetBool("attackerblind"), - isSuicide, event.GetInt("penetrated"), killedByBomb); + g_MatchID, g_MapNumber, g_RoundNumber, GetRoundTime(), GetPlayerObject(victim), + new Get5Weapon(weapon, weaponId), headshot, + validAttacker ? attackerTeam == victimTeam : false, event.GetBool("thrusmoke"), + event.GetBool("noscope"), event.GetBool("attackerblind"), isSuicide, + event.GetInt("penetrated"), killedByBomb); if (validAttacker) { playerDeathEvent.Attacker = GetPlayerObject(attacker); @@ -785,7 +790,7 @@ static void UpdateTradeStat(int attacker, int victim) { LOOP_CLIENTS(i) { if (IsPlayer(i) && g_PlayerKilledBy[i] == victim && GetClientTeam(i) == attackerTeam) { float dt = GetGameTime() - g_PlayerKilledByTime[i]; - if (dt < 1.5) { // "Time to trade" window fixed to 1.5 seconds. + if (dt < 1.5) { // "Time to trade" window fixed to 1.5 seconds. IncrementPlayerStat(attacker, STAT_TRADEKILL); // teammate (i) was traded g_PlayerRoundKillOrAssistOrTradedDeath[i] = true; @@ -972,45 +977,42 @@ static void InitPlayerStats(int client) { return; } - char keys[][] = { - STAT_KILLS, - STAT_DEATHS, - STAT_ASSISTS, - STAT_FLASHBANG_ASSISTS, - STAT_TEAMKILLS, - STAT_SUICIDES, - STAT_DAMAGE, - STAT_UTILITY_DAMAGE, - STAT_ENEMIES_FLASHED, - STAT_FRIENDLIES_FLASHED, - STAT_KNIFE_KILLS, - STAT_HEADSHOT_KILLS, - STAT_ROUNDSPLAYED, - STAT_BOMBDEFUSES, - STAT_BOMBPLANTS, - STAT_1K, - STAT_2K, - STAT_3K, - STAT_4K, - STAT_5K, - STAT_V1, - STAT_V2, - STAT_V3, - STAT_V4, - STAT_V5, - STAT_FIRSTKILL_T, - STAT_FIRSTKILL_CT, - STAT_FIRSTDEATH_T, - STAT_FIRSTDEATH_CT, - STAT_TRADEKILL, - STAT_KAST, - STAT_CONTRIBUTION_SCORE, - STAT_MVP - }; + char keys[][] = {STAT_KILLS, + STAT_DEATHS, + STAT_ASSISTS, + STAT_FLASHBANG_ASSISTS, + STAT_TEAMKILLS, + STAT_SUICIDES, + STAT_DAMAGE, + STAT_UTILITY_DAMAGE, + STAT_ENEMIES_FLASHED, + STAT_FRIENDLIES_FLASHED, + STAT_KNIFE_KILLS, + STAT_HEADSHOT_KILLS, + STAT_ROUNDSPLAYED, + STAT_BOMBDEFUSES, + STAT_BOMBPLANTS, + STAT_1K, + STAT_2K, + STAT_3K, + STAT_4K, + STAT_5K, + STAT_V1, + STAT_V2, + STAT_V3, + STAT_V4, + STAT_V5, + STAT_FIRSTKILL_T, + STAT_FIRSTKILL_CT, + STAT_FIRSTDEATH_T, + STAT_FIRSTDEATH_CT, + STAT_TRADEKILL, + STAT_KAST, + STAT_CONTRIBUTION_SCORE, + STAT_MVP}; int length = sizeof(keys); - for (int i = 0; i < length; i++) - { + for (int i = 0; i < length; i++) { g_StatsKv.SetNum(keys[i], 0); } @@ -1188,8 +1190,7 @@ public void PrintDamageInfo(int client) { g_DamagePrintFormatCvar.GetString(message, msgSize); ReplaceStringWithInt(message, msgSize, "{DMG_TO}", g_DamageDone[client][i], false); - ReplaceStringWithInt(message, msgSize, "{HITS_TO}", g_DamageDoneHits[client][i], - false); + ReplaceStringWithInt(message, msgSize, "{HITS_TO}", g_DamageDoneHits[client][i], false); if (g_DamageDoneKill[client][i]) { ReplaceString(message, msgSize, "{KILL_TO}", "{GREEN}X{NORMAL}", false); @@ -1202,8 +1203,7 @@ public void PrintDamageInfo(int client) { } ReplaceStringWithInt(message, msgSize, "{DMG_FROM}", g_DamageDone[i][client], false); - ReplaceStringWithInt(message, msgSize, "{HITS_FROM}", g_DamageDoneHits[i][client], - false); + ReplaceStringWithInt(message, msgSize, "{HITS_FROM}", g_DamageDoneHits[i][client], false); if (g_DamageDoneKill[i][client]) { ReplaceString(message, msgSize, "{KILL_FROM}", "{DARK_RED}X{NORMAL}", false); diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index f650d97d4..ddf9fe81d 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -1,9 +1,11 @@ public Action Command_JoinGame(int client, const char[] command, int argc) { - if (g_GameState != Get5State_None && g_CheckAuthsCvar.BoolValue && IsPlayer(client) && !g_PendingSideSwap) { - // In order to avoid duplication of team-join logic, we directly call the same handle that would be called - // if the user selected any team after joining. Since Command_JoinTeam handles the actual joining using a - // FakeClientCommand, we don't have to do any team-logic here and it won't matter what we pass to Command_JoinTeam. - // The only thing that's important is that the command argument is empty, as that avoids a call to GetCmdArg in that function. + if (g_GameState != Get5State_None && g_CheckAuthsCvar.BoolValue && IsPlayer(client) && + !g_PendingSideSwap) { + // In order to avoid duplication of team-join logic, we directly call the same handle that would + // be called if the user selected any team after joining. Since Command_JoinTeam handles the + // actual joining using a FakeClientCommand, we don't have to do any team-logic here and it + // won't matter what we pass to Command_JoinTeam. The only thing that's important is that the + // command argument is empty, as that avoids a call to GetCmdArg in that function. CreateTimer(0.1, Timer_PlacePlayerOnJoin, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } return Plugin_Continue; @@ -11,7 +13,7 @@ public Action Command_JoinGame(int client, const char[] command, int argc) { public Action Timer_PlacePlayerOnJoin(Handle timer, int userId) { int client = GetClientOfUserId(userId); - if (client) { // Client might have disconnected between timer and callback. + if (client) { // Client might have disconnected between timer and callback. Command_JoinTeam(client, "", 1); } } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index ac76c2fb4..c93ee3fbd 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -8,13 +8,11 @@ // Trying to write an empty string often results in the keyvalue not being written, so we use this. #define KEYVALUE_STRING_PLACEHOLDER "__placeholder" -static char _colorNames[][] = {"{NORMAL}", "{DARK_RED}", "{PINK}", "{GREEN}", - "{YELLOW}", "{LIGHT_GREEN}", "{LIGHT_RED}", "{GRAY}", - "{ORANGE}", "{LIGHT_BLUE}", "{DARK_BLUE}", "{PURPLE}", - "{GOLD}"}; -static char _colorCodes[][] = {"\x01", "\x02", "\x03", "\x04", "\x05", "\x06", - "\x07", "\x08", "\x09", "\x0B", "\x0C", "\x0E", - "\x10"}; +static char _colorNames[][] = {"{NORMAL}", "{DARK_RED}", "{PINK}", "{GREEN}", "{YELLOW}", + "{LIGHT_GREEN}", "{LIGHT_RED}", "{GRAY}", "{ORANGE}", "{LIGHT_BLUE}", + "{DARK_BLUE}", "{PURPLE}", "{GOLD}"}; +static char _colorCodes[][] = {"\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0B", "\x0C", "\x0E", "\x10"}; // Convenience macros. #define LOOP_TEAMS(%1) for (Get5Team %1 = Get5Team_1; %1 < Get5Team_Count; %1 ++) @@ -62,8 +60,9 @@ stock int SumHealthOfTeam(int team) { } stock int ConvertCSTeamToDefaultWinReason(int side) { - // This maps to https://github.com/VSES/SourceEngine2007/blob/master/se2007/game/shared/cstrike/cs_gamerules.h, which - // is the regular CSRoundEndReason + 1. + // This maps to + // https://github.com/VSES/SourceEngine2007/blob/master/se2007/game/shared/cstrike/cs_gamerules.h, + // which is the regular CSRoundEndReason + 1. return view_as(side == CS_TEAM_CT ? CSRoundEnd_CTWin : CSRoundEnd_TerroristWin) + 1; } @@ -686,7 +685,8 @@ stock Get5BombSite GetNearestBombsite(int client) { return (aDist < bDist) ? Get5BombSite_A : Get5BombSite_B; } -stock void convertSecondsToMinutesAndSeconds(int timeAsSeconds, char[] buffer, const int bufferSize) { +stock void convertSecondsToMinutesAndSeconds(int timeAsSeconds, char[] buffer, + const int bufferSize) { int minutes = 0; int seconds = timeAsSeconds; if (timeAsSeconds >= 60) { diff --git a/scripting/get5_apistats.sp b/scripting/get5_apistats.sp index 0b59b3a20..b7bcfc2ff 100644 --- a/scripting/get5_apistats.sp +++ b/scripting/get5_apistats.sp @@ -284,7 +284,8 @@ static void AddIntStat(Handle req, KeyValues kv, const char[] field) { AddIntParam(req, field, kv.GetNum(field)); } -public void UpdatePlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, const Get5Team team) { +public void UpdatePlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, + const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; diff --git a/scripting/get5_mysqlstats.sp b/scripting/get5_mysqlstats.sp index d9fc51129..af61c7e19 100644 --- a/scripting/get5_mysqlstats.sp +++ b/scripting/get5_mysqlstats.sp @@ -223,7 +223,8 @@ public void Get5_OnMapResult(const Get5MapResultEvent event) { db.Query(SQLErrorCheckCallback, queryBuffer); } -public void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, const Get5Team team) { +public void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, + const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; char nameSz[MAX_NAME_LENGTH * 2 + 1]; From e5e7ab09bd3770e2e16ed0472653e8455615f7f3 Mon Sep 17 00:00:00 2001 From: Sean Lewis Date: Tue, 16 Aug 2022 10:42:46 -0700 Subject: [PATCH 049/104] Fix typo. --- scripting/include/get5.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index 60e4af5f4..c0cef4a84 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -1872,6 +1872,6 @@ public __pl_get5_SetNTVOptional() { MarkNativeAsOptional("Get5_AddLiveCvar"); MarkNativeAsOptional("Get5_IncreasePlayerStat"); MarkNativeAsOptional("Get5_GetMatchStats"); - MarkNativeAsOptinoal("Get5_GetMapNumber"); + MarkNativeAsOptional("Get5_GetMapNumber"); } #endif From e6fd1c80d10fac5a29bb47d76874091704e48cd9 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 16 Aug 2022 23:05:51 +0200 Subject: [PATCH 050/104] stock required for .inc functions --- scripting/include/get5.inc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index c0cef4a84..6901d21cb 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -1599,7 +1599,7 @@ methodmap Get5BombDefusedEvent < Get5PlayerBombEvent { } } -void GameStateString(const Get5State state, char[] buffer, const int length) { +stock void GameStateString(const Get5State state, char[] buffer, const int length) { switch (state) { case Get5State_None: Format(buffer, length, "none"); @@ -1622,14 +1622,14 @@ void GameStateString(const Get5State state, char[] buffer, const int length) { } } -void ConvertGameStateToStringInJson(const JSON_Object obj, const char[] key, +stock void ConvertGameStateToStringInJson(const JSON_Object obj, const char[] key, const Get5State state) { char gameStateString[64]; GameStateString(state, gameStateString, sizeof(gameStateString)); obj.SetString(key, gameStateString); } -void ConvertGet5SideToStringInJson(const JSON_Object obj, const char[] key, Get5Side side) { +stock void ConvertGet5SideToStringInJson(const JSON_Object obj, const char[] key, Get5Side side) { if (side == Get5Side_T) { obj.SetString(key, "t"); } else if (side == Get5Side_CT) { @@ -1641,7 +1641,7 @@ void ConvertGet5SideToStringInJson(const JSON_Object obj, const char[] key, Get5 } } -void ConvertGet5TeamToStringInJson(const JSON_Object obj, const char[] key, Get5Team team) { +stock void ConvertGet5TeamToStringInJson(const JSON_Object obj, const char[] key, Get5Team team) { if (team == Get5Team_1) { obj.SetString(key, "team1"); } else if (team == Get5Team_2) { @@ -1653,7 +1653,7 @@ void ConvertGet5TeamToStringInJson(const JSON_Object obj, const char[] key, Get5 } } -void ConvertGet5PauseTypeToStringInJson(const JSON_Object obj, const char[] key, +stock void ConvertGet5PauseTypeToStringInJson(const JSON_Object obj, const char[] key, Get5PauseType pauseType) { if (pauseType == Get5PauseType_Admin) { obj.SetString(key, "admin"); @@ -1668,7 +1668,7 @@ void ConvertGet5PauseTypeToStringInJson(const JSON_Object obj, const char[] key, } } -void ConvertBombSiteToStringInJson(const JSON_Object obj, const char[] key, +stock void ConvertBombSiteToStringInJson(const JSON_Object obj, const char[] key, const Get5BombSite site) { if (site == Get5BombSite_A) { obj.SetString(key, "a"); From 3355e572360928c080ec70baf86b519c45f5682c Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 18 Aug 2022 17:57:40 +0200 Subject: [PATCH 051/104] Remove ready tags on knife round start (#846) --- scripting/get5/kniferounds.sp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index 16e06a662..b733209c7 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -2,6 +2,9 @@ public Action StartKnifeRound(Handle timer) { g_HasKnifeRoundStarted = false; g_PendingSideSwap = false; + // Removes ready tags + SetMatchTeamCvars(); + Get5_MessageToAll("%t", "KnifeIn5SecInfoMessage"); if (InWarmup()) { EndWarmup(5); From 85ca323e61c1fb873868d2fd11b45048bd4d47c9 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 19 Aug 2022 19:36:50 +0200 Subject: [PATCH 052/104] Ensure that g_MatchID is reset on series end and failed match load attempts (#849) --- scripting/get5.sp | 1 + scripting/get5/matchconfig.sp | 1 + 2 files changed, 2 insertions(+) diff --git a/scripting/get5.sp b/scripting/get5.sp index 243db6821..98d390a5b 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1343,6 +1343,7 @@ void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay EventLogger_LogAndDeleteEvent(event); ChangeState(Get5State_None); + g_MatchID = ""; // We don't want to kick players until after the specified delay, as it will kick casters // potentially before GOTV ends. diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index fa5f47ee9..83123a751 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -36,6 +36,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { ClearArray(GetTeamAuths(team)); } + g_MatchID = ""; g_ReadyTimeWaitingUsed = 0; g_HasKnifeRoundStarted = false; g_MapChangePending = false; From 0c58b7ffde22ef92448aed91a76f5bb2c4a28a95 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 22 Aug 2022 15:53:53 +0200 Subject: [PATCH 053/104] Allow for setting the knife config file location (#852) * Allow for setting the knife config file location, similarly to live and warmup. --- documentation/docs/configuration.md | 18 ++++++++++-------- scripting/get5.sp | 15 ++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 463e9b87b..11e78382e 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -20,8 +20,7 @@ You should also have three config files. These can be edited, but we recommend n blindly pasting another config in (e.g. ESL, CEVO). Configs that execute warmup commands (`mp_warmup_end`, for example) **will** cause problems. These must only include commands you would run in the console (such as `mp_friendly_fire 1`) and should determine the rules for those three stage of your match. You can -also [point to other files](#config-files) by editing -the main config file. +also [point to other files](#config-files) by editing the main config file. ```yaml cfg/get5/warmup.cfg # (1) @@ -232,14 +231,17 @@ the [`{MATCHID}`](#tag-matchid) variable, i.e. `backups/{MATCHID}/`. **`Default: ## Config Files ####`get5_live_cfg` -: Config file executed when the game goes live. **`Default: get5/live.cfg`** - -####`get5_autoload_config` -: A [match configuration](../match_schema/#schema) file, relative to the `csgo` directory, to autoload when a player joins the server -if no match is loaded. Set to empty string to disable. **`Default: ""`** +: Config file executed when the game goes live, relative to `csgo/cfg`.
**`Default: "get5/live.cfg"`** ####`get5_warmup_cfg` -: Config file executed in warmup periods. **`Default: get5/warmup.cfg`** +: Config file executed in warmup periods, relative to `csgo/cfg`.
**`Default: "get5/warmup.cfg"`** + +####`get5_knife_cfg` +: Config file executed for the knife round, relative to `csgo/cfg`.
**`Default: "get5/knife.cfg"`** + +####`get5_autoload_config` +: A [match configuration](../match_schema/#schema) file, relative to the `csgo` directory, to autoload when a player +joins the server if no match is loaded. Set to empty string to disable. **`Default: ""`** ## Substitution Variables diff --git a/scripting/get5.sp b/scripting/get5.sp index 98d390a5b..32f766cdf 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -44,7 +44,6 @@ #define TEAM2_COLOR "{PINK}" #define TEAM1_STARTING_SIDE CS_TEAM_CT #define TEAM2_STARTING_SIDE CS_TEAM_T -#define KNIFE_CONFIG "get5/knife.cfg" #define DEFAULT_TAG "[{YELLOW}Get5{NORMAL}]" #if !defined LATEST_VERSION_URL @@ -78,6 +77,8 @@ ConVar g_FixedPauseTimeCvar; ConVar g_KickClientImmunityCvar; ConVar g_KickClientsWithNoMatchCvar; ConVar g_LiveCfgCvar; +ConVar g_WarmupCfgCvar; +ConVar g_KnifeCfgCvar; ConVar g_LiveCountdownTimeCvar; ConVar g_MaxBackupAgeCvar; ConVar g_MaxTacticalPausesCvar; @@ -98,7 +99,6 @@ ConVar g_TeamTimeToStartCvar; ConVar g_TimeFormatCvar; ConVar g_VetoConfirmationTimeCvar; ConVar g_VetoCountdownCvar; -ConVar g_WarmupCfgCvar; ConVar g_PrintUpdateNoticeCvar; ConVar g_RoundBackupPathCvar; ConVar g_PhaseAnnouncementCountCvar; @@ -379,8 +379,9 @@ public void OnPluginStart() { g_KickClientsWithNoMatchCvar = CreateConVar("get5_kick_when_no_match_loaded", "0", "Whether the plugin kicks new clients when no match is loaded"); - g_LiveCfgCvar = - CreateConVar("get5_live_cfg", "get5/live.cfg", "Config file to exec when the game goes live"); + g_LiveCfgCvar = CreateConVar("get5_live_cfg", "get5/live.cfg", "Config file to exec when the game goes live."); + g_WarmupCfgCvar = CreateConVar("get5_warmup_cfg", "get5/warmup.cfg", "Config file to exec in warmup periods."); + g_KnifeCfgCvar = CreateConVar("get5_knife_cfg", "get5/knife.cfg", "Config file to exec in knife periods."); g_LiveCountdownTimeCvar = CreateConVar( "get5_live_countdown_time", "10", "Number of seconds used to count down when a match is going live", 0, true, 5.0, true, 60.0); @@ -434,8 +435,6 @@ public void OnPluginStart() { g_VetoCountdownCvar = CreateConVar("get5_veto_countdown", "5", "Seconds to countdown before veto process commences. Set to \"0\" to disable."); - g_WarmupCfgCvar = - CreateConVar("get5_warmup_cfg", "get5/warmup.cfg", "Config file to exec in warmup periods"); g_PrintUpdateNoticeCvar = CreateConVar( "get5_print_update_notice", "1", "Whether to print to chat when the game goes live if a new version of Get5 is available."); @@ -1763,7 +1762,9 @@ public void StartGame(bool knifeRound) { if (g_KnifeChangedCvars != INVALID_HANDLE) { CloseCvarStorage(g_KnifeChangedCvars); } - g_KnifeChangedCvars = ExecuteAndSaveCvars(KNIFE_CONFIG); + char knifeConfig[PLATFORM_MAX_PATH]; + g_KnifeCfgCvar.GetString(knifeConfig, sizeof(knifeConfig)); + g_KnifeChangedCvars = ExecuteAndSaveCvars(knifeConfig); CreateTimer(1.0, StartKnifeRound); } else { LogDebug("StartGame: about to go live"); From afd944eed026ce5d7679f0146c623dcb81f049fe Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 22 Aug 2022 16:02:11 +0200 Subject: [PATCH 054/104] Allow for customizing team color names (#851) * Allow for customizing team color names * Revert team name empty string --- documentation/docs/configuration.md | 12 +++++++ scripting/get5.sp | 20 +++++++----- scripting/get5/matchconfig.sp | 50 ++++++++++++++++------------- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 11e78382e..0f015e851 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -142,6 +142,18 @@ if [`get5_print_damage`](#get5_print_damage) is disabled. : The tag applied before plugin messages. Note that at least one character must come before a [color modifier](#color-substitutes). **`Default: "[{YELLOW}Get5{NORMAL}]"`** +####`get5_team1_color` +: The [color](#color-substitutes) to use when printing the name of `team1` in chat +messages.
**`Default: "{LIGHT_GREEN}"`** + +####`get5_team2_color` +: The [color](#color-substitutes) to use when printing the name of `team2` in chat +messages.
**`Default: "{PINK}"`** + +####`get5_spec_color` +: The [color](#color-substitutes) to use when printing the name of `spectators` in chat +messages.
**`Default: "{NORMAL}"`** + ## Pausing ####`get5_pausing_enabled` diff --git a/scripting/get5.sp b/scripting/get5.sp index 32f766cdf..468e0a8c9 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -40,8 +40,6 @@ #define MAX_CVAR_LENGTH 128 #define MATCH_END_DELAY_AFTER_TV 15 -#define TEAM1_COLOR "{LIGHT_GREEN}" -#define TEAM2_COLOR "{PINK}" #define TEAM1_STARTING_SIDE CS_TEAM_CT #define TEAM2_STARTING_SIDE CS_TEAM_T #define DEFAULT_TAG "[{YELLOW}Get5{NORMAL}]" @@ -102,6 +100,9 @@ ConVar g_VetoCountdownCvar; ConVar g_PrintUpdateNoticeCvar; ConVar g_RoundBackupPathCvar; ConVar g_PhaseAnnouncementCountCvar; +ConVar g_Team1NameColorCvar; +ConVar g_Team2NameColorCvar; +ConVar g_SpecNameColorCvar; // Autoset convars (not meant for users to set) ConVar g_GameStateCvar; @@ -215,12 +216,6 @@ bool g_ClientReady[MAXPLAYERS + 1]; // Whether clients are marked ready. int g_TeamSide[MATCHTEAM_COUNT]; // Current CS_TEAM_* side for the team. int g_TeamStartingSide[MATCHTEAM_COUNT]; int g_ReadyTimeWaitingUsed = 0; -char g_DefaultTeamColors[][] = { - TEAM1_COLOR, - TEAM2_COLOR, - "{NORMAL}", - "{NORMAL}", -}; char g_LastKickedPlayerAuth[64]; @@ -444,6 +439,15 @@ public void OnPluginStart() { g_PhaseAnnouncementCountCvar = CreateConVar( "get5_phase_announcement_count", "5", "The number of times Get5 will print 'Knife' or 'Match is LIVE' when the game starts. Set to 0 to disable."); + g_Team1NameColorCvar = CreateConVar( + "get5_team1_color", "{LIGHT_GREEN}", + "The color used for the name of team 1 in chat messages."); + g_Team2NameColorCvar = CreateConVar( + "get5_team2_color", "{PINK}", + "The color used for the name of team 2 in chat messages."); + g_SpecNameColorCvar = CreateConVar( + "get5_spec_color", "{NORMAL}", + "The color used for the name of spectators in chat messages."); /** Create and exec plugin's configuration file **/ AutoExecConfig(true, "get5"); diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 83123a751..feda545e2 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -428,9 +428,7 @@ static bool LoadMatchFromKv(KeyValues kv) { kv.GetString("name", g_TeamNames[Get5Team_Spec], MAX_CVAR_LENGTH, CONFIG_SPECTATORSNAME_DEFAULT); kv.GoBack(); - - Format(g_FormattedTeamNames[Get5Team_Spec], MAX_CVAR_LENGTH, "%s%s{NORMAL}", - g_DefaultTeamColors[Get5Team_Spec], g_TeamNames[Get5Team_Spec]); + FormatTeamName(Get5Team_Spec); } if (kv.JumpToKey("team1")) { @@ -546,9 +544,7 @@ static bool LoadMatchFromJson(JSON_Object json) { json_object_get_string_safe(spec, "name", g_TeamNames[Get5Team_Spec], MAX_CVAR_LENGTH, CONFIG_SPECTATORSNAME_DEFAULT); AddJsonAuthsToList(spec, "players", GetTeamAuths(Get5Team_Spec), AUTH_LENGTH); - - Format(g_FormattedTeamNames[Get5Team_Spec], MAX_CVAR_LENGTH, "%s%s{NORMAL}", - g_DefaultTeamColors[Get5Team_Spec], g_TeamNames[Get5Team_Spec]); + FormatTeamName(Get5Team_Spec); } JSON_Object team1 = json.GetObject("team1"); @@ -616,8 +612,8 @@ static void LoadTeamDataJson(JSON_Object json, Get5Team matchTeam) { if (StrEqual(fromfile, "")) { // TODO: this needs to support both an array and a dictionary // For now, it only supports an array - JSON_Object coaches = json.GetObject("coaches"); AddJsonAuthsToList(json, "players", GetTeamAuths(matchTeam), AUTH_LENGTH); + JSON_Object coaches = json.GetObject("coaches"); if (coaches != null) { AddJsonAuthsToList(json, "coaches", GetTeamCoaches(matchTeam), AUTH_LENGTH); } @@ -638,8 +634,7 @@ static void LoadTeamDataJson(JSON_Object json, Get5Team matchTeam) { } g_TeamSeriesScores[matchTeam] = json_object_get_int_safe(json, "series_score", 0); - Format(g_FormattedTeamNames[matchTeam], MAX_CVAR_LENGTH, "%s%s{NORMAL}", - g_DefaultTeamColors[matchTeam], g_TeamNames[matchTeam]); + FormatTeamName(matchTeam); } static void LoadTeamData(KeyValues kv, Get5Team matchTeam) { @@ -666,8 +661,21 @@ static void LoadTeamData(KeyValues kv, Get5Team matchTeam) { } g_TeamSeriesScores[matchTeam] = kv.GetNum("series_score", 0); - Format(g_FormattedTeamNames[matchTeam], MAX_CVAR_LENGTH, "%s%s{NORMAL}", - g_DefaultTeamColors[matchTeam], g_TeamNames[matchTeam]); + FormatTeamName(matchTeam); +} + +static void FormatTeamName(const Get5Team team) { + char color[32]; + if (team == Get5Team_1) { + g_Team1NameColorCvar.GetString(color, sizeof(color)); + } else if (team == Get5Team_2) { + g_Team2NameColorCvar.GetString(color, sizeof(color)); + } else if (team == Get5Team_Spec) { + g_SpecNameColorCvar.GetString(color, sizeof(color)); + } else { + color = "{NORMAL}"; + } + Format(g_FormattedTeamNames[team], MAX_CVAR_LENGTH, "%s%s{NORMAL}", color, g_TeamNames[team]); } static void LoadDefaultMapList(ArrayList list) { @@ -1065,14 +1073,18 @@ public Action Command_CreateMatch(int client, int args) { char teamName[MAX_CVAR_LENGTH]; + // If team names are empty because nobody is on on the server, the will be set by + // CheckTeamNameStatus during ready-phase. We cannot write empty strings to KeyValues, so we just skip them. kv.JumpToKey("team1", true); - int count = AddPlayersToAuthKv(kv, Get5Team_1, teamName); - kv.SetString("name", count > 0 ? teamName : "Team 1"); + if (AddPlayersToAuthKv(kv, Get5Team_1, teamName) > 0) { + kv.SetString("name", teamName); + } kv.GoBack(); kv.JumpToKey("team2", true); - count = AddPlayersToAuthKv(kv, Get5Team_2, teamName); - kv.SetString("name", count > 0 ? teamName : "Team 2"); + if (AddPlayersToAuthKv(kv, Get5Team_2, teamName) > 0) { + kv.SetString("name", teamName); + } kv.GoBack(); kv.JumpToKey("spectators", true); @@ -1282,12 +1294,6 @@ public void CheckTeamNameStatus(Get5Team team) { } } } - - char colorTag[32] = TEAM1_COLOR; - if (team == Get5Team_2) - colorTag = TEAM2_COLOR; - - Format(g_FormattedTeamNames[team], MAX_CVAR_LENGTH, "%s%s{NORMAL}", colorTag, - g_TeamNames[team]); + FormatTeamName(team); } } From 1130b6643e541063e445db4f895ced18c1160f50 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 23 Aug 2022 17:04:42 +0200 Subject: [PATCH 055/104] Fix problems with empty strings in cvars (#853) Refactor placeholder logic slightly Don't call LoadPlayerNames in loops --- scripting/get5/jsonhelpers.sp | 2 +- scripting/get5/matchconfig.sp | 51 +++++++++++++++++------------------ scripting/get5/natives.sp | 8 +++--- scripting/get5/teamlogic.sp | 9 +++---- scripting/get5/util.sp | 39 ++++++++++++++++++++------- scripting/include/get5.inc | 5 ++-- 6 files changed, 66 insertions(+), 48 deletions(-) diff --git a/scripting/get5/jsonhelpers.sp b/scripting/get5/jsonhelpers.sp index c24e3537b..3f3d9bf79 100644 --- a/scripting/get5/jsonhelpers.sp +++ b/scripting/get5/jsonhelpers.sp @@ -123,7 +123,7 @@ stock int AddJsonAuthsToList(JSON_Object json, const char[] key, ArrayList list, data.GetString(k, name, sizeof(name)); char steam64[AUTH_LENGTH]; if (ConvertAuthToSteam64(k, steam64)) { - Get5_SetPlayerName(steam64, name); + Get5_SetPlayerName(steam64, name, true); list.PushString(steam64); count++; } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index feda545e2..e58c8b6a8 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -124,12 +124,13 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { // that are set in the OnSeriesInit event. // Add to download table after setting. SetMatchTeamCvars(); + ExecuteMatchConfigCvars(); + LoadPlayerNames(); + AddTeamLogosToDownloadTable(); if (!restoreBackup) { - SetStartingTeams(); + SetStartingTeams(); // This cannot be called during backup, as it will reset the sides! ExecCfg(g_WarmupCfgCvar); - ExecuteMatchConfigCvars(); - LoadPlayerNames(); EnsureIndefiniteWarmup(); if (IsPaused()) { LogDebug("Match was paused when loading match config. Unpausing."); @@ -160,11 +161,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } } - AddTeamLogosToDownloadTable(); - ExecuteMatchConfigCvars(); - LoadPlayerNames(); strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); - Get5_MessageToAll("%t", "MatchConfigLoadedInfoMessage"); return true; } @@ -424,7 +421,7 @@ static bool LoadMatchFromKv(KeyValues kv) { GetTeamAuths(Get5Team_Spec).Clear(); if (kv.JumpToKey("spectators")) { - AddSubsectionAuthsToList(kv, "players", GetTeamAuths(Get5Team_Spec), AUTH_LENGTH); + AddSubsectionAuthsToList(kv, "players", GetTeamAuths(Get5Team_Spec)); kv.GetString("name", g_TeamNames[Get5Team_Spec], MAX_CVAR_LENGTH, CONFIG_SPECTATORSNAME_DEFAULT); kv.GoBack(); @@ -472,7 +469,7 @@ static bool LoadMatchFromKv(KeyValues kv) { char value[MAX_CVAR_LENGTH]; do { kv.GetSectionName(name, sizeof(name)); - kv.GetString(NULL_STRING, value, sizeof(value)); + ReadEmptyStringInsteadOfPlaceholder(kv, value, sizeof(value)); g_CvarNames.PushString(name); g_CvarValues.PushString(value); } while (kv.GotoNextKey(false)); @@ -643,8 +640,8 @@ static void LoadTeamData(KeyValues kv, Get5Team matchTeam) { kv.GetString("fromfile", fromfile, sizeof(fromfile)); if (StrEqual(fromfile, "")) { - AddSubsectionAuthsToList(kv, "players", GetTeamAuths(matchTeam), AUTH_LENGTH); - AddSubsectionAuthsToList(kv, "coaches", GetTeamCoaches(matchTeam), AUTH_LENGTH); + AddSubsectionAuthsToList(kv, "players", GetTeamAuths(matchTeam)); + AddSubsectionAuthsToList(kv, "coaches", GetTeamCoaches(matchTeam)); kv.GetString("name", g_TeamNames[matchTeam], MAX_CVAR_LENGTH, ""); kv.GetString("tag", g_TeamTags[matchTeam], MAX_CVAR_LENGTH, ""); kv.GetString("flag", g_TeamFlags[matchTeam], MAX_CVAR_LENGTH, ""); @@ -1145,24 +1142,12 @@ public Action Command_CreateScrim(int client, int args) { MatchConfigFail("Failed to read scrim template in %s", templateFile); return Plugin_Handled; } - + // Because we read the field and write it again, then load it as a match config, we have to make sure empty + // strings are not being skipped. if (kv.JumpToKey("team1") && kv.JumpToKey("players") && kv.GotoFirstSubKey(false)) { - // Empty string values are found when reading KeyValues, but don't get written out. - // So this adds a value for each auth so scrim templates don't have to insert fake values. + char name[MAX_NAME_LENGTH]; do { - char auth[AUTH_LENGTH]; - char name[MAX_NAME_LENGTH]; - kv.GetString(NULL_STRING, name, sizeof(name), KEYVALUE_STRING_PLACEHOLDER); - kv.GetSectionName(auth, sizeof(auth)); - - // This shouldn't be necessary, but when the name field was empty, the - // use of KEYVALUE_STRING_PLACEHOLDER as a default doesn't seem to work. - // TODO: figure out what's going on with needing this here. - if (StrEqual(name, "")) { - name = KEYVALUE_STRING_PLACEHOLDER; - } - - kv.SetString(NULL_STRING, name); + WritePlaceholderInsteadOfEmptyString(kv, name, sizeof(name)); } while (kv.GotoNextKey(false)); kv.Rewind(); } else { @@ -1171,6 +1156,18 @@ public Action Command_CreateScrim(int client, int args) { return Plugin_Handled; } + // Also ensure empty string values in cvars get printed to the match config. + if (kv.JumpToKey("cvars")) { + if (kv.GotoFirstSubKey(false)) { + char cVarValue[MAX_CVAR_LENGTH]; + do { + WritePlaceholderInsteadOfEmptyString(kv, cVarValue, sizeof(cVarValue)); + } while (kv.GotoNextKey(false)); + kv.GoBack(); + } + kv.GoBack(); + } + kv.JumpToKey("team2", true); kv.SetString("name", otherTeamName); kv.GoBack(); diff --git a/scripting/get5/natives.sp b/scripting/get5/natives.sp index 4f2271748..8f3036840 100644 --- a/scripting/get5/natives.sp +++ b/scripting/get5/natives.sp @@ -145,11 +145,13 @@ public int Native_SetPlayerName(Handle plugin, int numParams) { char name[MAX_NAME_LENGTH]; GetNativeString(1, auth, sizeof(auth)); GetNativeString(2, name, sizeof(name)); + bool suppressPlayerNameLoad = GetNativeCell(3); char steam64[AUTH_LENGTH]; - ConvertAuthToSteam64(auth, steam64); - if (strlen(name) > 0 && !StrEqual(name, KEYVALUE_STRING_PLACEHOLDER)) { + if (strlen(name) > 0 && ConvertAuthToSteam64(auth, steam64)) { g_PlayerNames.SetString(steam64, name); - LoadPlayerNames(); + if (!suppressPlayerNameLoad) { + LoadPlayerNames(); + } } } diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index ddf9fe81d..24cdb5a5d 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -480,8 +480,7 @@ public void LoadPlayerNames() { ArrayList coachIds = GetTeamCoaches(team); for (int i = 0; i < ids.Length; i++) { ids.GetString(i, id, sizeof(id)); - if (g_PlayerNames.GetString(id, name, sizeof(name)) && !StrEqual(name, "") && - !StrEqual(name, KEYVALUE_STRING_PLACEHOLDER)) { + if (g_PlayerNames.GetString(id, name, sizeof(name)) && !StrEqual(name, "")) { namesKv.SetString(id, name); numNames++; } @@ -490,8 +489,7 @@ public void LoadPlayerNames() { // There's a way to push an array of cells into the end, however, it // becomes a single element, rather than pushing individually. coachIds.GetString(i, id, sizeof(id)); - if (g_PlayerNames.GetString(id, name, sizeof(name)) && !StrEqual(name, "") && - !StrEqual(name, KEYVALUE_STRING_PLACEHOLDER)) { + if (g_PlayerNames.GetString(id, name, sizeof(name)) && !StrEqual(name, "")) { namesKv.SetString(id, name); numNames++; } @@ -503,8 +501,9 @@ public void LoadPlayerNames() { DeleteFile(nameFile); if (namesKv.ExportToFile(nameFile)) { ServerCommand("sv_load_forced_client_names_file %s", nameFile); + LogDebug("Wrote %d fixed player name(s) to %s.", numNames, nameFile); } else { - LogError("Failed to write names keyvalue file to %s", nameFile); + LogError("Failed to write fixed player names to %s.", nameFile); } } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index c93ee3fbd..b13748bf1 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -4,8 +4,8 @@ #define MAX_FLOAT_STRING_LENGTH 32 #define AUTH_LENGTH 64 -// Dummy value for when we need to write a keyvalue string, but we don't care about the value. -// Trying to write an empty string often results in the keyvalue not being written, so we use this. +// Dummy value for when we need to write a KeyValue string, but we don't care about the value *or* when the value is an +// empty string. Trying to write an empty string results in the KeyValue not being written, so we use this. #define KEYVALUE_STRING_PLACEHOLDER "__placeholder" static char _colorNames[][] = {"{NORMAL}", "{DARK_RED}", "{PINK}", "{GREEN}", "{YELLOW}", @@ -383,28 +383,27 @@ stock int AddKeysToList(KeyValues kv, ArrayList list, int maxKeyLength) { return count; } -stock int AddSubsectionAuthsToList(KeyValues kv, const char[] section, ArrayList list, - int maxKeyLength) { +stock int AddSubsectionAuthsToList(KeyValues kv, const char[] section, ArrayList list) { int count = 0; if (kv.JumpToKey(section)) { - count = AddAuthsToList(kv, list, maxKeyLength); + count = AddAuthsToList(kv, list); kv.GoBack(); } return count; } -stock int AddAuthsToList(KeyValues kv, ArrayList list, int maxKeyLength) { +stock int AddAuthsToList(KeyValues kv, ArrayList list) { int count = 0; - char[] buffer = new char[maxKeyLength]; + char buffer[AUTH_LENGTH]; char steam64[AUTH_LENGTH]; char name[MAX_NAME_LENGTH]; if (kv.GotoFirstSubKey(false)) { do { - kv.GetSectionName(buffer, maxKeyLength); - kv.GetString(NULL_STRING, name, sizeof(name)); + kv.GetSectionName(buffer, AUTH_LENGTH); + ReadEmptyStringInsteadOfPlaceholder(kv, name, sizeof(name)); if (ConvertAuthToSteam64(buffer, steam64)) { list.PushString(steam64); - Get5_SetPlayerName(steam64, name); + Get5_SetPlayerName(steam64, name, true); count++; } } while (kv.GotoNextKey(false)); @@ -422,6 +421,26 @@ stock bool RemoveStringFromArray(ArrayList list, const char[] str) { return false; } +// Because KeyValue cannot write empty strings, we use this to consistently read empty strings and replace +// our empty-string-placeholder with actual empty string. +stock bool ReadEmptyStringInsteadOfPlaceholder(const KeyValues kv, char[] buffer, const int bufferSize) { + kv.GetString(NULL_STRING, buffer, bufferSize); + if (StrEqual(KEYVALUE_STRING_PLACEHOLDER, buffer)) { + Format(buffer, bufferSize, ""); + return true; + } + return false; +} + +stock bool WritePlaceholderInsteadOfEmptyString(const KeyValues kv, char[] buffer, const int bufferSize) { + kv.GetString(NULL_STRING, buffer, bufferSize); + if (StrEqual("", buffer)) { + kv.SetString(NULL_STRING, KEYVALUE_STRING_PLACEHOLDER); + return true; + } + return false; +} + stock int OtherCSTeam(int team) { if (team == CS_TEAM_CT) { return CS_TEAM_T; diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index 6901d21cb..4bae96a8d 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -87,8 +87,9 @@ native bool Get5_LoadMatchConfigFromURL(const char[] url, ArrayList paramNames = native bool Get5_AddPlayerToTeam(const char[] steamId, Get5Team team, const char[] playerName = ""); -// Force sets a steam64 to map to a specified playername -native bool Get5_SetPlayerName(const char[] steamId, const char[] playerName); +// Force sets a steam64 to map to a specified playername. If calling this multiple times, you may want to +// suppress loading the player names until the last call. +native bool Get5_SetPlayerName(const char[] steamId, const char[] playerName, bool suppressNameLoading = false); // Removes a player from all match teams. // Returns if they were successfully removed (false if not round). From e6b7b800eeda8450af665b6359f8bd729f046f1b Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Tue, 23 Aug 2022 09:01:14 -0600 Subject: [PATCH 056/104] Udpate Command_Test to ensure it does not fail. Run autoformatter as well. Add message to inform user tests are complete. --- scripting/get5.sp | 24 ++++++++++++------------ scripting/get5/matchconfig.sp | 3 ++- scripting/get5/tests.sp | 3 ++- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 468e0a8c9..71a52af06 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -374,9 +374,12 @@ public void OnPluginStart() { g_KickClientsWithNoMatchCvar = CreateConVar("get5_kick_when_no_match_loaded", "0", "Whether the plugin kicks new clients when no match is loaded"); - g_LiveCfgCvar = CreateConVar("get5_live_cfg", "get5/live.cfg", "Config file to exec when the game goes live."); - g_WarmupCfgCvar = CreateConVar("get5_warmup_cfg", "get5/warmup.cfg", "Config file to exec in warmup periods."); - g_KnifeCfgCvar = CreateConVar("get5_knife_cfg", "get5/knife.cfg", "Config file to exec in knife periods."); + g_LiveCfgCvar = CreateConVar("get5_live_cfg", "get5/live.cfg", + "Config file to exec when the game goes live."); + g_WarmupCfgCvar = + CreateConVar("get5_warmup_cfg", "get5/warmup.cfg", "Config file to exec in warmup periods."); + g_KnifeCfgCvar = + CreateConVar("get5_knife_cfg", "get5/knife.cfg", "Config file to exec in knife periods."); g_LiveCountdownTimeCvar = CreateConVar( "get5_live_countdown_time", "10", "Number of seconds used to count down when a match is going live", 0, true, 5.0, true, 60.0); @@ -439,15 +442,12 @@ public void OnPluginStart() { g_PhaseAnnouncementCountCvar = CreateConVar( "get5_phase_announcement_count", "5", "The number of times Get5 will print 'Knife' or 'Match is LIVE' when the game starts. Set to 0 to disable."); - g_Team1NameColorCvar = CreateConVar( - "get5_team1_color", "{LIGHT_GREEN}", - "The color used for the name of team 1 in chat messages."); - g_Team2NameColorCvar = CreateConVar( - "get5_team2_color", "{PINK}", - "The color used for the name of team 2 in chat messages."); - g_SpecNameColorCvar = CreateConVar( - "get5_spec_color", "{NORMAL}", - "The color used for the name of spectators in chat messages."); + g_Team1NameColorCvar = CreateConVar("get5_team1_color", "{LIGHT_GREEN}", + "The color used for the name of team 1 in chat messages."); + g_Team2NameColorCvar = CreateConVar("get5_team2_color", "{PINK}", + "The color used for the name of team 2 in chat messages."); + g_SpecNameColorCvar = CreateConVar("get5_spec_color", "{NORMAL}", + "The color used for the name of spectators in chat messages."); /** Create and exec plugin's configuration file **/ AutoExecConfig(true, "get5"); diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index e58c8b6a8..f1bd519c2 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -1071,7 +1071,8 @@ public Action Command_CreateMatch(int client, int args) { char teamName[MAX_CVAR_LENGTH]; // If team names are empty because nobody is on on the server, the will be set by - // CheckTeamNameStatus during ready-phase. We cannot write empty strings to KeyValues, so we just skip them. + // CheckTeamNameStatus during ready-phase. We cannot write empty strings to KeyValues, so we just + // skip them. kv.JumpToKey("team1", true); if (AddPlayersToAuthKv(kv, Get5Team_1, teamName) > 0) { kv.SetString("name", teamName); diff --git a/scripting/get5/tests.sp b/scripting/get5/tests.sp index 418224bd0..71ae47145 100644 --- a/scripting/get5/tests.sp +++ b/scripting/get5/tests.sp @@ -15,6 +15,7 @@ public void Get5_Test() { KV_Test(); g_GameState = Get5State_None; + LogMessage("Tests complete!"); } static void Utils_Test() { @@ -72,7 +73,7 @@ static void KV_Test() { AssertEq("maps_to_win", g_MapsToWin, 2); AssertEq("bo2_series", g_BO2Match, false); AssertEq("skip_veto", g_SkipVeto, false); - AssertEq("players_per_team", g_PlayersPerTeam, 1); + AssertEq("players_per_team", g_PlayersPerTeam, 5); AssertEq("favored_percentage_team1", g_FavoredTeamPercentage, 65); AssertTrue("team1.name", StrEqual(g_TeamNames[Get5Team_1], "EnvyUs", false)); From 602a41566f9915ae475c68508b3a38bba4b3d63c Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 23 Aug 2022 19:23:32 +0200 Subject: [PATCH 057/104] Reset votes to restore when writing new backup (#854) Add message about resetting Rename TeamWantsToReloadLastRoundInfoMessage to TeamWantsToReloadCurrentRound --- documentation/docs/translations.md | 3 ++- scripting/get5.sp | 14 ++++++++++++-- translations/chi/get5.phrases.txt | 2 +- translations/da/get5.phrases.txt | 8 ++++++-- translations/de/get5.phrases.txt | 2 +- translations/es/get5.phrases.txt | 2 +- translations/fr/get5.phrases.txt | 2 +- translations/get5.phrases.txt | 9 +++++++-- translations/hu/get5.phrases.txt | 2 +- translations/pl/get5.phrases.txt | 2 +- translations/pt/get5.phrases.txt | 2 +- translations/ru/get5.phrases.txt | 2 +- 12 files changed, 35 insertions(+), 15 deletions(-) diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index 9a25cb1ce..d236b0ae3 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -104,6 +104,7 @@ end with a full stop as this is added automatically. | `TacticalPauseMidSentence` | _Team A_ (_CT_) __tactical pause__ (_1_/_2_). | HintText | | `TimeRemainingBeforeAnyoneCanUnpausePrefix` | _Team A_ (_CT_) technical pause (_1_/_2_). __Time remaining before anyone can unpause__: _2:30_ | HintText | | `StopCommandNotEnabled` | The stop command is not enabled. | Chat | +| `StopCommandVotingReset` | The request by _Team A_ to stop the game was canceled as a new round started. | Chat | | `PauseTimeRemainingPrefix` | _Team A_ (_CT_) tactical pause. __Remaining pause time__: _2:15_ | HintText | | `PausedForBackup` | The game was restored from a backup. Both teams must unpause to continue. | HintText | | `AwaitingUnpause` | _Team A_ (_CT_) tactical pause. __Awaiting unpause__. | HintText | @@ -132,7 +133,7 @@ end with a full stop as this is added automatically. | `AdminForceEndWithWinnerInfoMessage` | An admin force-ended the match, setting _Team 1_ as the winner. | Chat | | `AdminForcePauseInfoMessage` | An admin force-paused the match. | Chat | | `AdminForceUnPauseInfoMessage` | An admin unpaused the match. | Chat | -| `TeamWantsToReloadLastRoundInfoMessage` | _Team A_ wants to stop and reload last round. _Team B_ must confirm with _!stop_. | Chat | +| `TeamWantsToReloadCurrentRound` | _Team A_ wants to restore the game to the beginning of the current round. _Team B_ must confirm with _!stop_. | Chat | | `TeamWinningSeriesInfoMessage` | _Team A_ is winning the series _2_-_1_. | Chat | | `SeriesTiedInfoMessage` | The series is tied at _1_-_1_. | Chat | | `NextSeriesMapInfoMessage` | The next map in the series is _de_nuke_ and it will start in _1:30_. | Chat | diff --git a/scripting/get5.sp b/scripting/get5.sp index 71a52af06..16dcd410c 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1137,11 +1137,11 @@ public Action Command_Stop(int client, int args) { char stopCommandFormatted[64]; FormatChatCommand(stopCommandFormatted, sizeof(stopCommandFormatted), "!stop"); if (g_TeamGivenStopCommand[Get5Team_1] && !g_TeamGivenStopCommand[Get5Team_2]) { - Get5_MessageToAll("%t", "TeamWantsToReloadLastRoundInfoMessage", + Get5_MessageToAll("%t", "TeamWantsToReloadCurrentRound", g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2], stopCommandFormatted); } else if (!g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { - Get5_MessageToAll("%t", "TeamWantsToReloadLastRoundInfoMessage", + Get5_MessageToAll("%t", "TeamWantsToReloadCurrentRound", g_FormattedTeamNames[Get5Team_2], g_FormattedTeamNames[Get5Team_1], stopCommandFormatted); } else if (g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { @@ -1527,6 +1527,16 @@ public void WriteBackup() { LogDebug("Writing backup to %s", path); WriteBackStructure(path); g_LastGet5BackupCvar.SetString(path); + + // Reset this when writing a new backup, as voting has no reference to which round the teams wanted to restore to, so + // votes to restore during one round should not carry over into the next round, as it would just restore that round + // instead. + LOOP_TEAMS(t) { + if (g_TeamGivenStopCommand[t]) { + Get5_MessageToAll("%t", "StopCommandVotingReset", g_FormattedTeamNames[t]); + } + g_TeamGivenStopCommand[t] = false; + } } public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { diff --git a/translations/chi/get5.phrases.txt b/translations/chi/get5.phrases.txt index 76a183b35..7fab2975d 100644 --- a/translations/chi/get5.phrases.txt +++ b/translations/chi/get5.phrases.txt @@ -144,7 +144,7 @@ { "chi" "一名管理员强制解除了比赛暂停。" } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "chi" "{1}想停止比赛并重新加载比赛至上一回合,需要{2}输入{3}来确认。" } diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 3f826317e..5e660d581 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -216,9 +216,9 @@ { "da" "En admin har tvunget kampen til at fortsætte." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { - "da" "{1} vil stoppe og genindlæse sidste runde. Afventer {3} fra {2}." + "da" "{1} vil stoppe og genindlæse til starten af denne runde. Afventer {3} fra {2}." } "TeamWinningSeriesInfoMessage" { @@ -252,6 +252,10 @@ { "da" "Stop-kommandoen er ikke aktiveret." } + "StopCommandVotingReset" + { + "da" "Anmodningen fra {1} om at stoppe spillet blev annulleret, da en ny runde startede." + } "BackupLoadedInfoMessage" { "da" "Backup {1} indlæst." diff --git a/translations/de/get5.phrases.txt b/translations/de/get5.phrases.txt index 7e3250d80..2a13fabb7 100644 --- a/translations/de/get5.phrases.txt +++ b/translations/de/get5.phrases.txt @@ -120,7 +120,7 @@ { "de" "Ein Admin hat das Match fortgesetzt." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "de" "{1} möchte die letzte Runde wiederholen, {2} muss mit {3} bestätigen." } diff --git a/translations/es/get5.phrases.txt b/translations/es/get5.phrases.txt index 1f77e6164..0675e3676 100644 --- a/translations/es/get5.phrases.txt +++ b/translations/es/get5.phrases.txt @@ -148,7 +148,7 @@ { "es" "Un administrador forzó la recuperación de la partida." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "es" "{1} quiere parar y reiniciar la partida, esto requiere {2} confirmaciones con {3}." } diff --git a/translations/fr/get5.phrases.txt b/translations/fr/get5.phrases.txt index db8355ea8..30e3d633e 100644 --- a/translations/fr/get5.phrases.txt +++ b/translations/fr/get5.phrases.txt @@ -148,7 +148,7 @@ { "fr" "Un admin a forcé la reprise du match." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "fr" "{1} souhaite arrêter et recommencer au dernier round, cela nécessite que {2} confirme avec {3}." } diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 054cecb41..5bcf7ecff 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -258,10 +258,10 @@ { "en" "An admin unpaused the match." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "#format" "{1:s},{2:s},{3:s}" - "en" "{1} wants to stop and reload last round. {2} must confirm with {3}." + "en" "{1} wants to restore the game to the beginning of the current round. {2} must confirm with {3}." } "TeamWinningSeriesInfoMessage" { @@ -301,6 +301,11 @@ { "en" "The stop command is not enabled." } + "StopCommandVotingReset" + { + "#format" "{1:s}" + "en" "The request by {1} to stop the game was canceled as a new round started." + } "BackupLoadedInfoMessage" { "#format" "{1:s}" diff --git a/translations/hu/get5.phrases.txt b/translations/hu/get5.phrases.txt index 6d472405d..da23ee7ee 100644 --- a/translations/hu/get5.phrases.txt +++ b/translations/hu/get5.phrases.txt @@ -220,7 +220,7 @@ { "hu" "Egy admin, a mérkőzés folytatását hajtotta végre." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "hu" "{1} szeretne megállni és visszatölteni az előző kört. {2} el kell fogadni a {3} paranccsal." } diff --git a/translations/pl/get5.phrases.txt b/translations/pl/get5.phrases.txt index 2b6c09cc3..97c2d80f2 100644 --- a/translations/pl/get5.phrases.txt +++ b/translations/pl/get5.phrases.txt @@ -124,7 +124,7 @@ { "pl" "Administrator wznowił mecz." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "pl" "{1} chce zatrzymać mecz i wczytać poprzednią rundę, oczekiwanie na potwierdzenie przez {2} i wpisanie {3}." } diff --git a/translations/pt/get5.phrases.txt b/translations/pt/get5.phrases.txt index cc7f2bc7a..2c45f7a81 100644 --- a/translations/pt/get5.phrases.txt +++ b/translations/pt/get5.phrases.txt @@ -140,7 +140,7 @@ { "pt" "O administrador forçou o resumo de uma partida." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "pt" "{1} quer parar e recomeçar o último round. Para isso precisa que outros {2} confirmem digitanto {3}." } diff --git a/translations/ru/get5.phrases.txt b/translations/ru/get5.phrases.txt index 44d91619f..136319899 100644 --- a/translations/ru/get5.phrases.txt +++ b/translations/ru/get5.phrases.txt @@ -116,7 +116,7 @@ { "ru" "Администратор силы возобновленная игра." } - "TeamWantsToReloadLastRoundInfoMessage" + "TeamWantsToReloadCurrentRound" { "ru" "{1} желает остановить и перезагрузить последний раунд, ждем {2} чтобы написали {3} для подтверждения." } From fe9db191d30cae9a6ae36f0b410aaa46ea76e0d4 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 24 Aug 2022 00:27:58 +0200 Subject: [PATCH 058/104] Remove deprecate bo2_series and maps_to_win match config params (#856) --- scripting/get5.sp | 3 ++- scripting/get5/matchconfig.sp | 51 ++++++++++++----------------------- scripting/get5/tests.sp | 1 + 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 16dcd410c..75063221c 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -119,7 +119,8 @@ bool g_SeriesCanClinch = true; int g_RoundNumber = -1; // The round number, 0-indexed. -1 if the match is not live. // The active map number, used by stats. Required as the calculated round number changes immediately // as a map ends, but before the map changes to the next. -int g_MapNumber = 0; +int g_MapNumber = 0; // the current map number, starting at 0. +int g_NumberOfMapsInSeries = 0; // the number of maps to play in the series. char g_MatchID[MATCH_ID_LENGTH]; ArrayList g_MapPoolList = null; ArrayList g_TeamAuths[MATCHTEAM_COUNT]; diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index f1bd519c2..8f4c49a11 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -41,6 +41,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { g_HasKnifeRoundStarted = false; g_MapChangePending = false; g_MapNumber = 0; + g_NumberOfMapsInSeries = 0; g_RoundNumber = -1; g_LastVetoTeam = Get5Team_2; g_MapPoolList.Clear(); @@ -295,9 +296,8 @@ public int SteamWorks_OnMatchConfigReceived(Handle request, bool failure, bool r public void WriteMatchToKv(KeyValues kv) { kv.SetString("matchid", g_MatchID); kv.SetNum("scrim", g_InScrimMode); - kv.SetNum("maps_to_win", g_MapsToWin); - kv.SetNum("bo2_series", g_BO2Match); kv.SetNum("skip_veto", g_SkipVeto); + kv.SetNum("num_maps", g_NumberOfMapsInSeries); kv.SetNum("players_per_team", g_PlayersPerTeam); kv.SetNum("coaches_per_team", g_CoachesPerTeam); kv.SetNum("min_players_to_ready", g_MinPlayersToReady); @@ -386,26 +386,18 @@ static bool LoadMatchFromKv(KeyValues kv) { kv.GetNum("min_spectators_to_ready", CONFIG_MINSPECTATORSTOREADY_DEFAULT); g_SkipVeto = kv.GetNum("skip_veto", CONFIG_SKIPVETO_DEFAULT) != 0; - // bo2_series and maps_to_win are deprecated. They are used if provided, but otherwise - // num_maps' default is the fallback. - bool bo2 = (kv.GetNum("bo2_series", false) != 0); - int mapsToWin = kv.GetNum("maps_to_win", 0); - int numMaps = kv.GetNum("num_maps", CONFIG_NUM_MAPSDEFAULT); - if (bo2 || numMaps == 2) { + g_NumberOfMapsInSeries = kv.GetNum("num_maps", CONFIG_NUM_MAPSDEFAULT); + if (g_NumberOfMapsInSeries == 2) { g_BO2Match = true; g_MapsToWin = 2; } else { g_BO2Match = false; - if (mapsToWin >= 1) { - g_MapsToWin = mapsToWin; - } else { - // Normal path. No even numbers allowed since we already handled bo2. - if (numMaps % 2 == 0) { - MatchConfigFail("Cannot create a series of %d maps. Use a odd number or 2.", numMaps); - return false; - } - g_MapsToWin = (numMaps + 1) / 2; + // Normal path. No even numbers allowed since we already handled bo2. + if (g_NumberOfMapsInSeries % 2 == 0) { + MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); + return false; } + g_MapsToWin = (g_NumberOfMapsInSeries + 1) / 2; } char vetoFirstBuffer[64]; @@ -499,27 +491,18 @@ static bool LoadMatchFromJson(JSON_Object json) { CONFIG_MINSPECTATORSTOREADY_DEFAULT); g_SkipVeto = json_object_get_bool_safe(json, "skip_veto", CONFIG_SKIPVETO_DEFAULT); - // bo2_series and maps_to_win are deprecated. They are used if provided, but otherwise - // num_maps' default is the fallback. - bool bo2 = json_object_get_bool_safe(json, "bo2_series", false); - int mapsToWin = json_object_get_int_safe(json, "maps_to_win", 0); - int numMaps = json_object_get_int_safe(json, "num_maps", CONFIG_NUM_MAPSDEFAULT); - - if (bo2 || numMaps == 2) { + g_NumberOfMapsInSeries = json_object_get_int_safe(json, "num_maps", CONFIG_NUM_MAPSDEFAULT); + if (g_NumberOfMapsInSeries == 2) { g_BO2Match = true; g_MapsToWin = 2; } else { g_BO2Match = false; - if (mapsToWin >= 1) { - g_MapsToWin = mapsToWin; - } else { - // Normal path. No even numbers allowed since we already handled bo2. - if (numMaps % 2 == 0) { - MatchConfigFail("Cannot create a series of %d maps. Use a odd number or 2.", numMaps); - return false; - } - g_MapsToWin = (numMaps + 1) / 2; + // Normal path. No even numbers allowed since we already handled bo2. + if (g_NumberOfMapsInSeries % 2 == 0) { + MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); + return false; } + g_MapsToWin = (g_NumberOfMapsInSeries + 1) / 2; } char vetoFirstBuffer[64]; @@ -1059,7 +1042,7 @@ public Action Command_CreateMatch(int client, int args) { KeyValues kv = new KeyValues("Match"); kv.SetString("matchid", matchid); - kv.SetNum("maps_to_win", 1); + kv.SetNum("num_maps", 1); kv.SetNum("skip_veto", 1); kv.SetNum("players_per_team", 5); kv.SetNum("clinch_series", 1); diff --git a/scripting/get5/tests.sp b/scripting/get5/tests.sp index 71ae47145..fb45b217d 100644 --- a/scripting/get5/tests.sp +++ b/scripting/get5/tests.sp @@ -72,6 +72,7 @@ static void KV_Test() { AssertEq("maps_to_win", g_MapsToWin, 2); AssertEq("bo2_series", g_BO2Match, false); + AssertEq("num_maps", g_NumberOfMapsInSeries, 3); AssertEq("skip_veto", g_SkipVeto, false); AssertEq("players_per_team", g_PlayersPerTeam, 5); AssertEq("favored_percentage_team1", g_FavoredTeamPercentage, 65); From 279fd62768e537efcae3c94fe0cc7f84a18f5bf6 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 18 Aug 2022 15:38:10 +0200 Subject: [PATCH 059/104] Clean up coach logic Add docs for coaching Fix get5_addcoach command to actually place players on coach Prevent get5_addcoach from being used in scrims or during halftime Add some guards to prevent adding coaches or players during backup and halftime states Only update backup on round start or when setting match id --- documentation/docs/coaching.md | 64 +++++ documentation/docs/commands.md | 35 ++- documentation/docs/configuration.md | 5 +- documentation/docs/getting_started.md | 46 +++- documentation/docs/match_schema.md | 5 +- documentation/mkdocs.yml | 1 + scripting/get5.sp | 24 +- scripting/get5/backups.sp | 42 ++-- scripting/get5/debug.sp | 1 + scripting/get5/kniferounds.sp | 14 +- scripting/get5/matchconfig.sp | 70 ++++-- scripting/get5/teamlogic.sp | 342 +++++++++++--------------- scripting/get5/util.sp | 23 +- translations/get5.phrases.txt | 14 ++ 14 files changed, 376 insertions(+), 310 deletions(-) create mode 100644 documentation/docs/coaching.md diff --git a/documentation/docs/coaching.md b/documentation/docs/coaching.md new file mode 100644 index 000000000..8626d4d61 --- /dev/null +++ b/documentation/docs/coaching.md @@ -0,0 +1,64 @@ +# :material-headset: Coaching + +Get5 ships with mechanics to manage coaches, but the behavior differs _slightly_ from the built-in coaching +system found in the game, which avoids a +few ["minor" bugs](https://en.wikipedia.org/wiki/Counter-Strike_coaching_bug_scandal). + +### Server requirements {: #requirements } + +1. [`sv_coaching_enabled`](https://totalcsgo.com/command/svcoachingenabled) must be set. +2. [`coaches_per_team`](../match_schema/#schema) in your match configuration + or [scrim template](../getting_started/#scrims) must be larger than 0. +3. The [`-maxplayers_override`](https://developer.valvesoftware.com/wiki/Maxplayers) + launch parameter must be defined on your server to allow for the number of connected clients you expect, including + all players, spectators and coaches. + +### Becoming a coach {: #howto } + +Due to internal conflicts with how [backups](backup.md) and auto-assignment to teams works in Get5, the default +[`coach`](https://counterstrike.fandom.com/wiki/Coaching) console command is disabled. You can become a coach in one of +three ways: + +1. Use the [`!coach`](../commands/#coach) chat command during freezetime or warmup. +2. Be defined as a coach in the [match configuration](../match_schema/#schema) or + via [`get5_addcoach`](../commands/#get5_addcoach). +3. Join a game where the team is already full (determined by [`players_per_team`](../match_schema/#schema)) and where a + coach slot is available. + +!!! warning "Coaching is permanent" + + Once you are assigned as a coach in a non-scrim game, you cannot leave the coach slot unless you are removed from + coaching using [`get5_removeplayer`](../commands/#get5_removeplayer). + +If the current number of coaches exceeds or equals [`coaches_per_team`](../match_schema/#schema), including if it is +zero, additional players will be kicked from the match. However, if a connecting player is defined +in [`players`](../match_schema/#schema), the team is full and a coach slot is open, they will be permanently moved to +coaching for the series. + +This behavior allows you to define as many coaches and players in the match configuration as you want: As long as the +number of players and coaches on the server don't exceed [`players_per_team`](../match_schema/#schema) +and [`coaches_per_team`](../match_schema/#schema), respectively, Get5 will fill the +game slots with the appropriate number of players and coaches and kick the rest. Being in +the [`coaches`](../match_schema/#schema) section takes precedence over [`players`](../match_schema/#schema). + +!!! note "Decreasing the number of players" + + If a match configuration with [`players_per_team`](../match_schema/#schema) or + [`coaches_per_team`](../match_schema/#schema) set to a number *lower* than the number of players **already connected + to the server**, the entire team will be kicked and must reconnect. + +### Coaching in Scrims {: #scrims } + +When in [scrim-mode](../getting_started/#scrims), you cannot set the [`coaches`](../match_schema/#schema) key, and +players are never locked to the coaching slot. This means that to become a coach in a scrim, you must always +call [`!coach`](../commands/#coach) or join a team that already has [`players_per_team`](../match_schema/#schema) +players (i.e. is full). Contrary +to games with a complete [match configuration](../match_schema/#schema), you can also exit the coaching slot if the team +is not full by simply selecting any team using the team-join menu, which will respawn you as a player in the next +round (or immediately if still in freezetime), similarly to if you were to reconnect to the server. + +!!! danger "`players_per_team` matters in scrims!" + + Do not set [`players_per_team`](../match_schema/#schema) to a value larger than the number of players you expect. + If you do this, a player could go back and forth between coaching and playing during freezetime, potentially + dropping items for their team. If you play 5v5, don't set it to 6 to allow "anyone to join to coach". diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 853cf1e35..9231e4bec 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -30,8 +30,7 @@ Please note that these can be typed by *all players* in chat. ####`!coach` -: Moves a client to coach for their team. Requires that -the [`sv_coaching_enabled`](https://totalcsgo.com/command/svcoachingenabled) variable is set to `1`. +: Moves a client to [coach for their team](coaching.md). ####`!stay` @@ -51,13 +50,13 @@ the [get5_stop_command_enabled](../configuration/#get5_stop_command_enabled) is : Force-readies your team, marking all players on your team as ready. -####`!ringer` +####`!ringer ` {: #ringer } -: Adds/removes a ringer to/from the home scrim team. +: Alias for [`get5_ringer`](#get5_ringer). ####`!scrim` -: Shortcut for [`get5_scrim`](#get5_scrim). +: Alias for [`get5_scrim`](#get5_scrim). ####`!get5` @@ -100,12 +99,12 @@ to that team. Omitting the team argument sets no winner (tie). ####`get5_creatematch [map name] [matchid]` {: #get5_creatematch } : Creates a BO1 match with the current players on the server. `map name` defaults to the current map and `matchid` - defaults to `manual`. You should **not** provide a match ID if you use the [MySQL extension](../stats_system/#mysql). +defaults to `manual`. You should **not** provide a match ID if you use the [MySQL extension](../stats_system/#mysql). ####`get5_scrim [opposing team name] [map name] [matchid]` {: #get5_scrim } : Creates a [scrim](../getting_started/#scrims) on the current map. The opposing team name defaults to `Away` - and the map defaults to the current map. `matchid` defaults to `scrim`. You should **not** provide a match ID if - you use the [MySQL extension](../stats_system/#mysql). +and the map defaults to the current map. `matchid` defaults to `scrim`. You should **not** provide a match ID if +you use the [MySQL extension](../stats_system/#mysql). ####`get5_addplayer [name]` {: #get5_addplayer } : Adds a Steam ID to a team (can be any format for the Steam ID). The name parameter optionally locks the player's @@ -115,8 +114,10 @@ name. : Adds a Steam ID to a team as a coach. The name parameter optionally locks the player's name. -####`get5_removeplayer ` -: Removes a steam ID from all teams (can be any format for the Steam ID). +####`get5_removeplayer ` {: #get5_removeplayer} +: Removes a steam ID from all teams (can be any format for the Steam ID). This also removes the player as +a [coach](coaching.md). If [`get5_check_auths`](../configuration/#get5_check_auths) is set, the player will be removed +from the server immediately. ####`get5_addkickedplayer [name]` {: #get5_addkickedplayer } : Adds the last kicked Steam ID to a team. The name parameter optionally locks the player's name. @@ -223,9 +224,17 @@ name. : Lists backup files for the current match or a given match ID if provided. If you define [`get5_backup_path`](../configuration/#get5_backup_path), it will only list backups found under that prefix. -####`get5_ringer ` -: Adds/removes a ringer to/from the home scrim team. `player` is the name of the player. Similar -to [`!ringer`](../commands/#ringer) +####`get5_ringer ` {: #get5_ringer } +: Adds/removes a ringer to/from the home scrim team. `target` is the name of the player, their user ID or their Steam +ID. Similar to [`!ringer`](../commands/#ringer) in chat. + +!!! example "User ID vs client index" + + To view user IDs, type `users` in console. In this example, `3` is the user ID and `1` is the client index: + ``` + > users + 1:3:"Quinn" + ``` ####`get5_debuginfo [file]` {: #get5_debuginfo } : Dumps debug info to a file (`addons/sourcemod/logs/get5_debuginfo.txt` if no file parameter is provided). diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 0f015e851..1b41c8e7d 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -59,8 +59,9 @@ disconnect even in warmup with the intention to reconnect!). **`Default: 0`** : Whether to wait for map vetoes to be printed to GOTV before changing map. **`Default: 0`** ####`get5_check_auths` -: Whether the Steam IDs from a `players` of a [match configuration](../match_schema/#schema) section are used to -force players onto teams, kicking everyone else. **`Default: 1`** +: Whether the Steam IDs from the `players` and `coaches` sections of a [match configuration](../match_schema/#schema) +are used to force players onto teams. Anyone not defined will be removed from the game, or if +in [scrim mode](../getting_started/#scrims), put on `team2`. **`Default: 1`** ####`get5_print_update_notice` : Whether to print to chat when the game goes live if a new version of Get5 is available. This only works if diff --git a/documentation/docs/getting_started.md b/documentation/docs/getting_started.md index 484074cc1..94f9f86d8 100644 --- a/documentation/docs/getting_started.md +++ b/documentation/docs/getting_started.md @@ -5,7 +5,7 @@ While you can just jump right in, we recommend you read the [configuration](../configuration) and [match schema](../match_schema) sections of the documentation to understand what Get5 can do. -## Quick Start +## Quick Start {: #quick-start } If you want to create a match quickly without modifying anything, you must set two properties: @@ -18,17 +18,25 @@ call [`get5_creatematch`](../commands/#get5_creatematch). There is also a simple from by typing [`!get5`](../commands/#get5) in the game chat. Note that you must be [a server administrator](../installation/#administrators) to do this. -## Scrims +## Match Configuration {: #match-configuration } -While Get5 is intended for matches (league matches, LAN-matches, cups, etc.), it can be used for everyday -scrims/gathers/whatever as well. If that is your use case, you should do a few things differently. We call "_having a -home team defined and anyone else on the opposing team_" a **scrim**. +The default operation mode for Get5 is the configuration and loading of +a [match configuration file](../match_schema). This file should contain all the players and coaches, their team +name and optionally flag and logo as well as any spectators/casters. Once you've created your file you can load it +using the [`get5_loadmatch`](../commands/#get5_loadmatch) command or configure your server to automatically load the +file as soon as a player joins by setting [`get5_autoload_config`](../configuration/#get5_autoload_config). -### Letting the opposing team in {: #opposing-team } +!!! tip "Lock it down" -Get5 can be configured to kick all players from the server if no match is loaded. You should disable this for a scrim -server. To do so, edit [`cfg/sourcemod/get5.cfg`](../configuration/#main-config) and make sure that -[`get5_kick_when_no_match_loaded`](../configuration/#get5_kick_when_no_match_loaded) to `0`. + When loading match configurations, ensure that [`get5_check_auths`](../configuration/#get5_check_auths) is enabled. + This ensures that people are locked to the correct teams and that nobody else can join the server. + +## Scrims {: #scrims } + +While Get5 is intended for matches (league matches, LAN-matches, cups, etc.), it can be used for everyday +scrims/gathers/whatever as well. If that is your use case, you should do a few things differently. We call "_having a +home team defined and anyone else on the opposing team_" a **scrim**, and loading this configuration is referred to as +**scrim mode**. ### Adding your team's Steam IDs {: #home-team } @@ -37,10 +45,24 @@ located at `addons/sourcemod/configs/get5/scrim_template.cfg` and add in *your* their Steam IDs (any format works). After doing this, any user who does not belong in `team1` will implicitly be set to `team2`. +!!! warning "Coaches in scrims" + + You **cannot** set the [`coaches`](../match_schema/#schema) section in a scrim template. Instead, add everyone to + the [`players`](../match_schema/#schema) section and use the [`!coach`](../commands/#coach) command to become a + [coach](coaching.md) after joining the game. If the team is full (defined by + [`players_per_team`](../match_schema/#schema)), additional players will automatically be moved to coach if there are + available slots. + You can list however many players you want. Add all your coaches, analysts, ringers, and such. If someone on your list ends up being on the other team in a scrim, you can use the [`!ringer`](../commands/#ringer) command to temporarily swap them (similarly, you can use it to put someone not in the list on your team temporarily). +### Letting the opposing team in {: #opposing-team } + +Get5 can be configured to kick all players from the server if no match is loaded. You should disable this for a scrim +server. To do so, edit [`cfg/sourcemod/get5.cfg`](../configuration/#main-config) and make sure that +[`get5_kick_when_no_match_loaded`](../configuration/#get5_kick_when_no_match_loaded) is set to `0`. + ### Starting the Match Rather than creating a [match configuration](match_schema.md), you should @@ -48,7 +70,8 @@ use the [`get5_scrim`](../commands/#get5_scrim) command when the server is on th RCON or as a regular console command if you are [a server administrator](../installation/#administrators). You could also type [`!scrim`](../commands/#scrim) in chat. -Once you've done this, all that has to happen is teams to [ready up](../commands/#ready) to start the match. +Once you've done this, all that is required is for both teams to [ready up](../commands/#ready) and the match will +begin. !!! danger "Practice Mode" @@ -62,4 +85,5 @@ You can (and should) edit the [scrim template](https://github.com/splewis/get5/blob/master/configs/get5/scrim_template.cfg) at `addons/sourcemod/configs/get5/scrim_template.cfg`. In this you can set any scrim-specific properties in the `cvars` section. The template defaults to `mp_match_can_clinch 0` (designed for practice) which you should disable if playing a -real match. You may also want to lower `tv_delay` (and maybe `tv_enable` so you can record your scrims). +real match. You may also want to lower `tv_delay` (and maybe set `tv_enable 1` so you can [record your scrims](gotv.md)) +. diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index a4fef99e1..193c4f86c 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -105,8 +105,9 @@ interface Get5Match { 22. _Optional_
Various commands to execute on the server when loading the match configuration. This can be both regular server-commands and any [`Get5 configuration parameter`](configuration.md), i.e. `{"hostname": "Match #3123 - Astralis vs. NaVi"}`.

**`Default: undefined`** -23. _Optional_
Similarly to `players`, this object maps coaches using their Steam ID and - name.

**`Default: undefined`** +23. _Optional_
Similarly to `players`, this object maps [coaches](coaching.md) using their Steam ID and + name, locking them to the coach slot until removed + using [`get5_removeplayer`](../commands/#get5_removeplayer)

**`Default: undefined`** 24. _Required_
The players on the team. 25. _Optional_
Wrapper of the server's `mp_teammatchstat_txt` cvar, but can use `{MAPNUMBER}` and `{MAXMAPS}` as variables that get replaced with their integer values. In a BoX series, you probably don't want to set this since diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 625b2740a..ce980d6af 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -37,6 +37,7 @@ nav: - Getting Started: getting_started.md - Match Schema: match_schema.md - Commands: commands.md + - Coaching: coaching.md - Pausing: pausing.md - Backup System: backup.md - GOTV & Demos: gotv.md diff --git a/scripting/get5.sp b/scripting/get5.sp index 75063221c..07f8424d3 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -142,6 +142,7 @@ int g_MinSpectatorsToReady = 0; float g_RoundStartedTime = 0.0; float g_BombPlantedTime = 0.0; Get5BombSite g_BombSiteLastPlanted = Get5BombSite_Unknown; +bool g_AssignUnTeamedPlayersOnRoundStart = false; bool g_SkipVeto = false; float g_VetoMenuTime = 0.0; @@ -229,7 +230,6 @@ ArrayList g_ChatAliasesCommands; /** Map-game state not related to the actual gameplay. **/ char g_DemoFileName[PLATFORM_MAX_PATH]; bool g_MapChangePending = false; -bool g_MovingClientToCoach[MAXPLAYERS + 1]; bool g_PendingSideSwap = false; // version check state @@ -687,7 +687,6 @@ public Action Timer_InfoMessages(Handle timer) { public void OnClientAuthorized(int client, const char[] auth) { SetClientReady(client, false); - g_MovingClientToCoach[client] = false; if (StrEqual(auth, "BOT", false)) { return; } @@ -696,11 +695,8 @@ public void OnClientAuthorized(int client, const char[] auth) { Get5Team team = GetClientMatchTeam(client); if (team == Get5Team_None) { RememberAndKickClient(client, "%t", "YouAreNotAPlayerInfoMessage"); - } else { - int teamCount = CountPlayersOnMatchTeam(team, client); - if (teamCount >= g_PlayersPerTeam && !g_CoachingEnabledCvar.BoolValue) { - KickClient(client, "%t", "TeamIsFullInfoMessage"); - } + } else if (CountPlayersOnTeam(team, client) >= g_PlayersPerTeam && !g_CoachingEnabledCvar.BoolValue) { + KickClient(client, "%t", "TeamIsFullInfoMessage"); } } } @@ -1553,6 +1549,20 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas return; } + // Ensures that players who connect during halftime/team swap are placed in their correct slots as soon as the + // following round starts. Otherwise they could be left on the "no team" screen and potentially + // ghost, depending on where the camera drops them. Especially important for coaches. + if (g_AssignUnTeamedPlayersOnRoundStart) { + g_AssignUnTeamedPlayersOnRoundStart = false; + LOOP_CLIENTS(i) { + // We check only for connection here, as that's required to put them in the game, as they may + // not actually be considered "in the game" yet, so IsValidClient() might not work. + if (IsClientConnected(i)) { + CheckClientTeam(i); + } + } + } + Get5RoundStartedEvent startEvent = new Get5RoundStartedEvent(g_MatchID, g_MapNumber, g_RoundNumber); diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 5a61d995a..d4d869967 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -354,34 +354,21 @@ public void RestoreGet5Backup() { // This variable is reset on a timer since the implementation of the // mp_backup_restore_load_file doesn't do everything in one frame. g_DoingBackupRestoreNow = true; - ExecCfg(g_LiveCfgCvar); + ExecCfg(g_LiveCfgCvar); // async + CreateTimer(0.5, Timer_ExecMatchConfig, _, TIMER_FLAG_NO_MAPCHANGE); if (g_SavedValveBackup) { ChangeState(Get5State_Live); - SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - // There are some timing issues leading to incorrect score when restoring matches in second // half. Doing the restore on a timer CreateTimer(1.0, Time_StartRestore); } else { SetStartingTeams(); - SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - LOOP_CLIENTS(i) { - if (IsPlayer(i)) { - CheckClientTeam(i); - } - } - if (g_GameState == Get5State_Live) { EndWarmup(); EndWarmup(); ServerCommand("mp_restartgame 5"); PauseGame(Get5Team_None, Get5PauseType_Backup); - if (g_CoachingEnabledCvar.BoolValue) { - CreateTimer(6.0, Timer_SwapCoaches); - } } else { EnsureIndefiniteWarmup(); } @@ -390,13 +377,13 @@ public void RestoreGet5Backup() { } } -public Action Timer_SwapCoaches(Handle timer) { - LOOP_CLIENTS(i) { - if (IsAuthedPlayer(i)) { - CheckIfClientCoachingAndMoveToCoach(i, Get5Team_1); - CheckIfClientCoachingAndMoveToCoach(i, Get5Team_2); - } - } +public Action Timer_ExecMatchConfig(Handle timer) { + // This needs to go on a callback because ServerCommand("exec") is async, so the config will load + // *after* the match cvars if we put them in RestoreGet5Backup, which we don't want, as that's not the order in which + // they were loaded when the match was initially set up. + SetMatchTeamCvars(); + ExecuteMatchConfigCvars(); + return Plugin_Handled; } public Action Time_StartRestore(Handle timer) { @@ -405,7 +392,7 @@ public Action Time_StartRestore(Handle timer) { char tempValveBackup[PLATFORM_MAX_PATH]; GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); ServerCommand("mp_backup_restore_load_file \"%s\"", tempValveBackup); - CreateTimer(0.1, Timer_FinishBackup); + CreateTimer(0.5, Timer_FinishBackup); } public Action Timer_FinishBackup(Handle timer) { @@ -414,9 +401,12 @@ public Action Timer_FinishBackup(Handle timer) { // coaches get moved back onto the team. // We cannot trust Valve's system as a disconnected // player will count as a "player" and not be placed - // in the coach slot. So, we cannot enable warmup during - // the round restore process if using a Valve backup. - CreateTimer(0.5, Timer_SwapCoaches); + // in the coach slot. + LOOP_CLIENTS(i) { + if (IsValidClient(i)) { + CheckClientTeam(i); + } + } } g_DoingBackupRestoreNow = false; } diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index 81e86dc90..21e443b9a 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -111,6 +111,7 @@ static void AddGlobalStateInfo(File f) { f.WriteLine("g_InScrimMode = %d", g_InScrimMode); f.WriteLine("g_SeriesCanClinch = %d", g_SeriesCanClinch); f.WriteLine("g_HasKnifeRoundStarted = %d", g_HasKnifeRoundStarted); + f.WriteLine("g_AssignUnTeamedPlayersOnRoundStart = %d", g_AssignUnTeamedPlayersOnRoundStart); f.WriteLine("g_MapChangePending = %d", g_MapChangePending); f.WriteLine("g_PendingSideSwap = %d", g_PendingSideSwap); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index b733209c7..dd519315e 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -42,14 +42,12 @@ static void PerformSideSwap(bool swap) { LOOP_CLIENTS(i) { if (IsValidClient(i)) { - int team = GetClientTeam(i); - if (team == CS_TEAM_T) { - SwitchPlayerTeam(i, CS_TEAM_CT); - } else if (team == CS_TEAM_CT) { - SwitchPlayerTeam(i, CS_TEAM_T); - } else if (IsClientCoaching(i)) { - int correctTeam = Get5TeamToCSTeam(GetClientMatchTeam(i)); - UpdateCoachTarget(i, correctTeam); + if (IsFakeClient(i)) { + // Because bots never have an assigned team, they won't be moved around by CheckClientTeam. We kick them to + // prevent one team from having too many players. They will rejoin if defined in the live config. + KickClient(i); + } else { + CheckClientTeam(i); } } } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 8f4c49a11..76d11d09e 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -154,11 +154,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { - if (GetClientMatchTeam(i) == Get5Team_None) { - RememberAndKickClient(i, "%t", "YouAreNotAPlayerInfoMessage"); - } else { - CheckClientTeam(i); - } + CheckClientTeam(i); } } @@ -807,13 +803,16 @@ public Action Command_LoadTeam(int client, int args) { public Action Command_AddPlayer(int client, int args) { if (g_GameState == Get5State_None) { - ReplyToCommand(client, "Cannot change player lists when there is no match to modify"); + ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; - } - - if (g_InScrimMode) { - ReplyToCommand( - client, "Cannot use get5_addplayer in scrim mode. Use get5_ringer to swap a players team."); + } else if (g_InScrimMode) { + ReplyToCommand(client, "Cannot use get5_addplayer in scrim mode. Use get5_ringer to swap a player's team."); + return Plugin_Handled; + } else if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + ReplyToCommand(client, "Cannot add players while waiting for round backup."); + return Plugin_Handled; + } else if (g_PendingSideSwap || InHalftimePhase()) { + ReplyToCommand(client, "Cannot add players during halftime. Please wait until the next round starts."); return Plugin_Handled; } @@ -855,10 +854,19 @@ public Action Command_AddPlayer(int client, int args) { public Action Command_AddCoach(int client, int args) { if (g_GameState == Get5State_None) { - ReplyToCommand(client, "Cannot change coach targets when there is no match to modify"); + ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; } else if (!g_CoachingEnabledCvar.BoolValue) { - ReplyToCommand(client, "Cannot change coach targets if coaching is disabled."); + ReplyToCommand(client, "Coaching is not enabled."); + return Plugin_Handled; + } else if (g_InScrimMode) { + ReplyToCommand(client, "Coaches cannot be added in scrim mode. Use the !coach command in chat."); + return Plugin_Handled; + } else if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + ReplyToCommand(client, "Cannot add coaches while waiting for round backup."); + return Plugin_Handled; + } else if (g_PendingSideSwap || InHalftimePhase()) { + ReplyToCommand(client, "Cannot add coaches during halftime. Please wait until the next round starts."); return Plugin_Handled; } @@ -881,21 +889,29 @@ public Action Command_AddCoach(int client, int args) { return Plugin_Handled; } - if (GetTeamCoaches(team).Length == g_CoachesPerTeam) { + if (CountCoachesOnTeam(team) == g_CoachesPerTeam) { ReplyToCommand(client, "Coach Spots are full for %s.", teamString); return Plugin_Handled; } if (AddCoachToTeam(auth, team, name)) { - // Check if we are in the playerlist already and remove. + // If the player is already on the team as a regular player, remove them when adding to coaches. int index = GetTeamAuths(team).FindString(auth); if (index >= 0) { GetTeamAuths(team).Erase(index); } - // Update the backup structure as well for round restores, covers edge - // case of users joining, coaching, stopping, and getting 16k cash as player. - WriteBackup(); + ReplyToCommand(client, "Successfully added player %s as coach for %s.", auth, teamString); + + // If the user is already on the server as a player, move them to coaching immediately. + int addedClient = AuthToClient(auth); + if (addedClient > 0 && IsClientConnected(addedClient)) { + Get5Side side = view_as(Get5TeamToCSTeam(team)); + if (side != Get5Side_None) { + LogDebug("Player %s was present on the server when added as coach; moving them to coach for %d.", auth, team); + SetClientCoaching(addedClient, side); + } + } } else { ReplyToCommand( client, @@ -910,14 +926,16 @@ public Action Command_AddCoach(int client, int args) { public Action Command_AddKickedPlayer(int client, int args) { if (g_GameState == Get5State_None) { - ReplyToCommand(client, "Cannot change player lists when there is no match to modify"); + ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; - } - - if (g_InScrimMode) { - ReplyToCommand( - client, - "Cannot use get5_addkickedplayer in scrim mode. Use get5_ringer to swap a players team."); + } else if (g_InScrimMode) { + ReplyToCommand(client, "Cannot use get5_addkickedplayer in scrim mode. Use get5_ringer to swap a player's team."); + return Plugin_Handled; + } else if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + ReplyToCommand(client, "Cannot add players while waiting for round backup."); + return Plugin_Handled; + } else if (g_PendingSideSwap || InHalftimePhase()) { + ReplyToCommand(client, "Cannot add players during halftime. Please wait until the next round starts."); return Plugin_Handled; } @@ -1169,7 +1187,7 @@ public Action Command_CreateScrim(int client, int args) { public Action Command_Ringer(int client, int args) { if (g_GameState == Get5State_None || !g_InScrimMode) { - ReplyToCommand(client, "This command can only be used in scrim mode"); + ReplyToCommand(client, "This command can only be used in scrim mode."); return Plugin_Handled; } diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index 24cdb5a5d..7a515f944 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -1,11 +1,5 @@ public Action Command_JoinGame(int client, const char[] command, int argc) { - if (g_GameState != Get5State_None && g_CheckAuthsCvar.BoolValue && IsPlayer(client) && - !g_PendingSideSwap) { - // In order to avoid duplication of team-join logic, we directly call the same handle that would - // be called if the user selected any team after joining. Since Command_JoinTeam handles the - // actual joining using a FakeClientCommand, we don't have to do any team-logic here and it - // won't matter what we pass to Command_JoinTeam. The only thing that's important is that the - // command argument is empty, as that avoids a call to GetCmdArg in that function. + if (g_GameState != Get5State_None && g_CheckAuthsCvar.BoolValue && IsPlayer(client)) { CreateTimer(0.1, Timer_PlacePlayerOnJoin, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } return Plugin_Continue; @@ -14,217 +8,173 @@ public Action Command_JoinGame(int client, const char[] command, int argc) { public Action Timer_PlacePlayerOnJoin(Handle timer, int userId) { int client = GetClientOfUserId(userId); if (client) { // Client might have disconnected between timer and callback. - Command_JoinTeam(client, "", 1); + PlacePlayerOnTeam(client); } } public void CheckClientTeam(int client) { Get5Team correctTeam = GetClientMatchTeam(client); - int csTeam = Get5TeamToCSTeam(correctTeam); - int currentTeam = GetClientTeam(client); - - if (!CheckIfClientCoachingAndMoveToCoach(client, correctTeam) && csTeam != currentTeam) { - SwitchPlayerTeam(client, csTeam); - } -} - -public Action Command_JoinTeam(int client, const char[] command, int argc) { - if (!IsAuthedPlayer(client) || argc < 1) - return Plugin_Stop; - - // Don't do anything if not live/not in startup phase. - if (g_GameState == Get5State_None) { - return Plugin_Continue; + if (correctTeam == Get5Team_None) { + RememberAndKickClient(client, "%t", "YouAreNotAPlayerInfoMessage"); + return; } - // Don't enforce team joins. - if (!g_CheckAuthsCvar.BoolValue) { - return Plugin_Continue; - } + Get5Side correctSide = view_as(Get5TeamToCSTeam(correctTeam)); - if (g_PendingSideSwap) { - LogDebug("Blocking teamjoin due to pending swap"); - return Plugin_Stop; + if (correctSide == Get5Side_None) { + // This should not be possible. + LogError("Client %d belongs to no side. This is an unexpected error and should be reported.", client); + return; } - Get5Team correctTeam = GetClientMatchTeam(client); - int csTeam = Get5TeamToCSTeam(correctTeam); - - // This is required as it avoids an exception due to calling this function - // from Timer_PlacePlayerOnJoin, which gets called from Command_JoinGame. - if (!StrEqual("", command)) { - char arg[4]; - int team_to; - GetCmdArg(1, arg, sizeof(arg)); - team_to = StringToInt(arg); - - LogDebug("%L jointeam command, from %d to %d", client, GetClientTeam(client), team_to); - - // don't let someone change to a "none" team (e.g. using auto-select) - if (team_to == CS_TEAM_NONE) { - return Plugin_Stop; + int coachesOnTeam = CountCoachesOnTeam(correctTeam, client); + // If the player is fixed to coaching, always ensure they end there and on the correct side. + if (g_CoachingEnabledCvar.BoolValue && IsClientCoachForTeam(client, correctTeam)) { + if (GetClientCoachingSide(client) == correctSide) { + // Player is already coaching the correct team and is on spectator; do nothing. + return; } - - if (csTeam == team_to) { - if (CheckIfClientCoachingAndMoveToCoach(client, correctTeam)) { - return Plugin_Stop; - } else { - return Plugin_Continue; - } + // If there are free coach spots on the team, send the player there + if (coachesOnTeam < g_CoachesPerTeam) { + SetClientCoaching(client, correctSide); + } else { + KickClient(client, "%t", "TeamIsFullInfoMessage"); } + return; } - LogDebug("jointeam, gamephase = %d", GetGamePhase()); - - if (csTeam != GetClientTeam(client)) { - int count = CountPlayersOnCSTeam(csTeam); - - if (count >= g_PlayersPerTeam) { - if (!g_CoachingEnabledCvar.BoolValue) { - KickClient(client, "%t", "TeamIsFullInfoMessage"); - } else { - // Only attempt to move to coach if we are not full on coaches already. - if (GetTeamCoaches(correctTeam).Length <= g_CoachesPerTeam) { - char auth[AUTH_LENGTH]; - LogDebug("Forcing player %N to coach", client); - GetAuth(client, auth, sizeof(auth)); - // Only output MoveToCoachInfoMessage if we are not - // in the coach array already. - if (!IsAuthOnTeamCoach(auth, correctTeam)) { - Get5_Message(client, "%t", "MoveToCoachInfoMessage"); - } - MoveClientToCoach(client); - } else { - KickClient(client, "%t", "TeamIsFullInfoMessage"); - } - } - } else if (!CheckIfClientCoachingAndMoveToCoach(client, correctTeam)) { - LogDebug("Forcing player %N onto %d", client, csTeam); - FakeClientCommand(client, "jointeam %d", csTeam); + // If player was not locked to coaching, check if their team's current size -self is less than the max. + if (CountPlayersOnTeam(correctTeam, client) < g_PlayersPerTeam) { + SwitchPlayerTeam(client, view_as(correctSide)); + return; + } + + // We end here if a player was not a predefined coach while there was no space. If coaching is enabled, we drop + // the player in there, and if not, they must be kicked. + if (g_CoachingEnabledCvar.BoolValue && coachesOnTeam < g_CoachesPerTeam) { + Get5_Message(client, "%t", "MoveToCoachInfoMessage"); + // In scrim mode, we don't put coaches or players of the "away" team into any auth arrays; they default to the + // opposite of the home team. If a full team's coach disconnects or leaves, they should be placed on the coach + // team if their team is full. In a regular match, they will have called .coach before the map starts and will + // be placed by auth. + if (!g_InScrimMode) { + MovePlayerToCoachInConfig(client, correctTeam); } + SetClientCoaching(client, correctSide); + return; } - return Plugin_Stop; + KickClient(client, "%t", "TeamIsFullInfoMessage"); } -public bool CheckIfClientCoachingAndMoveToCoach(int client, Get5Team team) { - if (!g_CoachingEnabledCvar.BoolValue) { - return false; - } - // Force user to join the coach if specified by config or reconnect. - char clientAuth64[AUTH_LENGTH]; - GetAuth(client, clientAuth64, AUTH_LENGTH); - if (IsAuthOnTeamCoach(clientAuth64, team)) { - MoveClientToCoach(client); - return true; +static void PlacePlayerOnTeam(int client) { + if (g_PendingSideSwap || InHalftimePhase()) { + LogDebug("Blocking attempt to join a team due to halftime or pending team swap."); + g_AssignUnTeamedPlayersOnRoundStart = true; + return; } - return false; + CheckClientTeam(client); } -public void MoveClientToCoach(int client) { - LogDebug("MoveClientToCoach %L", client); - Get5Team matchTeam = GetClientMatchTeam(client); - if (matchTeam != Get5Team_1 && matchTeam != Get5Team_2) { - return; +public Action Command_JoinTeam(int client, const char[] command, int argc) { + if (g_GameState == Get5State_None || !g_CheckAuthsCvar.BoolValue) { + return Plugin_Continue; } - - if (!g_CoachingEnabledCvar.BoolValue) { - return; + if (!IsAuthedPlayer(client) || argc < 1) { + LogDebug("Preventing unauthorized/no argument client %d from joining a team.", client); + return Plugin_Stop; } - - int csTeam = Get5TeamToCSTeam(matchTeam); - - if (g_PendingSideSwap) { - LogDebug("Blocking coach move due to pending swap"); - return; + char arg[4]; + GetCmdArg(1, arg, sizeof(arg)); + int team_to = StringToInt(arg); + // Ensure team_to argument can be safely converted to Get5Side. Users *could* type "jointeam 4434" if they wanted. + if (team_to == CS_TEAM_T || team_to == CS_TEAM_CT || team_to == CS_TEAM_SPECTATOR) { + PlacePlayerOnTeam(client); } + return Plugin_Stop; +} - char teamString[4]; - char clientAuth[64]; - CSTeamString(csTeam, teamString, sizeof(teamString)); - GetAuth(client, clientAuth, AUTH_LENGTH); - if (!IsAuthOnTeamCoach(clientAuth, matchTeam)) { - AddCoachToTeam(clientAuth, matchTeam, ""); - // If we're already on the team, make sure we remove ourselves - // to ensure data is correct in the backups. - int index = GetTeamAuths(matchTeam).FindString(clientAuth); - if (index >= 0) { - GetTeamAuths(matchTeam).Erase(index); - } - } +static bool IsClientCoachForTeam(int client, Get5Team team) { + char clientAuth64[AUTH_LENGTH]; + return GetAuth(client, clientAuth64, AUTH_LENGTH) && IsAuthOnTeamCoach(clientAuth64, team); +} - // If we're in warmup we use the in-game - // coaching command. Otherwise we manually move them to spec - // and set the coaching target. - // If in freeze time, we have to manually move as well. - if (InWarmup()) { - LogDebug("Moving %L indirectly to coach slot via coach cmd", client); - g_MovingClientToCoach[client] = true; - FakeClientCommand(client, "coach %s", teamString); - g_MovingClientToCoach[client] = false; - } else { - LogDebug("Moving %L directly to coach slot", client); - SwitchPlayerTeam(client, CS_TEAM_SPECTATOR); - UpdateCoachTarget(client, csTeam); - // Need to set to avoid third person view bug. - SetEntProp(client, Prop_Send, "m_iObserverMode", 4); - } +void SetClientCoaching(int client, Get5Side side) { + LogDebug("Setting client %d as spectator and coach for side %d.", client, side); + SwitchPlayerTeam(client, CS_TEAM_SPECTATOR); + SetEntProp(client, Prop_Send, "m_iCoachingTeam", side); + SetEntProp(client, Prop_Send, "m_iObserverMode", 4); + SetEntProp(client, Prop_Send, "m_iAccount", 0); // Ensures coaches have no money if they were to rejoin the game. } public Action Command_SmCoach(int client, int args) { - char auth[AUTH_LENGTH]; if (g_GameState == Get5State_None) { return Plugin_Continue; } if (!g_CoachingEnabledCvar.BoolValue) { - return Plugin_Handled; + Get5_Message(client, "%t", "CoachingNotEnabled", "sv_coaching_enabled"); + return Plugin_Continue; } - GetAuth(client, auth, sizeof(auth)); Get5Team matchTeam = GetClientMatchTeam(client); - // Don't allow a new coach if spots are full. - if (GetTeamCoaches(matchTeam).Length > g_CoachesPerTeam) { - return Plugin_Stop; + + if (matchTeam == Get5Team_None) { + return Plugin_Continue; } - MoveClientToCoach(client); - // Update the backup structure as well for round restores, covers edge - // case of users joining, coaching, stopping, and getting 16k cash as player. - WriteBackup(); - return Plugin_Handled; -} + Get5Side currentlyCoaching = GetClientCoachingSide(client); + Get5Side side = view_as(Get5TeamToCSTeam(matchTeam)); -public Action Command_Coach(int client, const char[] command, int argc) { - if (g_GameState == Get5State_None) { + if (currentlyCoaching == side) { + // Already coaching for the right side. return Plugin_Continue; } - if (!g_CoachingEnabledCvar.BoolValue) { - return Plugin_Handled; + if (g_PendingSideSwap || InHalftimePhase() || (!InFreezeTime() && !InWarmup())) { + Get5_Message(client, "%t", "CanOnlyCoachDuringFreezetimeOrWarmup"); + return Plugin_Continue; } - if (!IsAuthedPlayer(client)) { - return Plugin_Stop; + // Don't allow a new coach if spots are full. + if (CountCoachesOnTeam(matchTeam, client) >= g_CoachesPerTeam) { + Get5_Message(client, "%t", "AllCoachSlotsFilledForTeam", g_CoachesPerTeam); + return Plugin_Continue; } - if (InHalftimePhase()) { - return Plugin_Stop; + if (!g_InScrimMode) { + // If we're in scrim mode, we don't update the coaches auth array ever. + MovePlayerToCoachInConfig(client, matchTeam); } - if (g_MovingClientToCoach[client] || !g_CheckAuthsCvar.BoolValue) { - LogDebug("Command_Coach: %L, letting pass-through", client); - return Plugin_Continue; + // Actually move the player to coach + spec + SetClientCoaching(client, side); + return Plugin_Continue; +} + +static void MovePlayerToCoachInConfig(const int client, const Get5Team team) { + char auth[AUTH_LENGTH]; + GetAuth(client, auth, sizeof(auth)); + if (AddCoachToTeam(auth, team, "")) { + // If we're already on the team, make sure we remove ourselves + // to ensure data is correct in the backups. + int index = GetTeamAuths(team).FindString(auth); + if (index >= 0) { + LogDebug("Removing client %d from regular team auth array for team %d", client, team); + GetTeamAuths(team).Erase(index); + } } +} - MoveClientToCoach(client); - // Update the backup structure as well for round restores, covers edge - // case of users joining, coaching, stopping, and getting 16k cash as player. - WriteBackup(); +public Action Command_Coach(int client, const char[] command, int argc) { + if (g_GameState == Get5State_None) { + return Plugin_Continue; + } + ReplyToCommand(client, "Please use .coach in chat or sm_coach instead of the built-in console coach command."); return Plugin_Stop; } -public Get5Team GetClientMatchTeam(int client) { +Get5Team GetClientMatchTeam(int client) { if (!g_CheckAuthsCvar.BoolValue) { return CSTeamToGet5Team(GetClientTeam(client)); } else { @@ -232,7 +182,7 @@ public Get5Team GetClientMatchTeam(int client) { if (GetAuth(client, auth, sizeof(auth))) { Get5Team playerTeam = GetAuthMatchTeam(auth); if (playerTeam == Get5Team_None) { - playerTeam = GetAuthMatchTeamCoach(auth); + playerTeam = GetAuthMatchCoachTeam(auth); } return playerTeam; } else { @@ -241,7 +191,7 @@ public Get5Team GetClientMatchTeam(int client) { } } -public int Get5TeamToCSTeam(Get5Team t) { +int Get5TeamToCSTeam(Get5Team t) { if (t == Get5Team_1) { return g_TeamSide[Get5Team_1]; } else if (t == Get5Team_2) { @@ -253,7 +203,7 @@ public int Get5TeamToCSTeam(Get5Team t) { } } -public Get5Team CSTeamToGet5Team(int csTeam) { +Get5Team CSTeamToGet5Team(int csTeam) { if (csTeam == g_TeamSide[Get5Team_1]) { return Get5Team_1; } else if (csTeam == g_TeamSide[Get5Team_2]) { @@ -265,7 +215,7 @@ public Get5Team CSTeamToGet5Team(int csTeam) { } } -public Get5Team GetAuthMatchTeam(const char[] steam64) { +Get5Team GetAuthMatchTeam(const char[] steam64) { if (g_GameState == Get5State_None) { return Get5Team_None; } @@ -283,7 +233,7 @@ public Get5Team GetAuthMatchTeam(const char[] steam64) { return Get5Team_None; } -public Get5Team GetAuthMatchTeamCoach(const char[] steam64) { +Get5Team GetAuthMatchCoachTeam(const char[] steam64) { if (g_GameState == Get5State_None) { return Get5Team_None; } @@ -301,38 +251,42 @@ public Get5Team GetAuthMatchTeamCoach(const char[] steam64) { return Get5Team_None; } -stock int CountPlayersOnCSTeam(int team, int exclude = -1) { +int CountCoachesOnTeam(Get5Team team, int exclude = -1) { int count = 0; + Get5Side side = view_as(Get5TeamToCSTeam(team)); LOOP_CLIENTS(i) { - if (i != exclude && IsAuthedPlayer(i) && GetClientTeam(i) == team) { + if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team && GetClientCoachingSide(i) == side) { count++; } } return count; } -stock int CountPlayersOnMatchTeam(Get5Team team, int exclude = -1) { +int CountPlayersOnTeam(Get5Team team, int exclude = -1) { int count = 0; + Get5Side side = view_as(Get5TeamToCSTeam(team)); LOOP_CLIENTS(i) { - if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team) { + if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team && view_as(GetClientTeam(i)) == side) { count++; } } return count; } -// Returns the match team a client is the captain of, or MatchTeam_None. -public Get5Team GetCaptainTeam(int client) { - if (client == GetTeamCaptain(Get5Team_1)) { - return Get5Team_1; - } else if (client == GetTeamCaptain(Get5Team_2)) { - return Get5Team_2; - } else { - return Get5Team_None; +Get5Side GetClientCoachingSide(int client) { + if (GetClientTeam(client) != CS_TEAM_SPECTATOR) { + return Get5Side_None; + } + int side = GetEntProp(client, Prop_Send, "m_iCoachingTeam"); + if (side == CS_TEAM_CT) { + return Get5Side_CT; + } else if (side == CS_TEAM_T) { + return Get5Side_T; } + return Get5Side_None; } -public int GetTeamCaptain(Get5Team team) { +int GetTeamCaptain(Get5Team team) { // If not forcing auths, take the 1st client on the team. if (!g_CheckAuthsCvar.BoolValue) { LOOP_CLIENTS(i) { @@ -356,7 +310,7 @@ public int GetTeamCaptain(Get5Team team) { return -1; } -public int GetNextTeamCaptain(int client) { +int GetNextTeamCaptain(int client) { if (client == g_VetoCaptains[Get5Team_1]) { return g_VetoCaptains[Get5Team_2]; } else { @@ -364,23 +318,23 @@ public int GetNextTeamCaptain(int client) { } } -public ArrayList GetTeamAuths(Get5Team team) { +ArrayList GetTeamAuths(Get5Team team) { return g_TeamAuths[team]; } -public ArrayList GetTeamCoaches(Get5Team team) { +ArrayList GetTeamCoaches(Get5Team team) { return g_TeamCoaches[team]; } -public bool IsAuthOnTeam(const char[] auth, Get5Team team) { +bool IsAuthOnTeam(const char[] auth, Get5Team team) { return GetTeamAuths(team).FindString(auth) >= 0; } -public bool IsAuthOnTeamCoach(const char[] auth, Get5Team team) { +bool IsAuthOnTeamCoach(const char[] auth, Get5Team team) { return GetTeamCoaches(team).FindString(auth) >= 0; } -public void SetStartingTeams() { +void SetStartingTeams() { int mapNumber = Get5_GetMapNumber(); if (mapNumber >= g_MapSides.Length || g_MapSides.Get(mapNumber) == SideChoice_KnifeRound) { g_TeamSide[Get5Team_1] = TEAM1_STARTING_SIDE; @@ -399,14 +353,10 @@ public void SetStartingTeams() { g_TeamStartingSide[Get5Team_2] = g_TeamSide[Get5Team_2]; } -public int GetMapScore(int mapNumber, Get5Team team) { +int GetMapScore(int mapNumber, Get5Team team) { return g_TeamScoresPerMap.Get(mapNumber, view_as(team)); } -public bool HasMapScore(int mapNumber) { - return GetMapScore(mapNumber, Get5Team_1) != 0 || GetMapScore(mapNumber, Get5Team_2) != 0; -} - bool AddPlayerToTeam(const char[] auth, Get5Team team, const char[] name) { char steam64[AUTH_LENGTH]; if (!ConvertAuthToSteam64(auth, steam64)) { @@ -433,7 +383,7 @@ bool AddCoachToTeam(const char[] auth, Get5Team team, const char[] name) { return false; } - if (GetAuthMatchTeamCoach(steam64) == Get5Team_None) { + if (GetAuthMatchCoachTeam(steam64) == Get5Team_None) { GetTeamCoaches(team).PushString(steam64); Get5_SetPlayerName(auth, name); return true; @@ -470,7 +420,7 @@ bool RemovePlayerFromTeams(const char[] auth) { return false; } -public void LoadPlayerNames() { +void LoadPlayerNames() { KeyValues namesKv = new KeyValues("Names"); int numNames = 0; LOOP_TEAMS(team) { @@ -510,7 +460,7 @@ public void LoadPlayerNames() { delete namesKv; } -public void SwapScrimTeamStatus(int client) { +void SwapScrimTeamStatus(int client) { // If we're in any team -> remove from any team list. // If we're not in any team -> add to team1. char auth[AUTH_LENGTH]; @@ -521,6 +471,6 @@ public void SwapScrimTeamStatus(int client) { ConvertAuthToSteam64(auth, steam64); GetTeamAuths(Get5Team_1).PushString(steam64); } + CheckClientTeam(client); } - CheckClientTeam(client); } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index b13748bf1..b4e57b6db 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -66,22 +66,12 @@ stock int ConvertCSTeamToDefaultWinReason(int side) { return view_as(side == CS_TEAM_CT ? CSRoundEnd_CTWin : CSRoundEnd_TerroristWin) + 1; } -/** - * Switches and respawns a player onto a new team. - */ stock void SwitchPlayerTeam(int client, int team) { + // Check avoids killing player if they're already on the right team. if (GetClientTeam(client) == team) { return; } - - LogDebug("SwitchPlayerTeam %L to %d", client, team); - if (team > CS_TEAM_SPECTATOR) { - CS_SwitchTeam(client, team); - CS_UpdateClientModel(client); - CS_RespawnPlayer(client); - } else { - ChangeClientTeam(client, team); - } + ChangeClientTeam(client, team); } /** @@ -208,12 +198,7 @@ stock void RestartGame(int delay) { } stock bool IsClientCoaching(int client) { - return GetClientTeam(client) == CS_TEAM_SPECTATOR && - GetEntProp(client, Prop_Send, "m_iCoachingTeam") != 0; -} - -stock void UpdateCoachTarget(int client, int csTeam) { - SetEntProp(client, Prop_Send, "m_iCoachingTeam", csTeam); + return GetClientCoachingSide(client) != Get5Side_None; } stock void SetTeamInfo(int csTeam, const char[] name, const char[] flag = "", @@ -699,7 +684,7 @@ stock Get5BombSite GetNearestBombsite(int client) { float aDist = GetVectorDistance(aCenter, pos, true); float bDist = GetVectorDistance(bCenter, pos, true); - LogDebug("Bomb planted. Distance to A: %d. Distance to B: %d.", aDist, bDist); + LogDebug("Bomb planted. Distance to A: %f. Distance to B: %f.", aDist, bDist); return (aDist < bDist) ? Get5BombSite_A : Get5BombSite_B; } diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index 5bcf7ecff..a00a673f3 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -389,6 +389,20 @@ { "en" "You were moved to the coach position because your team is full." } + "CoachingNotEnabled" + { + "#format" "{1:s}" + "en" "Coaching is not enabled. You must set {1} to 1." + } + "CanOnlyCoachDuringFreezetimeOrWarmup" + { + "en" "You can only request to coach during warmup or freezetime." + } + "AllCoachSlotsFilledForTeam" + { + "#format" "{1:d}" + "en" "All coach slots ({1}) are currently filled for your team." + } "ReadyTag" { "en" "[READY]" From d9e0a440c1b130a3e9c03c1b5788027d1026b4f5 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 19 Aug 2022 15:46:48 +0200 Subject: [PATCH 060/104] Make sure to call ExecuteMatchConfigCvars before placing players Bring in g_CheckAuthsCvar check to CheckClientTeam Bring in full-team check to include coaches on OnClientAuthorized Review adjustments Use CS_SwitchTeam etc. in knife-round swap Use Get5Side for side arguments in SwitchPlayerTeam Don't allow .coach after warmup Let .coach toggle coaching on/off Add "is coaching" message Add hook to monitor sv_coaching_enabled to prevent spectation instead of coaching by mistake Disable team-join menu entirely Don't allow loadbackup during halftime --- documentation/docs/coaching.md | 39 ++++--- documentation/docs/commands.md | 3 +- documentation/docs/getting_started.md | 4 +- documentation/docs/match_schema.md | 9 +- documentation/docs/translations.md | 13 ++- scripting/get5.sp | 12 ++- scripting/get5/backups.sp | 5 + scripting/get5/debug.sp | 2 +- scripting/get5/kniferounds.sp | 2 +- scripting/get5/matchconfig.sp | 15 ++- scripting/get5/teamlogic.sp | 140 +++++++++++++++++--------- scripting/get5/util.sp | 22 +++- translations/get5.phrases.txt | 17 +++- 13 files changed, 185 insertions(+), 98 deletions(-) diff --git a/documentation/docs/coaching.md b/documentation/docs/coaching.md index 8626d4d61..43bf21124 100644 --- a/documentation/docs/coaching.md +++ b/documentation/docs/coaching.md @@ -6,9 +6,9 @@ few ["minor" bugs](https://en.wikipedia.org/wiki/Counter-Strike_coaching_bug_sca ### Server requirements {: #requirements } -1. [`sv_coaching_enabled`](https://totalcsgo.com/command/svcoachingenabled) must be set. +1. [`sv_coaching_enabled`](https://totalcsgo.com/command/svcoachingenabled) must be set to 1. 2. [`coaches_per_team`](../match_schema/#schema) in your match configuration - or [scrim template](../getting_started/#scrims) must be larger than 0. + or [scrim](../getting_started/#scrims) template must be larger than 0. 3. The [`-maxplayers_override`](https://developer.valvesoftware.com/wiki/Maxplayers) launch parameter must be defined on your server to allow for the number of connected clients you expect, including all players, spectators and coaches. @@ -19,46 +19,43 @@ Due to internal conflicts with how [backups](backup.md) and auto-assignment to t [`coach`](https://counterstrike.fandom.com/wiki/Coaching) console command is disabled. You can become a coach in one of three ways: -1. Use the [`!coach`](../commands/#coach) chat command during freezetime or warmup. +1. Use the [`!coach`](../commands/#coach) chat command during warmup. 2. Be defined as a coach in the [match configuration](../match_schema/#schema) or via [`get5_addcoach`](../commands/#get5_addcoach). 3. Join a game where the team is already full (determined by [`players_per_team`](../match_schema/#schema)) and where a coach slot is available. -!!! warning "Coaching is permanent" +!!! warning "Coaching is permanent after warmup" - Once you are assigned as a coach in a non-scrim game, you cannot leave the coach slot unless you are removed from - coaching using [`get5_removeplayer`](../commands/#get5_removeplayer). + Once a game begins (goes past the warmup-phase), you cannot enter or leave the coach slot unless you are removed + from coaching using [`get5_removeplayer`](../commands/#get5_removeplayer). If the current number of coaches exceeds or equals [`coaches_per_team`](../match_schema/#schema), including if it is zero, additional players will be kicked from the match. However, if a connecting player is defined -in [`players`](../match_schema/#schema), the team is full and a coach slot is open, they will be permanently moved to -coaching for the series. +in [`players`](../match_schema/#schema), the team is full and a coach slot is open, they will be moved to coaching for +the series and can only stop coaching if the game is still in warmup. This behavior allows you to define as many coaches and players in the match configuration as you want: As long as the number of players and coaches on the server don't exceed [`players_per_team`](../match_schema/#schema) and [`coaches_per_team`](../match_schema/#schema), respectively, Get5 will fill the -game slots with the appropriate number of players and coaches and kick the rest. Being in +game's slots with the appropriate number of players and coaches and kick the rest. Being in the [`coaches`](../match_schema/#schema) section takes precedence over [`players`](../match_schema/#schema). !!! note "Decreasing the number of players" If a match configuration with [`players_per_team`](../match_schema/#schema) or [`coaches_per_team`](../match_schema/#schema) set to a number *lower* than the number of players **already connected - to the server**, the entire team will be kicked and must reconnect. + to the server**, the entire team's players or coaches (whichever is exceeded) will be kicked and must reconnect. -### Coaching in Scrims {: #scrims } +### Coaching in scrims {: #scrims } -When in [scrim-mode](../getting_started/#scrims), you cannot set the [`coaches`](../match_schema/#schema) key, and -players are never locked to the coaching slot. This means that to become a coach in a scrim, you must always +When in [scrim mode](../getting_started/#scrims), you cannot set the [`coaches`](../match_schema/#schema) key, and +players are never _locked_ to the coaching slot. This means that to become a coach in a scrim, you must always call [`!coach`](../commands/#coach) or join a team that already has [`players_per_team`](../match_schema/#schema) -players (i.e. is full). Contrary -to games with a complete [match configuration](../match_schema/#schema), you can also exit the coaching slot if the team -is not full by simply selecting any team using the team-join menu, which will respawn you as a player in the next -round (or immediately if still in freezetime), similarly to if you were to reconnect to the server. +players (i.e. is full). -!!! danger "`players_per_team` matters in scrims!" +!!! danger "`players_per_team` matters!" - Do not set [`players_per_team`](../match_schema/#schema) to a value larger than the number of players you expect. - If you do this, a player could go back and forth between coaching and playing during freezetime, potentially - dropping items for their team. If you play 5v5, don't set it to 6 to allow "anyone to join to coach". + Do not set [`players_per_team`](../match_schema/#schema) in your scrim template to a value larger than the number of + players you expect. If you do this, a coach - or any player defined in your scrim template - (re)connecting after + warmup will be put on the team and won't be able to become a coach. diff --git a/documentation/docs/commands.md b/documentation/docs/commands.md index 9231e4bec..13ab69240 100644 --- a/documentation/docs/commands.md +++ b/documentation/docs/commands.md @@ -30,7 +30,8 @@ Please note that these can be typed by *all players* in chat. ####`!coach` -: Moves a client to [coach for their team](coaching.md). +: Requests to become a [coach](coaching.md) for your team. If already coaching, this will move you back as a player +if possible. Can only be used during warmup. ####`!stay` diff --git a/documentation/docs/getting_started.md b/documentation/docs/getting_started.md index 94f9f86d8..185b387a8 100644 --- a/documentation/docs/getting_started.md +++ b/documentation/docs/getting_started.md @@ -33,8 +33,8 @@ file as soon as a player joins by setting [`get5_autoload_config`](../configurat ## Scrims {: #scrims } -While Get5 is intended for matches (league matches, LAN-matches, cups, etc.), it can be used for everyday -scrims/gathers/whatever as well. If that is your use case, you should do a few things differently. We call "_having a +While Get5 is intended for matches (league matches, LANs, cups, etc.), it can be used for everyday +scrims or gathers as well. If that is your use case, you should do a few things differently. We call "_having a home team defined and anyone else on the opposing team_" a **scrim**, and loading this configuration is referred to as **scrim mode**. diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 193c4f86c..86d165177 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -66,8 +66,9 @@ interface Get5Match { match IDs from another source, they **must** be integers (in a string) and must increment between matches.

**`Default: ""`** 2. _Optional_
The number of maps to play in the series.

**`Default: 3`** -3. _Optional_
The number of players per team.

**`Default: 5`** -4. _Optional_
The maximum number of coaches per team.

**`Default: 2`** +3. _Optional_
The number of players per team. You should **never** set this to a value higher than the number of + players you want to actually play in a game, *excluding* coaches.

**`Default: 5`** +4. _Optional_
The maximum number of [coaches](coaching.md) per team.

**`Default: 2`** 5. _Optional_
The minimum number of players of each team that must type [`!ready`](../commands/#ready) for the game to begin.

**`Default: 1`** 6. _Optional_
The minimum number of spectators that must be [`!ready`](../commands/#ready) for the game to @@ -106,8 +107,8 @@ interface Get5Match { regular server-commands and any [`Get5 configuration parameter`](configuration.md), i.e. `{"hostname": "Match #3123 - Astralis vs. NaVi"}`.

**`Default: undefined`** 23. _Optional_
Similarly to `players`, this object maps [coaches](coaching.md) using their Steam ID and - name, locking them to the coach slot until removed - using [`get5_removeplayer`](../commands/#get5_removeplayer)

**`Default: undefined`** + name, locking them to the coach slot unless removed using [`get5_removeplayer`](../commands/#get5_removeplayer). + Setting a Steam ID as coach takes precedence over being set as a player.

**`Default: undefined`** 24. _Required_
The players on the team. 25. _Optional_
Wrapper of the server's `mp_teammatchstat_txt` cvar, but can use `{MAPNUMBER}` and `{MAXMAPS}` as variables that get replaced with their integer values. In a BoX series, you probably don't want to set this since diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index d236b0ae3..62d4fbbe0 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -29,9 +29,9 @@ entire language file**. ``` 1. The `#format` parameter indicates the order and types of parameters. These will *not* be defined in other - and you should only provide the language string itself (with its language prefix, i.e. `en`). The original file - indicates what `{1}`, `{2}` and `{3}` are. In this case, the first and second arguments are strings and the third - is a number. + languages, and you should only provide the language string itself (with its language prefix, i.e. `en`). The + original file indicates what `{1}`, `{2}` and `{3}` are. In this case, the first and second arguments are strings + and the third is a number. 2. Use the English strings and the [reference](#reference) below to determine how to translate the string. As the string implies, this example is used when a team picks a map, and the output is printed to chat and looks like @@ -158,7 +158,12 @@ end with a full stop as this is added automatically. | `CaptainLeftOnVetoInfoMessage` | A captain left during the veto, pausing the veto. | Chat | | `ReadyToResumeVetoInfoMessage` | Type _!ready_ when you are ready to resume the veto. | Chat | | `MatchConfigLoadedInfoMessage` | Loaded match config. | Chat | -| `MoveToCoachInfoMessage` | You were moved to the coach position because your team is full. | Chat | +| `MoveToCoachInfoMessage` | You were moved to the coach position as your team is full. | Chat | +| `CannotLeaveCoachingTeamIsFull` | You cannot leave the coach position as your team is full. | Chat | +| `CoachingNotEnabled` | Coaching is not enabled. You must set _sv_coaching_enabled_ to 1. | Chat | +| `PlayerIsCoachingTeam` | _PlayerName_ is coaching _Team A_. | Chat | +| `CanOnlyCoachDuringWarmup` | You can only change to or from coach during warmup. | Chat | +| `AllCoachSlotsFilledForTeam` | All coach slots (_2_) are currently filled for your team. | Chat | | `ReadyTag` | **[READY]** PlayerName: Hey, I'm ready... | Chat | | `NotReadyTag` | **[NOT READY]** PlayerName: Hey, I'm not ready... | Chat | | `MapVetoPickMenuText` | Select a map to PLAY: | Menu | diff --git a/scripting/get5.sp b/scripting/get5.sp index 07f8424d3..ff4e98a04 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -142,7 +142,7 @@ int g_MinSpectatorsToReady = 0; float g_RoundStartedTime = 0.0; float g_BombPlantedTime = 0.0; Get5BombSite g_BombSiteLastPlanted = Get5BombSite_Unknown; -bool g_AssignUnTeamedPlayersOnRoundStart = false; +bool g_AssignTeamNonePlayersOnRoundStart = false; bool g_SkipVeto = false; float g_VetoMenuTime = 0.0; @@ -462,6 +462,7 @@ public void OnPluginStart() { g_VersionCvar.SetString(PLUGIN_VERSION); g_CoachingEnabledCvar = FindConVar("sv_coaching_enabled"); + g_CoachingEnabledCvar.AddChangeHook(CoachingChangedHook); // used to move people off coaching if it gets disabled. /** Client commands **/ g_ChatAliases = new ArrayList(ByteCountToCells(ALIAS_LENGTH)); @@ -695,7 +696,8 @@ public void OnClientAuthorized(int client, const char[] auth) { Get5Team team = GetClientMatchTeam(client); if (team == Get5Team_None) { RememberAndKickClient(client, "%t", "YouAreNotAPlayerInfoMessage"); - } else if (CountPlayersOnTeam(team, client) >= g_PlayersPerTeam && !g_CoachingEnabledCvar.BoolValue) { + } else if (CountPlayersOnTeam(team, client) >= g_PlayersPerTeam + && (!g_CoachingEnabledCvar.BoolValue || CountCoachesOnTeam(team, client) >= g_CoachesPerTeam)) { KickClient(client, "%t", "TeamIsFullInfoMessage"); } } @@ -1549,11 +1551,11 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas return; } - // Ensures that players who connect during halftime/team swap are placed in their correct slots as soon as the + // Ensures that players who connect during halftime/team swap are placed in their correct slots as soon as the // following round starts. Otherwise they could be left on the "no team" screen and potentially // ghost, depending on where the camera drops them. Especially important for coaches. - if (g_AssignUnTeamedPlayersOnRoundStart) { - g_AssignUnTeamedPlayersOnRoundStart = false; + if (g_AssignTeamNonePlayersOnRoundStart) { + g_AssignTeamNonePlayersOnRoundStart = false; LOOP_CLIENTS(i) { // We check only for connection here, as that's required to put them in the game, as they may // not actually be considered "in the game" yet, so IsValidClient() might not work. diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index d4d869967..2b4abe4b3 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -7,6 +7,11 @@ public Action Command_LoadBackup(int client, int args) { return Plugin_Handled; } + if (g_PendingSideSwap || InHalftimePhase()) { + ReplyToCommand(client, "You cannot load a backup during halftime."); + return Plugin_Handled; + } + char path[PLATFORM_MAX_PATH]; if (args >= 1 && GetCmdArg(1, path, sizeof(path))) { if (RestoreFromBackup(path)) { diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index 21e443b9a..f34f6c5bc 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -111,7 +111,7 @@ static void AddGlobalStateInfo(File f) { f.WriteLine("g_InScrimMode = %d", g_InScrimMode); f.WriteLine("g_SeriesCanClinch = %d", g_SeriesCanClinch); f.WriteLine("g_HasKnifeRoundStarted = %d", g_HasKnifeRoundStarted); - f.WriteLine("g_AssignUnTeamedPlayersOnRoundStart = %d", g_AssignUnTeamedPlayersOnRoundStart); + f.WriteLine("g_AssignTeamNonePlayersOnRoundStart = %d", g_AssignTeamNonePlayersOnRoundStart); f.WriteLine("g_MapChangePending = %d", g_MapChangePending); f.WriteLine("g_PendingSideSwap = %d", g_PendingSideSwap); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index dd519315e..64174d14e 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -47,7 +47,7 @@ static void PerformSideSwap(bool swap) { // prevent one team from having too many players. They will rejoin if defined in the live config. KickClient(i); } else { - CheckClientTeam(i); + CheckClientTeam(i, false); } } } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 76d11d09e..973c1a313 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -37,6 +37,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } g_MatchID = ""; + g_AssignTeamNonePlayersOnRoundStart = false; g_ReadyTimeWaitingUsed = 0; g_HasKnifeRoundStarted = false; g_MapChangePending = false; @@ -125,13 +126,12 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { // that are set in the OnSeriesInit event. // Add to download table after setting. SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - LoadPlayerNames(); - AddTeamLogosToDownloadTable(); if (!restoreBackup) { - SetStartingTeams(); // This cannot be called during backup, as it will reset the sides! + SetStartingTeams(); ExecCfg(g_WarmupCfgCvar); + ExecuteMatchConfigCvars(); + LoadPlayerNames(); EnsureIndefiniteWarmup(); if (IsPaused()) { LogDebug("Match was paused when loading match config. Unpausing."); @@ -152,13 +152,18 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { EventLogger_LogAndDeleteEvent(startEvent); } + AddTeamLogosToDownloadTable(); + ExecuteMatchConfigCvars(); + LoadPlayerNames(); + strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); + + // ExecuteMatchConfigCvars must be executed before we place players, as it might have get5_check_auths 1. LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { CheckClientTeam(i); } } - strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); Get5_MessageToAll("%t", "MatchConfigLoadedInfoMessage"); return true; } diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index 7a515f944..823041f1f 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -12,7 +12,12 @@ public Action Timer_PlacePlayerOnJoin(Handle timer, int userId) { } } -public void CheckClientTeam(int client) { +void CheckClientTeam(int client, bool useDefaultTeamSelection = true) { + if (!g_CheckAuthsCvar.BoolValue) { + // Teams are not enforced; do nothing. + return; + } + Get5Team correctTeam = GetClientMatchTeam(client); if (correctTeam == Get5Team_None) { RememberAndKickClient(client, "%t", "YouAreNotAPlayerInfoMessage"); @@ -20,7 +25,6 @@ public void CheckClientTeam(int client) { } Get5Side correctSide = view_as(Get5TeamToCSTeam(correctTeam)); - if (correctSide == Get5Side_None) { // This should not be possible. LogError("Client %d belongs to no side. This is an unexpected error and should be reported.", client); @@ -30,10 +34,6 @@ public void CheckClientTeam(int client) { int coachesOnTeam = CountCoachesOnTeam(correctTeam, client); // If the player is fixed to coaching, always ensure they end there and on the correct side. if (g_CoachingEnabledCvar.BoolValue && IsClientCoachForTeam(client, correctTeam)) { - if (GetClientCoachingSide(client) == correctSide) { - // Player is already coaching the correct team and is on spectator; do nothing. - return; - } // If there are free coach spots on the team, send the player there if (coachesOnTeam < g_CoachesPerTeam) { SetClientCoaching(client, correctSide); @@ -45,32 +45,31 @@ public void CheckClientTeam(int client) { // If player was not locked to coaching, check if their team's current size -self is less than the max. if (CountPlayersOnTeam(correctTeam, client) < g_PlayersPerTeam) { - SwitchPlayerTeam(client, view_as(correctSide)); + SwitchPlayerTeam(client, correctSide, useDefaultTeamSelection); return; } - // We end here if a player was not a predefined coach while there was no space. If coaching is enabled, we drop - // the player in there, and if not, they must be kicked. + // We end here if a player was not a predefined coach while there was no space as a regular player. If coaching is + // enabled, we drop the player in coach, and if not, they must be kicked. if (g_CoachingEnabledCvar.BoolValue && coachesOnTeam < g_CoachesPerTeam) { Get5_Message(client, "%t", "MoveToCoachInfoMessage"); // In scrim mode, we don't put coaches or players of the "away" team into any auth arrays; they default to the - // opposite of the home team. If a full team's coach disconnects or leaves, they should be placed on the coach - // team if their team is full. In a regular match, they will have called .coach before the map starts and will - // be placed by auth. + // opposite of the home team. If a full team's coach disconnects or leaves and rejoins, they should be placed on the + // coach team if their team is full. In a regular match, they will have called .coach before the map starts and will + // be placed by auth above. if (!g_InScrimMode) { MovePlayerToCoachInConfig(client, correctTeam); } SetClientCoaching(client, correctSide); return; } - KickClient(client, "%t", "TeamIsFullInfoMessage"); } static void PlacePlayerOnTeam(int client) { if (g_PendingSideSwap || InHalftimePhase()) { LogDebug("Blocking attempt to join a team due to halftime or pending team swap."); - g_AssignUnTeamedPlayersOnRoundStart = true; + g_AssignTeamNonePlayersOnRoundStart = true; return; } CheckClientTeam(client); @@ -80,15 +79,10 @@ public Action Command_JoinTeam(int client, const char[] command, int argc) { if (g_GameState == Get5State_None || !g_CheckAuthsCvar.BoolValue) { return Plugin_Continue; } - if (!IsAuthedPlayer(client) || argc < 1) { - LogDebug("Preventing unauthorized/no argument client %d from joining a team.", client); - return Plugin_Stop; - } - char arg[4]; - GetCmdArg(1, arg, sizeof(arg)); - int team_to = StringToInt(arg); - // Ensure team_to argument can be safely converted to Get5Side. Users *could* type "jointeam 4434" if they wanted. - if (team_to == CS_TEAM_T || team_to == CS_TEAM_CT || team_to == CS_TEAM_SPECTATOR) { + // If, in some odd case, a player should find themselves on no team while g_CheckAuthsCvar is true, we want to let + // them trigger the PlacePlayerOnTeam logic when clicking any team. In any other case, we just block. + // Blocking ensures that coaches in scrim-mode will not stop coaching if they select a team in the menu. + if (IsAuthedPlayer(client) && GetClientTeam(client) == CS_TEAM_NONE) { PlacePlayerOnTeam(client); } return Plugin_Stop; @@ -100,11 +94,33 @@ static bool IsClientCoachForTeam(int client, Get5Team team) { } void SetClientCoaching(int client, Get5Side side) { + if (GetClientCoachingSide(client) == side) { + return; + } LogDebug("Setting client %d as spectator and coach for side %d.", client, side); - SwitchPlayerTeam(client, CS_TEAM_SPECTATOR); + SwitchPlayerTeam(client, Get5Side_Spec); SetEntProp(client, Prop_Send, "m_iCoachingTeam", side); SetEntProp(client, Prop_Send, "m_iObserverMode", 4); SetEntProp(client, Prop_Send, "m_iAccount", 0); // Ensures coaches have no money if they were to rejoin the game. + + char formattedPlayerName[MAX_NAME_LENGTH]; + FormatPlayerName(formattedPlayerName, sizeof(formattedPlayerName), client, side); + Get5_MessageToAll("%t", "PlayerIsCoachingTeam", formattedPlayerName, g_FormattedTeamNames[GetClientMatchTeam(client)]); +} + +public void CoachingChangedHook(ConVar convar, const char[] oldValue, const char[] newValue) { + if (g_GameState == Get5State_None) { + return; + } + // If disabling coaching, make sure we swap coaches to team or kick them, as they are now regular spectators. + if (StringToInt(oldValue) != 0 && !convar.BoolValue) { + LogDebug("Detected sv_coaching_enabled was disabled. Checking for coaches."); + LOOP_CLIENTS(i) { + if (IsAuthedPlayer(i) && IsClientCoaching(i)) { + CheckClientTeam(i); + } + } + } } public Action Command_SmCoach(int client, int args) { @@ -123,32 +139,50 @@ public Action Command_SmCoach(int client, int args) { return Plugin_Continue; } - Get5Side currentlyCoaching = GetClientCoachingSide(client); - Get5Side side = view_as(Get5TeamToCSTeam(matchTeam)); - - if (currentlyCoaching == side) { - // Already coaching for the right side. + if (g_GameState > Get5State_Warmup) { + Get5_Message(client, "%t", "CanOnlyCoachDuringWarmup"); return Plugin_Continue; } - if (g_PendingSideSwap || InHalftimePhase() || (!InFreezeTime() && !InWarmup())) { - Get5_Message(client, "%t", "CanOnlyCoachDuringFreezetimeOrWarmup"); - return Plugin_Continue; - } - - // Don't allow a new coach if spots are full. - if (CountCoachesOnTeam(matchTeam, client) >= g_CoachesPerTeam) { - Get5_Message(client, "%t", "AllCoachSlotsFilledForTeam", g_CoachesPerTeam); - return Plugin_Continue; - } + // These counts are excluding the client, so >=. + bool coachSlotsFull = CountCoachesOnTeam(matchTeam, client) >= g_CoachesPerTeam; + bool playerSlotsFull = CountPlayersOnTeam(matchTeam, client) >= g_PlayersPerTeam; - if (!g_InScrimMode) { - // If we're in scrim mode, we don't update the coaches auth array ever. - MovePlayerToCoachInConfig(client, matchTeam); + // If we're in scrim mode, we don't update the coaches auth array ever. + if (g_InScrimMode) { + if (IsClientCoaching(client)) { + if (playerSlotsFull) { + Get5_Message(client, "%t", "CannotLeaveCoachingTeamIsFull"); + return Plugin_Continue; + } + // Fall-through to CheckClientTeam(i) below, which moves the player back on the team because they are not + // defined as a coach in auth. + } else { + if (coachSlotsFull) { + Get5_Message(client, "%t", "AllCoachSlotsFilledForTeam", g_CoachesPerTeam); + return Plugin_Continue; + } + // We use SetClientCoaching instead of fall-though because of missing auth. + SetClientCoaching(client, view_as(Get5TeamToCSTeam(matchTeam))); + return Plugin_Continue; + } + } else { + if (IsClientCoachForTeam(client, matchTeam)) { + if (playerSlotsFull) { + Get5_Message(client, "%t", "CannotLeaveCoachingTeamIsFull"); + return Plugin_Continue; + } + MoveCoachToPlayerInConfig(client, matchTeam); + } else { + if (coachSlotsFull) { + Get5_Message(client, "%t", "AllCoachSlotsFilledForTeam", g_CoachesPerTeam); + return Plugin_Continue; + } + MovePlayerToCoachInConfig(client, matchTeam); + } } - - // Actually move the player to coach + spec - SetClientCoaching(client, side); + // Move the player. This would potentially kick them if we did not perform above checks. + CheckClientTeam(client); return Plugin_Continue; } @@ -160,12 +194,26 @@ static void MovePlayerToCoachInConfig(const int client, const Get5Team team) { // to ensure data is correct in the backups. int index = GetTeamAuths(team).FindString(auth); if (index >= 0) { - LogDebug("Removing client %d from regular team auth array for team %d", client, team); + LogDebug("Removing client %d from player team auth array for team %d.", client, team); GetTeamAuths(team).Erase(index); } } } +static void MoveCoachToPlayerInConfig(const int client, const Get5Team team) { + char auth[AUTH_LENGTH]; + GetAuth(client, auth, sizeof(auth)); + AddPlayerToTeam(auth, team, ""); + // This differs from MovePlayerToCoachInConfig because being in coach array + player array will make coaching + // take precedence, so if you're being added from coach to player, and you're already defined as a player, the above + // function will return false, so we always remove from the coach array when moving from coach to player. + int index = GetTeamCoaches(team).FindString(auth); + if (index >= 0) { + LogDebug("Removing client %d from coach team auth array for team %d", client, team); + GetTeamCoaches(team).Erase(index); + } +} + public Action Command_Coach(int client, const char[] command, int argc) { if (g_GameState == Get5State_None) { return Plugin_Continue; diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index b4e57b6db..27b6d835f 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -66,12 +66,20 @@ stock int ConvertCSTeamToDefaultWinReason(int side) { return view_as(side == CS_TEAM_CT ? CSRoundEnd_CTWin : CSRoundEnd_TerroristWin) + 1; } -stock void SwitchPlayerTeam(int client, int team) { +stock void SwitchPlayerTeam(int client, Get5Side side, bool useDefaultTeamSelection = true) { // Check avoids killing player if they're already on the right team. + int team = view_as(side); if (GetClientTeam(client) == team) { return; } - ChangeClientTeam(client, team); + if (useDefaultTeamSelection || team == CS_TEAM_SPECTATOR) { + ChangeClientTeam(client, team); + } else { + // When doing side-swap in knife-rounds, we do this to prevent the score from going -1 for everyone. + CS_SwitchTeam(client, team); + CS_UpdateClientModel(client); + CS_RespawnPlayer(client); + } } /** @@ -116,8 +124,14 @@ stock void FormatChatCommand(char[] buffer, const int bufferLength, const char[] Format(buffer, bufferLength, "{GREEN}%s{NORMAL}", command); } -stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client) { - Get5Side side = view_as(IsClientInGame(client) ? GetClientTeam(client) : CS_TEAM_NONE); +stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client, const Get5Side forcedSide = Get5Side_None) { + // Used when injecting the team for coaching players, who are always on team spectator. + Get5Side side; + if (forcedSide == Get5Side_None) { + side = view_as(IsClientInGame(client) ? GetClientTeam(client) : CS_TEAM_NONE); + } else { + side = forcedSide; + } if (side == Get5Side_CT) { Format(buffer, bufferLength, "{LIGHT_BLUE}%N{NORMAL}", client); } else if (side == Get5Side_T) { diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index a00a673f3..dd0f050c5 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -61,7 +61,7 @@ } "TeamIsFullInfoMessage" { - "en" "Your team is full." + "en" "Your team is full" } "TeamForfeitInfoMessage" { @@ -387,16 +387,25 @@ } "MoveToCoachInfoMessage" { - "en" "You were moved to the coach position because your team is full." + "en" "You were moved to the coach position as your team is full." + } + "CannotLeaveCoachingTeamIsFull" + { + "en" "You cannot leave the coach position as your team is full." } "CoachingNotEnabled" { "#format" "{1:s}" "en" "Coaching is not enabled. You must set {1} to 1." } - "CanOnlyCoachDuringFreezetimeOrWarmup" + "PlayerIsCoachingTeam" + { + "#format" "{1:s},{2:s}" + "en" "{1} is coaching {2}." + } + "CanOnlyCoachDuringWarmup" { - "en" "You can only request to coach during warmup or freezetime." + "en" "You can only change to or from coach during warmup." } "AllCoachSlotsFilledForTeam" { From 3a2c066ceee4004902931d9f8ed1f920239d2bba Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 20 Aug 2022 16:06:09 +0200 Subject: [PATCH 061/104] Add supported languages section to docs Format cvar with colors in chat Add missing Danish translations related to coaching Move get5_check_auths warning to after match cvar init, as you may have get5_check_auths 1 in your cvar section --- documentation/docs/translations.md | 16 ++++++++++++++++ scripting/get5/goinglive.sp | 1 + scripting/get5/matchconfig.sp | 14 ++++++++------ scripting/get5/teamlogic.sp | 4 +++- scripting/get5/util.sp | 4 ++++ translations/da/get5.phrases.txt | 22 +++++++++++++++++++++- 6 files changed, 53 insertions(+), 8 deletions(-) diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index 62d4fbbe0..fb16ad9db 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -177,3 +177,19 @@ end with a full stop as this is added automatically. | `VetoCountdown` | Veto commencing in _3_ seconds. | Chat | | `NewVersionAvailable` | A newer version of Get5 is available. Please visit _splewis.github.io/get5_ to update. | Chat | | `PrereleaseVersionWarning` | You are running an unofficial version of Get5 (_0.9.0-c7af39a_) intended for development and testing only. This message can be disabled with _get5_print_update_notice_. | Chat | + +## Supported Languages {: #supported-languages } + +These are the languages Get5 supports. The links will take you to the source translation file for the language on +GitHub. Most languages are incomplete, and if a translation string is missing, the English default will be used. + +#### :flag_gb: [English](https://github.com/splewis/get5/blob/development/translations/get5.phrases.txt) (default) {: #en } +#### :flag_fr: [French](https://github.com/splewis/get5/tree/development/translations/fr/get5.phrases.txt) {: #fr } +#### :flag_de: [German](https://github.com/splewis/get5/tree/development/translations/de/get5.phrases.txt) {: #de } +#### :flag_es: [Spanish](https://github.com/splewis/get5/tree/development/translations/es/get5.phrases.txt) {: #es } +#### :flag_cn: [Chinese](https://github.com/splewis/get5/tree/development/translations/chi/get5.phrases.txt) {: #cn } +#### :flag_dk: [Danish](https://github.com/splewis/get5/tree/development/translations/da/get5.phrases.txt) {: #da } +#### :flag_hu: [Hungarian](https://github.com/splewis/get5/tree/development/translations/hu/get5.phrases.txt) {: #hu } +#### :flag_pl: [Polish](https://github.com/splewis/get5/tree/development/translations/pl/get5.phrases.txt) {: #pl } +#### :flag_pt: [Portuguese](https://github.com/splewis/get5/tree/development/translations/pt/get5.phrases.txt) {: #pt } +#### :flag_ru: [Russian](https://github.com/splewis/get5/tree/development/translations/ru/get5.phrases.txt) {: #ru } diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index bbba2decc..c16f5dea0 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -56,6 +56,7 @@ public Action MatchLive(Handle timer) { if (g_RunningPrereleaseVersion) { char conVarName[64]; g_PrintUpdateNoticeCvar.GetName(conVarName, sizeof(conVarName)); + FormatCvarName(conVarName, sizeof(conVarName), conVarName); Get5_MessageToAll("%t", "PrereleaseVersionWarning", PLUGIN_VERSION, conVarName); } else if (g_NewerVersionAvailable) { Get5_MessageToAll("%t", "NewVersionAvailable", GET5_GITHUB_PAGE); diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 973c1a313..d9c013148 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -63,12 +63,6 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { return false; } - if (!g_CheckAuthsCvar.BoolValue && - (GetTeamAuths(Get5Team_1).Length != 0 || GetTeamAuths(Get5Team_2).Length != 0)) { - LogError( - "Setting player auths in the \"players\" section has no impact with get5_check_auths 0"); - } - // Copy all the maps into the veto pool. char mapName[PLATFORM_MAX_PATH]; for (int i = 0; i < g_MapPoolList.Length; i++) { @@ -150,6 +144,14 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { Call_Finish(); EventLogger_LogAndDeleteEvent(startEvent); + + if (!g_CheckAuthsCvar.BoolValue && + (GetTeamAuths(Get5Team_1).Length != 0 + || GetTeamAuths(Get5Team_2).Length != 0 + || GetTeamCoaches(Get5Team_1).Length != 0 + || GetTeamCoaches(Get5Team_2).Length != 0)) { + LogError("Setting player auths in the \"players\" or \"coaches\" section has no impact with get5_check_auths 0"); + } } AddTeamLogosToDownloadTable(); diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index 823041f1f..bffa11c55 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -129,7 +129,9 @@ public Action Command_SmCoach(int client, int args) { } if (!g_CoachingEnabledCvar.BoolValue) { - Get5_Message(client, "%t", "CoachingNotEnabled", "sv_coaching_enabled"); + char formattedCoachingCvar[64]; + FormatCvarName(formattedCoachingCvar, sizeof(formattedCoachingCvar), "sv_coaching_enabled"); + Get5_Message(client, "%t", "CoachingNotEnabled", formattedCoachingCvar); return Plugin_Continue; } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 27b6d835f..5b3722ca2 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -124,6 +124,10 @@ stock void FormatChatCommand(char[] buffer, const int bufferLength, const char[] Format(buffer, bufferLength, "{GREEN}%s{NORMAL}", command); } +stock void FormatCvarName(char[] buffer, const int bufferLength, const char[] cVar) { + Format(buffer, bufferLength, "{GRAY}%s{NORMAL}", cVar); +} + stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client, const Get5Side forcedSide = Get5Side_None) { // Used when injecting the team for coaching players, who are always on team spectator. Get5Side side; diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index 5e660d581..af33f2dfd 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -326,7 +326,27 @@ } "MoveToCoachInfoMessage" { - "da" "Da dit hold er fuldt, blev du flyttet til trænerposition." + "da" "Da dit hold er fyldt, blev du flyttet til trænerposition." + } + "CannotLeaveCoachingTeamIsFull" + { + "da" "Du kan ikke forlade trænerpositionen, da dit hold er fyldt." + } + "CoachingNotEnabled" + { + "da" "Trænerindstillingen er ikke aktiveret. {1} skal sættes til 1." + } + "PlayerIsCoachingTeam" + { + "da" "{1} er træner for {2}." + } + "CanOnlyCoachDuringWarmup" + { + "da" "Du kan kun skifte til og fra trænerposition under opvarmning," + } + "AllCoachSlotsFilledForTeam" + { + "da" "Alle trænerpositioner ({1}) for dit hold er fyldt." } "ReadyTag" { From fb7e2451c24ea5e7c390eb95cac7afcb8cb0d188 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 22 Aug 2022 00:37:17 +0200 Subject: [PATCH 062/104] Move StopRecording before EndSeries as it depends on global state --- scripting/get5.sp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index ff4e98a04..ae300456a 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1010,6 +1010,8 @@ public Action Command_EndMatch(int client, int args) { Call_Finish(); EventLogger_LogAndDeleteEvent(mapResultEvent); + StopRecording(1.0); // must go before EndSeries as it depends on g_MatchID. + // No delay required when not kicking players. EndSeries(winningTeam, false, 0.0, false); @@ -1036,7 +1038,6 @@ public Action Command_EndMatch(int client, int args) { delete g_KnifeDecisionTimer; } - StopRecording(1.0); ServerCommand("mp_restartgame 1"); return Plugin_Handled; From 335918d3b58242887aa45147f360d005e94c0dd5 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 22 Aug 2022 00:59:21 +0200 Subject: [PATCH 063/104] Fix missing "powered by tag" if print update notice was disabled --- scripting/get5/goinglive.sp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index c16f5dea0..0d8176c23 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -49,17 +49,15 @@ public Action MatchLive(Handle timer) { AnnouncePhaseChange("%t", "MatchIsLiveInfoMessage"); - if (!g_PrintUpdateNoticeCvar.BoolValue) { - return Plugin_Handled; - } - - if (g_RunningPrereleaseVersion) { - char conVarName[64]; - g_PrintUpdateNoticeCvar.GetName(conVarName, sizeof(conVarName)); - FormatCvarName(conVarName, sizeof(conVarName), conVarName); - Get5_MessageToAll("%t", "PrereleaseVersionWarning", PLUGIN_VERSION, conVarName); - } else if (g_NewerVersionAvailable) { - Get5_MessageToAll("%t", "NewVersionAvailable", GET5_GITHUB_PAGE); + if (g_PrintUpdateNoticeCvar.BoolValue) { + if (g_RunningPrereleaseVersion) { + char conVarName[64]; + g_PrintUpdateNoticeCvar.GetName(conVarName, sizeof(conVarName)); + FormatCvarName(conVarName, sizeof(conVarName), conVarName); + Get5_MessageToAll("%t", "PrereleaseVersionWarning", PLUGIN_VERSION, conVarName); + } else if (g_NewerVersionAvailable) { + Get5_MessageToAll("%t", "NewVersionAvailable", GET5_GITHUB_PAGE); + } } /** From 640bbc7ba56b6098ad434ec7f046c83819593951 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 22 Aug 2022 04:44:25 +0200 Subject: [PATCH 064/104] Always call SetMatchTeamCvars and ExecuteMatchConfigCvars async when using ExecCfg Clean up warmup logic Make all restart game calls use RestartGame util Fix going-live problems causing duplicate backups and a round1 backup after knife Remove missing call to kill knife decision timer when selecting side --- scripting/get5.sp | 59 ++++++++++++++++++----------------- scripting/get5/backups.sp | 18 ++--------- scripting/get5/goinglive.sp | 52 ++++++++++++++---------------- scripting/get5/kniferounds.sp | 12 +++---- scripting/get5/matchconfig.sp | 20 +++++++++--- scripting/get5/util.sp | 39 ++++++++--------------- 6 files changed, 91 insertions(+), 109 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index ae300456a..0cae7fa88 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -718,7 +718,7 @@ public void OnClientPutInServer(int client) { if (g_GameState <= Get5State_Warmup && g_GameState != Get5State_None) { if (GetRealClientCount() <= 1) { ExecCfg(g_WarmupCfgCvar); - EnsureIndefiniteWarmup(); + StartWarmup(); } } @@ -832,9 +832,7 @@ public void OnMapStart() { if (g_WaitingForRoundBackup) { ChangeState(Get5State_Warmup); ExecCfg(g_LiveCfgCvar); - SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - EnsureIndefiniteWarmup(); + StartWarmup(); } } @@ -848,9 +846,7 @@ public void OnConfigsExecuted() { if (g_GameState == Get5State_Warmup || g_GameState == Get5State_Veto) { ExecCfg(g_WarmupCfgCvar); - SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - EnsureIndefiniteWarmup(); + StartWarmup(); } } @@ -874,7 +870,7 @@ public Action Timer_CheckReady(Handle timer) { // We don't wait for spectators when initiating veto LogDebug("Timer_CheckReady: starting veto"); ChangeState(Get5State_Veto); - ServerCommand("mp_restartgame 1"); + RestartGame(); CreateVeto(); } else { CheckReadyWaitingTimes(); @@ -1038,7 +1034,7 @@ public Action Command_EndMatch(int client, int args) { delete g_KnifeDecisionTimer; } - ServerCommand("mp_restartgame 1"); + RestartGame(); return Plugin_Handled; } @@ -1406,22 +1402,31 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad } if (g_PendingSideSwap) { - g_PendingSideSwap = false; SwapSides(); } + g_PendingSideSwap = false; - if (g_GameState == Get5State_GoingLive) { - ChangeState(Get5State_Live); - } - - if (g_GameState == Get5State_WaitingForKnifeRoundDecision && !InWarmup()) { - // Ensures that round end after knife sends players directly into warmup. - // This immediately triggers another Event_RoundPreStart, so we can return here and avoid - // writing backup twice. - LogDebug("Changed to warmup post knife."); - ExecCfg(g_WarmupCfgCvar); - EnsureIndefiniteWarmup(); - return Plugin_Continue; + if (!InWarmup()) { + if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { + // Ensures that round end after knife sends players directly into warmup. + // This immediately triggers another Event_RoundPreStart, so we can return here and avoid + // writing backup twice. + LogDebug("Changed to warmup post knife."); + ExecCfg(g_WarmupCfgCvar); + StartWarmup(); + return Plugin_Continue; + } + // We also cannot do this during warmup, as sending users into warmup post-knife triggers a round start event, + // hence why it's in here. We add an extra restart to clear lingering state from the knife round, such as the round + // indicator in the middle of the scoreboard not being reset. This also tightly couples the live-announcement to + // the actual live start. + if (g_GameState == Get5State_GoingLive) { + LogDebug("Changed to live."); + ChangeState(Get5State_Live); + RestartGame(); + CreateTimer(3.0, MatchLive, _, TIMER_FLAG_NO_MAPCHANGE); + return Plugin_Continue; // Next round start will take care of below, such as writing backup. + } } Stats_ResetRoundValues(); @@ -1749,8 +1754,6 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) public void SwapSides() { LogDebug("SwapSides"); - // EventLogger_SideSwap(g_TeamSide[Get5Team_1], g_TeamSide[Get5Team_2]); - int tmp = g_TeamSide[Get5Team_1]; g_TeamSide[Get5Team_1] = g_TeamSide[Get5Team_2]; g_TeamSide[Get5Team_2] = tmp; @@ -1780,11 +1783,10 @@ public Action Event_CvarChanged(Event event, const char[] name, bool dontBroadca public void StartGame(bool knifeRound) { LogDebug("StartGame"); - - ExecCfg(g_LiveCfgCvar); StartRecording(); if (knifeRound) { + ExecCfg(g_LiveCfgCvar); // live first, then apply and save knife cvars below LogDebug("StartGame: about to begin knife round"); ChangeState(Get5State_KnifeRound); if (g_KnifeChangedCvars != INVALID_HANDLE) { @@ -1795,9 +1797,8 @@ public void StartGame(bool knifeRound) { g_KnifeChangedCvars = ExecuteAndSaveCvars(knifeConfig); CreateTimer(1.0, StartKnifeRound); } else { - LogDebug("StartGame: about to go live"); - ChangeState(Get5State_GoingLive); - CreateTimer(3.0, StartGoingLive, _, TIMER_FLAG_NO_MAPCHANGE); + // If there is no knife round, we go directly to live, which loads the live config etc. on its own. + StartGoingLive(); } } diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 2b4abe4b3..ae08e10d3 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -359,8 +359,7 @@ public void RestoreGet5Backup() { // This variable is reset on a timer since the implementation of the // mp_backup_restore_load_file doesn't do everything in one frame. g_DoingBackupRestoreNow = true; - ExecCfg(g_LiveCfgCvar); // async - CreateTimer(0.5, Timer_ExecMatchConfig, _, TIMER_FLAG_NO_MAPCHANGE); + ExecCfg(g_LiveCfgCvar); if (g_SavedValveBackup) { ChangeState(Get5State_Live); @@ -371,26 +370,15 @@ public void RestoreGet5Backup() { SetStartingTeams(); if (g_GameState == Get5State_Live) { EndWarmup(); - EndWarmup(); - ServerCommand("mp_restartgame 5"); + RestartGame(5); PauseGame(Get5Team_None, Get5PauseType_Backup); } else { - EnsureIndefiniteWarmup(); + StartWarmup(); } - g_DoingBackupRestoreNow = false; } } -public Action Timer_ExecMatchConfig(Handle timer) { - // This needs to go on a callback because ServerCommand("exec") is async, so the config will load - // *after* the match cvars if we put them in RestoreGet5Backup, which we don't want, as that's not the order in which - // they were loaded when the match was initially set up. - SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - return Plugin_Handled; -} - public Action Time_StartRestore(Handle timer) { PauseGame(Get5Team_None, Get5PauseType_Backup); diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index 0d8176c23..28a015423 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -1,24 +1,6 @@ -public Action StartGoingLive(Handle timer) { +void StartGoingLive() { LogDebug("StartGoingLive"); ExecCfg(g_LiveCfgCvar); - SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - - // Force kill the warmup if we (still) need to. - Get5_MessageToAll("%t", "MatchBeginInSecondsInfoMessage", g_LiveCountdownTimeCvar.IntValue); - if (InWarmup()) { - EndWarmup(g_LiveCountdownTimeCvar.IntValue); - } else { - RestartGame(g_LiveCountdownTimeCvar.IntValue); - } - - // Always disable sv_cheats! - ServerCommand("sv_cheats 0"); - - // Delayed an extra 5 seconds for the final 3-second countdown - // the game uses after the origina countdown. - float delay = float(5 + g_LiveCountdownTimeCvar.IntValue); - CreateTimer(delay, MatchLive); Get5GoingLiveEvent liveEvent = new Get5GoingLiveEvent(g_MatchID, g_MapNumber); @@ -30,23 +12,35 @@ public Action StartGoingLive(Handle timer) { EventLogger_LogAndDeleteEvent(liveEvent); + ChangeState(Get5State_GoingLive); + + // This ensures that we can send send the game to warmup and count down *even if* someone had put "mp_warmup_end", or + // something else that would mess up warmup, in their live config, which they shouldn't. But we can't be sure. + CreateTimer(1.0, Timer_GoToLiveAfterWarmupCountdown, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action Timer_GoToLiveAfterWarmupCountdown(Handle timer) { + if (g_GameState != Get5State_GoingLive) { + return Plugin_Handled; // super defensive race-condition check. + } + // Always disable sv_cheats! + ServerCommand("sv_cheats 0"); + // Ensure we're in warmup and counting down to live. Round_PreStart handles the rest. + int countdown = g_LiveCountdownTimeCvar.IntValue; + if (countdown < 5) { + countdown = 5; // ensures that a cvar countdown value of 0 does not leave the game forever in warmup. + } + Get5_MessageToAll("%t", "MatchBeginInSecondsInfoMessage", countdown); + StartWarmup(countdown); + LogDebug("Started warmup countdown to live in %s seconds.", countdown); return Plugin_Handled; } public Action MatchLive(Handle timer) { - if (g_GameState == Get5State_None) { + if (g_GameState != Get5State_Live) { return Plugin_Handled; } - // Reset match config cvars. The problem is that when they are first - // set in StartGoingLive is that setting them right after executing the - // live config causes the live config values to get used for some reason - // (asynchronous command execution/cvar setting?), so they're set again - // to be sure. - SetMatchTeamCvars(); - ExecuteMatchConfigCvars(); - g_PendingSideSwap = false; - AnnouncePhaseChange("%t", "MatchIsLiveInfoMessage"); if (g_PrintUpdateNoticeCvar.BoolValue) { diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index 64174d14e..a9577f6f3 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -1,6 +1,5 @@ public Action StartKnifeRound(Handle timer) { g_HasKnifeRoundStarted = false; - g_PendingSideSwap = false; // Removes ready tags SetMatchTeamCvars(); @@ -81,13 +80,14 @@ public void EndKnifeRound(bool swap) { Call_PushCell(knifeEvent); Call_Finish(); - EventLogger_LogAndDeleteEvent(knifeEvent); - - ChangeState(Get5State_GoingLive); - CreateTimer(3.0, StartGoingLive, _, TIMER_FLAG_NO_MAPCHANGE); + if (g_KnifeDecisionTimer != INVALID_HANDLE) { + LogDebug("Stopped knife decision timer as a choice was made before it expired."); + delete g_KnifeDecisionTimer; + } + EventLogger_LogAndDeleteEvent(knifeEvent); g_KnifeWinnerTeam = Get5Team_None; - EnsureIndefiniteWarmup(); + StartGoingLive(); } static bool AwaitingKnifeDecision(int client) { diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index d9c013148..294aa6fdd 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -124,9 +124,7 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { if (!restoreBackup) { SetStartingTeams(); ExecCfg(g_WarmupCfgCvar); - ExecuteMatchConfigCvars(); - LoadPlayerNames(); - EnsureIndefiniteWarmup(); + StartWarmup(); if (IsPaused()) { LogDebug("Match was paused when loading match config. Unpausing."); UnpauseGame(Get5Team_None); @@ -155,7 +153,6 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } AddTeamLogosToDownloadTable(); - ExecuteMatchConfigCvars(); LoadPlayerNames(); strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); @@ -1303,3 +1300,18 @@ public void CheckTeamNameStatus(Get5Team team) { FormatTeamName(team); } } + +void ExecCfg(ConVar cvar) { + char cfg[PLATFORM_MAX_PATH]; + cvar.GetString(cfg, sizeof(cfg)); + ServerCommand("exec \"%s\"", cfg); + CreateTimer(0.1, Timer_ExecMatchConfig, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Action Timer_ExecMatchConfig(Handle timer) { + // When we load config files using ServerCommand("exec") above, which is async, we want match config cvars to always + // override. + SetMatchTeamCvars(); + ExecuteMatchConfigCvars(); + return Plugin_Handled; +} diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 5b3722ca2..ff2b948bd 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -174,27 +174,20 @@ stock bool InFreezeTime() { return GameRules_GetProp("m_bFreezePeriod") != 0; } -stock void EnsureIndefiniteWarmup() { +stock void StartWarmup(int warmupTime = 0) { + ServerCommand("mp_do_warmup_period 1"); if (!InWarmup()) { - LogDebug("EnsureIndefiniteWarmup: Not in warmup; calling StartWarmup()"); - StartWarmup(); - } else { - LogDebug("EnsureIndefiniteWarmup: Already in warmup; setting indefinite"); - ServerCommand("mp_warmup_pausetimer 1"); - ServerCommand("mp_do_warmup_period 1"); - ServerCommand("mp_warmup_pausetimer 1"); + ServerCommand("mp_warmup_start"); } -} - -stock void StartWarmup(bool indefiniteWarmup = true, int warmupTime = 60) { - ServerCommand("mp_do_warmup_period 1"); - ServerCommand("mp_warmuptime %d", warmupTime); - ServerCommand("mp_warmup_start"); - - // For some reason it needs to get sent twice. Ask Valve. - if (indefiniteWarmup) { - ServerCommand("mp_warmup_pausetimer 1"); + if (warmupTime < 1) { + LogDebug("Setting indefinite pause."); + // Setting mp_warmuptime to anything less than 7 triggers the countdown to restart regardless of + // mp_warmup_pausetimer 1, and this might be tick-related, so we set it to 10 just for good measure. + ServerCommand("mp_warmuptime 10"); ServerCommand("mp_warmup_pausetimer 1"); + } else { + ServerCommand("mp_warmuptime %d", warmupTime); + ServerCommand("mp_warmup_pausetimer 0"); } } @@ -202,8 +195,8 @@ stock void EndWarmup(int time = 0) { if (time == 0) { ServerCommand("mp_warmup_end"); } else { - ServerCommand("mp_warmup_pausetimer 0"); ServerCommand("mp_warmuptime %d", time); + ServerCommand("mp_warmup_pausetimer 0"); } } @@ -211,7 +204,7 @@ stock bool IsPaused() { return GameRules_GetProp("m_bMatchWaitingForResume") != 0; } -stock void RestartGame(int delay) { +stock void RestartGame(int delay = 1) { ServerCommand("mp_restartgame %d", delay); } @@ -552,12 +545,6 @@ public void MatchSideTypeToString(MatchSideType type, char[] str, int len) { } } -stock void ExecCfg(ConVar cvar) { - char cfg[PLATFORM_MAX_PATH]; - cvar.GetString(cfg, sizeof(cfg)); - ServerCommand("exec \"%s\"", cfg); -} - // Taken from Zephyrus (https://forums.alliedmods.net/showpost.php?p=2231850&postcount=2) stock bool ConvertSteam2ToSteam64(const char[] steam2Auth, char[] steam64Auth, int size) { if (strlen(steam2Auth) < 11 || steam2Auth[0] != 'S' || steam2Auth[6] == 'I') { From 3ddcf671cc44609acd00332c741a8939111f174e Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 22 Aug 2022 16:07:15 +0200 Subject: [PATCH 065/104] Adjust config templates and add discouraged commands section --- cfg/get5/warmup.cfg | 1 - documentation/docs/configuration.md | 21 +++++++++++++++++++++ scripting/get5/util.sp | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/cfg/get5/warmup.cfg b/cfg/get5/warmup.cfg index ac3275eab..ea518e163 100644 --- a/cfg/get5/warmup.cfg +++ b/cfg/get5/warmup.cfg @@ -15,7 +15,6 @@ mp_solid_teammates 0 mp_spectators_max 20 mp_startmoney 16000 mp_timelimit 0 -mp_warmuptime_all_players_connected 0 sv_alltalk 1 sv_auto_full_alltalk_during_warmup_half_end 1 sv_coaching_enabled 1 diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 1b41c8e7d..9be2371dd 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -32,6 +32,27 @@ cfg/get5/live.cfg # (3) 2. Executed when the knife-round starts. 3. Executed when the game goes live. +!!! danger "Prohibited options" + + You should avoid these commands in your live, knife and warmup configuration files, as all of these are handled by + Get5 automatically. Introducing restarts, warmup changes or [GOTV](gotv.md) delay modifications can cause problems. + If you want to set your `tv_delay`, do it in the `cvars` section of your [match configuration](match_schema.md). + + ``` + mp_do_warmup_period + mp_restartgame + mp_warmup_end + mp_warmup_pausetimer + mp_warmup_start + mp_warmuptime + mp_warmuptime_all_players_connected + tv_delay + tv_delaymapchange + tv_enable + tv_record + tv_stoprecord + ``` + ## Server Setup **These options will generally not be directly presented to clients.** diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index ff2b948bd..5405549ed 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -176,6 +176,7 @@ stock bool InFreezeTime() { stock void StartWarmup(int warmupTime = 0) { ServerCommand("mp_do_warmup_period 1"); + ServerCommand("mp_warmuptime_all_players_connected 0"); if (!InWarmup()) { ServerCommand("mp_warmup_start"); } From 21eba72911d9b28ab3e5f7b65d49a753f37474cf Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 22 Aug 2022 16:24:45 +0200 Subject: [PATCH 066/104] Refactor backup and match init --- documentation/docs/configuration.md | 3 +- scripting/get5.sp | 146 ++++++++++++++++------------ scripting/get5/backups.sp | 94 +++++++++--------- scripting/get5/debug.sp | 1 - scripting/get5/kniferounds.sp | 2 +- scripting/get5/matchconfig.sp | 22 ++--- scripting/get5/teamlogic.sp | 7 +- scripting/get5/util.sp | 2 +- 8 files changed, 148 insertions(+), 129 deletions(-) diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 9be2371dd..9cb6324bc 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -17,8 +17,7 @@ the explanation of the [match schema](../match_schema), that section will overri ### Phase Configuration Files You should also have three config files. These can be edited, but we recommend not -blindly pasting another config in (e.g. ESL, CEVO). Configs that execute warmup commands (`mp_warmup_end`, for -example) **will** cause problems. These must only include commands you would run in the console (such +blindly pasting another config in (e.g. ESL, CEVO). These must only include commands you would run in the console (such as `mp_friendly_fire 1`) and should determine the rules for those three stage of your match. You can also [point to other files](#config-files) by editing the main config file. diff --git a/scripting/get5.sp b/scripting/get5.sp index 0cae7fa88..1e361e0a7 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -142,7 +142,6 @@ int g_MinSpectatorsToReady = 0; float g_RoundStartedTime = 0.0; float g_BombPlantedTime = 0.0; Get5BombSite g_BombSiteLastPlanted = Get5BombSite_Unknown; -bool g_AssignTeamNonePlayersOnRoundStart = false; bool g_SkipVeto = false; float g_VetoMenuTime = 0.0; @@ -714,15 +713,24 @@ public void OnClientPutInServer(int client) { return; } - CheckAutoLoadConfig(); - if (g_GameState <= Get5State_Warmup && g_GameState != Get5State_None) { + // If a player joins during freezetime, ensure their round stats are 0, as there will be no round-start event to do it. + // Maybe this could just be freezetime end? + Stats_ResetClientRoundValues(client); + + // This checks for gamestate none on its own. + if (CheckAutoLoadConfig()) { + return; + } + + // Because OnConfigsExecuted may run before a client is on the server, we have to repeat the logic here when the + // first client connects. + if ((g_GameState <= Get5State_Warmup || g_WaitingForRoundBackup) && g_GameState != Get5State_None) { if (GetRealClientCount() <= 1) { + ChangeState(Get5State_Warmup); ExecCfg(g_WarmupCfgCvar); StartWarmup(); } } - - Stats_ResetClientRoundValues(client); } public void OnClientPostAdminCheck(int client) { @@ -809,7 +817,8 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr } } -public void OnMapStart() { +public void OnConfigsExecuted() { + LogDebug("OnConfigsExecuted"); g_MapChangePending = false; // Recording is always automatically stopped on map change, and // since there are no hooks to detect tv_stoprecord, we reset @@ -817,34 +826,33 @@ public void OnMapStart() { g_DemoFileName = ""; DeleteOldBackups(); + // Always reset ready status on map start ResetReadyStatus(); + + if (CheckAutoLoadConfig()) { + // If gamestate is none and a config was autoloaded, a match config will set all of the below state. + LogMessage("Match configuration was loaded via get5_autoload_config."); + return; + } + LOOP_TEAMS(team) { g_TeamGivenStopCommand[team] = false; g_TeamReadyForUnpause[team] = false; - g_TacticalPauseTimeUsed[team] = 0; - g_TacticalPausesUsed[team] = 0; - g_TechnicalPausesUsed[team] = 0; + if (!g_WaitingForRoundBackup) { + g_TacticalPauseTimeUsed[team] = 0; + g_TacticalPausesUsed[team] = 0; + g_TechnicalPausesUsed[team] = 0; + } } g_ReadyTimeWaitingUsed = 0; g_HasKnifeRoundStarted = false; - if (g_WaitingForRoundBackup) { - ChangeState(Get5State_Warmup); - ExecCfg(g_LiveCfgCvar); - StartWarmup(); - } -} - -public void OnConfigsExecuted() { - SetStartingTeams(); - CheckAutoLoadConfig(); - - if (g_GameState == Get5State_PostGame) { + // On map start, always put the game in warmup mode. + // When executing a backup load, the live config is loaded and warmup ends. + if (g_GameState != Get5State_None || g_WaitingForRoundBackup) { + LogDebug("Putting game into warmup in OnConfigsExecuted."); ChangeState(Get5State_Warmup); - } - - if (g_GameState == Get5State_Warmup || g_GameState == Get5State_Veto) { ExecCfg(g_WarmupCfgCvar); StartWarmup(); } @@ -952,14 +960,16 @@ static bool CheckReadyWaitingTime(Get5Team team) { return false; } -static void CheckAutoLoadConfig() { - if (g_GameState == Get5State_None) { +static bool CheckAutoLoadConfig() { + if (g_GameState == Get5State_None && !g_WaitingForRoundBackup) { char autoloadConfig[PLATFORM_MAX_PATH]; g_AutoLoadConfigCvar.GetString(autoloadConfig, sizeof(autoloadConfig)); if (!StrEqual(autoloadConfig, "")) { LoadMatchConfig(autoloadConfig); + return true; } } + return false; } /** @@ -1406,29 +1416,6 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad } g_PendingSideSwap = false; - if (!InWarmup()) { - if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { - // Ensures that round end after knife sends players directly into warmup. - // This immediately triggers another Event_RoundPreStart, so we can return here and avoid - // writing backup twice. - LogDebug("Changed to warmup post knife."); - ExecCfg(g_WarmupCfgCvar); - StartWarmup(); - return Plugin_Continue; - } - // We also cannot do this during warmup, as sending users into warmup post-knife triggers a round start event, - // hence why it's in here. We add an extra restart to clear lingering state from the knife round, such as the round - // indicator in the middle of the scoreboard not being reset. This also tightly couples the live-announcement to - // the actual live start. - if (g_GameState == Get5State_GoingLive) { - LogDebug("Changed to live."); - ChangeState(Get5State_Live); - RestartGame(); - CreateTimer(3.0, MatchLive, _, TIMER_FLAG_NO_MAPCHANGE); - return Plugin_Continue; // Next round start will take care of below, such as writing backup. - } - } - Stats_ResetRoundValues(); // We need this for events that fire after the map ends, such as grenades detonating (or someone @@ -1440,9 +1427,6 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad // Round number always -1 if not live. g_RoundNumber = g_GameState != Get5State_Live ? -1 : GetRoundsPlayed(); - if (g_GameState >= Get5State_Warmup) { - WriteBackup(); - } return Plugin_Continue; } @@ -1504,6 +1488,14 @@ public void WriteBackup() { return; } + if (g_PauseType == Get5PauseType_Backup) { + // If there was no valve backup found when restoring the game, the above checks will not trigger a return, + // so we do this to prevent overwriting an already-written backup in the event the game starts in the same round + // it was *just* restored to and this function runs. + LogDebug("Skipping backup round write as the round started paused for backup."); + return; + } + char folder[PLATFORM_MAX_PATH]; g_RoundBackupPathCvar.GetString(folder, sizeof(folder)); ReplaceString(folder, sizeof(folder), "{MATCHID}", g_MatchID); @@ -1530,7 +1522,7 @@ public void WriteBackup() { g_MapNumber); } LogDebug("Writing backup to %s", path); - WriteBackStructure(path); + WriteBackupStructure(path); g_LastGet5BackupCvar.SetString(path); // Reset this when writing a new backup, as voting has no reference to which round the teams wanted to restore to, so @@ -1553,24 +1545,47 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas g_BombPlantedTime = 0.0; g_BombSiteLastPlanted = Get5BombSite_Unknown; - if (g_GameState != Get5State_Live) { - return; + // We cannot do this during warmup, as sending users into warmup post-knife triggers a round start event. + // We add an extra restart to clear lingering state from the knife round, such as the round + // indicator in the middle of the scoreboard not being reset. This also tightly couples the live-announcement to + // the actual live start. + if (!InWarmup()) { + if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { + // Ensures that round end after knife sends players directly into warmup. + // This immediately triggers another Event_RoundStart, so we can return here and avoid + // writing backup twice. + LogDebug("Changed to warmup post knife."); + ExecCfg(g_WarmupCfgCvar); + StartWarmup(); + return; + } + if (g_GameState == Get5State_GoingLive) { + LogDebug("Changed to live."); + ChangeState(Get5State_Live); + RestartGame(); + CreateTimer(3.0, MatchLive, _, TIMER_FLAG_NO_MAPCHANGE); + return; // Next round start will take care of below, such as writing backup. + } } // Ensures that players who connect during halftime/team swap are placed in their correct slots as soon as the // following round starts. Otherwise they could be left on the "no team" screen and potentially // ghost, depending on where the camera drops them. Especially important for coaches. - if (g_AssignTeamNonePlayersOnRoundStart) { - g_AssignTeamNonePlayersOnRoundStart = false; - LOOP_CLIENTS(i) { - // We check only for connection here, as that's required to put them in the game, as they may - // not actually be considered "in the game" yet, so IsValidClient() might not work. - if (IsClientConnected(i)) { - CheckClientTeam(i); - } + // We do this step *before* we write the backup, so we don't have any lingering players in case of a restore. + LOOP_CLIENTS(i) { + if (IsPlayer(i) && !IsClientSourceTV(i) && GetClientTeam(i) == CS_TEAM_NONE) { + CheckClientTeam(i); } } + if (g_GameState >= Get5State_Warmup) { + WriteBackup(); + } + + if (g_GameState != Get5State_Live) { + return; + } + Get5RoundStartedEvent startEvent = new Get5RoundStartedEvent(g_MatchID, g_MapNumber, g_RoundNumber); @@ -1803,6 +1818,11 @@ public void StartGame(bool knifeRound) { } public void ChangeState(Get5State state) { + if (g_GameState == state) { + LogDebug("Ignoring request to change game state. Already in state %d.", state); + return; + } + g_GameStateCvar.IntValue = view_as(state); Get5GameStateChangedEvent event = new Get5GameStateChangedEvent(state, g_GameState); diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index ae08e10d3..50c2425f5 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -66,7 +66,7 @@ public Action Command_ListBackups(int client, int args) { return Plugin_Handled; } -public bool GetBackupInfo(const char[] path, char[] info, int maxlength) { +bool GetBackupInfo(const char[] path, char[] info, int maxlength) { KeyValues kv = new KeyValues("Backup"); if (!kv.ImportFromFile(path)) { LogError("Failed to find or read backup file \"%s\"", path); @@ -143,7 +143,7 @@ public bool GetBackupInfo(const char[] path, char[] info, int maxlength) { return true; } -public void WriteBackStructure(const char[] path) { +void WriteBackupStructure(const char[] path) { KeyValues kv = new KeyValues("Backup"); char timeString[PLATFORM_MAX_PATH]; FormatTime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", GetTime()); @@ -175,6 +175,11 @@ public void WriteBackStructure(const char[] path) { kv.SetNum("series_draw", g_TeamSeriesScores[Get5Team_None]); + kv.SetNum("team1_pauses_used", g_TacticalPausesUsed[Get5Team_1]); + kv.SetNum("team2_pauses_used", g_TacticalPausesUsed[Get5Team_2]); + kv.SetNum("team1_pause_time_used", g_TacticalPauseTimeUsed[Get5Team_1]); + kv.SetNum("team2_pause_time_used", g_TacticalPauseTimeUsed[Get5Team_2]); + // Write original maplist. kv.JumpToKey("maps", true); for (int i = 0; i < g_MapsToPlay.Length; i++) { @@ -225,7 +230,7 @@ public void WriteBackStructure(const char[] path) { delete kv; } -public bool RestoreFromBackup(const char[] path) { +bool RestoreFromBackup(const char[] path) { KeyValues kv = new KeyValues("Backup"); if (!kv.ImportFromFile(path)) { LogError("Failed to read backup file \"%s\"", path); @@ -240,12 +245,10 @@ public bool RestoreFromBackup(const char[] path) { if (!LoadMatchConfig(tempBackupFile, true)) { delete kv; LogError("Could not restore from match config \"%s\"", tempBackupFile); - if (g_GameState != Get5State_None) { - // If the backup load fails, all the game configs will have been reset by LoadMatchConfig, - // but the game state won't. This ensure we don't end up a in a "live" state with no get5 - // variables set, which would prevent a call to load a new match. - ChangeState(Get5State_None); - } + // If the backup load fails, all the game configs will have been reset by LoadMatchConfig, + // but the game state won't. This ensures we don't end up a in a "live" state with no get5 + // variables set, which would prevent a call to load a new match. + ChangeState(Get5State_None); return false; } kv.GoBack(); @@ -267,6 +270,12 @@ public bool RestoreFromBackup(const char[] path) { // draws. g_TeamSeriesScores[Get5Team_None] = kv.GetNum("series_draw", 0); + // This isn't perfect, but it's better than resetting all pauses used to zero in cases of restore on a new server. + g_TacticalPausesUsed[Get5Team_1] = kv.GetNum("team1_pauses_used", 0); + g_TacticalPausesUsed[Get5Team_2] = kv.GetNum("team2_pauses_used", 0); + g_TacticalPauseTimeUsed[Get5Team_1] = kv.GetNum("team1_pause_time_used", 0); + g_TacticalPauseTimeUsed[Get5Team_2] = kv.GetNum("team2_pause_time_used", 0); + // Immediately set map number global var to ensure anything below doesn't break. g_MapNumber = Get5_GetMapNumber(); @@ -332,9 +341,8 @@ public bool RestoreFromBackup(const char[] path) { g_MapsToPlay.GetString(g_MapNumber, currentSeriesMap, sizeof(currentSeriesMap)); if (!StrEqual(currentMap, currentSeriesMap)) { - ChangeMap(currentSeriesMap, 1.0); - g_WaitingForRoundBackup = (g_GameState >= Get5State_Live); - + ChangeMap(currentSeriesMap, 3.0); + g_WaitingForRoundBackup = true; } else { RestoreGet5Backup(); } @@ -355,33 +363,29 @@ public bool RestoreFromBackup(const char[] path) { return true; } -public void RestoreGet5Backup() { - // This variable is reset on a timer since the implementation of the - // mp_backup_restore_load_file doesn't do everything in one frame. - g_DoingBackupRestoreNow = true; - ExecCfg(g_LiveCfgCvar); - +void RestoreGet5Backup() { if (g_SavedValveBackup) { - ChangeState(Get5State_Live); - // There are some timing issues leading to incorrect score when restoring matches in second - // half. Doing the restore on a timer - CreateTimer(1.0, Time_StartRestore); - } else { - SetStartingTeams(); - if (g_GameState == Get5State_Live) { - EndWarmup(); - RestartGame(5); - PauseGame(Get5Team_None, Get5PauseType_Backup); - } else { - StartWarmup(); + // If you load a backup during a live round, the game might get stuck if there are only bots remaining and no + // players are alive. Other stuff will probably also go wrong, so we just reset the game before loading the + // backup to avoid any weird edge-cases. + if (!InWarmup()) { + RestartGame(); } - g_DoingBackupRestoreNow = false; + ExecCfg(g_LiveCfgCvar); + PauseGame(Get5Team_None, Get5PauseType_Backup); + g_DoingBackupRestoreNow = true; // reset after the backup has completed, suppresses various events and hooks until then. + CreateTimer(1.5, Time_StartRestore); + } else { + // Backup was for veto or warmup; go to warmup. + UnpauseGame(Get5Team_None); + ExecCfg(g_WarmupCfgCvar); + RestartGame(); + StartWarmup(); } } public Action Time_StartRestore(Handle timer) { - PauseGame(Get5Team_None, Get5PauseType_Backup); - + ChangeState(Get5State_Live); char tempValveBackup[PLATFORM_MAX_PATH]; GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); ServerCommand("mp_backup_restore_load_file \"%s\"", tempValveBackup); @@ -389,22 +393,24 @@ public Action Time_StartRestore(Handle timer) { } public Action Timer_FinishBackup(Handle timer) { - if (g_CoachingEnabledCvar.BoolValue) { - // If we are coaching we want to ensure our - // coaches get moved back onto the team. - // We cannot trust Valve's system as a disconnected - // player will count as a "player" and not be placed - // in the coach slot. - LOOP_CLIENTS(i) { - if (IsValidClient(i)) { - CheckClientTeam(i); - } + // This ensures that coaches are moved to their slots. + LOOP_CLIENTS(i) { + if (IsPlayer(i) && !IsClientSourceTV(i)) { + CheckClientTeam(i); } } g_DoingBackupRestoreNow = false; + // Delete the temporary backup file we just wrote and restored from. + char tempValveBackup[PLATFORM_MAX_PATH]; + GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); + if (DeleteFile(tempValveBackup)) { + LogDebug("Deleted temp valve backup file: %s", tempValveBackup); + } else { + LogDebug("Failed to delete temp valve backup file: %s", tempValveBackup); + } } -public void DeleteOldBackups() { +void DeleteOldBackups() { int maxTimeDifference = g_MaxBackupAgeCvar.IntValue; if (maxTimeDifference <= 0) { LogDebug("Backups are not being deleted as get5_max_backup_age is 0."); diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index f34f6c5bc..81e86dc90 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -111,7 +111,6 @@ static void AddGlobalStateInfo(File f) { f.WriteLine("g_InScrimMode = %d", g_InScrimMode); f.WriteLine("g_SeriesCanClinch = %d", g_SeriesCanClinch); f.WriteLine("g_HasKnifeRoundStarted = %d", g_HasKnifeRoundStarted); - f.WriteLine("g_AssignTeamNonePlayersOnRoundStart = %d", g_AssignTeamNonePlayersOnRoundStart); f.WriteLine("g_MapChangePending = %d", g_MapChangePending); f.WriteLine("g_PendingSideSwap = %d", g_PendingSideSwap); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index a9577f6f3..ddf982c91 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -45,7 +45,7 @@ static void PerformSideSwap(bool swap) { // Because bots never have an assigned team, they won't be moved around by CheckClientTeam. We kick them to // prevent one team from having too many players. They will rejoin if defined in the live config. KickClient(i); - } else { + } else if (!IsClientSourceTV(i)) { CheckClientTeam(i, false); } } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 294aa6fdd..776fbe0dd 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -14,7 +14,7 @@ #define CONFIG_VETOFIRST_DEFAULT "team1" #define CONFIG_SIDETYPE_DEFAULT "standard" -stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { +bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { if (g_GameState != Get5State_None && !restoreBackup) { return false; } @@ -37,7 +37,6 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } g_MatchID = ""; - g_AssignTeamNonePlayersOnRoundStart = false; g_ReadyTimeWaitingUsed = 0; g_HasKnifeRoundStarted = false; g_MapChangePending = false; @@ -101,7 +100,6 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } } - ChangeState(Get5State_Warmup); if (!restoreBackup) { // When restoring from backup, changelevel is called after loading the match config. g_MapPoolList.GetString(Get5_GetMapNumber(), mapName, sizeof(mapName)); @@ -111,18 +109,22 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { ChangeMap(mapName); } } - } else { + } else if (!restoreBackup) { ChangeState(Get5State_PreVeto); } - // We need to ensure our match team CVARs are set - // before calling the event so we can grab values - // that are set in the OnSeriesInit event. - // Add to download table after setting. + // Before we run the Get5_OnSeriesInit forward, we want to ensure that as much game state is set as possible, + // so that any implementation reacting to that event/forward will have all the natives return proper data. SetMatchTeamCvars(); + LoadPlayerNames(); + AddTeamLogosToDownloadTable(); + // This gets called twice because ExecCfg(g_WarmupCfgCvar) also does it async, but we need it here. + ExecuteMatchConfigCvars(); if (!restoreBackup) { + // SetStartingTeams must *not* be called when loading a backup, as it will override the sides saved in the backup! SetStartingTeams(); + ChangeState(Get5State_Warmup); ExecCfg(g_WarmupCfgCvar); StartWarmup(); if (IsPaused()) { @@ -152,13 +154,11 @@ stock bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } } - AddTeamLogosToDownloadTable(); - LoadPlayerNames(); strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); // ExecuteMatchConfigCvars must be executed before we place players, as it might have get5_check_auths 1. LOOP_CLIENTS(i) { - if (IsAuthedPlayer(i)) { + if (IsAuthedPlayer(i) && !IsClientSourceTV(i)) { CheckClientTeam(i); } } diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index bffa11c55..4ccf266c2 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -13,7 +13,7 @@ public Action Timer_PlacePlayerOnJoin(Handle timer, int userId) { } void CheckClientTeam(int client, bool useDefaultTeamSelection = true) { - if (!g_CheckAuthsCvar.BoolValue) { + if (!g_CheckAuthsCvar.BoolValue || IsFakeClient(client) || IsClientSourceTV(client)) { // Teams are not enforced; do nothing. return; } @@ -69,7 +69,6 @@ void CheckClientTeam(int client, bool useDefaultTeamSelection = true) { static void PlacePlayerOnTeam(int client) { if (g_PendingSideSwap || InHalftimePhase()) { LogDebug("Blocking attempt to join a team due to halftime or pending team swap."); - g_AssignTeamNonePlayersOnRoundStart = true; return; } CheckClientTeam(client); @@ -266,10 +265,6 @@ Get5Team CSTeamToGet5Team(int csTeam) { } Get5Team GetAuthMatchTeam(const char[] steam64) { - if (g_GameState == Get5State_None) { - return Get5Team_None; - } - if (g_InScrimMode) { return IsAuthOnTeam(steam64, Get5Team_1) ? Get5Team_1 : Get5Team_2; } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 5405549ed..ef23a0b69 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -181,7 +181,7 @@ stock void StartWarmup(int warmupTime = 0) { ServerCommand("mp_warmup_start"); } if (warmupTime < 1) { - LogDebug("Setting indefinite pause."); + LogDebug("Setting indefinite warmup."); // Setting mp_warmuptime to anything less than 7 triggers the countdown to restart regardless of // mp_warmup_pausetimer 1, and this might be tick-related, so we set it to 10 just for good measure. ServerCommand("mp_warmuptime 10"); From 38694ba57115b67a2542a769d03034b3ad3c3b46 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 23 Aug 2022 18:54:02 +0200 Subject: [PATCH 067/104] Update docs Only restore pauses if not live Return LoadMatchConfig result in CheckAutoLoadConfig --- documentation/docs/backup.md | 19 ++++++++++++++----- scripting/get5.sp | 24 ++++++++++++++---------- scripting/get5/backups.sp | 23 +++++++++++++++-------- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/documentation/docs/backup.md b/documentation/docs/backup.md index fa7e6cd2e..5aa096849 100644 --- a/documentation/docs/backup.md +++ b/documentation/docs/backup.md @@ -10,14 +10,15 @@ the entire [match configuration](../match_schema) and the match series score for The backup system must be [enabled](../configuration/#get5_backup_system_enabled) for this to work. -## How does it work? +### How does it work? Every time a round starts, CS:GO automatically writes a round backup file into the root of the `csgo` directory based on the value of `mp_backup_round_file`. The default value for this is `backup`. Get5 reads this file and copies it into its -own file called `get5_backup_match%s_map%d_round%d.cfg`, where the arguments are `matchid`, `mapnumber` and `roundnumber`, -respectively. A special backup called `get5_backup_match%s_map%d_prelive.cfg` is created for the knife round. +own file called `get5_backup_match%s_map%d_round%d.cfg`, where the arguments are `matchid`, `mapnumber` +and `roundnumber`, respectively. A special backup called `get5_backup_match%s_map%d_prelive.cfg` is created and should +be used if you want to restore to the beginning of the map, before the knife round. -## Example +### Example When in a match, you can call [`get5_listbackups`](../commands/#get5_listbackups) to view all backups for the current match. Note that all rounds and map numbers start at 0. @@ -39,6 +40,14 @@ get5_backup_match1844_map0_round17.cfg 2022-07-26 19:03:39 "Team A" "Team B" de_ To load at the beginning of round 13 of the first map of match ID 1844, all players should be connected to the server, and you use the [`get5_loadbackup`](../commands/#get5_loadbackup) command: -`get5_loadbackup get5_backup_match1844_map0_round12.cfg`. +`get5_loadbackup get5_backup_match1844_map0_round12.cfg`. The game should restore in a paused state and both teams must [`!unpause`](../commands/#unpause) to continue. + +### Pauses in backups + +When restoring from a backup, the [consumed pauses](pausing.md) are reset to the state they were in at the beginning +of the round you restore to, but only if the game state is not currently live. This means that using +the [`!stop`](../commands/#stop) command or the [`get5_loadbackup`](../commands/#get5_loadbackup) command **for the same +match and map** would retain the currently used pauses. If restarting the server or loading the backup from scratch, the +pauses from the backup file will be used. diff --git a/scripting/get5.sp b/scripting/get5.sp index 1e361e0a7..2e216e13d 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -708,7 +708,7 @@ public void RememberAndKickClient(int client, const char[] format, const char[] } public void OnClientPutInServer(int client) { - Stats_HookDamageForClient(client); + Stats_HookDamageForClient(client); // Also needed for bots! if (IsFakeClient(client)) { return; } @@ -717,7 +717,7 @@ public void OnClientPutInServer(int client) { // Maybe this could just be freezetime end? Stats_ResetClientRoundValues(client); - // This checks for gamestate none on its own. + // This checks for gamestate none and pending backup on its own. if (CheckAutoLoadConfig()) { return; } @@ -817,6 +817,7 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr } } +// This runs every time a map starts *or* when the plugin is reloaded. public void OnConfigsExecuted() { LogDebug("OnConfigsExecuted"); g_MapChangePending = false; @@ -831,18 +832,18 @@ public void OnConfigsExecuted() { if (CheckAutoLoadConfig()) { // If gamestate is none and a config was autoloaded, a match config will set all of the below state. - LogMessage("Match configuration was loaded via get5_autoload_config."); return; } LOOP_TEAMS(team) { g_TeamGivenStopCommand[team] = false; g_TeamReadyForUnpause[team] = false; - if (!g_WaitingForRoundBackup) { - g_TacticalPauseTimeUsed[team] = 0; - g_TacticalPausesUsed[team] = 0; - g_TechnicalPausesUsed[team] = 0; - } + + // We don't need to check for g_WaitingForRoundBackup here, as a backup will override the pauses consumed anyway; if + // the map is changed, we always load the backup pauses. + g_TacticalPauseTimeUsed[team] = 0; + g_TacticalPausesUsed[team] = 0; + g_TechnicalPausesUsed[team] = 0; } g_ReadyTimeWaitingUsed = 0; @@ -965,8 +966,11 @@ static bool CheckAutoLoadConfig() { char autoloadConfig[PLATFORM_MAX_PATH]; g_AutoLoadConfigCvar.GetString(autoloadConfig, sizeof(autoloadConfig)); if (!StrEqual(autoloadConfig, "")) { - LoadMatchConfig(autoloadConfig); - return true; + bool loaded = LoadMatchConfig(autoloadConfig); // return false if match config load fails! + if (loaded) { + LogMessage("Match configuration was loaded via get5_autoload_config."); + } + return loaded; } } return false; diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 50c2425f5..c756a871f 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -175,8 +175,10 @@ void WriteBackupStructure(const char[] path) { kv.SetNum("series_draw", g_TeamSeriesScores[Get5Team_None]); - kv.SetNum("team1_pauses_used", g_TacticalPausesUsed[Get5Team_1]); - kv.SetNum("team2_pauses_used", g_TacticalPausesUsed[Get5Team_2]); + kv.SetNum("team1_tac_pauses_used", g_TacticalPausesUsed[Get5Team_1]); + kv.SetNum("team2_tac_pauses_used", g_TacticalPausesUsed[Get5Team_2]); + kv.SetNum("team1_tech_pauses_used", g_TechnicalPausesUsed[Get5Team_1]); + kv.SetNum("team2_tech_pauses_used", g_TechnicalPausesUsed[Get5Team_2]); kv.SetNum("team1_pause_time_used", g_TacticalPauseTimeUsed[Get5Team_1]); kv.SetNum("team2_pause_time_used", g_TacticalPauseTimeUsed[Get5Team_2]); @@ -254,6 +256,17 @@ bool RestoreFromBackup(const char[] path) { kv.GoBack(); } + if (g_GameState != Get5State_Live) { + // This isn't perfect, but it's better than resetting all pauses used to zero in cases of restore on a new server. + // If restoring while live, we just retain the current pauses used, as they should be the "most correct". + g_TacticalPausesUsed[Get5Team_1] = kv.GetNum("team1_tac_pauses_used", 0); + g_TacticalPausesUsed[Get5Team_2] = kv.GetNum("team2_tac_pauses_used", 0); + g_TechnicalPausesUsed[Get5Team_1] = kv.GetNum("team1_tech_pauses_used", 0); + g_TechnicalPausesUsed[Get5Team_2] = kv.GetNum("team2_tech_pauses_used", 0); + g_TacticalPauseTimeUsed[Get5Team_1] = kv.GetNum("team1_pause_time_used", 0); + g_TacticalPauseTimeUsed[Get5Team_2] = kv.GetNum("team2_pause_time_used", 0); + } + kv.GetString("matchid", g_MatchID, sizeof(g_MatchID)); g_GameState = view_as(kv.GetNum("gamestate")); @@ -270,12 +283,6 @@ bool RestoreFromBackup(const char[] path) { // draws. g_TeamSeriesScores[Get5Team_None] = kv.GetNum("series_draw", 0); - // This isn't perfect, but it's better than resetting all pauses used to zero in cases of restore on a new server. - g_TacticalPausesUsed[Get5Team_1] = kv.GetNum("team1_pauses_used", 0); - g_TacticalPausesUsed[Get5Team_2] = kv.GetNum("team2_pauses_used", 0); - g_TacticalPauseTimeUsed[Get5Team_1] = kv.GetNum("team1_pause_time_used", 0); - g_TacticalPauseTimeUsed[Get5Team_2] = kv.GetNum("team2_pause_time_used", 0); - // Immediately set map number global var to ensure anything below doesn't break. g_MapNumber = Get5_GetMapNumber(); From 37ccb331a6c220957735ecb71ff9d2c52d0b8c39 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Tue, 23 Aug 2022 20:54:23 +0200 Subject: [PATCH 068/104] Add collision protection to names file --- scripting/get5/backups.sp | 1 + scripting/get5/teamlogic.sp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index c756a871f..1e79f8653 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -1,5 +1,6 @@ #define TEMP_MATCHCONFIG_BACKUP_PATTERN "get5_match_config_backup%d.txt" #define TEMP_VALVE_BACKUP_PATTERN "get5_temp_backup%d.txt" +#define TEMP_VALVE_NAMES_FILE_PATTERN "get5_names%d.txt" public Action Command_LoadBackup(int client, int args) { if (!g_BackupSystemEnabledCvar.BoolValue) { diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index 4ccf266c2..bd12f5149 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -492,7 +492,8 @@ void LoadPlayerNames() { } if (numNames > 0) { - char nameFile[] = "get5_names.txt"; + char nameFile[PLATFORM_MAX_PATH]; + GetTempFilePath(nameFile, sizeof(nameFile), TEMP_VALVE_NAMES_FILE_PATTERN); DeleteFile(nameFile); if (namesKv.ExportToFile(nameFile)) { ServerCommand("sv_load_forced_client_names_file %s", nameFile); From 181b8dbd954a8bad871f0ad8125a1808ff96136e Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 24 Aug 2022 02:33:42 +0200 Subject: [PATCH 069/104] Add g_MapSides arraylist to AddGlobalStateInfo --- scripting/get5/debug.sp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index 81e86dc90..4779f9b51 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -99,8 +99,17 @@ static void AddGlobalStateInfo(File f) { WriteArrayList(f, "g_MapPoolList", g_MapPoolList); WriteArrayList(f, "g_MapsToPlay", g_MapsToPlay); WriteArrayList(f, "g_MapsLeftInVetoPool", g_MapsLeftInVetoPool); - // TODO: write g_MapSides (it's not a string so WriteArrayList doesn't work). - + f.WriteLine("Defined map sides:"); + for (int i = 0; i < g_MapSides.Length; i++) { + SideChoice c = g_MapSides.Get(i); + if (c == SideChoice_Team1CT) { + f.WriteLine("g_MapSides(%d) = team1_ct", i); + } else if (c == SideChoice_Team1T) { + f.WriteLine("g_MapSides(%d) = team1_t", i); + } else { + f.WriteLine("g_MapSides(%d) = knife", i); + } + } f.WriteLine("g_MatchTitle = %s", g_MatchTitle); f.WriteLine("g_PlayersPerTeam = %d", g_PlayersPerTeam); f.WriteLine("g_CoachesPerTeam = %d", g_CoachesPerTeam); From 79ada11e3bd4516b6d81e1ced94b3658f1e6fbd7 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 24 Aug 2022 03:40:10 +0200 Subject: [PATCH 070/104] Get rid of odd MaxMapsToPlay and g_BO2Match; use g_NumberOfMapsInSeries instead --- scripting/get5.sp | 6 ++-- scripting/get5/debug.sp | 1 - scripting/get5/mapveto.sp | 6 ++-- scripting/get5/matchconfig.sp | 53 ++++++++++------------------------- scripting/get5/stats.sp | 2 +- scripting/get5/tests.sp | 15 ++++++---- scripting/get5/util.sp | 11 ++++---- 7 files changed, 36 insertions(+), 58 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 2e216e13d..c78db828f 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -114,7 +114,6 @@ ConVar g_CoachingEnabledCvar; /** Series config game-state **/ int g_MapsToWin = 1; // Maps needed to win the series. -bool g_BO2Match = false; bool g_SeriesCanClinch = true; int g_RoundNumber = -1; // The round number, 0-indexed. -1 if the match is not live. // The active map number, used by stats. Required as the calculated round number changes immediately @@ -1908,11 +1907,10 @@ public bool FormatCvarString(ConVar cvar, char[] buffer, int len) { strcopy(team2Str, sizeof(team2Str), g_TeamNames[Get5Team_2]); ReplaceString(team2Str, sizeof(team2Str), " ", "_"); - int mapNumber = g_TeamSeriesScores[Get5Team_1] + g_TeamSeriesScores[Get5Team_2] + 1; // MATCHTITLE must go first as it can contain other placeholders ReplaceString(buffer, len, "{MATCHTITLE}", g_MatchTitle, false); - ReplaceStringWithInt(buffer, len, "{MAPNUMBER}", mapNumber, false); - ReplaceStringWithInt(buffer, len, "{MAXMAPS}", MaxMapsToPlay(g_MapsToWin)); + ReplaceStringWithInt(buffer, len, "{MAPNUMBER}", Get5_GetMapNumber() + 1, false); + ReplaceStringWithInt(buffer, len, "{MAXMAPS}", g_NumberOfMapsInSeries, false); ReplaceString(buffer, len, "{MATCHID}", g_MatchID, false); ReplaceString(buffer, len, "{MAPNAME}", mapName, false); ReplaceStringWithInt(buffer, len, "{SERVERID}", g_ServerIdCvar.IntValue, false); diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index 4779f9b51..60da3fb5b 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -94,7 +94,6 @@ static void AddGlobalStateInfo(File f) { f.WriteLine("g_MatchID = %s", g_MatchID); f.WriteLine("g_RoundNumber = %d", g_RoundNumber); f.WriteLine("g_MapsToWin = %d", g_MapsToWin); - f.WriteLine("g_BO2Match = %d", g_BO2Match); f.WriteLine("g_LastVetoTeam = %d", g_LastVetoTeam); WriteArrayList(f, "g_MapPoolList", g_MapPoolList); WriteArrayList(f, "g_MapsToPlay", g_MapsToPlay); diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 1109c983a..27bc12f73 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -82,7 +82,7 @@ public void VetoController(int client) { } int mapsLeft = g_MapsLeftInVetoPool.Length; - int maxMaps = MaxMapsToPlay(g_MapsToWin); + int maxMaps = g_NumberOfMapsInSeries; int mapsPicked = g_MapsToPlay.Length; int sidesSet = g_MapSides.Length; @@ -110,7 +110,7 @@ public void VetoController(int client) { // The purpose is to force the veto process to take a // ban/ban/ban/ban/pick/pick/last map unused process for BO2's. bool bo2_hack = false; - if (g_BO2Match && (mapsLeft == 3 || mapsLeft == 2)) { + if (g_NumberOfMapsInSeries == 2 && (mapsLeft == 3 || mapsLeft == 2)) { bo2_hack = true; } @@ -157,7 +157,7 @@ public void VetoController(int client) { VetoFinished(); } else if (mapsLeft == 1) { - if (g_BO2Match) { + if (g_NumberOfMapsInSeries == 2) { // Terminate the veto since we've had ban-ban-ban-ban-pick-pick VetoFinished(); return; diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 776fbe0dd..dadb632a0 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -72,29 +72,22 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { g_TeamScoresPerMap.Set(g_TeamScoresPerMap.Length - 1, 0, 1); } - if (g_BO2Match) { - g_MapsToWin = 2; - } - - if (MaxMapsToPlay(g_MapsToWin) > g_MapPoolList.Length) { - MatchConfigFail("Cannot play a series of %d maps with a maplist of %d maps", - MaxMapsToPlay(g_MapsToWin), g_MapPoolList.Length); + if (g_NumberOfMapsInSeries > g_MapPoolList.Length) { + MatchConfigFail("Cannot play a series of %d maps with a maplist of %d maps", g_NumberOfMapsInSeries, g_MapPoolList.Length); return false; } if (g_SkipVeto) { // Copy the first k maps from the maplist to the final match maps. - for (int i = 0; i < MaxMapsToPlay(g_MapsToWin); i++) { + for (int i = 0; i < g_NumberOfMapsInSeries; i++) { g_MapPoolList.GetString(i, mapName, sizeof(mapName)); g_MapsToPlay.PushString(mapName); // Push a map side if one hasn't been set yet. if (g_MapSides.Length < g_MapsToPlay.Length) { - if (g_MatchSideType == MatchSideType_Standard) { - g_MapSides.Push(SideChoice_KnifeRound); - } else if (g_MatchSideType == MatchSideType_AlwaysKnife) { + if (g_MatchSideType == MatchSideType_Standard || g_MatchSideType == MatchSideType_AlwaysKnife) { g_MapSides.Push(SideChoice_KnifeRound); - } else if (g_MatchSideType == MatchSideType_NeverKnife) { + } else { g_MapSides.Push(SideChoice_Team1CT); } } @@ -387,17 +380,10 @@ static bool LoadMatchFromKv(KeyValues kv) { g_SkipVeto = kv.GetNum("skip_veto", CONFIG_SKIPVETO_DEFAULT) != 0; g_NumberOfMapsInSeries = kv.GetNum("num_maps", CONFIG_NUM_MAPSDEFAULT); - if (g_NumberOfMapsInSeries == 2) { - g_BO2Match = true; - g_MapsToWin = 2; - } else { - g_BO2Match = false; - // Normal path. No even numbers allowed since we already handled bo2. - if (g_NumberOfMapsInSeries % 2 == 0) { - MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); - return false; - } - g_MapsToWin = (g_NumberOfMapsInSeries + 1) / 2; + g_MapsToWin = MapsToWin(g_NumberOfMapsInSeries); + if (g_NumberOfMapsInSeries != 2 && g_NumberOfMapsInSeries % 2 == 0) { + MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); + return false; } char vetoFirstBuffer[64]; @@ -492,17 +478,10 @@ static bool LoadMatchFromJson(JSON_Object json) { g_SkipVeto = json_object_get_bool_safe(json, "skip_veto", CONFIG_SKIPVETO_DEFAULT); g_NumberOfMapsInSeries = json_object_get_int_safe(json, "num_maps", CONFIG_NUM_MAPSDEFAULT); - if (g_NumberOfMapsInSeries == 2) { - g_BO2Match = true; - g_MapsToWin = 2; - } else { - g_BO2Match = false; - // Normal path. No even numbers allowed since we already handled bo2. - if (g_NumberOfMapsInSeries % 2 == 0) { - MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); - return false; - } - g_MapsToWin = (g_NumberOfMapsInSeries + 1) / 2; + g_MapsToWin = MapsToWin(g_NumberOfMapsInSeries); + if (g_NumberOfMapsInSeries != 2 && g_NumberOfMapsInSeries % 2 == 0) { + MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); + return false; } char vetoFirstBuffer[64]; @@ -686,8 +665,6 @@ public void SetMatchTeamCvars() { tTeam = Get5Team_1; } - int mapsPlayed = Get5_GetMapNumber(); - // Get the match configs set by the config file. // These might be modified so copies are made here. char ctMatchText[MAX_CVAR_LENGTH]; @@ -698,8 +675,8 @@ public void SetMatchTeamCvars() { // Update mp_teammatchstat_txt with the match title. char mapstat[MAX_CVAR_LENGTH]; strcopy(mapstat, sizeof(mapstat), g_MatchTitle); - ReplaceStringWithInt(mapstat, sizeof(mapstat), "{MAPNUMBER}", mapsPlayed + 1); - ReplaceStringWithInt(mapstat, sizeof(mapstat), "{MAXMAPS}", MaxMapsToPlay(g_MapsToWin)); + ReplaceStringWithInt(mapstat, sizeof(mapstat), "{MAPNUMBER}", Get5_GetMapNumber() + 1, false); + ReplaceStringWithInt(mapstat, sizeof(mapstat), "{MAXMAPS}", g_NumberOfMapsInSeries, false); SetConVarStringSafe("mp_teammatchstat_txt", mapstat); if (g_MapsToWin >= 3) { diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 96650fda4..168bf59ed 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -166,7 +166,7 @@ public void Stats_Reset() { public void Stats_InitSeries() { Stats_Reset(); char seriesType[32]; - Format(seriesType, sizeof(seriesType), "bo%d", MaxMapsToPlay(g_MapsToWin)); + Format(seriesType, sizeof(seriesType), "bo%d", g_NumberOfMapsInSeries); g_StatsKv.SetString(STAT_SERIESTYPE, seriesType); g_StatsKv.SetString(STAT_SERIES_TEAM1NAME, g_TeamNames[Get5Team_1]); g_StatsKv.SetString(STAT_SERIES_TEAM2NAME, g_TeamNames[Get5Team_2]); diff --git a/scripting/get5/tests.sp b/scripting/get5/tests.sp index fb45b217d..e0a0cadd6 100644 --- a/scripting/get5/tests.sp +++ b/scripting/get5/tests.sp @@ -21,11 +21,15 @@ public void Get5_Test() { static void Utils_Test() { SetTestContext("Utils_Test"); - // MaxMapsToPlay - AssertEq("MaxMapsToPlay1", MaxMapsToPlay(1), 1); - AssertEq("MaxMapsToPlay2", MaxMapsToPlay(2), 3); - AssertEq("MaxMapsToPlay3", MaxMapsToPlay(3), 5); - AssertEq("MaxMapsToPlay4", MaxMapsToPlay(4), 7); + // MapsToWin + AssertEq("MapsToWin1", MapsToWin(1), 1); + AssertEq("MapsToWin2", MapsToWin(2), 2); + AssertEq("MapsToWin3", MapsToWin(3), 2); + AssertEq("MapsToWin4", MapsToWin(4), 3); + AssertEq("MapsToWin5", MapsToWin(5), 3); + AssertEq("MapsToWin6", MapsToWin(6), 4); + AssertEq("MapsToWin7", MapsToWin(7), 4); + AssertEq("MapsToWin8", MapsToWin(8), 5); // ConvertAuthToSteam64 char input[64] = "STEAM_0:1:52245092"; @@ -71,7 +75,6 @@ static void KV_Test() { SetTestContext("KV_Test"); AssertEq("maps_to_win", g_MapsToWin, 2); - AssertEq("bo2_series", g_BO2Match, false); AssertEq("num_maps", g_NumberOfMapsInSeries, 3); AssertEq("skip_veto", g_SkipVeto, false); AssertEq("players_per_team", g_PlayersPerTeam, 5); diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index ef23a0b69..6256afd7a 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -497,11 +497,12 @@ stock int AuthToClient(const char[] auth) { return -1; } -stock int MaxMapsToPlay(int mapsToWin) { - if (g_BO2Match) - return 2; - else - return 2 * mapsToWin - 1; +stock int MapsToWin(int numberOfMaps) { + if (numberOfMaps <= 2) { + return numberOfMaps; + } else { + return (numberOfMaps / 2) + 1; + } } stock void CSTeamString(int csTeam, char[] buffer, int len) { From 07227cee49ef2017b4952dfa08bc0ad5df59fe26 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 24 Aug 2022 03:46:09 +0200 Subject: [PATCH 071/104] Various doc adjustments; fix team assignment and sides set during backups --- documentation/docs/gotv.md | 19 +++++++++------ documentation/docs/match_schema.md | 4 ++- scripting/get5.sp | 10 +++++--- scripting/get5/backups.sp | 39 +++++++++++++++++++----------- scripting/get5/goinglive.sp | 2 +- scripting/get5/matchconfig.sp | 23 ++++++++++-------- 6 files changed, 61 insertions(+), 36 deletions(-) diff --git a/documentation/docs/gotv.md b/documentation/docs/gotv.md index e4ec458a0..550c9600b 100644 --- a/documentation/docs/gotv.md +++ b/documentation/docs/gotv.md @@ -4,13 +4,6 @@ Get5 can be configured to automatically record matches. This is enabled by defau of [`get5_demo_name_format`](../configuration/#get5_demo_name_format) and can be disabled by setting that parameter to an empty string. -!!! warning "Don't mess too much with the delay!" - - Changing the `tv_delay` or `tv_enable` in `warmup.cfg`, `live.cfg` etc. is going to cause problems with your demos. - We recommend you set this variable either on your server in general or only once in the `cvar` section of your - [match configuration](../match_schema). You should also not set `tv_delaymapchange` as Get5 handles this - automatically. - Demo recording starts once all teams have readied up and ends shortly following a map result. When a demo file is written to disk, the [`Get5_OnDemoFinished`](events_and_forwards.md) forward is called, which you can use to move the file or upload it somewhere. The filename can also be found in the map-section of the @@ -20,3 +13,15 @@ Get5 will automatically adjust the [`mp_match_restart_delay`](https://totalcsgo. map ends if GOTV is enabled to ensure that it won't be shorter than what is required for the GOTV broadcast to finish. Players will also not be [kicked from the server](../configuration/#get5_kick_when_no_match_loaded) before this delay has passed. + +!!! warning "Don't mess too much with the TV! :tv:" + + Changing `tv_delay` or `tv_enable` in `warmup.cfg`, `live.cfg` etc. is going to cause problems with your demos. + We recommend you set `tv_delay` either on your server in general or only once in the `cvars` section of your + [match configuration](../match_schema). You should also not set `tv_delaymapchange` as Get5 handles this + automatically. + + We recommend that you **do not** set `tv_enable` in your match configuration, as it **requires** a map change for + the GOTV bot to join the server. You should enable GOTV in your general server config and refrain from turning it on + and off with Get5. Note that setting `tv_enable 1` won't allow people to join your server's GOTV. You must also set + `tv_advertise_watchable 1`, so you don't have to worry about ghosting if this is disabled. diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 86d165177..82b73126f 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -93,7 +93,9 @@ interface Get5Match { 14. _Optional_
Wrapper for the server's `mp_teamprediction_pct`. This determines the chances of `team1` winning.

**`Default: 0`** 15. _Optional_
Wrapper for the server's `mp_teamprediction_txt`.

**`Default: ""`** -16. _Required_
The team's name. Sets `mp_teamname_1` or `mp_teamname_2`. Printed frequently in chat. +16. _Optional_
The team's name. Sets `mp_teamname_1` or `mp_teamname_2`. Printed frequently in chat. If you don't + define a team name, it will be set to `team_` followed by the name of the captain, i.e. `team_s1mple`. +

**`Default: ""`** 17. _Optional_
A short version of the team name, used in clan tags in-game (requires that [`get5_set_client_clan_tags`](../configuration#get5_set_client_clan_tags) is disabled).

**`Default: ""`** diff --git a/scripting/get5.sp b/scripting/get5.sp index c78db828f..2a83b775e 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -837,9 +837,8 @@ public void OnConfigsExecuted() { LOOP_TEAMS(team) { g_TeamGivenStopCommand[team] = false; g_TeamReadyForUnpause[team] = false; - // We don't need to check for g_WaitingForRoundBackup here, as a backup will override the pauses consumed anyway; if - // the map is changed, we always load the backup pauses. + // the map is changed, we always load the backup pauses. See the RestoreFromBackup function. g_TacticalPauseTimeUsed[team] = 0; g_TacticalPausesUsed[team] = 0; g_TechnicalPausesUsed[team] = 0; @@ -856,6 +855,11 @@ public void OnConfigsExecuted() { ExecCfg(g_WarmupCfgCvar); StartWarmup(); } + // This must not be called when waiting for a backup, as it will set the sides incorrectly if the team swapped in + // knife or if the backup target is the second half. + if (!g_WaitingForRoundBackup) { + SetStartingTeams(); + } } public Action Timer_CheckReady(Handle timer) { @@ -1581,7 +1585,7 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas } } - if (g_GameState >= Get5State_Warmup) { + if (g_GameState == Get5State_Warmup || g_GameState == Get5State_KnifeRound || g_GameState == Get5State_Live) { WriteBackup(); } diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 1e79f8653..afee9fbb9 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -210,18 +210,21 @@ void WriteBackupStructure(const char[] path) { WriteMatchToKv(kv); kv.GoBack(); - // Write valve's backup format into the file. - char lastBackup[PLATFORM_MAX_PATH]; - ConVar lastBackupCvar = FindConVar("mp_backup_round_file_last"); - if (g_GameState == Get5State_Live && lastBackupCvar != null) { - lastBackupCvar.GetString(lastBackup, sizeof(lastBackup)); - KeyValues valveBackup = new KeyValues("valve_backup"); - if (valveBackup.ImportFromFile(lastBackup)) { - kv.JumpToKey("valve_backup", true); - KvCopySubkeys(valveBackup, kv); - kv.GoBack(); + if (g_GameState == Get5State_Live) { + // Write valve's backup format into the file. This only applies to live rounds, as any pre-live backups should + // just restart the game to the knife round. + char lastBackup[PLATFORM_MAX_PATH]; + ConVar lastBackupCvar = FindConVar("mp_backup_round_file_last"); + if (lastBackupCvar != null) { + lastBackupCvar.GetString(lastBackup, sizeof(lastBackup)); + KeyValues valveBackup = new KeyValues("valve_backup"); + if (valveBackup.ImportFromFile(lastBackup)) { + kv.JumpToKey("valve_backup", true); + KvCopySubkeys(valveBackup, kv); + kv.GoBack(); + } + delete valveBackup; } - delete valveBackup; } // Write the get5 stats into the file. @@ -328,9 +331,8 @@ bool RestoreFromBackup(const char[] path) { kv.GoBack(); } - // When loading round 0, there is no valve backup, so we assume round 0 if the game is live, - // otherwise -1 - int roundNumberRestoredTo = g_GameState == Get5State_Live ? 0 : -1; + // When loading pre-live, there is no Valve backup, so we assume -1. + int roundNumberRestoredTo = -1; char tempValveBackup[PLATFORM_MAX_PATH]; GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); if (kv.JumpToKey("valve_backup")) { @@ -373,6 +375,7 @@ bool RestoreFromBackup(const char[] path) { void RestoreGet5Backup() { if (g_SavedValveBackup) { + LogDebug("Restored backup with Valve backup. Doing match restore..."); // If you load a backup during a live round, the game might get stuck if there are only bots remaining and no // players are alive. Other stuff will probably also go wrong, so we just reset the game before loading the // backup to avoid any weird edge-cases. @@ -384,12 +387,20 @@ void RestoreGet5Backup() { g_DoingBackupRestoreNow = true; // reset after the backup has completed, suppresses various events and hooks until then. CreateTimer(1.5, Time_StartRestore); } else { + LogDebug("Restored backup with no Valve backup. Going to warmup now."); // Backup was for veto or warmup; go to warmup. UnpauseGame(Get5Team_None); ExecCfg(g_WarmupCfgCvar); RestartGame(); StartWarmup(); } + // Last step is assigning players to their teams. This is normally done inside LoadMatchConfig, but since we need + // the team sides to be applied from the backup, we skip it then and do it here. + LOOP_CLIENTS(i) { + if (IsAuthedPlayer(i) && !IsClientSourceTV(i)) { + CheckClientTeam(i); + } + } } public Action Time_StartRestore(Handle timer) { diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index 28a015423..1cb0f0fd7 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -32,7 +32,7 @@ public Action Timer_GoToLiveAfterWarmupCountdown(Handle timer) { } Get5_MessageToAll("%t", "MatchBeginInSecondsInfoMessage", countdown); StartWarmup(countdown); - LogDebug("Started warmup countdown to live in %s seconds.", countdown); + LogDebug("Started warmup countdown to live in %d seconds.", countdown); return Plugin_Handled; } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index dadb632a0..4d1880d01 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -111,12 +111,12 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { SetMatchTeamCvars(); LoadPlayerNames(); AddTeamLogosToDownloadTable(); - // This gets called twice because ExecCfg(g_WarmupCfgCvar) also does it async, but we need it here. + // ExecuteMatchConfigCvars gets called twice because ExecCfg(g_WarmupCfgCvar) also does it async, but we need it here + // as the team assigment below depends on it. ExecuteMatchConfigCvars(); + SetStartingTeams(); if (!restoreBackup) { - // SetStartingTeams must *not* be called when loading a backup, as it will override the sides saved in the backup! - SetStartingTeams(); ChangeState(Get5State_Warmup); ExecCfg(g_WarmupCfgCvar); StartWarmup(); @@ -145,17 +145,20 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { || GetTeamCoaches(Get5Team_2).Length != 0)) { LogError("Setting player auths in the \"players\" or \"coaches\" section has no impact with get5_check_auths 0"); } - } - - strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); - // ExecuteMatchConfigCvars must be executed before we place players, as it might have get5_check_auths 1. - LOOP_CLIENTS(i) { - if (IsAuthedPlayer(i) && !IsClientSourceTV(i)) { - CheckClientTeam(i); + // ExecuteMatchConfigCvars must be executed before we place players, as it might have get5_check_auths 1. + // We must also have called SetStartingTeams to get the sides right. + // When restoring from backup, assigning to teams is done after loading the match config as it depends on the sides + // being set correctly by the backup, so we put it inside this "if" here. + LOOP_CLIENTS(i) { + if (IsAuthedPlayer(i) && !IsClientSourceTV(i)) { + CheckClientTeam(i); + } } } + strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), config); + Get5_MessageToAll("%t", "MatchConfigLoadedInfoMessage"); return true; } From 87c359cdd55ad808e92d2f8e1b7b16eb196ee216 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 24 Aug 2022 05:15:22 +0200 Subject: [PATCH 072/104] Don't kick GOTV on team swap --- scripting/get5/kniferounds.sp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index ddf982c91..deffdee4e 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -40,12 +40,12 @@ static void PerformSideSwap(bool swap) { g_TeamSide[Get5Team_1] = tmp; LOOP_CLIENTS(i) { - if (IsValidClient(i)) { + if (IsValidClient(i) && !IsClientSourceTV(i)) { if (IsFakeClient(i)) { // Because bots never have an assigned team, they won't be moved around by CheckClientTeam. We kick them to // prevent one team from having too many players. They will rejoin if defined in the live config. KickClient(i); - } else if (!IsClientSourceTV(i)) { + } else { CheckClientTeam(i, false); } } From 99edab079f033d8b50484bc805a066286e2dbc4c Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 24 Aug 2022 14:45:29 +0200 Subject: [PATCH 073/104] Don't use Cleanup + delete - throws invalid handle error --- scripting/get5/matchconfig.sp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 4d1880d01..9c5e9100d 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -590,8 +590,7 @@ static void LoadTeamDataJson(JSON_Object json, Get5Team matchTeam) { LogError("Cannot load team config from file \"%s\", fromfile"); } else { LoadTeamDataJson(fromfileJson, matchTeam); - fromfileJson.Cleanup(); - delete fromfileJson; + json_cleanup_and_delete(fromfileJson); } } From 5db5c4293ba403f3344e2624b8fec5fbe26f3da8 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 24 Aug 2022 16:22:35 +0200 Subject: [PATCH 074/104] Specify that `fromfile` when loading teams requires the file to be in the same format --- documentation/docs/match_schema.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 82b73126f..9676355e6 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -123,7 +123,8 @@ interface Get5Match { cvars.

**`Default: ""`** 28. Match teams can also be loaded from a separate file, allowing you to easily re-use a match configuration for different sets of teams. A `fromfile` value could be `"addons/sourcemod/configs/get5/team_nip.json"`, and that file - should contain a valid `Get5MatchTeam` object. + should contain a valid `Get5MatchTeam` object. Note that the file you point to must be in the same format as the + main file, so pointing to a `.cfg` file when the main file is `.json` will **not** work. 29. _Optional_
The name of the spectator team.

**`Default: "casters"`** 30. _Optional_
The spectator/caster Steam IDs and names. 31. _Optional_
Determines the starting sides for each map. If this array is shorter than `num_maps`, `side_type` will From fcffa44551a3b292b48cbc72f361ba302f1efa57 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 25 Aug 2022 17:18:48 +0200 Subject: [PATCH 075/104] IsClientGOTV already handled by IsFakeClient Swap IsAuthedPlayer with more defensive IsPlayer call where applicable --- scripting/get5.sp | 2 +- scripting/get5/backups.sp | 4 ++-- scripting/get5/matchconfig.sp | 2 +- scripting/get5/teamlogic.sp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 2a83b775e..a09d7af2d 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1580,7 +1580,7 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas // ghost, depending on where the camera drops them. Especially important for coaches. // We do this step *before* we write the backup, so we don't have any lingering players in case of a restore. LOOP_CLIENTS(i) { - if (IsPlayer(i) && !IsClientSourceTV(i) && GetClientTeam(i) == CS_TEAM_NONE) { + if (IsPlayer(i) && GetClientTeam(i) == CS_TEAM_NONE) { CheckClientTeam(i); } } diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index afee9fbb9..d802f26fa 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -397,7 +397,7 @@ void RestoreGet5Backup() { // Last step is assigning players to their teams. This is normally done inside LoadMatchConfig, but since we need // the team sides to be applied from the backup, we skip it then and do it here. LOOP_CLIENTS(i) { - if (IsAuthedPlayer(i) && !IsClientSourceTV(i)) { + if (IsPlayer(i)) { CheckClientTeam(i); } } @@ -414,7 +414,7 @@ public Action Time_StartRestore(Handle timer) { public Action Timer_FinishBackup(Handle timer) { // This ensures that coaches are moved to their slots. LOOP_CLIENTS(i) { - if (IsPlayer(i) && !IsClientSourceTV(i)) { + if (IsPlayer(i)) { CheckClientTeam(i); } } diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 9c5e9100d..ba9316866 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -151,7 +151,7 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { // When restoring from backup, assigning to teams is done after loading the match config as it depends on the sides // being set correctly by the backup, so we put it inside this "if" here. LOOP_CLIENTS(i) { - if (IsAuthedPlayer(i) && !IsClientSourceTV(i)) { + if (IsPlayer(i)) { CheckClientTeam(i); } } diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index bd12f5149..4c83eff7b 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -13,7 +13,7 @@ public Action Timer_PlacePlayerOnJoin(Handle timer, int userId) { } void CheckClientTeam(int client, bool useDefaultTeamSelection = true) { - if (!g_CheckAuthsCvar.BoolValue || IsFakeClient(client) || IsClientSourceTV(client)) { + if (!g_CheckAuthsCvar.BoolValue || IsFakeClient(client)) { // Teams are not enforced; do nothing. return; } @@ -115,7 +115,7 @@ public void CoachingChangedHook(ConVar convar, const char[] oldValue, const char if (StringToInt(oldValue) != 0 && !convar.BoolValue) { LogDebug("Detected sv_coaching_enabled was disabled. Checking for coaches."); LOOP_CLIENTS(i) { - if (IsAuthedPlayer(i) && IsClientCoaching(i)) { + if (IsPlayer(i) && IsClientCoaching(i)) { CheckClientTeam(i); } } From 8851f3f55892f9b6d2a0c0fc8d99fa22c1f9fc6b Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 25 Aug 2022 19:32:35 +0200 Subject: [PATCH 076/104] Use MapsToWin when calculating team scores --- scripting/get5.sp | 2 +- scripting/get5/util.sp | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index a09d7af2d..acb5e557b 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1273,7 +1273,7 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast } } else if (g_SeriesCanClinch) { // This adjusts for ties! - int actualMapsToWin = ((g_MapsToPlay.Length - tiedMaps) / 2) + 1; + int actualMapsToWin = MapsToWin(g_MapsToPlay.Length - tiedMaps); if (t1maps == actualMapsToWin) { // Team 1 won EndSeries(Get5Team_1, true, restartDelay); diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 6256afd7a..ad9aef92d 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -498,11 +498,8 @@ stock int AuthToClient(const char[] auth) { } stock int MapsToWin(int numberOfMaps) { - if (numberOfMaps <= 2) { - return numberOfMaps; - } else { - return (numberOfMaps / 2) + 1; - } + // This works because integers are rounded down; so 3 / 2 = 1.5, which becomes 1 as integer. + return (numberOfMaps / 2) + 1; } stock void CSTeamString(int csTeam, char[] buffer, int len) { From 821a4f22995a5dc2a6e20a5b6fc99da8af90a3bf Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 26 Aug 2022 19:20:43 +0200 Subject: [PATCH 077/104] Various adjustments to 0.10 (#857) * Ensure demofile is written to stats immediately as demo starts recording. * Adjust default {TIME} tag to include minutes and seconds Use {TIME} in default demo format to prevent collisions because of backups * Detach StartRecording from StartGame Always restart GOTV when using get5_loadbackup, but not when using !stop Go directly to default !ready-up if loading a prelive backup; prevents double ready-up when restoring to prelive on a different map Get rid of g_SavedValveBackup; can be replaced by just g_WaitingForRoundBackup * Unify map name formatting * Prevent race-conditions in the backup system if restoring during a live round * Fix problems with game state when loading match * Move !stop reset to round end Remove unnecessary grenade clean event during halftime * Use utility function to check for pending map or backup state --- documentation/docs/configuration.md | 9 +-- documentation/docs/translations.md | 2 +- scripting/get5.sp | 78 ++++++++++----------- scripting/get5/backups.sp | 92 +++++++++++++++---------- scripting/get5/debug.sp | 1 - scripting/get5/maps.sp | 4 +- scripting/get5/mapveto.sp | 10 +-- scripting/get5/matchconfig.sp | 6 +- scripting/get5/recording.sp | 1 + scripting/get5/stats.sp | 101 +++++++++++++++------------- scripting/get5/util.sp | 17 ++++- translations/da/get5.phrases.txt | 2 +- translations/get5.phrases.txt | 2 +- 13 files changed, 184 insertions(+), 141 deletions(-) diff --git a/documentation/docs/configuration.md b/documentation/docs/configuration.md index 9cb6324bc..0e6bb164c 100644 --- a/documentation/docs/configuration.md +++ b/documentation/docs/configuration.md @@ -218,19 +218,20 @@ must confirm. **`Default: 0`** ####`get5_time_format` : Time format string. This determines the [`{TIME}`](#tag-time) tag. **Do not change this unless you know what you are -doing! Avoid using spaces or colons.** **`Default: %Y-%m-%d_%H`** +doing! Avoid using spaces or colons.** **`Default: "%Y-%m-%d_%H-%M-%S"`** ####`get5_demo_name_format` : Format to use for demo files when [recording matches](gotv.md). Do not include a file extension (`.dem` is added -automatically). Set to empty string to disable.
Note that the [`{MAPNUMBER}`](#tag-mapnumber) variable is not -zero-indexed!
**`Default: {MATCHID}_map{MAPNUMBER}_{MAPNAME}`** +automatically). Set to empty string to disable. If you do not include the [`{TIME}`](#tag-time) tag, you will have +problems with duplicate files if restoring a game from a backup.
Note that the [`{MAPNUMBER}`](#tag-mapnumber) +variable is not zero-indexed!
**`Default: "{TIME}_{MATCHID}_map{MAPNUMBER}_{MAPNAME}"`** ####`get5_event_log_format` : Format to write event logs to. Set to empty string to disable. **`Default: ""`** ####`get5_stats_path_format` : Path where stats are output at each map end if it is set. Set to empty string to -disable. **`Default: get5_matchstats_{MATCHID}.cfg`** +disable. **`Default: "get5_matchstats_{MATCHID}.cfg"`** ## Backup System diff --git a/documentation/docs/translations.md b/documentation/docs/translations.md index fb16ad9db..544d31052 100644 --- a/documentation/docs/translations.md +++ b/documentation/docs/translations.md @@ -104,7 +104,7 @@ end with a full stop as this is added automatically. | `TacticalPauseMidSentence` | _Team A_ (_CT_) __tactical pause__ (_1_/_2_). | HintText | | `TimeRemainingBeforeAnyoneCanUnpausePrefix` | _Team A_ (_CT_) technical pause (_1_/_2_). __Time remaining before anyone can unpause__: _2:30_ | HintText | | `StopCommandNotEnabled` | The stop command is not enabled. | Chat | -| `StopCommandVotingReset` | The request by _Team A_ to stop the game was canceled as a new round started. | Chat | +| `StopCommandVotingReset` | The request by _Team A_ to stop the game was canceled as the round ended. | Chat | | `PauseTimeRemainingPrefix` | _Team A_ (_CT_) tactical pause. __Remaining pause time__: _2:15_ | HintText | | `PausedForBackup` | The game was restored from a backup. Both teams must unpause to continue. | HintText | | `AwaitingUnpause` | _Team A_ (_CT_) tactical pause. __Awaiting unpause__. | HintText | diff --git a/scripting/get5.sp b/scripting/get5.sp index acb5e557b..ef783ffde 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -178,7 +178,6 @@ Menu g_ActiveVetoMenu = null; /** Backup data **/ bool g_WaitingForRoundBackup = false; -bool g_SavedValveBackup = false; bool g_DoingBackupRestoreNow = false; // Stats values @@ -353,8 +352,8 @@ public void OnPluginStart() { g_CheckAuthsCvar = CreateConVar("get5_check_auths", "1", "If set to 0, get5 will not force players to the correct team based on steamid"); - g_DemoNameFormatCvar = CreateConVar("get5_demo_name_format", "{MATCHID}_map{MAPNUMBER}_{MAPNAME}", - "Format for demo file names, use \"\" to disable"); + g_DemoNameFormatCvar = CreateConVar("get5_demo_name_format", "{TIME}_{MATCHID}_map{MAPNUMBER}_{MAPNAME}", + "Format for demo file names, use \"\" to disable. Do not remove the {TIME} placeholder if you use the backup system."); g_DisplayGotvVetoCvar = CreateConVar("get5_display_gotv_veto", "0", "Whether to wait for map vetos to be printed to GOTV before changing map"); @@ -424,7 +423,7 @@ public void OnPluginStart() { "get5_time_to_make_knife_decision", "60", "Time (in seconds) a team has to make a !stay/!swap decision after winning knife round, 0=unlimited"); g_TimeFormatCvar = CreateConVar( - "get5_time_format", "%Y-%m-%d_%H", + "get5_time_format", "%Y-%m-%d_%H-%M-%S", "Time format to use when creating file names. Don't tweak this unless you know what you're doing! Avoid using spaces or colons."); g_VetoConfirmationTimeCvar = CreateConVar( "get5_veto_confirmation_time", "2.0", @@ -820,6 +819,9 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr public void OnConfigsExecuted() { LogDebug("OnConfigsExecuted"); g_MapChangePending = false; + g_DoingBackupRestoreNow = false; + g_ReadyTimeWaitingUsed = 0; + g_HasKnifeRoundStarted = false; // Recording is always automatically stopped on map change, and // since there are no hooks to detect tv_stoprecord, we reset // our recording var if a map change is performed unexpectedly. @@ -844,12 +846,9 @@ public void OnConfigsExecuted() { g_TechnicalPausesUsed[team] = 0; } - g_ReadyTimeWaitingUsed = 0; - g_HasKnifeRoundStarted = false; - // On map start, always put the game in warmup mode. - // When executing a backup load, the live config is loaded and warmup ends. - if (g_GameState != Get5State_None || g_WaitingForRoundBackup) { + // When executing a backup load, the live config is loaded and warmup ends after players ready-up again. + if (g_GameState != Get5State_None) { LogDebug("Putting game into warmup in OnConfigsExecuted."); ChangeState(Get5State_Warmup); ExecCfg(g_WarmupCfgCvar); @@ -909,6 +908,7 @@ public Action Timer_CheckReady(Handle timer) { LogDebug("Timer_CheckReady: starting without a knife round"); StartGame(false); } + StartRecording(); } else { CheckReadyWaitingTimes(); } @@ -1172,7 +1172,7 @@ public void RestoreLastRound(int client) { char lastBackup[PLATFORM_MAX_PATH]; g_LastGet5BackupCvar.GetString(lastBackup, sizeof(lastBackup)); if (!StrEqual(lastBackup, "")) { - if (RestoreFromBackup(lastBackup)) { + if (RestoreFromBackup(lastBackup, false)) { Get5_MessageToAll("%t", "BackupLoadedInfoMessage", lastBackup); // Fix the last backup cvar since it gets reset. g_LastGet5BackupCvar.SetString(lastBackup); @@ -1234,9 +1234,8 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast winningTeam = Get5Team_2; } - // If the round ends because the match is over, we clear the grenade container immediately as - // there will be no RoundStart event to do it, and the sideSwap check in RoundEnd will not - // trigger it either. + // If the round ends because the match is over, we clear the grenade container immediately as they will not fire + // on their own if the game state is not live. Stats_ResetGrenadeContainers(); // Update series scores @@ -1309,7 +1308,7 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast sizeof(timeToMapChangeFormatted)); g_MapChangePending = true; - Format(nextMap, sizeof(nextMap), "{GREEN}%s{NORMAL}", nextMap); + FormatMapName(nextMap, nextMap, sizeof(nextMap), true, true); Get5_MessageToAll("%t", "NextSeriesMapInfoMessage", nextMap, timeToMapChangeFormatted); ChangeState(Get5State_PostGame); // Subtracting 4 seconds makes the map change 1 second before the timer expires, as there is a 3 @@ -1449,7 +1448,7 @@ public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast // We always want this to be correct, regardless of game state. g_RoundStartedTime = GetEngineTime(); - if (g_GameState == Get5State_Live && !g_DoingBackupRestoreNow && !g_WaitingForRoundBackup) { + if (g_GameState == Get5State_Live && !IsDoingRestoreOrMapChange()) { Stats_RoundStart(); } } @@ -1491,15 +1490,7 @@ static bool CreateBackupFolderStructure(const char[] path) { } public void WriteBackup() { - if (!g_BackupSystemEnabledCvar.BoolValue || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { - return; - } - - if (g_PauseType == Get5PauseType_Backup) { - // If there was no valve backup found when restoring the game, the above checks will not trigger a return, - // so we do this to prevent overwriting an already-written backup in the event the game starts in the same round - // it was *just* restored to and this function runs. - LogDebug("Skipping backup round write as the round started paused for backup."); + if (!g_BackupSystemEnabledCvar.BoolValue || IsDoingRestoreOrMapChange()) { return; } @@ -1531,16 +1522,6 @@ public void WriteBackup() { LogDebug("Writing backup to %s", path); WriteBackupStructure(path); g_LastGet5BackupCvar.SetString(path); - - // Reset this when writing a new backup, as voting has no reference to which round the teams wanted to restore to, so - // votes to restore during one round should not carry over into the next round, as it would just restore that round - // instead. - LOOP_TEAMS(t) { - if (g_TeamGivenStopCommand[t]) { - Get5_MessageToAll("%t", "StopCommandVotingReset", g_FormattedTeamNames[t]); - } - g_TeamGivenStopCommand[t] = false; - } } public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { @@ -1552,6 +1533,11 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas g_BombPlantedTime = 0.0; g_BombSiteLastPlanted = Get5BombSite_Unknown; + if (g_WaitingForRoundBackup || g_MapChangePending) { + // We don't want g_DoingBackupRestoreNow filtered here, as we need the round start event after restoring a match. + return; + } + // We cannot do this during warmup, as sending users into warmup post-knife triggers a round start event. // We add an extra restart to clear lingering state from the knife round, such as the round // indicator in the middle of the scoreboard not being reset. This also tightly couples the live-announcement to @@ -1586,13 +1572,16 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas } if (g_GameState == Get5State_Warmup || g_GameState == Get5State_KnifeRound || g_GameState == Get5State_Live) { - WriteBackup(); + WriteBackup(); // Filters out backup states on its own } if (g_GameState != Get5State_Live) { return; } + // We still want to fire the Get5_OnRoundStart event when doing a backup (g_DoingBackupRestoreNow), as this may be + // required to insert the round into a database or event log, as the round is actually starting now and may have been + // deleted when the backup load was requested. Get5RoundStartedEvent startEvent = new Get5RoundStartedEvent(g_MatchID, g_MapNumber, g_RoundNumber); @@ -1679,7 +1668,7 @@ public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundEnd"); - if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -1747,12 +1736,6 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) } } - if (g_PendingSideSwap) { - // Normally we would do this in RoundStart, but since there is a significant delay between - // round *actual end* and and RoundStart when swapping sides, we do it here instead. - Stats_ResetGrenadeContainers(); - } - // CSRoundEndReason is incorrect in CSGO compared to the enumerations defined here: // https://github.com/alliedmodders/sourcemod/blob/master/plugins/include/cstrike.inc#L53-L77 // - which is why we subtract one. @@ -1770,6 +1753,16 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) Call_Finish(); EventLogger_LogAndDeleteEvent(roundEndEvent); + + // Reset this when a round ends, as voting has no reference to which round the teams wanted to restore to, so + // votes to restore during one round should not carry over into the next round, as it would just restore that round + // instead. + LOOP_TEAMS(t) { + if (g_TeamGivenStopCommand[t]) { + Get5_MessageToAll("%t", "StopCommandVotingReset", g_FormattedTeamNames[t]); + } + g_TeamGivenStopCommand[t] = false; + } } return Plugin_Continue; } @@ -1805,7 +1798,6 @@ public Action Event_CvarChanged(Event event, const char[] name, bool dontBroadca public void StartGame(bool knifeRound) { LogDebug("StartGame"); - StartRecording(); if (knifeRound) { ExecCfg(g_LiveCfgCvar); // live first, then apply and save knife cvars below diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index d802f26fa..ca8ff5ffb 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -4,7 +4,7 @@ public Action Command_LoadBackup(int client, int args) { if (!g_BackupSystemEnabledCvar.BoolValue) { - ReplyToCommand(client, "The backup system is disabled"); + ReplyToCommand(client, "The backup system is disabled."); return Plugin_Handled; } @@ -17,6 +17,7 @@ public Action Command_LoadBackup(int client, int args) { if (args >= 1 && GetCmdArg(1, path, sizeof(path))) { if (RestoreFromBackup(path)) { Get5_MessageToAll("%t", "BackupLoadedInfoMessage", path); + g_LastGet5BackupCvar.SetString(path); } else { ReplyToCommand(client, "Failed to load backup %s - check error logs", path); } @@ -236,7 +237,7 @@ void WriteBackupStructure(const char[] path) { delete kv; } -bool RestoreFromBackup(const char[] path) { +bool RestoreFromBackup(const char[] path, bool restartRecording = true) { KeyValues kv = new KeyValues("Backup"); if (!kv.ImportFromFile(path)) { LogError("Failed to read backup file \"%s\"", path); @@ -244,6 +245,12 @@ bool RestoreFromBackup(const char[] path) { return false; } + if (restartRecording) { + // We must stop recording when loading a backup, and we must do it before we load the match config, or the g_MatchID + // variable will be incorrect. This is suppressed when using the !stop command. + StopRecording(); + } + if (kv.JumpToKey("Match")) { char tempBackupFile[PLATFORM_MAX_PATH]; GetTempFilePath(tempBackupFile, sizeof(tempBackupFile), TEMP_MATCHCONFIG_BACKUP_PATTERN); @@ -332,16 +339,15 @@ bool RestoreFromBackup(const char[] path) { } // When loading pre-live, there is no Valve backup, so we assume -1. + g_WaitingForRoundBackup = false; int roundNumberRestoredTo = -1; - char tempValveBackup[PLATFORM_MAX_PATH]; - GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); if (kv.JumpToKey("valve_backup")) { - g_SavedValveBackup = true; + g_WaitingForRoundBackup = true; + char tempValveBackup[PLATFORM_MAX_PATH]; + GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); kv.ExportToFile(tempValveBackup); roundNumberRestoredTo = kv.GetNum("round", 0); kv.GoBack(); - } else { - g_SavedValveBackup = false; } char currentMap[PLATFORM_MAX_PATH]; @@ -351,10 +357,29 @@ bool RestoreFromBackup(const char[] path) { g_MapsToPlay.GetString(g_MapNumber, currentSeriesMap, sizeof(currentSeriesMap)); if (!StrEqual(currentMap, currentSeriesMap)) { + // We don't need to assign players if changing map; this will be done when the players rejoin. + // If a map is to be changed, we want to suppress all stats events immediately, as the Get5_OnBackupRestore is + // called now and we don't want events firing after this until the game is live again. ChangeMap(currentSeriesMap, 3.0); - g_WaitingForRoundBackup = true; } else { - RestoreGet5Backup(); + // We must assign players to their teams. This is normally done inside LoadMatchConfig, but since we need + // the team sides to be applied from the backup, we skip it then and do it here. + LOOP_CLIENTS(i) { + if (IsPlayer(i)) { + CheckClientTeam(i); + } + } + if (g_WaitingForRoundBackup) { + // Same map, but round restore with a Valve backup; do normal restore immediately with no ready-up. + RestoreGet5Backup(restartRecording); + } else { + // We are restarting to the same map for prelive; just go back into warmup and let players ready-up again. + ResetReadyStatus(); + UnpauseGame(Get5Team_None); + ChangeState(Get5State_Warmup); + ExecCfg(g_WarmupCfgCvar); + StartWarmup(); + } } delete kv; @@ -373,34 +398,31 @@ bool RestoreFromBackup(const char[] path) { return true; } -void RestoreGet5Backup() { - if (g_SavedValveBackup) { - LogDebug("Restored backup with Valve backup. Doing match restore..."); - // If you load a backup during a live round, the game might get stuck if there are only bots remaining and no - // players are alive. Other stuff will probably also go wrong, so we just reset the game before loading the - // backup to avoid any weird edge-cases. - if (!InWarmup()) { - RestartGame(); - } - ExecCfg(g_LiveCfgCvar); - PauseGame(Get5Team_None, Get5PauseType_Backup); - g_DoingBackupRestoreNow = true; // reset after the backup has completed, suppresses various events and hooks until then. - CreateTimer(1.5, Time_StartRestore); - } else { - LogDebug("Restored backup with no Valve backup. Going to warmup now."); - // Backup was for veto or warmup; go to warmup. - UnpauseGame(Get5Team_None); - ExecCfg(g_WarmupCfgCvar); - RestartGame(); - StartWarmup(); +void RestoreGet5Backup(bool restartRecording = true) { + // If you load a backup during a live round, the game might get stuck if there are only bots remaining and no + // players are alive. Other stuff will probably also go wrong, so we just reset the game before loading the + // backup to avoid any weird edge-cases. + if (!InWarmup()) { + RestartGame(); } - // Last step is assigning players to their teams. This is normally done inside LoadMatchConfig, but since we need - // the team sides to be applied from the backup, we skip it then and do it here. - LOOP_CLIENTS(i) { - if (IsPlayer(i)) { - CheckClientTeam(i); - } + ExecCfg(g_LiveCfgCvar); + PauseGame(Get5Team_None, Get5PauseType_Backup); + g_DoingBackupRestoreNow = true; // reset after the backup has completed, suppresses various events and hooks until then. + g_WaitingForRoundBackup = false; + CreateTimer(1.5, Time_StartRestore); + if (restartRecording) { + // Since a backup command forces the recording to stop, we restart it here once the backup has completed. + // We have to do this on a delay, as when loading from a live game, the backup will already be recording and must + // flush before a new record command can be issued. This is suppressed when using the !stop command! + CreateTimer(3.0, Timer_StartRecordingAfterBackup, _, TIMER_FLAG_NO_MAPCHANGE); + } +} + +public Action Timer_StartRecordingAfterBackup(Handle timer) { + if (g_GameState != Get5State_Live) { + return; } + StartRecording(); } public Action Time_StartRestore(Handle timer) { diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index 60da3fb5b..6373da5b8 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -123,7 +123,6 @@ static void AddGlobalStateInfo(File f) { f.WriteLine("g_MapChangePending = %d", g_MapChangePending); f.WriteLine("g_PendingSideSwap = %d", g_PendingSideSwap); f.WriteLine("g_WaitingForRoundBackup = %d", g_WaitingForRoundBackup); - f.WriteLine("g_SavedValveBackup = %d", g_SavedValveBackup); f.WriteLine("g_DoingBackupRestoreNow = %d", g_DoingBackupRestoreNow); f.WriteLine("g_ReadyTimeWaitingUsed = %d", g_ReadyTimeWaitingUsed); f.WriteLine("g_PausingTeam = %d", g_PausingTeam); diff --git a/scripting/get5/maps.sp b/scripting/get5/maps.sp index 0c857e89f..119eadd29 100644 --- a/scripting/get5/maps.sp +++ b/scripting/get5/maps.sp @@ -1,6 +1,6 @@ stock void ChangeMap(const char[] map, float delay = 3.0) { - char formattedMapName[32]; - Format(formattedMapName, sizeof(formattedMapName), "{GREEN}%s{NORMAL}", map); + char formattedMapName[64]; + FormatMapName(map, formattedMapName, sizeof(formattedMapName), true, true); Get5_MessageToAll("%t", "ChangingMapInfoMessage", formattedMapName); // pass the "true" name to a timer to changelevel diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 27bc12f73..2ae3cf130 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -53,13 +53,13 @@ public void VetoFinished() { UnpauseGame(Get5Team_None); } - // Use total series score as starting point, to not print skipped maps - int seriesScore = g_TeamSeriesScores[Get5Team_1] + g_TeamSeriesScores[Get5Team_2]; - for (int i = seriesScore; i < g_MapsToPlay.Length; i++) { + // If a team has a map advantage, don't print that map. + int mapNumber = Get5_GetMapNumber(); + for (int i = mapNumber; i < g_MapsToPlay.Length; i++) { char map[PLATFORM_MAX_PATH]; g_MapsToPlay.GetString(i, map, sizeof(map)); - Format(map, sizeof(map), "{GREEN}%s{NORMAL}", map); - Get5_MessageToAll("%t", "MapIsInfoMessage", i + 1 - seriesScore, map); + FormatMapName(map, map, sizeof(map), true, true); + Get5_MessageToAll("%t", "MapIsInfoMessage", i + 1 - mapNumber, map); } float delay = 10.0; diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index ba9316866..120557f97 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -94,6 +94,7 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } if (!restoreBackup) { + ChangeState(Get5State_Warmup); // When restoring from backup, changelevel is called after loading the match config. g_MapPoolList.GetString(Get5_GetMapNumber(), mapName, sizeof(mapName)); char currentMap[PLATFORM_MAX_PATH]; @@ -101,6 +102,10 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { if (!StrEqual(mapName, currentMap)) { ChangeMap(mapName); } + } else if (g_GameState == Get5State_None) { + // If going directly from gamestate none to a backup with no veto config, we set warmup which is then changed + // as soon as the backup loads. It just can't be gamestate none for the rest of the function. + ChangeState(Get5State_Warmup); } } else if (!restoreBackup) { ChangeState(Get5State_PreVeto); @@ -117,7 +122,6 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { SetStartingTeams(); if (!restoreBackup) { - ChangeState(Get5State_Warmup); ExecCfg(g_WarmupCfgCvar); StartWarmup(); if (IsPaused()) { diff --git a/scripting/get5/recording.sp b/scripting/get5/recording.sp index fc354e907..db4f66b6b 100644 --- a/scripting/get5/recording.sp +++ b/scripting/get5/recording.sp @@ -28,6 +28,7 @@ bool StartRecording() { strcopy(szDemoName, sizeof(szDemoName), demoName); ReplaceString(szDemoName, sizeof(szDemoName), "\"", "\\\""); ServerCommand("tv_record \"%s\"", szDemoName); + Stats_SetDemoName(g_DemoFileName); return true; } diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 168bf59ed..16cbae43d 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -18,7 +18,7 @@ public void Stats_PluginStart() { public Action HandlePlayerDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } LogDebug("HandlePlayerDamage(victim=%d, attacker=%d, inflictor=%d, damage=%f, damageType=%d)", @@ -330,15 +330,17 @@ public void Stats_RoundEnd(int csTeamWinner) { public void Stats_UpdateMapScore(Get5Team winner) { GoToMap(); - char winnerString[16]; GetTeamString(winner, winnerString, sizeof(winnerString)); - g_StatsKv.SetString(STAT_MAPWINNER, winnerString); - g_StatsKv.SetString(STAT_DEMOFILENAME, g_DemoFileName); - GoBackFromMap(); + DumpToFile(); +} +void Stats_SetDemoName(const char[] demoFileName) { + GoToMap(); + g_StatsKv.SetString(STAT_DEMOFILENAME, demoFileName); + GoBackFromMap(); DumpToFile(); } @@ -360,16 +362,16 @@ public void EndMolotovEvent(const char[] molotovKey) { Get5MolotovDetonatedEvent molotovObject; if (g_MolotovContainer.GetValue(molotovKey, molotovObject)) { - molotovObject.EndTime = GetRoundTime(); - - LogDebug("Calling Get5_OnMolotovDetonated()"); - - Call_StartForward(g_OnMolotovDetonated); - Call_PushCell(molotovObject); - Call_Finish(); - - EventLogger_LogAndDeleteEvent(molotovObject); - + if (IsDoingRestoreOrMapChange()) { + delete molotovObject; + } else { + molotovObject.EndTime = GetRoundTime(); + LogDebug("Calling Get5_OnMolotovDetonated()"); + Call_StartForward(g_OnMolotovDetonated); + Call_PushCell(molotovObject); + Call_Finish(); + EventLogger_LogAndDeleteEvent(molotovObject); + } g_MolotovContainer.Remove(molotovKey); } } @@ -377,14 +379,15 @@ public void EndMolotovEvent(const char[] molotovKey) { public void EndHEEvent(const char[] grenadeKey) { Get5HEDetonatedEvent heObject; if (g_HEGrenadeContainer.GetValue(grenadeKey, heObject)) { - LogDebug("Calling Get5_OnHEGrenadeDetonated()"); - - Call_StartForward(g_OnHEGrenadeDetonated); - Call_PushCell(heObject); - Call_Finish(); - - EventLogger_LogAndDeleteEvent(heObject); - + if (IsDoingRestoreOrMapChange()) { + delete heObject; + } else { + LogDebug("Calling Get5_OnHEGrenadeDetonated()"); + Call_StartForward(g_OnHEGrenadeDetonated); + Call_PushCell(heObject); + Call_Finish(); + EventLogger_LogAndDeleteEvent(heObject); + } g_HEGrenadeContainer.Remove(grenadeKey); } } @@ -392,20 +395,21 @@ public void EndHEEvent(const char[] grenadeKey) { public void EndFlashbangEvent(const char[] flashKey) { Get5FlashbangDetonatedEvent flashEvent; if (g_FlashbangContainer.GetValue(flashKey, flashEvent)) { - LogDebug("Calling Get5_OnFlashbangDetonated()"); - - Call_StartForward(g_OnFlashbangDetonated); - Call_PushCell(flashEvent); - Call_Finish(); - - EventLogger_LogAndDeleteEvent(flashEvent); - + if (IsDoingRestoreOrMapChange()) { + delete flashEvent; + } else { + LogDebug("Calling Get5_OnFlashbangDetonated()"); + Call_StartForward(g_OnFlashbangDetonated); + Call_PushCell(flashEvent); + Call_Finish(); + EventLogger_LogAndDeleteEvent(flashEvent); + } g_FlashbangContainer.Remove(flashKey); } } public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -430,7 +434,7 @@ public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontB } public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -458,7 +462,7 @@ public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bo } public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -486,7 +490,7 @@ public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool d } public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -502,7 +506,9 @@ public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, boo } public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + // No backup check; the event is deleted in EndMolotovEvent to prevent leaks, as this function works like the + // the HE/flash timer callbacks which also do not check for backup state. + if (g_GameState != Get5State_Live) { return Plugin_Continue; } @@ -519,7 +525,7 @@ public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontB } public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -539,7 +545,7 @@ public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool do } public Action Stats_FlashbangDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -573,7 +579,7 @@ public Action Timer_HandleFlashbang(Handle timer, int entityId) { } public Action Stats_HEGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -607,7 +613,7 @@ public Action Timer_HandleHEGrenade(Handle timer, int entityId) { } public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -636,9 +642,12 @@ public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dont } public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBroadcast) { + if (IsDoingRestoreOrMapChange()) { + return Plugin_Continue; + } int attacker = GetClientOfUserId(event.GetInt("attacker")); - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live) { if (g_AutoReadyActivePlayersCvar.BoolValue && IsAuthedPlayer(attacker)) { // HandleReadyCommand checks for game state, so we don't need to do that here as well. HandleReadyCommand(attacker, true); @@ -800,7 +809,7 @@ static void UpdateTradeStat(int attacker, int victim) { } public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -829,7 +838,7 @@ public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBr } public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -862,7 +871,7 @@ public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBr } public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -881,7 +890,7 @@ public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontB } public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -923,7 +932,7 @@ public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBr } public Action Stats_RoundMVPEvent(Event event, const char[] name, bool dontBroadcast) { - if (g_GameState != Get5State_Live || g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { + if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index ad9aef92d..9b06e3a13 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -305,7 +305,7 @@ stock int GetCvarIntSafe(const char[] cvarName) { } } -stock void FormatMapName(const char[] mapName, char[] buffer, int len, bool cleanName = false) { +stock void FormatMapName(const char[] mapName, char[] buffer, int len, bool cleanName = false, bool color = false) { // explode map by '/' so we can remove any directory prefixes (e.g. workshop stuff) char buffers[4][PLATFORM_MAX_PATH]; int numSplits = ExplodeString(mapName, "/", buffers, sizeof(buffers), PLATFORM_MAX_PATH); @@ -338,8 +338,19 @@ stock void FormatMapName(const char[] mapName, char[] buffer, int len, bool clea strcopy(buffer, len, "Vertigo"); } else if (StrEqual(buffer, "de_ancient")) { strcopy(buffer, len, "Ancient"); + } else if (StrEqual(buffer, "de_tuscan")) { + strcopy(buffer, len, "Tuscan"); + } else if (StrEqual(buffer, "de_prime")) { + strcopy(buffer, len, "Prime"); + } else if (StrEqual(buffer, "de_grind")) { + strcopy(buffer, len, "Grind"); + } else if (StrEqual(buffer, "de_mocha")) { + strcopy(buffer, len, "Mocha"); } } + if (color) { + Format(buffer, len, "{GREEN}%s{NORMAL}", buffer); + } } stock void GetCleanMapName(char[] buffer, int size) { @@ -703,3 +714,7 @@ stock void convertSecondsToMinutesAndSeconds(int timeAsSeconds, char[] buffer, } Format(buffer, bufferSize, seconds < 10 ? "%d:0%d" : "%d:%d", minutes, seconds); } + +stock bool IsDoingRestoreOrMapChange() { + return g_DoingBackupRestoreNow || g_WaitingForRoundBackup || g_MapChangePending; +} diff --git a/translations/da/get5.phrases.txt b/translations/da/get5.phrases.txt index af33f2dfd..7ee6e30e8 100644 --- a/translations/da/get5.phrases.txt +++ b/translations/da/get5.phrases.txt @@ -254,7 +254,7 @@ } "StopCommandVotingReset" { - "da" "Anmodningen fra {1} om at stoppe spillet blev annulleret, da en ny runde startede." + "da" "Anmodningen fra {1} om at stoppe spillet blev annulleret, da runden sluttede." } "BackupLoadedInfoMessage" { diff --git a/translations/get5.phrases.txt b/translations/get5.phrases.txt index dd0f050c5..87412ae7a 100644 --- a/translations/get5.phrases.txt +++ b/translations/get5.phrases.txt @@ -304,7 +304,7 @@ "StopCommandVotingReset" { "#format" "{1:s}" - "en" "The request by {1} to stop the game was canceled as a new round started." + "en" "The request by {1} to stop the game was canceled as the round ended." } "BackupLoadedInfoMessage" { From 4dc74bae6b1db8f208eaccf2a2269385c13a3f04 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 26 Aug 2022 19:31:42 +0200 Subject: [PATCH 078/104] Simplify match state if/else --- scripting/get5/matchconfig.sp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 120557f97..7e4acd8c3 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -102,15 +102,17 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { if (!StrEqual(mapName, currentMap)) { ChangeMap(mapName); } - } else if (g_GameState == Get5State_None) { - // If going directly from gamestate none to a backup with no veto config, we set warmup which is then changed - // as soon as the backup loads. It just can't be gamestate none for the rest of the function. - ChangeState(Get5State_Warmup); } } else if (!restoreBackup) { ChangeState(Get5State_PreVeto); } + if (g_GameState == Get5State_None) { + // Make sure here that we don't run the code below in game state none, but also not overriding PreVeto. + // Currently, this could happen if you restored a backup with skip_veto:false. + ChangeState(Get5State_Warmup); + } + // Before we run the Get5_OnSeriesInit forward, we want to ensure that as much game state is set as possible, // so that any implementation reacting to that event/forward will have all the natives return proper data. SetMatchTeamCvars(); From def230d84cb1c1b5c737a0a287270ebb0ea808d9 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 26 Aug 2022 20:53:05 +0200 Subject: [PATCH 079/104] Run ExecuteMatchConfigCvars before other config init functions --- scripting/get5/matchconfig.sp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 7e4acd8c3..cff67f773 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -115,12 +115,13 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { // Before we run the Get5_OnSeriesInit forward, we want to ensure that as much game state is set as possible, // so that any implementation reacting to that event/forward will have all the natives return proper data. + // ExecuteMatchConfigCvars gets called twice because ExecCfg(g_WarmupCfgCvar) also does it async, but we need it here + // as the team assigment below depends on it. We set this one first as the others may depend on something changed in + // the match cvars section. + ExecuteMatchConfigCvars(); SetMatchTeamCvars(); LoadPlayerNames(); AddTeamLogosToDownloadTable(); - // ExecuteMatchConfigCvars gets called twice because ExecCfg(g_WarmupCfgCvar) also does it async, but we need it here - // as the team assigment below depends on it. - ExecuteMatchConfigCvars(); SetStartingTeams(); if (!restoreBackup) { @@ -1296,7 +1297,7 @@ void ExecCfg(ConVar cvar) { public Action Timer_ExecMatchConfig(Handle timer) { // When we load config files using ServerCommand("exec") above, which is async, we want match config cvars to always // override. - SetMatchTeamCvars(); ExecuteMatchConfigCvars(); + SetMatchTeamCvars(); return Plugin_Handled; } From 5e50215eb91bcd991b42d5c37ed9bb0cb2a74fbc Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 28 Aug 2022 00:54:02 +0200 Subject: [PATCH 080/104] Fix errors and adjust examples in match config documentation --- documentation/docs/match_schema.md | 233 ++++++++++++++++++++--------- documentation/mkdocs.yml | 2 + 2 files changed, 164 insertions(+), 71 deletions(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 9676355e6..f0228323b 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -37,26 +37,26 @@ interface Get5MatchTeamFromFile { interface Get5Match { "match_title": string // (25) "matchid": string, // (1) + "clinch_series": boolean // (32) "num_maps": number, // (2) "players_per_team": number, // (3) "coaches_per_team": number, // (4) "min_players_to_ready": number, // (5) "min_spectators_to_ready": number, // (6) "skip_veto": boolean, // (7), - "veto_first": "team1" | "team2", // (11) + "veto_first": "team1" | "team2" | "random", // (11) "side_type": "standard" | "always_knife" | "never_knife", // (12) "map_sides": ["team1_ct" | "team1_t" | "knife"], // (31) "spectators": { // (10) "name": string // (29) "players": Get5PlayerSet // (30) }, - "map_list": [string], // (13) + "maplist": [string], // (13) "favored_percentage_team1": number, // (14) "favored_percentage_text": string, // (15) "team1": Get5MatchTeam | Get5MatchTeamFromFile, // (20) "team2": Get5MatchTeam | Get5MatchTeamFromFile, // (21) - "cvars": { [key: string]: string }, // (22) - "clinch_series": boolean // (32) + "cvars": { [key: string]: string } // (22) } ``` @@ -69,7 +69,7 @@ interface Get5Match { 3. _Optional_
The number of players per team. You should **never** set this to a value higher than the number of players you want to actually play in a game, *excluding* coaches.

**`Default: 5`** 4. _Optional_
The maximum number of [coaches](coaching.md) per team.

**`Default: 2`** -5. _Optional_
The minimum number of players of each team that must type [`!ready`](../commands/#ready) for the game +5. _Optional_
The minimum number of players from each team that must type [`!ready`](../commands/#ready) for the game to begin.

**`Default: 1`** 6. _Optional_
The minimum number of spectators that must be [`!ready`](../commands/#ready) for the game to begin.

**`Default: 0`** @@ -133,77 +133,168 @@ interface Get5Match { 32. _Optional_
If `false`, the entire map list will be played, regardless of score. If `true`, a series will be won when the series score for a team exceeds the number of maps divided by two.

**`Default: true`** -!!! warning "SteamID64 in `.cfg` files" +## Examples {: #example } - You may have trouble using SteamID64 inside a KeyValue (`.cfg`) match config. The Valve KeyValue parser will - interpret any integer string as an integer (even if read as a string), and this value will - not fit inside a SourceMod-internal 32-bit cell. For `.cfg`, use the regular steamID, i.e. `STEAM_0:0:13723968`. - This is *not* a problem if you use the JSON format. Also, remember not to pass SteamID 64 as numbers, as they are - too large to reliably handle in JavaScript; always enclose them in quotes. +These examples are identical in the way they would work if loaded. -#### Example +=== "JSON (recommended)" -```typescript title="JSON example with Node.js" -const match_schema: Match = { - "match_title": "Astralis vs. NaVi", - "matchid": "3123", - "num_maps": 3, - "players_per_team": 5, - "coaches_per_team": 2, - "min_players_to_ready": 2, - "min_spectators_to_ready": 0, - "skip_veto": false, - "veto_first": "team1", - "side_type": "standard", - "spectators": { - "name": "Blast PRO 2021", - "players": { - "76561197987511774": "Anders Blume" - } - }, - "map_list": ["de_dust2", "de_nuke", "de_inferno", "de_mirage", "de_vertigo", "de_ancient", "de_overpass"], - "team1": { - "name": "Natus Vincere", - "tag": "NaVi", - "flag": "UA", - "logo": "nv", - "players": { - "76561198034202275": "s1mple", - "76561198044045107": "electronic", - "76561198246607476": "b1t", - "76561198121220486": "Perfecto", - "76561198040577200": "sdy" + ```typescript title="JSON example with Node.js" + const match_schema: Get5Match = { + "match_title": "Astralis vs. NaVi", + "matchid": "3123", + "clinch_series": true, + "num_maps": 3, + "players_per_team": 5, + "coaches_per_team": 2, + "min_players_to_ready": 2, + "min_spectators_to_ready": 0, + "skip_veto": false, + "veto_first": "team1", + "side_type": "standard", + "spectators": { + "name": "Blast PRO 2021", + "players": { + "76561197987511774": "Anders Blume" + } }, - "coaches": { - "76561198013523865": "B1ad3" - } - }, - "team2": { - "name": "Astralis", - "tag": "Astralis", - "flag": "DK", - "logo": "as", - "players": { - "76561197990682262": "Xyp9x", - "76561198010511021": "gla1ve", - "76561197979669175": "K0nfig", - "76561198028458803": "BlameF", - "76561198024248129": "farlig" + "maplist": ["de_dust2", "de_nuke", "de_inferno", "de_mirage", "de_vertigo", "de_ancient", "de_overpass"], + "map_sides": ["team1_ct", "team2_ct", "knife"] // Example; would only work with "skip_veto": true + "team1": { + "name": "Natus Vincere", + "tag": "NaVi", + "flag": "UA", + "logo": "nv", + "players": { + "76561198034202275": "s1mple", + "76561198044045107": "electronic", + "76561198246607476": "b1t", + "76561198121220486": "Perfecto", + "76561198040577200": "sdy" + }, + "coaches": { + "76561198013523865": "B1ad3" + } }, - "coaches": { - "76561197987144812": "Trace" + "team2": { + "name": "Astralis", + "tag": "Astralis", + "flag": "DK", + "logo": "as", + "players": { + "76561197990682262": "Xyp9x", + "76561198010511021": "gla1ve", + "76561197979669175": "K0nfig", + "76561198028458803": "BlameF", + "76561198024248129": "farlig" + }, + "coaches": { + "76561197987144812": "Trace" + } + }, + "cvars": { + "hostname": "Get5 Match #3123", + "mp_friendly_fire": "0", + "get5_end_match_on_empty_server": "0", + "get5_stop_command_enabled": "0", + "sm_practicemode_can_be_started": "0" } - }, - "cvars": { - "hostname": "Get5 Match #3123", - "mp_friendly_fire": "0", - "get5_end_match_on_empty_server": "0", - "get5_stop_command_enabled": "0", - "sm_practicemode_can_be_started": "0" } -} + + // And the config file could be placed on the server like this: + const json = JSON.stringify(match_schema); + fs.writeFileSync('addons/sourcemod/get5/astralis_vs_navi_3123.json', json); + ``` +=== "KeyValue" -// And the config file could be placed on the server like this: -const json = JSON.stringify(match_schema); -fs.writeFileSync('addons/sourcemod/get5/astralis_vs_navi_3123.json', json); -``` + !!! warning "All strings, no brakes" + + Note that `false` does not exist in the KeyValue format and that all numerical values are wrapped in quotes. The + empty strings as values in dictionaries (`maplist` and `map_sides`) are also required. + + ```cfg title="Valve KeyValue" + "Match" + { + "match_title" "Astralis vs. NaVi" + "matchid" "3123" + "clinch_series" "1" + "num_maps" "3" + "players_per_team" "5" + "coaches_per_team" "2" + "min_players_to_ready" "2" + "min_spectators_to_ready" "0" + "skip_veto" "0" + "veto_first" "team1" + "side_type" "standard" + "spectators" + { + "name" "Blast PRO 2021" + "players" + { + "76561197987511774" "Anders Blume" + } + } + "maplist" + { + "de_dust2" "" + "de_nuke" "" + "de_inferno" "" + "de_mirage" "" + "de_vertigo" "" + "de_ancient" "" + "de_overpass" "" + } + "map_sides" // Example; would only work with "skip_veto" "1" + { + "team1_ct" "" + "team2_ct" "" + "knife" "" + } + "team1" + { + "name" "Natus Vincere" + "tag" "NaVi" + "flag" "UA" + "logo" "nv" + "players" + { + "76561198034202275" "s1mple" + "76561198044045107" "electronic" + "76561198246607476" "b1t" + "76561198121220486" "Perfecto" + "76561198040577200" "sdy" + } + "coaches" + { + "76561198013523865" "B1ad3" + } + } + "team2" + { + "name" "Astralis" + "tag" "Astralis" + "flag" "DK" + "logo" "as" + "players" + { + "76561197990682262" "Xyp9x" + "76561198010511021" "gla1ve" + "76561197979669175" "K0nfig" + "76561198028458803" "BlameF" + "76561198024248129" "farlig" + } + "coaches" + { + "76561197987144812" "Trace" + } + } + "cvars" + { + "hostname" "Get5 Match #3123" + "mp_friendly_fire" "0" + "get5_end_match_on_empty_server" "0" + "get5_stop_command_enabled" "0" + "sm_practicemode_can_be_started" "0" + } + } + ``` \ No newline at end of file diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index ce980d6af..24eb9f4f2 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -59,6 +59,8 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true - pymdownx.highlight: anchor_linenums: true - pymdownx.emoji: From 84e5241a5d97a475ace3ee66f7627ed5dcd21861 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 28 Aug 2022 13:21:38 +0200 Subject: [PATCH 081/104] Don't add rounds played stats for coaches to stats KV and exclude the mysql update for them --- scripting/get5/stats.sp | 20 +++++++++++++++----- scripting/get5_mysqlstats.sp | 3 +++ scripting/include/get5.inc | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 16cbae43d..8a6f9d960 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -250,12 +250,19 @@ public void Stats_ResetGrenadeContainers() { public void Stats_RoundStart() { LOOP_CLIENTS(i) { if (IsPlayer(i)) { + // Ensures that each player has zero-filled stats on freeze-time end. + // Since joining the game after freeze-time will render you dead, you cannot obtain stats + // until next round. + Get5Side side = view_as(GetClientTeam(i)); + if (side == Get5Side_None) { + continue; // Don't do anything to players pending team join. + } Get5Team team = GetClientMatchTeam(i); if (team == Get5Team_1 || team == Get5Team_2) { - // Ensures that each player has zero-filled stats on freeze-time end. - // Since joining the game after freeze-time will render you dead, you cannot obtain stats - // until next round. - InitPlayerStats(i); + InitPlayerStats(i, side); + if (side == Get5Side_Spec) { + continue; // exclude coaches from STAT_ROUNDSPLAYED. + } IncrementPlayerStat(i, STAT_ROUNDSPLAYED); } } @@ -970,7 +977,7 @@ static int SetPlayerStat(int client, const char[] field, int newValue) { return newValue; } -static void InitPlayerStats(int client) { +static void InitPlayerStats(int client, Get5Side side) { if (!GoToPlayer(client)) { return; } @@ -980,6 +987,9 @@ static void InitPlayerStats(int client) { GetClientName(client, name, sizeof(name)); g_StatsKv.SetString(STAT_NAME, name); + // Update if client is coaching. Spectators are excluded as their match team is spec; this checks side only. + g_StatsKv.SetNum(STAT_COACHING, side == Get5Side_Spec); + // If the player already had their stats set, don't override them. if (g_StatsKv.GetNum(STAT_INIT, 0) > 0) { GoBackFromPlayer(); diff --git a/scripting/get5_mysqlstats.sp b/scripting/get5_mysqlstats.sp index af61c7e19..5a9f255a0 100644 --- a/scripting/get5_mysqlstats.sp +++ b/scripting/get5_mysqlstats.sp @@ -235,6 +235,9 @@ public void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyV if (kv.GotoFirstSubKey()) { do { + if (kv.GetNum(STAT_COACHING, 0) > 0) { + continue; // Don't update stats for coaches. + } kv.GetSectionName(auth, sizeof(auth)); kv.GetString("name", name, sizeof(name)); db.Escape(auth, authSz, sizeof(authSz)); diff --git a/scripting/include/get5.inc b/scripting/include/get5.inc index 4bae96a8d..e4672aa11 100644 --- a/scripting/include/get5.inc +++ b/scripting/include/get5.inc @@ -1807,6 +1807,7 @@ forward void Get5_OnBackupRestore(const Get5BackupRestoredEvent event); // Player stats (under map section, then team section, then player's steam64) // If adding stuff here, also add to the InitPlayerStats function! #define STAT_INIT "init" // used to zero-fill stats only. Not a real stat. +#define STAT_COACHING "coaching" // indicates if the player is a coach. #define STAT_NAME "name" #define STAT_KILLS "kills" #define STAT_DEATHS "deaths" From 23af42f1b3c55196578ef05a39bc9eea8b0c91a1 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 28 Aug 2022 20:07:05 +0200 Subject: [PATCH 082/104] Prevent Get5_OnRoundStart from misfiring during backups --- scripting/get5.sp | 10 ++-------- scripting/get5/backups.sp | 13 +++++++++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index ef783ffde..1bfdcb517 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1533,8 +1533,8 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas g_BombPlantedTime = 0.0; g_BombSiteLastPlanted = Get5BombSite_Unknown; - if (g_WaitingForRoundBackup || g_MapChangePending) { - // We don't want g_DoingBackupRestoreNow filtered here, as we need the round start event after restoring a match. + if (IsDoingRestoreOrMapChange()) { + // Get5_OnRoundStart() is fired from within the backup event when loading the valve backup. return; } @@ -1579,18 +1579,12 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas return; } - // We still want to fire the Get5_OnRoundStart event when doing a backup (g_DoingBackupRestoreNow), as this may be - // required to insert the round into a database or event log, as the round is actually starting now and may have been - // deleted when the backup load was requested. Get5RoundStartedEvent startEvent = new Get5RoundStartedEvent(g_MatchID, g_MapNumber, g_RoundNumber); - LogDebug("Calling Get5_OnRoundStart()"); - Call_StartForward(g_OnRoundStart); Call_PushCell(startEvent); Call_Finish(); - EventLogger_LogAndDeleteEvent(startEvent); } diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index ca8ff5ffb..402552e2e 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -431,6 +431,19 @@ public Action Time_StartRestore(Handle timer) { GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); ServerCommand("mp_backup_restore_load_file \"%s\"", tempValveBackup); CreateTimer(0.5, Timer_FinishBackup); + + // We need to fire the OnRoundStarted event manually, as it will be suppressed during backups and won't fire while + // g_DoingBackupRestoreNow is true. + KeyValues kv = new KeyValues("Backup"); + if (kv.ImportFromFile(tempValveBackup)) { + Get5RoundStartedEvent startEvent = new Get5RoundStartedEvent(g_MatchID, g_MapNumber, kv.GetNum("round", 0)); + LogDebug("Calling Get5_OnRoundStart() via backup."); + Call_StartForward(g_OnRoundStart); + Call_PushCell(startEvent); + Call_Finish(); + EventLogger_LogAndDeleteEvent(startEvent); + } + delete kv; } public Action Timer_FinishBackup(Handle timer) { From 60067926f4c61945a1928c9739a818aeba198dd9 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 28 Aug 2022 21:24:22 +0200 Subject: [PATCH 083/104] Doc adjustments --- documentation/docs/index.md | 4 ++-- documentation/docs/installation.md | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/documentation/docs/index.md b/documentation/docs/index.md index 630aa0099..49c8848cc 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -16,11 +16,11 @@ Highlights of Get5 include: - In-game map-veto support from the match's list of maps - Support for multi-map series (Bo1, Bo2, Bo3, Bo5, etc.) - Warmup and [`!ready`](commands/#ready)-system for each team -- Automatic GOTV demo recording +- [Automatic GOTV demo recording](gotv.md) - [Advanced backup system](backup.md) built on top of Valve's backup system - Knifing for sides - [Advanced pausing](pausing.md) support -- Coaching support +- [Coaching](coaching.md) support - Lightweight usage for [scrims](getting_started/#scrims) - [Event logging and SourceMod forwards](events_and_forwards.md) you can interface with, allowing for collection of stats etc. - [Commands](commands/#serveradmin-commands) allow remote management of the plugin diff --git a/documentation/docs/installation.md b/documentation/docs/installation.md index 3ad110347..30c37b5de 100644 --- a/documentation/docs/installation.md +++ b/documentation/docs/installation.md @@ -12,19 +12,27 @@ You can get the latest versions here: Remember to select the correct OS type (Windows/Linux/Mac) for **both** plugins. This should be the OS of the server. -## Download Get5 +## Get5 -The latest release of Get5 can be found [here](https://github.com/splewis/get5/releases/latest). Older Releases of -Get5 can be found in the [Releases](https://github.com/splewis/get5/releases) section of the repo. Anything *not* -marked as "Nightly" in the title are known to be stable, but may be lacking features that are currently in development. -If you would like to test new features, or be on the "bleeding edge", you can also download any of the latest -pre-releases found at the same link above that are marked in the title with "Nightly" or are marked as "Pre-release". +### Latest version {: #latest } + +The latest version of Get5 can be found here. Older releases can be found in +the [Releases](https://github.com/splewis/get5/releases) section of the repository on GitHub. + +[:material-download: Download Get5](https://github.com/splewis/get5/releases/latest){ .md-button .md-button--primary } + +### Test and development {: #development } + +If you would like to test new features, or be on the "bleeding edge", you can also download any of the releases found at +the link above that are marked in the title with **Nightly** or as **Pre-release**. Please note that these versions are +meant for testers and developers and should not be deployed to production servers unless you have a good reason to do +so or can live with the potential consequences. !!! info Get5 itself is OS-agnostic, meaning the same file works on any OS. -## Download SteamWorks (Recommended) {: #steamworks } +## SteamWorks (Recommended) {: #steamworks } SteamWorks is not required for Get5 to work on your game server, however it is required if you wish to [load match configs remotely](../commands#get5_loadmatch_url) or if you want Get5 to [automatically From f93f9378a85311f81458ae011ff052b96a81f57c Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sun, 28 Aug 2022 22:39:09 +0200 Subject: [PATCH 084/104] Translation format errors --- translations/chi/get5.phrases.txt | 2 +- translations/de/get5.phrases.txt | 12 ++++++------ translations/es/get5.phrases.txt | 2 +- translations/fr/get5.phrases.txt | 2 +- translations/pl/get5.phrases.txt | 4 ++-- translations/pt/get5.phrases.txt | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/translations/chi/get5.phrases.txt b/translations/chi/get5.phrases.txt index 7fab2975d..b7e055548 100644 --- a/translations/chi/get5.phrases.txt +++ b/translations/chi/get5.phrases.txt @@ -86,7 +86,7 @@ } "MatchUnpauseInfoMessage" { - "chi" "{1:N}解除了比赛的暂停。" + "chi" "{1}解除了比赛的暂停。" } "WaitingForUnpauseInfoMessage" { diff --git a/translations/de/get5.phrases.txt b/translations/de/get5.phrases.txt index 2a13fabb7..9e9a85aa5 100644 --- a/translations/de/get5.phrases.txt +++ b/translations/de/get5.phrases.txt @@ -2,7 +2,7 @@ { "ReadyToVetoInfoMessage" { - "de" "Tippe {1}wenn dein Team bereit zum Voten ist." + "de" "Tippe {1} wenn dein Team bereit zum Voten ist." } "WaitingForCastersReadyInfoMessage" { @@ -10,19 +10,19 @@ } "ReadyToRestoreBackupInfoMessage" { - "de" "Tippe {1}wenn dein Team bereit ist das Match Backup wieder einzuspielen." + "de" "Tippe {1} wenn dein Team bereit ist das Match Backup wieder einzuspielen." } "ReadyToKnifeInfoMessage" { - "de" "Tippe {1}wenn dein Team bereit ist für die Messer-Runde." + "de" "Tippe {1} wenn dein Team bereit ist für die Messer-Runde." } "ReadyToStartInfoMessage" { - "de" "Tippe {1}wenn dein Team bereit ist zu beginnen." + "de" "Tippe {1} wenn dein Team bereit ist zu beginnen." } "WaitingForEnemySwapInfoMessage" { - "de" "{1} gewinnt die Seitenwahl. Wartend auf {2} oder {3} ." + "de" "{1} gewinnt die Seitenwahl. Wartend auf {2} oder {3}." } "WaitingForGOTVBrodcastEndingInfoMessage" { @@ -74,7 +74,7 @@ } "MatchUnpauseInfoMessage" { - "de" "{1:N} hat das Match fortgesetzt." + "de" "{1} hat das Match fortgesetzt." } "WaitingForUnpauseInfoMessage" { diff --git a/translations/es/get5.phrases.txt b/translations/es/get5.phrases.txt index 0675e3676..c4fa6dc46 100644 --- a/translations/es/get5.phrases.txt +++ b/translations/es/get5.phrases.txt @@ -90,7 +90,7 @@ } "MatchUnpauseInfoMessage" { - "es" "{1:N} terminó su pausa." + "es" "{1} terminó su pausa." } "WaitingForUnpauseInfoMessage" { diff --git a/translations/fr/get5.phrases.txt b/translations/fr/get5.phrases.txt index 30e3d633e..e5e838f30 100644 --- a/translations/fr/get5.phrases.txt +++ b/translations/fr/get5.phrases.txt @@ -90,7 +90,7 @@ } "MatchUnpauseInfoMessage" { - "fr" "{1:N} a mis fin à la pause." + "fr" "{1} a mis fin à la pause." } "WaitingForUnpauseInfoMessage" { diff --git a/translations/pl/get5.phrases.txt b/translations/pl/get5.phrases.txt index 97c2d80f2..26cc6d05a 100644 --- a/translations/pl/get5.phrases.txt +++ b/translations/pl/get5.phrases.txt @@ -74,7 +74,7 @@ } "MatchUnpauseInfoMessage" { - "pl" "{1:N} odpauzowało mecz." + "pl" "{1} odpauzowało mecz." } "WaitingForUnpauseInfoMessage" { @@ -86,7 +86,7 @@ } "TeamFailToReadyMinPlayerCheck" { - "pl" "Musi być conajmniej {1}graczy na serwerze aby móc być gotowym." + "pl" "Musi być conajmniej {1} graczy na serwerze aby móc być gotowym." } "TeamReadyToVetoInfoMessage" { diff --git a/translations/pt/get5.phrases.txt b/translations/pt/get5.phrases.txt index 2c45f7a81..615cb1ffa 100644 --- a/translations/pt/get5.phrases.txt +++ b/translations/pt/get5.phrases.txt @@ -82,7 +82,7 @@ } "MatchUnpauseInfoMessage" { - "pt" "{1:N} resumiu a partida." + "pt" "{1} resumiu a partida." } "WaitingForUnpauseInfoMessage" { From de6e1e73777fec0b9db4dca923909ee568a1ceb9 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 29 Aug 2022 03:24:30 +0200 Subject: [PATCH 085/104] Fix color injection into map veto --- scripting/get5/mapveto.sp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 2ae3cf130..02db88b42 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -295,8 +295,11 @@ public int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int para RemoveStringFromArray(g_MapsLeftInVetoPool, mapName); - Format(mapName, sizeof(mapName), "{LIGHT_RED}%s{NORMAL}", mapName); - Get5_MessageToAll("%t", "TeamVetoedMapInfoMessage", g_FormattedTeamNames[team], mapName); + char formattedMapName[PLATFORM_MAX_PATH]; + FormatMapName(mapName, formattedMapName, sizeof(formattedMapName), true, false); + // Add color here as FormatMapName would make the color green. + Format(formattedMapName, sizeof(formattedMapName), "{LIGHT_RED}%s{NORMAL}", formattedMapName); + Get5_MessageToAll("%t", "TeamVetoedMapInfoMessage", g_FormattedTeamNames[team], formattedMapName); Get5MapVetoedEvent event = new Get5MapVetoedEvent(g_MatchID, team, mapName); @@ -364,7 +367,7 @@ public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int para RemoveStringFromArray(g_MapsLeftInVetoPool, mapName); char mapNameFormatted[PLATFORM_MAX_PATH]; - Format(mapNameFormatted, sizeof(mapNameFormatted), "{GREEN}%s{NORMAL}", mapName); + FormatMapName(mapName, mapNameFormatted, sizeof(mapNameFormatted), true, true); Get5_MessageToAll("%t", "TeamPickedMapInfoMessage", g_FormattedTeamNames[team], mapNameFormatted, g_MapsToPlay.Length); g_LastVetoTeam = team; From 3d8e91ca4f34cc43916a10a30caa619d35c6dcfd Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 29 Aug 2022 04:10:53 +0200 Subject: [PATCH 086/104] Correctly handle veto state if server is empty when match config is loaded. Don't reset pauses on config execute as this happens *after* a backup is loaded if a mapchange is required. --- scripting/get5.sp | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 1bfdcb517..46c078e56 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -719,16 +719,9 @@ public void OnClientPutInServer(int client) { if (CheckAutoLoadConfig()) { return; } - - // Because OnConfigsExecuted may run before a client is on the server, we have to repeat the logic here when the + // Because OnConfigsExecuted may run before a client is on the server, we have to repeat the start-logic here when the // first client connects. - if ((g_GameState <= Get5State_Warmup || g_WaitingForRoundBackup) && g_GameState != Get5State_None) { - if (GetRealClientCount() <= 1) { - ChangeState(Get5State_Warmup); - ExecCfg(g_WarmupCfgCvar); - StartWarmup(); - } - } + SetServerStateOnStartup(false); } public void OnClientPostAdminCheck(int client) { @@ -839,21 +832,16 @@ public void OnConfigsExecuted() { LOOP_TEAMS(team) { g_TeamGivenStopCommand[team] = false; g_TeamReadyForUnpause[team] = false; - // We don't need to check for g_WaitingForRoundBackup here, as a backup will override the pauses consumed anyway; if - // the map is changed, we always load the backup pauses. See the RestoreFromBackup function. - g_TacticalPauseTimeUsed[team] = 0; - g_TacticalPausesUsed[team] = 0; - g_TechnicalPausesUsed[team] = 0; + if (!g_WaitingForRoundBackup) { + g_TacticalPauseTimeUsed[team] = 0; + g_TacticalPausesUsed[team] = 0; + g_TechnicalPausesUsed[team] = 0; + } } // On map start, always put the game in warmup mode. // When executing a backup load, the live config is loaded and warmup ends after players ready-up again. - if (g_GameState != Get5State_None) { - LogDebug("Putting game into warmup in OnConfigsExecuted."); - ChangeState(Get5State_Warmup); - ExecCfg(g_WarmupCfgCvar); - StartWarmup(); - } + SetServerStateOnStartup(true); // This must not be called when waiting for a backup, as it will set the sides incorrectly if the team swapped in // knife or if the backup target is the second half. if (!g_WaitingForRoundBackup) { @@ -1810,6 +1798,25 @@ public void StartGame(bool knifeRound) { } } +void SetServerStateOnStartup(bool force) { + if (g_GameState == Get5State_None) { + return; + } + if (!force && GetRealClientCount() != 1) { + // Only run on first client connect or if forced (during OnConfigsExecuted). + return; + } + if (g_GameState <= Get5State_Warmup || g_WaitingForRoundBackup) { + // If the server is in veto/preveto when someone joins or the configs exec, it should remain in that state. + // This would happen if the a config with veto is loaded before someone joins the server. + if (g_GameState != Get5State_Veto && g_GameState != Get5State_PreVeto) { + ChangeState(Get5State_Warmup); + } + ExecCfg(g_WarmupCfgCvar); + StartWarmup(); + } +} + public void ChangeState(Get5State state) { if (g_GameState == state) { LogDebug("Ignoring request to change game state. Already in state %d.", state); From 7c12eaedf15ae898fe2c127188bc6b6803f5ba72 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 29 Aug 2022 04:21:43 +0200 Subject: [PATCH 087/104] If nobody is on a team, veto controller should return (invalid client index -1 otherwise) --- scripting/get5/mapveto.sp | 1 + 1 file changed, 1 insertion(+) diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 02db88b42..ca6d16af7 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -79,6 +79,7 @@ public void VetoFinished() { public void VetoController(int client) { if (!IsPlayer(client) || GetClientMatchTeam(client) == Get5Team_Spec) { AbortVeto(); + return; } int mapsLeft = g_MapsLeftInVetoPool.Length; From 876eb2d99cbab74ed2514ecfb20f8b5411d3351e Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 29 Aug 2022 15:43:29 +0200 Subject: [PATCH 088/104] Add `fromfile` examples to match schema docs and adjusted formatting/wording --- documentation/docs/match_schema.md | 124 ++++++++++++++++------------- 1 file changed, 69 insertions(+), 55 deletions(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index f0228323b..4c8856cc6 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -16,17 +16,17 @@ better support in various programming languages than Valve's KeyValue format (wh ## The schema {: #schema } ```typescript title="TypeScript interface definition of a match configuration" -type Get5PlayerSteamID = string; // (8) -type Get5PlayerSet = { [key: Get5PlayerSteamID]: string }; // (9) +type SteamID = string // (8) +type Get5PlayerSet = { [key: SteamID]: string } | [SteamID] // (9) interface Get5MatchTeam { - "players": Get5PlayerSet, // (24) + "players": Get5PlayerSet // (24) "coaches": Get5PlayerSet // (23) - "name": string, // (16) - "tag": string, // (17) - "flag": string, // (18) - "logo": string, // (19) - "series_score": number, // (26) + "name": string // (16) + "tag": string // (17) + "flag": string // (18) + "logo": string // (19) + "series_score": number // (26) "matchtext": string // (27) } @@ -36,26 +36,26 @@ interface Get5MatchTeamFromFile { interface Get5Match { "match_title": string // (25) - "matchid": string, // (1) + "matchid": string // (1) "clinch_series": boolean // (32) - "num_maps": number, // (2) - "players_per_team": number, // (3) - "coaches_per_team": number, // (4) - "min_players_to_ready": number, // (5) - "min_spectators_to_ready": number, // (6) - "skip_veto": boolean, // (7), - "veto_first": "team1" | "team2" | "random", // (11) - "side_type": "standard" | "always_knife" | "never_knife", // (12) - "map_sides": ["team1_ct" | "team1_t" | "knife"], // (31) + "num_maps": number // (2) + "players_per_team": number // (3) + "coaches_per_team": number // (4) + "min_players_to_ready": number // (5) + "min_spectators_to_ready": number // (6) + "skip_veto": boolean // (7), + "veto_first": "team1" | "team2" | "random" // (11) + "side_type": "standard" | "always_knife" | "never_knife" // (12) + "map_sides": ["team1_ct" | "team1_t" | "knife"] // (31) "spectators": { // (10) "name": string // (29) "players": Get5PlayerSet // (30) }, - "maplist": [string], // (13) - "favored_percentage_team1": number, // (14) - "favored_percentage_text": string, // (15) - "team1": Get5MatchTeam | Get5MatchTeamFromFile, // (20) - "team2": Get5MatchTeam | Get5MatchTeamFromFile, // (21) + "maplist": [string] // (13) + "favored_percentage_team1": number // (14) + "favored_percentage_text": string // (15) + "team1": Get5MatchTeam | Get5MatchTeamFromFile // (20) + "team2": Get5MatchTeam | Get5MatchTeamFromFile // (21) "cvars": { [key: string]: string } // (22) } ``` @@ -77,8 +77,9 @@ interface Get5Match { not set, sides are determined by `side_type`.

**`Default: false`** 8. A player's :material-steam: Steam ID. This can be in any format, but we recommend a string representation of SteamID 64, i.e. `"76561197987713664"`. -9. Players are represented each with a mapping of `Get5PlayerSteamID -> PlayerName` as a key-value dictionary. The name - is optional and should be set to an empty string to let players decide their own name. +9. Players are represented each with a mapping of `SteamID -> PlayerName` as a key-value dictionary. The name + is optional and should be set to an empty string to let players decide their own name. You can also provide a simple + string array of `SteamID` disable name-locking. 10. _Optional_
The spectators to allow into the game. If not defined, spectators cannot join the game.

**`Default: undefined`** 11. _Optional_
The team that vetoes first.

**`Default: "team1"`** @@ -161,20 +162,7 @@ These examples are identical in the way they would work if loaded. "maplist": ["de_dust2", "de_nuke", "de_inferno", "de_mirage", "de_vertigo", "de_ancient", "de_overpass"], "map_sides": ["team1_ct", "team2_ct", "knife"] // Example; would only work with "skip_veto": true "team1": { - "name": "Natus Vincere", - "tag": "NaVi", - "flag": "UA", - "logo": "nv", - "players": { - "76561198034202275": "s1mple", - "76561198044045107": "electronic", - "76561198246607476": "b1t", - "76561198121220486": "Perfecto", - "76561198040577200": "sdy" - }, - "coaches": { - "76561198013523865": "B1ad3" - } + "fromfile": "team_navi.json" }, "team2": { "name": "Astralis", @@ -205,6 +193,26 @@ These examples are identical in the way they would work if loaded. const json = JSON.stringify(match_schema); fs.writeFileSync('addons/sourcemod/get5/astralis_vs_navi_3123.json', json); ``` + `fromfile` example: + ```typescript title="team_navi.json" + { + "name": "Natus Vincere", + "tag": "NaVi", + "flag": "UA", + "logo": "nv", + "players": { + "76561198034202275": "s1mple", + "76561198044045107": "electronic", + "76561198246607476": "b1t", + "76561198121220486": "Perfecto", + "76561198040577200": "sdy" + }, + "coaches": { + "76561198013523865": "B1ad3" + } + } + ``` + === "KeyValue" !!! warning "All strings, no brakes" @@ -252,22 +260,7 @@ These examples are identical in the way they would work if loaded. } "team1" { - "name" "Natus Vincere" - "tag" "NaVi" - "flag" "UA" - "logo" "nv" - "players" - { - "76561198034202275" "s1mple" - "76561198044045107" "electronic" - "76561198246607476" "b1t" - "76561198121220486" "Perfecto" - "76561198040577200" "sdy" - } - "coaches" - { - "76561198013523865" "B1ad3" - } + "fromfile" "team_navi.cfg" } "team2" { @@ -297,4 +290,25 @@ These examples are identical in the way they would work if loaded. "sm_practicemode_can_be_started" "0" } } + ``` + `fromfile` example: + ```cfg title="team_navi.cfg" + { + "name" "Natus Vincere" + "tag" "NaVi" + "flag" "UA" + "logo" "nv" + "players" + { + "76561198034202275" "s1mple" + "76561198044045107" "electronic" + "76561198246607476" "b1t" + "76561198121220486" "Perfecto" + "76561198040577200" "sdy" + } + "coaches" + { + "76561198013523865" "B1ad3" + } + } ``` \ No newline at end of file From bcaa29e438705f8e7bac48127da3c2ab034963f9 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Mon, 29 Aug 2022 16:18:08 +0200 Subject: [PATCH 089/104] Adjust match config examples to include regular JSON --- documentation/docs/match_schema.md | 124 ++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 28 deletions(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 4c8856cc6..2c4bf3423 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -123,9 +123,10 @@ interface Get5Match { series, `mp_teamscore` cvars are automatically set and take the place of the `mp_teammatchstat_x` cvars.

**`Default: ""`** 28. Match teams can also be loaded from a separate file, allowing you to easily re-use a match configuration for - different sets of teams. A `fromfile` value could be `"addons/sourcemod/configs/get5/team_nip.json"`, and that file - should contain a valid `Get5MatchTeam` object. Note that the file you point to must be in the same format as the - main file, so pointing to a `.cfg` file when the main file is `.json` will **not** work. + different sets of teams. A `fromfile` value could be `"addons/sourcemod/configs/get5/team_nip.json"`, and is always + relative to the `csgo` directory. The file should contain a valid `Get5MatchTeam` object. Note that the file you + point to must be in the same format as the main file, so pointing to a `.cfg` file when the main file is `.json` + will **not** work. 29. _Optional_
The name of the spectator team.

**`Default: "casters"`** 30. _Optional_
The spectator/caster Steam IDs and names. 31. _Optional_
Determines the starting sides for each map. If this array is shorter than `num_maps`, `side_type` will @@ -140,7 +141,93 @@ These examples are identical in the way they would work if loaded. === "JSON (recommended)" - ```typescript title="JSON example with Node.js" + !!! tip "Example only" + + `map_sides` would only work with `skip_veto: true`. + + ```json title="addons/sourcemod/get5/astralis_vs_navi_3123.json" + { + "match_title": "Astralis vs. NaVi", + "matchid": "3123", + "clinch_series": true, + "num_maps": 3, + "players_per_team": 5, + "coaches_per_team": 2, + "min_players_to_ready": 2, + "min_spectators_to_ready": 0, + "skip_veto": false, + "veto_first": "team1", + "side_type": "standard", + "spectators": { + "name": "Blast PRO 2021", + "players": { + "76561197987511774": "Anders Blume" + } + }, + "maplist": [ + "de_dust2", + "de_nuke", + "de_inferno", + "de_mirage", + "de_vertigo", + "de_ancient", + "de_overpass" + ], + "map_sides": [ + "team1_ct", + "team2_ct", + "knife" + ], + "team1": { + "fromfile": "addons/sourcemod/get5/team_navi.json" + }, + "team2": { + "name": "Astralis", + "tag": "Astralis", + "flag": "DK", + "logo": "as", + "players": { + "76561197990682262": "Xyp9x", + "76561198010511021": "gla1ve", + "76561197979669175": "K0nfig", + "76561198028458803": "BlameF", + "76561198024248129": "farlig" + }, + "coaches": { + "76561197987144812": "Trace" + } + }, + "cvars": { + "hostname": "Get5 Match #3123", + "mp_friendly_fire": "0", + "get5_end_match_on_empty_server": "0", + "get5_stop_command_enabled": "0", + "sm_practicemode_can_be_started": "0" + } + } + ``` + `fromfile` example: + ```json title="addons/sourcemod/get5/team_navi.json" + { + "name": "Natus Vincere", + "tag": "NaVi", + "flag": "UA", + "logo": "nv", + "players": { + "76561198034202275": "s1mple", + "76561198044045107": "electronic", + "76561198246607476": "b1t", + "76561198121220486": "Perfecto", + "76561198040577200": "sdy" + }, + "coaches": { + "76561198013523865": "B1ad3" + } + } + ``` + + And in TypeScript, using the above interface definition file: + ```typescript title="Typescript JSON example with Node.js" const match_schema: Get5Match = { "match_title": "Astralis vs. NaVi", "matchid": "3123", @@ -160,9 +247,9 @@ These examples are identical in the way they would work if loaded. } }, "maplist": ["de_dust2", "de_nuke", "de_inferno", "de_mirage", "de_vertigo", "de_ancient", "de_overpass"], - "map_sides": ["team1_ct", "team2_ct", "knife"] // Example; would only work with "skip_veto": true + "map_sides": ["team1_ct", "team2_ct", "knife"], // Example; would only work with "skip_veto": true "team1": { - "fromfile": "team_navi.json" + "fromfile": "addons/sourcemod/get5/team_navi.json" }, "team2": { "name": "Astralis", @@ -193,25 +280,6 @@ These examples are identical in the way they would work if loaded. const json = JSON.stringify(match_schema); fs.writeFileSync('addons/sourcemod/get5/astralis_vs_navi_3123.json', json); ``` - `fromfile` example: - ```typescript title="team_navi.json" - { - "name": "Natus Vincere", - "tag": "NaVi", - "flag": "UA", - "logo": "nv", - "players": { - "76561198034202275": "s1mple", - "76561198044045107": "electronic", - "76561198246607476": "b1t", - "76561198121220486": "Perfecto", - "76561198040577200": "sdy" - }, - "coaches": { - "76561198013523865": "B1ad3" - } - } - ``` === "KeyValue" @@ -220,7 +288,7 @@ These examples are identical in the way they would work if loaded. Note that `false` does not exist in the KeyValue format and that all numerical values are wrapped in quotes. The empty strings as values in dictionaries (`maplist` and `map_sides`) are also required. - ```cfg title="Valve KeyValue" + ```cfg title="addons/sourcemod/get5/astralis_vs_navi_3123.cfg" "Match" { "match_title" "Astralis vs. NaVi" @@ -260,7 +328,7 @@ These examples are identical in the way they would work if loaded. } "team1" { - "fromfile" "team_navi.cfg" + "fromfile" "addons/sourcemod/get5/team_navi.cfg" } "team2" { @@ -292,7 +360,7 @@ These examples are identical in the way they would work if loaded. } ``` `fromfile` example: - ```cfg title="team_navi.cfg" + ```cfg title="addons/sourcemod/get5/team_navi.cfg" { "name" "Natus Vincere" "tag" "NaVi" From 7fa4fa96ad389e42bc1b143b94d9b112ada249b2 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 31 Aug 2022 03:05:28 +0200 Subject: [PATCH 090/104] Dont backup veto state Fix lingering handles to veto menu Reply to backup command if no results --- scripting/get5.sp | 11 ++++++-- scripting/get5/backups.sp | 58 +++++++++++++++++++++------------------ scripting/get5/mapveto.sp | 4 +++ 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 46c078e56..d5423e58e 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1482,6 +1482,13 @@ public void WriteBackup() { return; } + if (g_GameState != Get5State_Warmup + && g_GameState != Get5State_KnifeRound + && g_GameState != Get5State_Live) { + LogDebug("Not writing backup for game state %d.", g_GameState); + return; // Only backup post-veto warmup, knife and live. + } + char folder[PLATFORM_MAX_PATH]; g_RoundBackupPathCvar.GetString(folder, sizeof(folder)); ReplaceString(folder, sizeof(folder), "{MATCHID}", g_MatchID); @@ -1559,9 +1566,7 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas } } - if (g_GameState == Get5State_Warmup || g_GameState == Get5State_KnifeRound || g_GameState == Get5State_Live) { - WriteBackup(); // Filters out backup states on its own - } + WriteBackup(); if (g_GameState != Get5State_Live) { return; diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 402552e2e..9f4ea9c5b 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -46,6 +46,7 @@ public Action Command_ListBackups(int client, int args) { ReplaceString(path, sizeof(path), "{MATCHID}", matchID); DirectoryListing files = OpenDirectory(strlen(path) > 0 ? path : "."); + bool foundBackups = false; if (files != null) { char backupInfo[256]; char pattern[PLATFORM_MAX_PATH]; @@ -54,6 +55,7 @@ public Action Command_ListBackups(int client, int args) { char filename[PLATFORM_MAX_PATH]; while (files.GetNext(filename, sizeof(filename))) { if (StrContains(filename, pattern) == 0) { + foundBackups = true; Format(filename, sizeof(filename), "%s%s", path, filename); if (GetBackupInfo(filename, backupInfo, sizeof(backupInfo))) { ReplyToCommand(client, backupInfo); @@ -65,6 +67,10 @@ public Action Command_ListBackups(int client, int args) { delete files; } + if (!foundBackups) { + ReplyToCommand(client, "Found no backup files matching the provided parameters."); + } + return Plugin_Handled; } @@ -298,38 +304,36 @@ bool RestoreFromBackup(const char[] path, bool restartRecording = true) { g_MapNumber = Get5_GetMapNumber(); char mapName[PLATFORM_MAX_PATH]; - if (g_GameState > Get5State_Veto) { - if (kv.JumpToKey("maps")) { - g_MapsToPlay.Clear(); - g_MapSides.Clear(); - if (kv.GotoFirstSubKey(false)) { - do { - kv.GetSectionName(mapName, sizeof(mapName)); - SideChoice sides = view_as(kv.GetNum(NULL_STRING)); - g_MapsToPlay.PushString(mapName); - g_MapSides.Push(sides); - } while (kv.GotoNextKey(false)); - kv.GoBack(); - } + if (kv.JumpToKey("maps")) { + g_MapsToPlay.Clear(); + g_MapSides.Clear(); + if (kv.GotoFirstSubKey(false)) { + do { + kv.GetSectionName(mapName, sizeof(mapName)); + SideChoice sides = view_as(kv.GetNum(NULL_STRING)); + g_MapsToPlay.PushString(mapName); + g_MapSides.Push(sides); + } while (kv.GotoNextKey(false)); kv.GoBack(); } + kv.GoBack(); + } - if (kv.JumpToKey("map_scores")) { - if (kv.GotoFirstSubKey()) { - do { - char buf[32]; - kv.GetSectionName(buf, sizeof(buf)); - int map = StringToInt(buf); - - int t1 = kv.GetNum("team1"); - int t2 = kv.GetNum("team2"); - g_TeamScoresPerMap.Set(map, t1, view_as(Get5Team_1)); - g_TeamScoresPerMap.Set(map, t2, view_as(Get5Team_2)); - } while (kv.GotoNextKey()); - kv.GoBack(); - } + if (kv.JumpToKey("map_scores")) { + if (kv.GotoFirstSubKey()) { + do { + char buf[32]; + kv.GetSectionName(buf, sizeof(buf)); + int map = StringToInt(buf); + + int t1 = kv.GetNum("team1"); + int t2 = kv.GetNum("team2"); + g_TeamScoresPerMap.Set(map, t1, view_as(Get5Team_1)); + g_TeamScoresPerMap.Set(map, t2, view_as(Get5Team_2)); + } while (kv.GotoNextKey()); kv.GoBack(); } + kv.GoBack(); } if (kv.JumpToKey("stats")) { diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index ca6d16af7..19b8a3a04 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -72,6 +72,7 @@ public void VetoFinished() { } // Always end recording here; ensures that we can successfully start one after veto. StopRecording(delay); + WriteBackup(); // Write first pre-live backup after veto. } // Main Veto Controller @@ -321,6 +322,7 @@ public int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int para } else if (action == MenuAction_End) { delete menu; + g_ActiveVetoMenu = null; } } @@ -393,6 +395,7 @@ public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int para } else if (action == MenuAction_End) { delete menu; + g_ActiveVetoMenu = null; } } @@ -473,5 +476,6 @@ public int SidePickMenuHandler(Menu menu, MenuAction action, int param1, int par } else if (action == MenuAction_End) { delete menu; + g_ActiveVetoMenu = null; } } From 8a3a00d0c4bf207e49e6d19d6fcc6ad5b0a492c5 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 31 Aug 2022 19:06:25 +0200 Subject: [PATCH 091/104] Ensure no events fire if match state is none Don't return Plugin_Continue/Plugin_Handled when not necessary --- scripting/get5.sp | 53 ++++++++++---------------- scripting/get5/stats.sp | 83 +++++++++++++---------------------------- 2 files changed, 46 insertions(+), 90 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index d5423e58e..a2137dbcb 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -974,7 +974,7 @@ static bool CheckAutoLoadConfig() { public Action Command_EndMatch(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "No match is configured; nothing to end."); - return Plugin_Handled; + return; } Get5Team winningTeam = Get5Team_None; // defaults to tie @@ -987,7 +987,7 @@ public Action Command_EndMatch(int client, int args) { winningTeam = Get5Team_2; } else { ReplyToCommand(client, "Usage: get5_endmatch (omit team for tie)"); - return Plugin_Handled; + return; } } @@ -1040,14 +1040,12 @@ public Action Command_EndMatch(int client, int args) { } RestartGame(); - - return Plugin_Handled; } public Action Command_LoadMatch(int client, int args) { if (g_GameState != Get5State_None) { - ReplyToCommand(client, "Cannot load a match when a match is already loaded"); - return Plugin_Handled; + ReplyToCommand(client, "Cannot load a match config when another is already loaded."); + return; } char arg[PLATFORM_MAX_PATH]; @@ -1058,14 +1056,12 @@ public Action Command_LoadMatch(int client, int args) { } else { ReplyToCommand(client, "Usage: get5_loadmatch "); } - - return Plugin_Handled; } public Action Command_LoadMatchUrl(int client, int args) { if (g_GameState != Get5State_None) { - ReplyToCommand(client, "Cannot load a match config with another match already loaded"); - return Plugin_Handled; + ReplyToCommand(client, "Cannot load a match config when another is already loaded."); + return; } bool steamWorksAvaliable = LibraryExists("SteamWorks"); @@ -1084,14 +1080,12 @@ public Action Command_LoadMatchUrl(int client, int args) { ReplyToCommand(client, "Usage: get5_loadmatch_url "); } } - - return Plugin_Handled; } public Action Command_DumpStats(int client, int args) { if (g_GameState == Get5State_None) { - ReplyToCommand(client, "Cannot dump match stats with no match existing"); - return Plugin_Handled; + ReplyToCommand(client, "Cannot dump match stats when no match is loaded."); + return; } char arg[PLATFORM_MAX_PATH]; @@ -1107,8 +1101,6 @@ public Action Command_DumpStats(int client, int args) { } else { ReplyToCommand(client, "Failed to save match stats to %s", arg); } - - return Plugin_Handled; } public Action Command_Stop(int client, int args) { @@ -1192,7 +1184,7 @@ public Action Timer_ReplenishMoney(Handle timer, int client) { public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_MatchOver"); if (g_GameState == Get5State_None) { - return Plugin_Continue; + return; } // This ensures that the mp_match_restart_delay is not shorter @@ -1256,7 +1248,7 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast // clinch config. if (remainingMaps <= 0) { EndSeries(Get5Team_None, true, restartDelay); - return Plugin_Continue; + return; } } else if (g_SeriesCanClinch) { // This adjusts for ties! @@ -1264,16 +1256,16 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast if (t1maps == actualMapsToWin) { // Team 1 won EndSeries(Get5Team_1, true, restartDelay); - return Plugin_Continue; + return; } else if (t2maps == actualMapsToWin) { // Team 2 won EndSeries(Get5Team_2, true, restartDelay); - return Plugin_Continue; + return; } } else if (remainingMaps <= 0) { EndSeries(t1maps > t2maps ? Get5Team_1 : Get5Team_2, true, restartDelay); // Tie handled in first if-block - return Plugin_Continue; + return; } if (t1maps > t2maps) { @@ -1303,8 +1295,6 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast // second built-in delay in the ChangeMap function called by Timer_NextMatchMap. CreateTimer(restartDelay - 4, Timer_NextMatchMap); } - - return Plugin_Continue; } public Action Timer_NextMatchMap(Handle timer) { @@ -1399,6 +1389,9 @@ public Action Timer_RestoreMatchCvars(Handle timer) { public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundPreStart"); + if (g_GameState == Get5State_None) { + return; + } if (g_GameState == Get5State_Live) { // End lingering grenade trackers from previous round. @@ -1420,8 +1413,6 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad g_MapNumber = Get5_GetMapNumber(); // Round number always -1 if not live. g_RoundNumber = g_GameState != Get5State_Live ? -1 : GetRoundsPlayed(); - - return Plugin_Continue; } public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast) { @@ -1528,7 +1519,7 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas g_BombPlantedTime = 0.0; g_BombSiteLastPlanted = Get5BombSite_Unknown; - if (IsDoingRestoreOrMapChange()) { + if (g_GameState == Get5State_None || IsDoingRestoreOrMapChange()) { // Get5_OnRoundStart() is fired from within the backup event when loading the valve backup. return; } @@ -1650,13 +1641,12 @@ public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad } event.SetInt("final_event", ConvertCSTeamToDefaultWinReason(winningCSTeam)); } - return Plugin_Continue; } public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundEnd"); - if (IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + if (g_GameState == Get5State_None || IsDoingRestoreOrMapChange()) { + return; } if (g_GameState == Get5State_WaitingForKnifeRoundDecision && g_KnifeWinnerTeam != Get5Team_None) { @@ -1665,7 +1655,7 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) // We override this event only to have the correct audio callout in the game. event.SetInt("winner", winningCSTeam); event.SetInt("reason", ConvertCSTeamToDefaultWinReason(winningCSTeam)); - return Plugin_Continue; + return; } if (g_GameState == Get5State_Live) { @@ -1751,7 +1741,6 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) g_TeamGivenStopCommand[t] = false; } } - return Plugin_Continue; } public void SwapSides() { @@ -1779,8 +1768,6 @@ public Action Event_CvarChanged(Event event, const char[] name, bool dontBroadca event.BroadcastDisabled = true; } } - - return Plugin_Continue; } public void StartGame(bool knifeRound) { diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 8a6f9d960..8f9204990 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -417,13 +417,13 @@ public void EndFlashbangEvent(const char[] flashKey) { public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int attacker = GetClientOfUserId(event.GetInt("userid")); if (!IsValidClient(attacker)) { - return Plugin_Continue; + return; } Get5DecoyStartedEvent decoyObject = new Get5DecoyStartedEvent( @@ -436,20 +436,18 @@ public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontB Call_Finish(); EventLogger_LogAndDeleteEvent(decoyObject); - - return Plugin_Continue; } public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int attacker = GetClientOfUserId(event.GetInt("userid")); if (!IsValidClient(attacker)) { g_LatestMolotovToExtinguishBySmoke = 0; // If someone disconnects after throwing grenade. - return Plugin_Continue; + return; } Get5SmokeDetonatedEvent smokeEvent = new Get5SmokeDetonatedEvent( @@ -464,18 +462,16 @@ public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bo // Reset this so other smokes don't get extinguish attribution. g_LatestMolotovToExtinguishBySmoke = 0; - - return Plugin_Continue; } public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } if (g_LatestUserIdToDetonateMolotov == 0) { // If user disconnected after throwing the molotov, this will be 0. - return Plugin_Continue; + return; } int entityId = event.GetInt("entityid"); @@ -492,13 +488,11 @@ public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool d GetPlayerObject(g_LatestUserIdToDetonateMolotov) // Set in molotov detonate event ), true); - - return Plugin_Continue; } public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int entityId = event.GetInt("entityid"); @@ -508,15 +502,13 @@ public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, boo g_LatestMolotovToExtinguishBySmoke = entityId; LogDebug("Molotov Event: %s, %d", name, entityId); - - return Plugin_Continue; } public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontBroadcast) { // No backup check; the event is deleted in EndMolotovEvent to prevent leaks, as this function works like the // the HE/flash timer callbacks which also do not check for backup state. if (g_GameState != Get5State_Live) { - return Plugin_Continue; + return; } int entityId = event.GetInt("entityid"); @@ -527,13 +519,11 @@ public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontB IntToString(entityId, molotovKey, sizeof(molotovKey)); EndMolotovEvent(molotovKey); - - return Plugin_Continue; } public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int attacker = GetClientOfUserId(event.GetInt("userid")); @@ -543,23 +533,21 @@ public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool do if (!IsValidClient(attacker)) { // Could happen if someone disconnects after throwing a grenade, but before it pops. g_LatestUserIdToDetonateMolotov = 0; - return Plugin_Continue; + return; } g_LatestUserIdToDetonateMolotov = attacker; - - return Plugin_Continue; } public Action Stats_FlashbangDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int attacker = GetClientOfUserId(event.GetInt("userid")); if (!IsValidClient(attacker)) { - return Plugin_Continue; + return; } int entityId = event.GetInt("entityid"); @@ -572,8 +560,6 @@ public Action Stats_FlashbangDetonateEvent(Event event, const char[] name, bool g_FlashbangContainer.SetValue(flashKey, flashEvent, true); CreateTimer(0.001, Timer_HandleFlashbang, entityId, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; } public Action Timer_HandleFlashbang(Handle timer, int entityId) { @@ -587,13 +573,13 @@ public Action Timer_HandleFlashbang(Handle timer, int entityId) { public Action Stats_HEGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int attacker = GetClientOfUserId(event.GetInt("userid")); if (!IsValidClient(attacker)) { - return Plugin_Continue; + return; } int entityId = event.GetInt("entityid"); @@ -606,8 +592,6 @@ public Action Stats_HEGrenadeDetonateEvent(Event event, const char[] name, bool g_HEGrenadeContainer.SetValue(grenadeKey, grenadeObject, true); CreateTimer(0.001, Timer_HandleHEGrenade, entityId, TIMER_FLAG_NO_MAPCHANGE); - - return Plugin_Continue; } public Action Timer_HandleHEGrenade(Handle timer, int entityId) { @@ -621,13 +605,13 @@ public Action Timer_HandleHEGrenade(Handle timer, int entityId) { public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int attacker = GetClientOfUserId(event.GetInt("userid")); if (!IsValidClient(attacker)) { - return Plugin_Continue; + return; } char weapon[32]; @@ -644,13 +628,11 @@ public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dont Call_Finish(); EventLogger_LogAndDeleteEvent(grenadeEvent); - - return Plugin_Continue; } public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBroadcast) { if (IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int attacker = GetClientOfUserId(event.GetInt("attacker")); @@ -659,7 +641,7 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr // HandleReadyCommand checks for game state, so we don't need to do that here as well. HandleReadyCommand(attacker, true); } - return Plugin_Continue; + return; } int victim = GetClientOfUserId(event.GetInt("userid")); @@ -670,8 +652,7 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr bool validAssister = IsValidClient(assister); if (!validVictim) { - return Plugin_Continue; // Not sure how this would happen, but it's not something we care - // about. + return; // Not sure how this would happen, but it's not something we care about. } // Update "clutch" (1vx) data structures to check if the clutcher wins the round @@ -796,8 +777,6 @@ public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBr Call_Finish(); EventLogger_LogAndDeleteEvent(playerDeathEvent); - - return Plugin_Continue; } static void UpdateTradeStat(int attacker, int victim) { @@ -817,7 +796,7 @@ static void UpdateTradeStat(int attacker, int victim) { public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } g_BombPlantedTime = GetEngineTime(); @@ -840,13 +819,11 @@ public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBr EventLogger_LogAndDeleteEvent(bombEvent); } - - return Plugin_Continue; } public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int client = GetClientOfUserId(event.GetInt("userid")); @@ -873,13 +850,11 @@ public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBr EventLogger_LogAndDeleteEvent(defuseEvent); } - - return Plugin_Continue; } public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } Get5BombExplodedEvent bombExplodedEvent = new Get5BombExplodedEvent( @@ -892,13 +867,11 @@ public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontB Call_Finish(); EventLogger_LogAndDeleteEvent(bombExplodedEvent); - - return Plugin_Continue; } public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } float duration = event.GetFloat("blind_duration"); @@ -906,12 +879,12 @@ public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBr int attacker = GetClientOfUserId(event.GetInt("attacker")); if (!IsValidClient(attacker) || !IsValidClient(victim)) { - return Plugin_Continue; + return; } int victimTeam = GetClientTeam(victim); if (victimTeam == CS_TEAM_SPECTATOR || victimTeam == CS_TEAM_NONE) { - return Plugin_Continue; + return; } bool friendlyFire = GetClientTeam(attacker) == victimTeam; @@ -934,13 +907,11 @@ public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBr new Get5BlindedGrenadeVictim(GetPlayerObject(victim), friendlyFire, duration)); } } - - return Plugin_Continue; } public Action Stats_RoundMVPEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { - return Plugin_Continue; + return; } int client = GetClientOfUserId(event.GetInt("userid")); @@ -959,8 +930,6 @@ public Action Stats_RoundMVPEvent(Event event, const char[] name, bool dontBroad EventLogger_LogAndDeleteEvent(mvpEvent); } - - return Plugin_Continue; } static int GetPlayerStat(int client, const char[] field) { From 7980c87e283eec306f14ef3bc676ac8b41bf542a Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 31 Aug 2022 23:10:03 +0200 Subject: [PATCH 092/104] Cancel veto countdown if match is ended More defensive reset of g_ActiveVetoMenu --- scripting/get5/mapveto.sp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 19b8a3a04..3ef267454 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -22,6 +22,10 @@ public void CreateVeto() { public Action Timer_VetoCountdown(Handle timer) { static int warningsPrinted = 0; + if (g_GameState != Get5State_Veto) { + warningsPrinted = 0; + return Plugin_Stop; + } if (warningsPrinted >= g_VetoCountdownCvar.IntValue) { warningsPrinted = 0; Get5Team startingTeam = OtherMatchTeam(g_LastVetoTeam); @@ -43,6 +47,9 @@ static void AbortVeto() { FormatChatCommand(readyCommandFormatted, sizeof(readyCommandFormatted), "!ready"); Get5_MessageToAll("%t", "ReadyToResumeVetoInfoMessage", readyCommandFormatted); ChangeState(Get5State_PreVeto); + if (g_ActiveVetoMenu != null) { + g_ActiveVetoMenu.Cancel(); + } } public void VetoFinished() { @@ -279,6 +286,9 @@ public void GiveMapVetoMenu(int client) { public int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { + if (g_GameState != Get5State_Veto) { + return; + } int client = param1; Get5Team team = GetClientMatchTeam(client); char mapName[PLATFORM_MAX_PATH]; @@ -319,10 +329,11 @@ public int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int para if (g_GameState == Get5State_Veto) { AbortVeto(); } - } else if (action == MenuAction_End) { + if (menu == g_ActiveVetoMenu) { + g_ActiveVetoMenu = null; + } delete menu; - g_ActiveVetoMenu = null; } } @@ -350,6 +361,9 @@ public void GiveMapPickMenu(int client) { public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { + if (g_GameState != Get5State_Veto) { + return; + } int client = param1; Get5Team team = GetClientMatchTeam(client); char mapName[PLATFORM_MAX_PATH]; @@ -392,10 +406,11 @@ public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int para if (g_GameState == Get5State_Veto) { AbortVeto(); } - } else if (action == MenuAction_End) { + if (menu == g_ActiveVetoMenu) { + g_ActiveVetoMenu = null; + } delete menu; - g_ActiveVetoMenu = null; } } @@ -416,6 +431,9 @@ public void GiveSidePickMenu(int client) { public int SidePickMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { + if (g_GameState != Get5State_Veto) { + return; + } int client = param1; Get5Team team = GetClientMatchTeam(client); char choice[PLATFORM_MAX_PATH]; @@ -473,9 +491,10 @@ public int SidePickMenuHandler(Menu menu, MenuAction action, int param1, int par if (g_GameState == Get5State_Veto) { AbortVeto(); } - } else if (action == MenuAction_End) { + if (menu == g_ActiveVetoMenu) { + g_ActiveVetoMenu = null; + } delete menu; - g_ActiveVetoMenu = null; } } From 749febe6eaf3721ef529765a668df5f9cd1fd86e Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Wed, 31 Aug 2022 23:16:26 +0200 Subject: [PATCH 093/104] Unpause game if veto is aborted --- scripting/get5/mapveto.sp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 3ef267454..4a3e4c13a 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -50,6 +50,9 @@ static void AbortVeto() { if (g_ActiveVetoMenu != null) { g_ActiveVetoMenu.Cancel(); } + if (IsPaused()) { + UnpauseGame(Get5Team_None); + } } public void VetoFinished() { From 5c1b82fbbe93ff038b15a5968864c0e26beec272 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 1 Sep 2022 00:50:18 +0200 Subject: [PATCH 094/104] Adjust docs for min_players_to_ready --- documentation/docs/match_schema.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index 2c4bf3423..fca124626 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -69,8 +69,9 @@ interface Get5Match { 3. _Optional_
The number of players per team. You should **never** set this to a value higher than the number of players you want to actually play in a game, *excluding* coaches.

**`Default: 5`** 4. _Optional_
The maximum number of [coaches](coaching.md) per team.

**`Default: 2`** -5. _Optional_
The minimum number of players from each team that must type [`!ready`](../commands/#ready) for the game - to begin.

**`Default: 1`** +5. _Optional_
The minimum number of players that must be present for the [`!forceready`](../commands/#forceready) + command to succeed. If not forcing a team ready, **all** players must [`!ready`](../commands/#ready) up + themselves.

**`Default: 0`** 6. _Optional_
The minimum number of spectators that must be [`!ready`](../commands/#ready) for the game to begin.

**`Default: 0`** 7. _Optional_
Whether to skip the veto phase. When skipping veto, `map_sides` determines sides, and if `map_sides` is @@ -379,4 +380,4 @@ These examples are identical in the way they would work if loaded. "76561198013523865" "B1ad3" } } - ``` \ No newline at end of file + ``` From 49cee157804fbd7ad8da01c145de68bfdd59faca Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 1 Sep 2022 02:41:17 +0200 Subject: [PATCH 095/104] Put OnConfigsExecuted on a timer callback to prevent problems with get5_autoload_config when hibernation is enabled Fix wrong logos in example config --- documentation/docs/match_schema.md | 10 +++++----- scripting/get5.sp | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/documentation/docs/match_schema.md b/documentation/docs/match_schema.md index fca124626..8e020f2d8 100644 --- a/documentation/docs/match_schema.md +++ b/documentation/docs/match_schema.md @@ -186,7 +186,7 @@ These examples are identical in the way they would work if loaded. "name": "Astralis", "tag": "Astralis", "flag": "DK", - "logo": "as", + "logo": "astr", "players": { "76561197990682262": "Xyp9x", "76561198010511021": "gla1ve", @@ -213,7 +213,7 @@ These examples are identical in the way they would work if loaded. "name": "Natus Vincere", "tag": "NaVi", "flag": "UA", - "logo": "nv", + "logo": "navi", "players": { "76561198034202275": "s1mple", "76561198044045107": "electronic", @@ -256,7 +256,7 @@ These examples are identical in the way they would work if loaded. "name": "Astralis", "tag": "Astralis", "flag": "DK", - "logo": "as", + "logo": "astr", "players": { "76561197990682262": "Xyp9x", "76561198010511021": "gla1ve", @@ -336,7 +336,7 @@ These examples are identical in the way they would work if loaded. "name" "Astralis" "tag" "Astralis" "flag" "DK" - "logo" "as" + "logo" "astr" "players" { "76561197990682262" "Xyp9x" @@ -366,7 +366,7 @@ These examples are identical in the way they would work if loaded. "name" "Natus Vincere" "tag" "NaVi" "flag" "UA" - "logo" "nv" + "logo" "navi" "players" { "76561198034202275" "s1mple" diff --git a/scripting/get5.sp b/scripting/get5.sp index a2137dbcb..0f5f1449f 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -706,6 +706,7 @@ public void RememberAndKickClient(int client, const char[] format, const char[] } public void OnClientPutInServer(int client) { + LogDebug("OnClientPutInServer"); Stats_HookDamageForClient(client); // Also needed for bots! if (IsFakeClient(client)) { return; @@ -811,6 +812,16 @@ public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr // This runs every time a map starts *or* when the plugin is reloaded. public void OnConfigsExecuted() { LogDebug("OnConfigsExecuted"); + // If the server has hibernation enabled, running this without a delay will cause it to frequently fail with + // "Gamerules lookup failed" probably due to some odd internal race-condition where the game is not yet running + // when we attempt to determine its "is paused" or "is in warmup" state. Putting it on a 1 second callback seems + // to solve this problem. + CreateTimer(1.0, Timer_ConfigsExecutedCallback); +} + +public Action Timer_ConfigsExecutedCallback(Handle timer) { + LogDebug("OnConfigsExecuted timer callback"); + g_MapChangePending = false; g_DoingBackupRestoreNow = false; g_ReadyTimeWaitingUsed = 0; From 3fc38ed271e4becdd5364dac2d67fa3d512ba277 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Thu, 1 Sep 2022 20:37:36 +0200 Subject: [PATCH 096/104] Remove redundant calls to IsClientConnected and IsClientInGame --- scripting/get5/natives.sp | 6 +++--- scripting/get5/stats.sp | 2 +- scripting/get5/util.sp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripting/get5/natives.sp b/scripting/get5/natives.sp index 8f3036840..c69393223 100644 --- a/scripting/get5/natives.sp +++ b/scripting/get5/natives.sp @@ -31,7 +31,7 @@ public int Native_GetGameState(Handle plugin, int numParams) { public int Native_Message(Handle plugin, int numParams) { int client = GetNativeCell(1); - if (client != 0 && (!IsClientConnected(client) || !IsClientInGame(client))) + if (client != 0 && !IsClientInGame(client)) return; char buffer[1024]; @@ -51,7 +51,7 @@ public int Native_Message(Handle plugin, int numParams) { if (client == 0) { Colorize(finalMsg, sizeof(finalMsg), false); PrintToConsole(client, finalMsg); - } else if (IsClientInGame(client)) { + } else { Colorize(finalMsg, sizeof(finalMsg)); PrintToChat(client, finalMsg); } @@ -92,7 +92,7 @@ public int Native_MessageToAll(Handle plugin, int numParams) { // Don't use LOOP_CLIENTS(i) because we need client 0 here. for (int i = 0; i <= MaxClients; i++) { - if (i != 0 && (!IsClientConnected(i) || !IsClientInGame(i))) { + if (i != 0 && !IsClientInGame(i)) { continue; } diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index 8f9204990..eee7c18f3 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -1171,7 +1171,7 @@ public void PrintDamageInfo(int client) { int otherTeam = (team == CS_TEAM_T) ? CS_TEAM_CT : CS_TEAM_T; LOOP_CLIENTS(i) { - if (IsValidClient(i) && IsClientInGame(i) && GetClientTeam(i) == otherTeam) { + if (IsValidClient(i) && GetClientTeam(i) == otherTeam) { int health = IsPlayerAlive(i) ? GetClientHealth(i) : 0; char name[64]; GetClientName(i, name, sizeof(name)); diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 9b06e3a13..1c4f5d214 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -86,7 +86,7 @@ stock void SwitchPlayerTeam(int client, Get5Side side, bool useDefaultTeamSelect * Returns if a client is valid. */ stock bool IsValidClient(int client) { - return client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client); + return client > 0 && client <= MaxClients && IsClientInGame(client); } stock bool IsPlayer(int client) { From cb98d19f783d920d4d077a3c47099b57547ee1c5 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 2 Sep 2022 01:22:20 +0200 Subject: [PATCH 097/104] Invert team ready tag vs name to avoid "[" team name UI bug --- scripting/get5/util.sp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 1c4f5d214..759d1f712 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -235,9 +235,9 @@ stock void SetTeamInfo(int csTeam, const char[] name, const char[] flag = "", !g_DoingBackupRestoreNow) { Get5Team matchTeam = CSTeamToGet5Team(csTeam); if (IsTeamReady(matchTeam)) { - Format(taggedName, sizeof(taggedName), "%T %s", "ReadyTag", LANG_SERVER, name); + Format(taggedName, sizeof(taggedName), "%s %T", name, "ReadyTag", LANG_SERVER); } else { - Format(taggedName, sizeof(taggedName), "%T %s", "NotReadyTag", LANG_SERVER, name); + Format(taggedName, sizeof(taggedName), "%s %T", name, "NotReadyTag", LANG_SERVER); } } else { strcopy(taggedName, sizeof(taggedName), name); From f1dfeefbfabca304656996a7bbebc512773dc00a Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 2 Sep 2022 01:35:45 +0200 Subject: [PATCH 098/104] Fix team placement race conditions related to match autoloading via get5_autoload_config --- scripting/get5.sp | 8 +------- scripting/get5/matchconfig.sp | 18 +++++++++++++++++- scripting/get5/teamlogic.sp | 17 +++++++---------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 0f5f1449f..d8e70ebfa 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -711,15 +711,9 @@ public void OnClientPutInServer(int client) { if (IsFakeClient(client)) { return; } - // If a player joins during freezetime, ensure their round stats are 0, as there will be no round-start event to do it. // Maybe this could just be freezetime end? Stats_ResetClientRoundValues(client); - - // This checks for gamestate none and pending backup on its own. - if (CheckAutoLoadConfig()) { - return; - } // Because OnConfigsExecuted may run before a client is on the server, we have to repeat the start-logic here when the // first client connects. SetServerStateOnStartup(false); @@ -963,7 +957,7 @@ static bool CheckReadyWaitingTime(Get5Team team) { return false; } -static bool CheckAutoLoadConfig() { +bool CheckAutoLoadConfig() { if (g_GameState == Get5State_None && !g_WaitingForRoundBackup) { char autoloadConfig[PLATFORM_MAX_PATH]; g_AutoLoadConfigCvar.GetString(autoloadConfig, sizeof(autoloadConfig)); diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index cff67f773..88893cc39 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -157,9 +157,19 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { // We must also have called SetStartingTeams to get the sides right. // When restoring from backup, assigning to teams is done after loading the match config as it depends on the sides // being set correctly by the backup, so we put it inside this "if" here. + // When the match is loaded, we do not want to assign players on no team, as they may be in the process of joining + // the server, which is the reason for the timer callback. This has caused problems with players getting stuck on + // no team when using match config autoload, essentially recreating the "coaching bug". Adding a second seems to + // solve this problem. We cannot just skip team none, as players may also just be on the team selection menu when + // the match is loaded, meaning they will never have a joingame hook, as it already happened, and we still want + // those players placed. LOOP_CLIENTS(i) { if (IsPlayer(i)) { - CheckClientTeam(i); + if (GetClientTeam(i) == CS_TEAM_NONE) { + CreateTimer(1.0, Timer_PlacePlayerFromTeamNone, i, TIMER_FLAG_NO_MAPCHANGE); + } else { + CheckClientTeam(i); + } } } } @@ -170,6 +180,12 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { return true; } +static Action Timer_PlacePlayerFromTeamNone(Handle timer, int client) { + if (g_GameState != Get5State_None && IsPlayer(client)) { + CheckClientTeam(client); + } +} + public bool LoadMatchFile(const char[] config) { Get5PreloadMatchConfigEvent event = new Get5PreloadMatchConfigEvent(config); diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index 4c83eff7b..84b72f83d 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -1,13 +1,10 @@ -public Action Command_JoinGame(int client, const char[] command, int argc) { - if (g_GameState != Get5State_None && g_CheckAuthsCvar.BoolValue && IsPlayer(client)) { - CreateTimer(0.1, Timer_PlacePlayerOnJoin, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); +Action Command_JoinGame(int client, const char[] command, int argc) { + LogDebug("Client %d sent joingame command.", client); + if (CheckAutoLoadConfig()) { + // Autoload places players on teams. + return; } - return Plugin_Continue; -} - -public Action Timer_PlacePlayerOnJoin(Handle timer, int userId) { - int client = GetClientOfUserId(userId); - if (client) { // Client might have disconnected between timer and callback. + if (g_GameState != Get5State_None && g_CheckAuthsCvar.BoolValue && IsPlayer(client)) { PlacePlayerOnTeam(client); } } @@ -74,7 +71,7 @@ static void PlacePlayerOnTeam(int client) { CheckClientTeam(client); } -public Action Command_JoinTeam(int client, const char[] command, int argc) { +Action Command_JoinTeam(int client, const char[] command, int argc) { if (g_GameState == Get5State_None || !g_CheckAuthsCvar.BoolValue) { return Plugin_Continue; } From 750a81eed5a1d6ae144b30888e70c7fe0e122ec2 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 2 Sep 2022 01:49:24 +0200 Subject: [PATCH 099/104] Correctly color player name with team --- scripting/get5/pausing.sp | 8 ++++---- scripting/get5/readysystem.sp | 2 +- scripting/get5/teamlogic.sp | 5 +++-- scripting/get5/util.sp | 9 ++------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/scripting/get5/pausing.sp b/scripting/get5/pausing.sp index ad0d9c7a0..6b170e944 100644 --- a/scripting/get5/pausing.sp +++ b/scripting/get5/pausing.sp @@ -124,7 +124,7 @@ public Action Command_TechPause(int client, int args) { PauseGame(team, Get5PauseType_Tech); char formattedClientName[MAX_NAME_LENGTH]; - FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client, team); Get5_MessageToAll("%t", "MatchTechPausedByTeamMessage", formattedClientName); if (maxTechPauses > 0) { Get5_MessageToAll("%t", "TechPausePausesRemaining", g_FormattedTeamNames[team], @@ -185,7 +185,7 @@ public Action Command_Pause(int client, int args) { if (IsPlayer(client)) { char formattedClientName[MAX_NAME_LENGTH]; - FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client, team); Get5_MessageToAll("%t", "MatchPausedByTeamMessage", formattedClientName); } @@ -235,7 +235,7 @@ public Action Command_Unpause(int client, int args) { UnpauseGame(team); if (IsPlayer(client)) { char formattedClientName[MAX_NAME_LENGTH]; - FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client, team); Get5_MessageToAll("%t", "MatchUnpauseInfoMessage", formattedClientName); } return Plugin_Handled; @@ -248,7 +248,7 @@ public Action Command_Unpause(int client, int args) { UnpauseGame(team); if (IsPlayer(client)) { char formattedClientName[MAX_NAME_LENGTH]; - FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client, team); Get5_MessageToAll("%t", "MatchUnpauseInfoMessage", formattedClientName); } } else if (!g_TeamReadyForUnpause[Get5Team_2]) { diff --git a/scripting/get5/readysystem.sp b/scripting/get5/readysystem.sp index 90d9bdd0c..6dfb6dcef 100644 --- a/scripting/get5/readysystem.sp +++ b/scripting/get5/readysystem.sp @@ -212,7 +212,7 @@ public Action Command_ForceReadyClient(int client, int args) { return Plugin_Handled; } char formattedClientName[MAX_NAME_LENGTH]; - FormatPlayerName(formattedClientName, sizeof(formattedClientName), client); + FormatPlayerName(formattedClientName, sizeof(formattedClientName), client, team); LOOP_CLIENTS(i) { if (IsPlayer(i) && GetClientMatchTeam(i) == team) { SetClientReady(i, true); diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index 84b72f83d..f2831a546 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -100,8 +100,9 @@ void SetClientCoaching(int client, Get5Side side) { SetEntProp(client, Prop_Send, "m_iAccount", 0); // Ensures coaches have no money if they were to rejoin the game. char formattedPlayerName[MAX_NAME_LENGTH]; - FormatPlayerName(formattedPlayerName, sizeof(formattedPlayerName), client, side); - Get5_MessageToAll("%t", "PlayerIsCoachingTeam", formattedPlayerName, g_FormattedTeamNames[GetClientMatchTeam(client)]); + Get5Team team = GetClientMatchTeam(client); + FormatPlayerName(formattedPlayerName, sizeof(formattedPlayerName), client, team); + Get5_MessageToAll("%t", "PlayerIsCoachingTeam", formattedPlayerName, g_FormattedTeamNames[team]); } public void CoachingChangedHook(ConVar convar, const char[] oldValue, const char[] newValue) { diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 759d1f712..69dc322ad 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -128,14 +128,9 @@ stock void FormatCvarName(char[] buffer, const int bufferLength, const char[] cV Format(buffer, bufferLength, "{GRAY}%s{NORMAL}", cVar); } -stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client, const Get5Side forcedSide = Get5Side_None) { +stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client, const Get5Team team) { // Used when injecting the team for coaching players, who are always on team spectator. - Get5Side side; - if (forcedSide == Get5Side_None) { - side = view_as(IsClientInGame(client) ? GetClientTeam(client) : CS_TEAM_NONE); - } else { - side = forcedSide; - } + Get5Side side = view_as(Get5_Get5TeamToCSTeam(team)); if (side == Get5Side_CT) { Format(buffer, bufferLength, "{LIGHT_BLUE}%N{NORMAL}", client); } else if (side == Get5Side_T) { From e1ce0c96fc4d49db6a6afe6041b94c55b8b25421 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 2 Sep 2022 01:54:50 +0200 Subject: [PATCH 100/104] Console color should just replace colors, not put in white --- scripting/get5/util.sp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 69dc322ad..7d391f6af 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -113,7 +113,7 @@ stock int GetRealClientCount() { stock void Colorize(char[] msg, int size, bool stripColor = false) { for (int i = 0; i < sizeof(_colorNames); i++) { if (stripColor) { - ReplaceString(msg, size, _colorNames[i], "\x01"); // replace with white + ReplaceString(msg, size, _colorNames[i], ""); // replace with no color tag } else { ReplaceString(msg, size, _colorNames[i], _colorCodes[i]); } From 79314a4500661800139ab8d76ee46fb4e7dab5c3 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 2 Sep 2022 03:12:00 +0200 Subject: [PATCH 101/104] Colorize strip parameter was inverted --- scripting/get5/natives.sp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripting/get5/natives.sp b/scripting/get5/natives.sp index c69393223..aebb20bdd 100644 --- a/scripting/get5/natives.sp +++ b/scripting/get5/natives.sp @@ -49,7 +49,7 @@ public int Native_Message(Handle plugin, int numParams) { Format(finalMsg, sizeof(finalMsg), "%s %s", prefix, buffer); if (client == 0) { - Colorize(finalMsg, sizeof(finalMsg), false); + Colorize(finalMsg, sizeof(finalMsg), true); PrintToConsole(client, finalMsg); } else { Colorize(finalMsg, sizeof(finalMsg)); @@ -105,12 +105,12 @@ public int Native_MessageToAll(Handle plugin, int numParams) { else Format(finalMsg, sizeof(finalMsg), "%s %s", prefix, buffer); - if (i != 0) { + if (i == 0) { + Colorize(finalMsg, sizeof(finalMsg), true); + PrintToConsole(i, finalMsg); + } else { Colorize(finalMsg, sizeof(finalMsg)); PrintToChat(i, finalMsg); - } else { - Colorize(finalMsg, sizeof(finalMsg), false); - PrintToConsole(i, finalMsg); } } } From b8c61517622e37abacaad8e0620f51670ce3ec47 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 2 Sep 2022 15:04:53 +0200 Subject: [PATCH 102/104] Remove incorrect visibility modifiers Move WriteBackup(), CreateDirectoryWithPermissions() and CreateBackupFolderStructure() to backup.sp Move IsClientCoaching() to teamlogic.sp Remove unused parameter from CheckForChatAlias Remove unused method GetMapWinner() Make all functions in util.sp stock --- scripting/get5.sp | 154 ++++++++------------------------- scripting/get5/backups.sp | 94 ++++++++++++++++++-- scripting/get5/chatcommands.sp | 6 +- scripting/get5/debug.sp | 2 +- scripting/get5/get5menu.sp | 8 +- scripting/get5/goinglive.sp | 4 +- scripting/get5/kniferounds.sp | 16 ++-- scripting/get5/maps.sp | 4 +- scripting/get5/mapveto.sp | 22 ++--- scripting/get5/matchconfig.sp | 42 ++++----- scripting/get5/pausing.sp | 18 ++-- scripting/get5/readysystem.sp | 46 +++++----- scripting/get5/recording.sp | 6 +- scripting/get5/stats.sp | 80 ++++++++--------- scripting/get5/teamlogic.sp | 16 ++-- scripting/get5/tests.sp | 4 +- scripting/get5/util.sp | 18 ++-- scripting/get5_apistats.sp | 16 ++-- scripting/get5_mysqlstats.sp | 8 +- 19 files changed, 277 insertions(+), 287 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index d8e70ebfa..7a9ae0b98 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -626,7 +626,7 @@ public void OnPluginStart() { CheckForLatestVersion(); } -public Action Timer_InfoMessages(Handle timer) { +static Action Timer_InfoMessages(Handle timer) { if (g_GameState == Get5State_Live || g_GameState == Get5State_None) { return Plugin_Continue; } @@ -700,7 +700,7 @@ public void OnClientAuthorized(int client, const char[] auth) { } } -public void RememberAndKickClient(int client, const char[] format, const char[] translationPhrase) { +void RememberAndKickClient(int client, const char[] format, const char[] translationPhrase) { GetAuth(client, g_LastKickedPlayerAuth, sizeof(g_LastKickedPlayerAuth)); KickClient(client, format, translationPhrase); } @@ -747,7 +747,7 @@ public void OnClientSayCommand_Post(int client, const char[] command, const char EventLogger_LogAndDeleteEvent(event); } } - CheckForChatAlias(client, command, sArgs); + CheckForChatAlias(client, sArgs); } /** @@ -756,7 +756,7 @@ public void OnClientSayCommand_Post(int client, const char[] command, const char * if a player does not select a team but leaves their mouse over one, they are * put on that team and spawned, so we can't allow that. */ -public Action Event_PlayerConnectFull(Event event, const char[] name, bool dontBroadcast) { +static Action Event_PlayerConnectFull(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if (IsValidClient(client)) { char ipAddress[32]; @@ -777,7 +777,7 @@ public Action Event_PlayerConnectFull(Event event, const char[] name, bool dontB } } -public Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) { +static Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if (client > 0) { @@ -813,7 +813,7 @@ public void OnConfigsExecuted() { CreateTimer(1.0, Timer_ConfigsExecutedCallback); } -public Action Timer_ConfigsExecutedCallback(Handle timer) { +static Action Timer_ConfigsExecutedCallback(Handle timer) { LogDebug("OnConfigsExecuted timer callback"); g_MapChangePending = false; @@ -854,7 +854,7 @@ public Action Timer_ConfigsExecutedCallback(Handle timer) { } } -public Action Timer_CheckReady(Handle timer) { +static Action Timer_CheckReady(Handle timer) { if (g_GameState == Get5State_None) { return Plugin_Continue; } @@ -976,7 +976,7 @@ bool CheckAutoLoadConfig() { * Client and server commands. */ -public Action Command_EndMatch(int client, int args) { +static Action Command_EndMatch(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "No match is configured; nothing to end."); return; @@ -1047,7 +1047,7 @@ public Action Command_EndMatch(int client, int args) { RestartGame(); } -public Action Command_LoadMatch(int client, int args) { +static Action Command_LoadMatch(int client, int args) { if (g_GameState != Get5State_None) { ReplyToCommand(client, "Cannot load a match config when another is already loaded."); return; @@ -1063,7 +1063,7 @@ public Action Command_LoadMatch(int client, int args) { } } -public Action Command_LoadMatchUrl(int client, int args) { +static Action Command_LoadMatchUrl(int client, int args) { if (g_GameState != Get5State_None) { ReplyToCommand(client, "Cannot load a match config when another is already loaded."); return; @@ -1087,7 +1087,7 @@ public Action Command_LoadMatchUrl(int client, int args) { } } -public Action Command_DumpStats(int client, int args) { +static Action Command_DumpStats(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "Cannot dump match stats when no match is loaded."); return; @@ -1108,7 +1108,7 @@ public Action Command_DumpStats(int client, int args) { } } -public Action Command_Stop(int client, int args) { +static Action Command_Stop(int client, int args) { if (!g_StopCommandEnabledCvar.BoolValue) { Get5_MessageToAll("%t", "StopCommandNotEnabled"); return Plugin_Handled; @@ -1149,7 +1149,7 @@ public Action Command_Stop(int client, int args) { return Plugin_Handled; } -public void RestoreLastRound(int client) { +void RestoreLastRound(int client) { LOOP_TEAMS(x) { g_TeamGivenStopCommand[x] = false; } @@ -1173,20 +1173,20 @@ public void RestoreLastRound(int client) { * Game Events *not* related to the stats tracking system. */ -public Action Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { +static Action Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_None && g_GameState < Get5State_KnifeRound) { int client = GetClientOfUserId(event.GetInt("userid")); CreateTimer(0.1, Timer_ReplenishMoney, client, TIMER_FLAG_NO_MAPCHANGE); } } -public Action Timer_ReplenishMoney(Handle timer, int client) { +static Action Timer_ReplenishMoney(Handle timer, int client) { if (IsPlayer(client) && OnActiveTeam(client)) { SetEntProp(client, Prop_Send, "m_iAccount", GetCvarIntSafe("mp_maxmoney")); } } -public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast) { +static Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_MatchOver"); if (g_GameState == Get5State_None) { return; @@ -1302,7 +1302,7 @@ public Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast } } -public Action Timer_NextMatchMap(Handle timer) { +Action Timer_NextMatchMap(Handle timer) { char map[PLATFORM_MAX_PATH]; g_MapsToPlay.GetString(Get5_GetMapNumber(), map, sizeof(map)); // If you change these 3 seconds for whatever reason, you must adjust the counter-offset in @@ -1310,7 +1310,7 @@ public Action Timer_NextMatchMap(Handle timer) { ChangeMap(map, 3.0); } -void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay, +static void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay, bool kickPlayers = true) { Stats_SeriesEnd(winningTeam); @@ -1365,7 +1365,7 @@ void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay } } -public Action Timer_KickOnEnd(Handle timer) { +static Action Timer_KickOnEnd(Handle timer) { if (g_GameState == Get5State_None) { // If a match was started before this event is triggered, don't do anything. KickPlayers(); @@ -1383,7 +1383,7 @@ static void KickPlayers() { } } -public Action Timer_RestoreMatchCvars(Handle timer) { +static Action Timer_RestoreMatchCvars(Handle timer) { if (g_GameState == Get5State_None) { // Only reset if no game is running, otherwise a game started before the restart delay for // another ends will mess this up. @@ -1392,7 +1392,7 @@ public Action Timer_RestoreMatchCvars(Handle timer) { return Plugin_Handled; } -public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroadcast) { +static Action Event_RoundPreStart(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundPreStart"); if (g_GameState == Get5State_None) { return; @@ -1420,7 +1420,7 @@ public Action Event_RoundPreStart(Event event, const char[] name, bool dontBroad g_RoundNumber = g_GameState != Get5State_Live ? -1 : GetRoundsPlayed(); } -public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast) { +static Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_FreezeEnd"); // If someone changes the map while in a pause, we have to make sure we reset this state, as the @@ -1437,85 +1437,7 @@ public Action Event_FreezeEnd(Event event, const char[] name, bool dontBroadcast } } -static bool CreateDirectoryWithPermissions(const char[] directory) { - LogDebug("Creating directory: %s", directory); - return CreateDirectory(directory, // sets 777 permissions. - FPERM_U_READ | FPERM_U_WRITE | FPERM_U_EXEC | FPERM_G_READ | - FPERM_G_WRITE | FPERM_G_EXEC | FPERM_O_READ | FPERM_O_WRITE | - FPERM_O_EXEC); -} - -static bool CreateBackupFolderStructure(const char[] path) { - if (strlen(path) == 0 || DirExists(path)) { - return true; - } - - LogDebug("Creating backup directory %s because it does not exist.", path); - char folders[16][PLATFORM_MAX_PATH]; // {folder1, folder2, etc} - char fullFolderPath[PLATFORM_MAX_PATH] = - ""; // initially empty, but we append every time a folder is created/verified - char currentFolder[PLATFORM_MAX_PATH]; // shorthand for folders[i] - - ExplodeString(path, "/", folders, sizeof(folders), PLATFORM_MAX_PATH, true); - for (int i = 0; i < sizeof(folders); i++) { - currentFolder = folders[i]; - if (strlen(currentFolder) == - 0) { // as the loop is a fixed size, we stop when there are no more pieces. - break; - } - // Append the current folder to the full path - Format(fullFolderPath, sizeof(fullFolderPath), "%s%s/", fullFolderPath, currentFolder); - if (!DirExists(fullFolderPath) && !CreateDirectoryWithPermissions(fullFolderPath)) { - LogError("Failed to create or verify existence of directory: %s", fullFolderPath); - return false; - } - } - return true; -} - -public void WriteBackup() { - if (!g_BackupSystemEnabledCvar.BoolValue || IsDoingRestoreOrMapChange()) { - return; - } - - if (g_GameState != Get5State_Warmup - && g_GameState != Get5State_KnifeRound - && g_GameState != Get5State_Live) { - LogDebug("Not writing backup for game state %d.", g_GameState); - return; // Only backup post-veto warmup, knife and live. - } - - char folder[PLATFORM_MAX_PATH]; - g_RoundBackupPathCvar.GetString(folder, sizeof(folder)); - ReplaceString(folder, sizeof(folder), "{MATCHID}", g_MatchID); - - int backupFolderLength = strlen(folder); - if (backupFolderLength > 0 && - (folder[0] == '/' || folder[0] == '.' || folder[backupFolderLength - 1] != '/' || - StrContains(folder, "//") != -1)) { - LogError( - "get5_backup_path must end with a slash and must not start with a slash or dot. It will be reset to an empty string! Current value: %s", - folder); - folder = ""; - g_RoundBackupPathCvar.SetString(folder, false, false); - } else { - CreateBackupFolderStructure(folder); - } - - char path[PLATFORM_MAX_PATH]; - if (g_GameState == Get5State_Live) { - Format(path, sizeof(path), "%sget5_backup_match%s_map%d_round%d.cfg", folder, g_MatchID, - g_MapNumber, g_RoundNumber); - } else { - Format(path, sizeof(path), "%sget5_backup_match%s_map%d_prelive.cfg", folder, g_MatchID, - g_MapNumber); - } - LogDebug("Writing backup to %s", path); - WriteBackupStructure(path); - g_LastGet5BackupCvar.SetString(path); -} - -public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { +static Action Event_RoundStart(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundStart"); // Always reset these on round start, regardless of game state. @@ -1547,7 +1469,7 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas LogDebug("Changed to live."); ChangeState(Get5State_Live); RestartGame(); - CreateTimer(3.0, MatchLive, _, TIMER_FLAG_NO_MAPCHANGE); + CreateTimer(3.0, Timer_MatchLive, _, TIMER_FLAG_NO_MAPCHANGE); return; // Next round start will take care of below, such as writing backup. } } @@ -1577,7 +1499,7 @@ public Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas EventLogger_LogAndDeleteEvent(startEvent); } -public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroadcast) { +static Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundWinPanel"); if (g_GameState == Get5State_KnifeRound && g_HasKnifeRoundStarted) { g_HasKnifeRoundStarted = false; @@ -1648,7 +1570,7 @@ public Action Event_RoundWinPanel(Event event, const char[] name, bool dontBroad } } -public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { +static Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { LogDebug("Event_RoundEnd"); if (g_GameState == Get5State_None || IsDoingRestoreOrMapChange()) { return; @@ -1748,7 +1670,7 @@ public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) } } -public void SwapSides() { +static void SwapSides() { LogDebug("SwapSides"); int tmp = g_TeamSide[Get5Team_1]; g_TeamSide[Get5Team_1] = g_TeamSide[Get5Team_2]; @@ -1765,7 +1687,7 @@ public void SwapSides() { /** * Silences cvar changes when executing live/knife/warmup configs, *unless* it's sv_cheats. */ -public Action Event_CvarChanged(Event event, const char[] name, bool dontBroadcast) { +static Action Event_CvarChanged(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_None) { char cvarName[MAX_CVAR_LENGTH]; event.GetString("cvarname", cvarName, sizeof(cvarName)); @@ -1775,7 +1697,7 @@ public Action Event_CvarChanged(Event event, const char[] name, bool dontBroadca } } -public void StartGame(bool knifeRound) { +static void StartGame(bool knifeRound) { LogDebug("StartGame"); if (knifeRound) { @@ -1795,7 +1717,7 @@ public void StartGame(bool knifeRound) { } } -void SetServerStateOnStartup(bool force) { +static void SetServerStateOnStartup(bool force) { if (g_GameState == Get5State_None) { return; } @@ -1814,7 +1736,7 @@ void SetServerStateOnStartup(bool force) { } } -public void ChangeState(Get5State state) { +void ChangeState(Get5State state) { if (g_GameState == state) { LogDebug("Ignoring request to change game state. Already in state %d.", state); return; @@ -1835,7 +1757,7 @@ public void ChangeState(Get5State state) { g_GameState = state; } -public Action Command_Status(int client, int args) { +static Action Command_Status(int client, int args) { Get5Status status = new Get5Status(PLUGIN_VERSION, g_GameState, IsPaused()); if (g_GameState != Get5State_None) { @@ -1876,7 +1798,7 @@ static Get5StatusTeam GetTeamInfo(Get5Team team) { IsTeamReady(team), view_as(side), GetNumHumansOnTeam(side)); } -public bool FormatCvarString(ConVar cvar, char[] buffer, int len) { +bool FormatCvarString(ConVar cvar, char[] buffer, int len) { cvar.GetString(buffer, len); if (StrEqual(buffer, "")) { return false; @@ -1917,11 +1839,11 @@ public bool FormatCvarString(ConVar cvar, char[] buffer, int len) { // Formats a temp file path based ont he server id. The pattern parameter is expected to have a %d // token in it. -public void GetTempFilePath(char[] path, int len, const char[] pattern) { +void GetTempFilePath(char[] path, int len, const char[] pattern) { Format(path, len, pattern, g_ServerIdCvar.IntValue); } -public int GetRoundTime() { +int GetRoundTime() { int time = GetMilliSecondsPassedSince(g_RoundStartedTime); if (time < 0) { return 0; @@ -1929,7 +1851,7 @@ public int GetRoundTime() { return time; } -public void EventLogger_LogAndDeleteEvent(Get5Event event) { +void EventLogger_LogAndDeleteEvent(Get5Event event) { int options = g_PrettyPrintJsonCvar.BoolValue ? JSON_ENCODE_PRETTY : 0; int bufferSize = event.EncodeSize(options); @@ -1958,7 +1880,7 @@ public void EventLogger_LogAndDeleteEvent(Get5Event event) { json_cleanup_and_delete(event); } -stock void CheckForLatestVersion() { +static void CheckForLatestVersion() { // both x.y.z-dev and x.y.z-abcdef contain a single dash, so we can look for that. g_RunningPrereleaseVersion = StrContains(PLUGIN_VERSION, "-", true) > -1; if (g_RunningPrereleaseVersion) { @@ -1979,7 +1901,7 @@ release version to remove this message."); SteamWorks_SendHTTPRequest(req); } -stock int VersionCheckRequestCallback(Handle request, bool failure, bool requestSuccessful, +static int VersionCheckRequestCallback(Handle request, bool failure, bool requestSuccessful, EHTTPStatusCode statusCode) { if (failure || !requestSuccessful) { LogError("Failed to check for Get5 update. HTTP error code: %d.", statusCode); diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 9f4ea9c5b..76237a73f 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -2,7 +2,7 @@ #define TEMP_VALVE_BACKUP_PATTERN "get5_temp_backup%d.txt" #define TEMP_VALVE_NAMES_FILE_PATTERN "get5_names%d.txt" -public Action Command_LoadBackup(int client, int args) { +Action Command_LoadBackup(int client, int args) { if (!g_BackupSystemEnabledCvar.BoolValue) { ReplyToCommand(client, "The backup system is disabled."); return Plugin_Handled; @@ -28,7 +28,7 @@ public Action Command_LoadBackup(int client, int args) { return Plugin_Handled; } -public Action Command_ListBackups(int client, int args) { +Action Command_ListBackups(int client, int args) { if (!g_BackupSystemEnabledCvar.BoolValue) { ReplyToCommand(client, "The backup system is disabled"); return Plugin_Handled; @@ -74,7 +74,7 @@ public Action Command_ListBackups(int client, int args) { return Plugin_Handled; } -bool GetBackupInfo(const char[] path, char[] info, int maxlength) { +static bool GetBackupInfo(const char[] path, char[] info, int maxlength) { KeyValues kv = new KeyValues("Backup"); if (!kv.ImportFromFile(path)) { LogError("Failed to find or read backup file \"%s\"", path); @@ -151,7 +151,85 @@ bool GetBackupInfo(const char[] path, char[] info, int maxlength) { return true; } -void WriteBackupStructure(const char[] path) { +void WriteBackup() { + if (!g_BackupSystemEnabledCvar.BoolValue || IsDoingRestoreOrMapChange()) { + return; + } + + if (g_GameState != Get5State_Warmup + && g_GameState != Get5State_KnifeRound + && g_GameState != Get5State_Live) { + LogDebug("Not writing backup for game state %d.", g_GameState); + return; // Only backup post-veto warmup, knife and live. + } + + char folder[PLATFORM_MAX_PATH]; + g_RoundBackupPathCvar.GetString(folder, sizeof(folder)); + ReplaceString(folder, sizeof(folder), "{MATCHID}", g_MatchID); + + int backupFolderLength = strlen(folder); + if (backupFolderLength > 0 && + (folder[0] == '/' || folder[0] == '.' || folder[backupFolderLength - 1] != '/' || + StrContains(folder, "//") != -1)) { + LogError( + "get5_backup_path must end with a slash and must not start with a slash or dot. It will be reset to an empty string! Current value: %s", + folder); + folder = ""; + g_RoundBackupPathCvar.SetString(folder, false, false); + } else { + CreateBackupFolderStructure(folder); + } + + char path[PLATFORM_MAX_PATH]; + if (g_GameState == Get5State_Live) { + Format(path, sizeof(path), "%sget5_backup_match%s_map%d_round%d.cfg", folder, g_MatchID, + g_MapNumber, g_RoundNumber); + } else { + Format(path, sizeof(path), "%sget5_backup_match%s_map%d_prelive.cfg", folder, g_MatchID, + g_MapNumber); + } + LogDebug("Writing backup to %s", path); + WriteBackupStructure(path); + g_LastGet5BackupCvar.SetString(path); +} + +static bool CreateDirectoryWithPermissions(const char[] directory) { + LogDebug("Creating directory: %s", directory); + return CreateDirectory(directory, // sets 777 permissions. + FPERM_U_READ | FPERM_U_WRITE | FPERM_U_EXEC | FPERM_G_READ | + FPERM_G_WRITE | FPERM_G_EXEC | FPERM_O_READ | FPERM_O_WRITE | + FPERM_O_EXEC); +} + +static bool CreateBackupFolderStructure(const char[] path) { + if (strlen(path) == 0 || DirExists(path)) { + return true; + } + + LogDebug("Creating backup directory %s because it does not exist.", path); + char folders[16][PLATFORM_MAX_PATH]; // {folder1, folder2, etc} + char fullFolderPath[PLATFORM_MAX_PATH] = + ""; // initially empty, but we append every time a folder is created/verified + char currentFolder[PLATFORM_MAX_PATH]; // shorthand for folders[i] + + ExplodeString(path, "/", folders, sizeof(folders), PLATFORM_MAX_PATH, true); + for (int i = 0; i < sizeof(folders); i++) { + currentFolder = folders[i]; + if (strlen(currentFolder) == + 0) { // as the loop is a fixed size, we stop when there are no more pieces. + break; + } + // Append the current folder to the full path + Format(fullFolderPath, sizeof(fullFolderPath), "%s%s/", fullFolderPath, currentFolder); + if (!DirExists(fullFolderPath) && !CreateDirectoryWithPermissions(fullFolderPath)) { + LogError("Failed to create or verify existence of directory: %s", fullFolderPath); + return false; + } + } + return true; +} + +static void WriteBackupStructure(const char[] path) { KeyValues kv = new KeyValues("Backup"); char timeString[PLATFORM_MAX_PATH]; FormatTime(timeString, sizeof(timeString), "%Y-%m-%d %H:%M:%S", GetTime()); @@ -413,7 +491,7 @@ void RestoreGet5Backup(bool restartRecording = true) { PauseGame(Get5Team_None, Get5PauseType_Backup); g_DoingBackupRestoreNow = true; // reset after the backup has completed, suppresses various events and hooks until then. g_WaitingForRoundBackup = false; - CreateTimer(1.5, Time_StartRestore); + CreateTimer(1.5, Timer_StartRestore); if (restartRecording) { // Since a backup command forces the recording to stop, we restart it here once the backup has completed. // We have to do this on a delay, as when loading from a live game, the backup will already be recording and must @@ -422,14 +500,14 @@ void RestoreGet5Backup(bool restartRecording = true) { } } -public Action Timer_StartRecordingAfterBackup(Handle timer) { +static Action Timer_StartRecordingAfterBackup(Handle timer) { if (g_GameState != Get5State_Live) { return; } StartRecording(); } -public Action Time_StartRestore(Handle timer) { +static Action Timer_StartRestore(Handle timer) { ChangeState(Get5State_Live); char tempValveBackup[PLATFORM_MAX_PATH]; GetTempFilePath(tempValveBackup, sizeof(tempValveBackup), TEMP_VALVE_BACKUP_PATTERN); @@ -450,7 +528,7 @@ public Action Time_StartRestore(Handle timer) { delete kv; } -public Action Timer_FinishBackup(Handle timer) { +static Action Timer_FinishBackup(Handle timer) { // This ensures that coaches are moved to their slots. LOOP_CLIENTS(i) { if (IsPlayer(i)) { diff --git a/scripting/get5/chatcommands.sp b/scripting/get5/chatcommands.sp index f7305d119..39ca648a0 100644 --- a/scripting/get5/chatcommands.sp +++ b/scripting/get5/chatcommands.sp @@ -1,4 +1,4 @@ -public void AddAliasedCommand(const char[] command, ConCmd callback, const char[] description) { +void AddAliasedCommand(const char[] command, ConCmd callback, const char[] description) { char smCommandBuffer[COMMAND_LENGTH]; Format(smCommandBuffer, sizeof(smCommandBuffer), "sm_%s", command); RegConsoleCmd(smCommandBuffer, callback, description); @@ -8,7 +8,7 @@ public void AddAliasedCommand(const char[] command, ConCmd callback, const char[ AddChatAlias(dotCommandBuffer, smCommandBuffer); } -public void AddChatAlias(const char[] alias, const char[] command) { +static void AddChatAlias(const char[] alias, const char[] command) { // Don't allow duplicate aliases to be added. if (g_ChatAliases.FindString(alias) == -1) { g_ChatAliases.PushString(alias); @@ -16,7 +16,7 @@ public void AddChatAlias(const char[] alias, const char[] command) { } } -public void CheckForChatAlias(int client, const char[] command, const char[] sArgs) { +void CheckForChatAlias(int client, const char[] sArgs) { // No chat aliases are needed if the game isn't setup at all. if (g_GameState == Get5State_None) { return; diff --git a/scripting/get5/debug.sp b/scripting/get5/debug.sp index 6373da5b8..bfc645fca 100644 --- a/scripting/get5/debug.sp +++ b/scripting/get5/debug.sp @@ -1,7 +1,7 @@ // TODO: Also try to write the original match config file. // Also consider the last K lines from the most recent errors_* file? -public Action Command_DebugInfo(int client, int args) { +Action Command_DebugInfo(int client, int args) { char path[PLATFORM_MAX_PATH + 1]; if (args == 0 || !GetCmdArg(1, path, sizeof(path))) { diff --git a/scripting/get5/get5menu.sp b/scripting/get5/get5menu.sp index 2f78db77b..a36e9b4de 100644 --- a/scripting/get5/get5menu.sp +++ b/scripting/get5/get5menu.sp @@ -1,7 +1,7 @@ // TODO: Add translations for this. // TODO: Add admin top menu integration. -public Action Command_Get5AdminMenu(int client, int args) { +Action Command_Get5AdminMenu(int client, int args) { Menu menu = new Menu(AdminMenuHandler); menu.SetTitle("Get5 Admin Menu"); @@ -33,7 +33,7 @@ static int EnabledIf(bool cond) { return cond ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED; } -public int AdminMenuHandler(Menu menu, MenuAction action, int param1, int param2) { +static int AdminMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { int client = param1; char infoString[64]; @@ -52,7 +52,7 @@ public int AdminMenuHandler(Menu menu, MenuAction action, int param1, int param2 } } -public void GiveRingerMenu(int client) { +static void GiveRingerMenu(int client) { Menu menu = new Menu(RingerMenuHandler); menu.SetTitle("Switch scrim team status"); menu.ExitButton = true; @@ -70,7 +70,7 @@ public void GiveRingerMenu(int client) { menu.Display(client, MENU_TIME_FOREVER); } -public int RingerMenuHandler(Menu menu, MenuAction action, int param1, int param2) { +static int RingerMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { int client = param1; char infoString[64]; diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index 1cb0f0fd7..a8e7aee06 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -19,7 +19,7 @@ void StartGoingLive() { CreateTimer(1.0, Timer_GoToLiveAfterWarmupCountdown, _, TIMER_FLAG_NO_MAPCHANGE); } -public Action Timer_GoToLiveAfterWarmupCountdown(Handle timer) { +static Action Timer_GoToLiveAfterWarmupCountdown(Handle timer) { if (g_GameState != Get5State_GoingLive) { return Plugin_Handled; // super defensive race-condition check. } @@ -36,7 +36,7 @@ public Action Timer_GoToLiveAfterWarmupCountdown(Handle timer) { return Plugin_Handled; } -public Action MatchLive(Handle timer) { +Action Timer_MatchLive(Handle timer) { if (g_GameState != Get5State_Live) { return Plugin_Handled; } diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index deffdee4e..f8c008a87 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -1,4 +1,4 @@ -public Action StartKnifeRound(Handle timer) { +Action StartKnifeRound(Handle timer) { g_HasKnifeRoundStarted = false; // Removes ready tags @@ -15,7 +15,7 @@ public Action StartKnifeRound(Handle timer) { return Plugin_Handled; } -public Action Timer_AnnounceKnife(Handle timer) { +static Action Timer_AnnounceKnife(Handle timer) { g_KnifeCountdownTimer = INVALID_HANDLE; AnnouncePhaseChange("{GREEN}%t", "KnifeInfoMessage"); @@ -67,7 +67,7 @@ static void PerformSideSwap(bool swap) { SetMatchTeamCvars(); } -public void EndKnifeRound(bool swap) { +static void EndKnifeRound(bool swap) { PerformSideSwap(swap); Get5KnifeRoundWonEvent knifeEvent = @@ -97,7 +97,7 @@ static bool AwaitingKnifeDecision(int client) { return waiting && (onWinningTeam || admin); } -public Action Command_Stay(int client, int args) { +Action Command_Stay(int client, int args) { if (AwaitingKnifeDecision(client)) { Get5_MessageToAll("%t", "TeamDecidedToStayInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); @@ -106,7 +106,7 @@ public Action Command_Stay(int client, int args) { return Plugin_Handled; } -public Action Command_Swap(int client, int args) { +Action Command_Swap(int client, int args) { if (AwaitingKnifeDecision(client)) { Get5_MessageToAll("%t", "TeamDecidedToSwapInfoMessage", g_FormattedTeamNames[g_KnifeWinnerTeam]); @@ -118,7 +118,7 @@ public Action Command_Swap(int client, int args) { return Plugin_Handled; } -public Action Command_Ct(int client, int args) { +Action Command_Ct(int client, int args) { if (IsPlayer(client)) { if (GetClientTeam(client) == CS_TEAM_CT) FakeClientCommand(client, "sm_stay"); @@ -133,7 +133,7 @@ public Action Command_Ct(int client, int args) { return Plugin_Handled; } -public Action Command_T(int client, int args) { +Action Command_T(int client, int args) { if (IsPlayer(client)) { if (GetClientTeam(client) == CS_TEAM_T) FakeClientCommand(client, "sm_stay"); @@ -143,7 +143,7 @@ public Action Command_T(int client, int args) { return Plugin_Handled; } -public Action Timer_ForceKnifeDecision(Handle timer) { +Action Timer_ForceKnifeDecision(Handle timer) { g_KnifeDecisionTimer = INVALID_HANDLE; if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { Get5_MessageToAll("%t", "TeamLostTimeToDecideInfoMessage", diff --git a/scripting/get5/maps.sp b/scripting/get5/maps.sp index 119eadd29..35ae5240b 100644 --- a/scripting/get5/maps.sp +++ b/scripting/get5/maps.sp @@ -1,4 +1,4 @@ -stock void ChangeMap(const char[] map, float delay = 3.0) { +void ChangeMap(const char[] map, float delay = 3.0) { char formattedMapName[64]; FormatMapName(map, formattedMapName, sizeof(formattedMapName), true, true); Get5_MessageToAll("%t", "ChangingMapInfoMessage", formattedMapName); @@ -11,7 +11,7 @@ stock void ChangeMap(const char[] map, float delay = 3.0) { CreateTimer(delay, Timer_DelayedChangeMap, data); } -public Action Timer_DelayedChangeMap(Handle timer, Handle pack) { +static Action Timer_DelayedChangeMap(Handle timer, Handle pack) { char map[PLATFORM_MAX_PATH]; ResetPack(pack); ReadPackString(pack, map, sizeof(map)); diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 4a3e4c13a..0cc8e9950 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -4,7 +4,7 @@ #define CONFIRM_NEGATIVE_VALUE "_" -public void CreateVeto() { +void CreateVeto() { if (g_MapPoolList.Length % 2 == 0) { LogError( "Warning, the maplist is even number sized (%d maps), vetos may not function correctly!", @@ -20,7 +20,7 @@ public void CreateVeto() { CreateTimer(1.0, Timer_VetoCountdown, _, TIMER_REPEAT); } -public Action Timer_VetoCountdown(Handle timer) { +static Action Timer_VetoCountdown(Handle timer) { static int warningsPrinted = 0; if (g_GameState != Get5State_Veto) { warningsPrinted = 0; @@ -55,7 +55,7 @@ static void AbortVeto() { } } -public void VetoFinished() { +static void VetoFinished() { ChangeState(Get5State_Warmup); Get5_MessageToAll("%t", "MapDecidedInfoMessage"); @@ -87,7 +87,7 @@ public void VetoFinished() { // Main Veto Controller -public void VetoController(int client) { +static void VetoController(int client) { if (!IsPlayer(client) || GetClientMatchTeam(client) == Get5Team_Spec) { AbortVeto(); return; @@ -209,7 +209,7 @@ public void VetoController(int client) { // Confirmations -public void GiveConfirmationMenu(int client, MenuHandler handler, const char[] title, +static void GiveConfirmationMenu(int client, MenuHandler handler, const char[] title, const char[] confirmChoice) { // Figure out text for positive and negative values char positiveBuffer[1024], negativeBuffer[1024]; @@ -267,7 +267,7 @@ static bool ConfirmationNegative(const char[] choice) { // Map Vetos -public void GiveMapVetoMenu(int client) { +static void GiveMapVetoMenu(int client) { Menu menu = new Menu(MapVetoMenuHandler); menu.SetTitle("%T", "MapVetoBanMenuText", client); menu.ExitButton = false; @@ -287,7 +287,7 @@ public void GiveMapVetoMenu(int client) { SetConfirmationTime(true); } -public int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int param2) { +static int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { if (g_GameState != Get5State_Veto) { return; @@ -342,7 +342,7 @@ public int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int para // Map Picks -public void GiveMapPickMenu(int client) { +static void GiveMapPickMenu(int client) { Menu menu = new Menu(MapPickMenuHandler); menu.SetTitle("%T", "MapVetoPickMenuText", client); menu.ExitButton = false; @@ -362,7 +362,7 @@ public void GiveMapPickMenu(int client) { SetConfirmationTime(true); } -public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int param2) { +static int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { if (g_GameState != Get5State_Veto) { return; @@ -419,7 +419,7 @@ public int MapPickMenuHandler(Menu menu, MenuAction action, int param1, int para // Side Picks -public void GiveSidePickMenu(int client) { +static void GiveSidePickMenu(int client) { Menu menu = new Menu(SidePickMenuHandler); menu.ExitButton = false; char mapName[PLATFORM_MAX_PATH]; @@ -432,7 +432,7 @@ public void GiveSidePickMenu(int client) { SetConfirmationTime(true); } -public int SidePickMenuHandler(Menu menu, MenuAction action, int param1, int param2) { +static int SidePickMenuHandler(Menu menu, MenuAction action, int param1, int param2) { if (action == MenuAction_Select) { if (g_GameState != Get5State_Veto) { return; diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index 88893cc39..ac2a415cb 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -186,7 +186,7 @@ static Action Timer_PlacePlayerFromTeamNone(Handle timer, int client) { } } -public bool LoadMatchFile(const char[] config) { +static bool LoadMatchFile(const char[] config) { Get5PreloadMatchConfigEvent event = new Get5PreloadMatchConfigEvent(config); LogDebug("Calling Get5_OnPreLoadMatchConfig()"); @@ -297,7 +297,7 @@ stock bool LoadMatchFromUrl(const char[] url, ArrayList paramNames = null, } // SteamWorks HTTP callback for fetching a workshop collection -public int SteamWorks_OnMatchConfigReceived(Handle request, bool failure, bool requestSuccessful, +static int SteamWorks_OnMatchConfigReceived(Handle request, bool failure, bool requestSuccessful, EHTTPStatusCode statusCode, Handle data) { if (failure || !requestSuccessful) { MatchConfigFail("Steamworks GET request failed, HTTP status code = %d", statusCode); @@ -312,7 +312,7 @@ public int SteamWorks_OnMatchConfigReceived(Handle request, bool failure, bool r strcopy(g_LoadedConfigFile, sizeof(g_LoadedConfigFile), g_LoadedConfigUrl); } -public void WriteMatchToKv(KeyValues kv) { +void WriteMatchToKv(KeyValues kv) { kv.SetString("matchid", g_MatchID); kv.SetNum("scrim", g_InScrimMode); kv.SetNum("skip_veto", g_SkipVeto); @@ -682,7 +682,7 @@ static void LoadDefaultMapList(ArrayList list) { } } -public void SetMatchTeamCvars() { +void SetMatchTeamCvars() { Get5Team ctTeam = Get5Team_1; Get5Team tTeam = Get5Team_2; if (g_TeamStartingSide[Get5Team_1] == CS_TEAM_T) { @@ -739,17 +739,7 @@ public void SetMatchTeamCvars() { } } -public Get5Team GetMapWinner(int mapNumber) { - int team1score = GetMapScore(mapNumber, Get5Team_1); - int team2score = GetMapScore(mapNumber, Get5Team_2); - if (team1score > team2score) { - return Get5Team_1; - } else { - return Get5Team_2; - } -} - -public void ExecuteMatchConfigCvars() { +static void ExecuteMatchConfigCvars() { // Save the original match cvar values if we haven't already. if (g_MatchConfigChangedCvars == INVALID_HANDLE) { g_MatchConfigChangedCvars = SaveCvars(g_CvarNames); @@ -769,7 +759,7 @@ public void ExecuteMatchConfigCvars() { } } -public Action Command_LoadTeam(int client, int args) { +Action Command_LoadTeam(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "Cannot change player lists when there is no match to modify"); return Plugin_Handled; @@ -807,7 +797,7 @@ public Action Command_LoadTeam(int client, int args) { return Plugin_Handled; } -public Action Command_AddPlayer(int client, int args) { +Action Command_AddPlayer(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; @@ -858,7 +848,7 @@ public Action Command_AddPlayer(int client, int args) { return Plugin_Handled; } -public Action Command_AddCoach(int client, int args) { +Action Command_AddCoach(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; @@ -930,7 +920,7 @@ public Action Command_AddCoach(int client, int args) { return Plugin_Handled; } -public Action Command_AddKickedPlayer(int client, int args) { +Action Command_AddKickedPlayer(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; @@ -985,7 +975,7 @@ public Action Command_AddKickedPlayer(int client, int args) { return Plugin_Handled; } -public Action Command_RemovePlayer(int client, int args) { +Action Command_RemovePlayer(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "Cannot change player lists when there is no match to modify"); return Plugin_Handled; @@ -1012,7 +1002,7 @@ public Action Command_RemovePlayer(int client, int args) { return Plugin_Handled; } -public Action Command_RemoveKickedPlayer(int client, int args) { +Action Command_RemoveKickedPlayer(int client, int args) { if (g_GameState == Get5State_None) { ReplyToCommand(client, "Cannot change player lists when there is no match to modify."); return Plugin_Handled; @@ -1039,7 +1029,7 @@ public Action Command_RemoveKickedPlayer(int client, int args) { return Plugin_Handled; } -public Action Command_CreateMatch(int client, int args) { +Action Command_CreateMatch(int client, int args) { if (g_GameState != Get5State_None) { ReplyToCommand(client, "Cannot create a match when a match is already loaded"); return Plugin_Handled; @@ -1107,7 +1097,7 @@ public Action Command_CreateMatch(int client, int args) { return Plugin_Handled; } -public Action Command_CreateScrim(int client, int args) { +Action Command_CreateScrim(int client, int args) { if (g_GameState != Get5State_None) { ReplyToCommand(client, "Cannot create a match when a match is already loaded"); return Plugin_Handled; @@ -1191,7 +1181,7 @@ public Action Command_CreateScrim(int client, int args) { return Plugin_Handled; } -public Action Command_Ringer(int client, int args) { +Action Command_Ringer(int client, int args) { if (g_GameState == Get5State_None || !g_InScrimMode) { ReplyToCommand(client, "This command can only be used in scrim mode."); return Plugin_Handled; @@ -1287,7 +1277,7 @@ static void AddTeamLogoToDownloadTable(const char[] logoName) { } } -public void CheckTeamNameStatus(Get5Team team) { +void CheckTeamNameStatus(Get5Team team) { if (StrEqual(g_TeamNames[team], "") && team != Get5Team_Spec) { LOOP_CLIENTS(i) { if (IsAuthedPlayer(i)) { @@ -1310,7 +1300,7 @@ void ExecCfg(ConVar cvar) { CreateTimer(0.1, Timer_ExecMatchConfig, _, TIMER_FLAG_NO_MAPCHANGE); } -public Action Timer_ExecMatchConfig(Handle timer) { +static Action Timer_ExecMatchConfig(Handle timer) { // When we load config files using ServerCommand("exec") above, which is async, we want match config cvars to always // override. ExecuteMatchConfigCvars(); diff --git a/scripting/get5/pausing.sp b/scripting/get5/pausing.sp index 6b170e944..c9ef6cf85 100644 --- a/scripting/get5/pausing.sp +++ b/scripting/get5/pausing.sp @@ -1,10 +1,10 @@ -public bool PauseableGameState() { +static bool PauseableGameState() { return (g_GameState == Get5State_KnifeRound || g_GameState == Get5State_WaitingForKnifeRoundDecision || g_GameState == Get5State_Live || g_GameState == Get5State_GoingLive); } -public void PauseGame(Get5Team team, Get5PauseType type) { +void PauseGame(Get5Team team, Get5PauseType type) { if (type == Get5PauseType_None) { LogError("PauseGame() called with Get5PauseType_None. Please call UnpauseGame() instead."); UnpauseGame(team); @@ -36,11 +36,11 @@ public void PauseGame(Get5Team team, Get5PauseType type) { CreateTimer(0.1, Timer_ResetPauseRestriction); } -public Action Timer_ResetPauseRestriction(Handle timer, int data) { +static Action Timer_ResetPauseRestriction(Handle timer, int data) { g_IsChangingPauseState = false; } -stock void UnpauseGame(Get5Team team) { +void UnpauseGame(Get5Team team) { Get5MatchUnpausedEvent event = new Get5MatchUnpausedEvent(g_MatchID, g_MapNumber, team, g_PauseType); @@ -60,7 +60,7 @@ stock void UnpauseGame(Get5Team team) { CreateTimer(0.1, Timer_ResetPauseRestriction); } -public Action Command_PauseOrUnpauseMatch(int client, const char[] command, int argc) { +Action Command_PauseOrUnpauseMatch(int client, const char[] command, int argc) { if (g_GameState == Get5State_None || g_IsChangingPauseState) { return Plugin_Continue; } @@ -69,7 +69,7 @@ public Action Command_PauseOrUnpauseMatch(int client, const char[] command, int return Plugin_Stop; } -public Action Command_TechPause(int client, int args) { +Action Command_TechPause(int client, int args) { if (client == 0) { // Redirect admin use of sm_tech to regular pause. We only have one type of admin pause. return Command_Pause(client, args); @@ -133,7 +133,7 @@ public Action Command_TechPause(int client, int args) { return Plugin_Handled; } -public Action Command_Pause(int client, int args) { +Action Command_Pause(int client, int args) { if (client == 0) { PauseGame(Get5Team_None, Get5PauseType_Admin); Get5_MessageToAll("%t", "AdminForcePauseInfoMessage"); @@ -198,7 +198,7 @@ public Action Command_Pause(int client, int args) { return Plugin_Handled; } -public Action Command_Unpause(int client, int args) { +Action Command_Unpause(int client, int args) { if (!IsPaused()) { // Game is not paused; ignore command. return Plugin_Handled; @@ -262,7 +262,7 @@ public Action Command_Unpause(int client, int args) { return Plugin_Handled; } -public Action Timer_PauseTimeCheck(Handle timer) { +static Action Timer_PauseTimeCheck(Handle timer) { if (g_PauseType == Get5PauseType_None || !IsPaused()) { LogDebug("Stopping pause timer as game is not paused."); return Plugin_Stop; diff --git a/scripting/get5/readysystem.sp b/scripting/get5/readysystem.sp index 6dfb6dcef..4afb3ffbf 100644 --- a/scripting/get5/readysystem.sp +++ b/scripting/get5/readysystem.sp @@ -2,27 +2,27 @@ * Ready System */ -public void ResetReadyStatus() { +void ResetReadyStatus() { SetAllTeamsForcedReady(false); SetAllClientsReady(false); } -public bool IsReadyGameState() { +static bool IsReadyGameState() { return (g_GameState == Get5State_PreVeto || g_GameState == Get5State_Warmup) && !g_MapChangePending; } // Client ready status -public bool IsClientReady(int client) { +static bool IsClientReady(int client) { return g_ClientReady[client] == true; } -public void SetClientReady(int client, bool ready) { +void SetClientReady(int client, bool ready) { g_ClientReady[client] = ready; } -public void SetAllClientsReady(bool ready) { +static void SetAllClientsReady(bool ready) { LOOP_CLIENTS(i) { SetClientReady(i, ready); } @@ -30,15 +30,15 @@ public void SetAllClientsReady(bool ready) { // Team ready override -public bool IsTeamForcedReady(Get5Team team) { +static bool IsTeamForcedReady(Get5Team team) { return g_TeamReadyOverride[team] == true; } -public void SetTeamForcedReady(Get5Team team, bool ready) { +static void SetTeamForcedReady(Get5Team team, bool ready) { g_TeamReadyOverride[team] = ready; } -public void SetAllTeamsForcedReady(bool ready) { +static void SetAllTeamsForcedReady(bool ready) { LOOP_TEAMS(team) { SetTeamForcedReady(team, ready); } @@ -46,15 +46,15 @@ public void SetAllTeamsForcedReady(bool ready) { // Team ready status -public bool IsTeamsReady() { +bool IsTeamsReady() { return IsTeamReady(Get5Team_1) && IsTeamReady(Get5Team_2); } -public bool IsSpectatorsReady() { +bool IsSpectatorsReady() { return IsTeamReady(Get5Team_Spec); } -public bool IsTeamReady(Get5Team team) { +bool IsTeamReady(Get5Team team) { if (g_GameState == Get5State_Live) { return true; } @@ -83,7 +83,7 @@ public bool IsTeamReady(Get5Team team) { return false; } -public int GetTeamReadyCount(Get5Team team) { +static int GetTeamReadyCount(Get5Team team) { int readyCount = 0; LOOP_CLIENTS(i) { if (IsPlayer(i) && GetClientMatchTeam(i) == team && !IsClientCoaching(i) && IsClientReady(i)) { @@ -93,7 +93,7 @@ public int GetTeamReadyCount(Get5Team team) { return readyCount; } -public int GetTeamPlayerCount(Get5Team team) { +static int GetTeamPlayerCount(Get5Team team) { int playerCount = 0; LOOP_CLIENTS(i) { if (IsPlayer(i) && GetClientMatchTeam(i) == team && !IsClientCoaching(i)) { @@ -103,7 +103,7 @@ public int GetTeamPlayerCount(Get5Team team) { return playerCount; } -public int GetTeamMinReady(Get5Team team) { +static int GetTeamMinReady(Get5Team team) { if (team == Get5Team_1 || team == Get5Team_2) { return g_MinPlayersToReady; } else if (team == Get5Team_Spec) { @@ -113,7 +113,7 @@ public int GetTeamMinReady(Get5Team team) { } } -public int GetPlayersPerTeam(Get5Team team) { +static int GetPlayersPerTeam(Get5Team team) { if (team == Get5Team_1 || team == Get5Team_2) { return g_PlayersPerTeam; } else if (team == Get5Team_Spec) { @@ -126,7 +126,7 @@ public int GetPlayersPerTeam(Get5Team team) { // Admin commands -public Action Command_AdminForceReady(int client, int args) { +Action Command_AdminForceReady(int client, int args) { if (!IsReadyGameState()) { return Plugin_Handled; } @@ -141,7 +141,7 @@ public Action Command_AdminForceReady(int client, int args) { // Client commands // Re-used to automatically ready players on warmup-activity, hence the helper-method. -public void HandleReadyCommand(int client, bool autoReady) { +void HandleReadyCommand(int client, bool autoReady) { if (!IsReadyGameState()) { return; } @@ -165,12 +165,12 @@ public void HandleReadyCommand(int client, bool autoReady) { } } -public Action Command_Ready(int client, int args) { +Action Command_Ready(int client, int args) { HandleReadyCommand(client, false); return Plugin_Handled; } -public Action Command_NotReady(int client, int args) { +Action Command_NotReady(int client, int args) { Get5Team team = GetClientMatchTeam(client); if (!IsReadyGameState() || team == Get5Team_None || !IsClientReady(client)) { return Plugin_Handled; @@ -198,7 +198,7 @@ public Action Command_NotReady(int client, int args) { return Plugin_Handled; } -public Action Command_ForceReadyClient(int client, int args) { +Action Command_ForceReadyClient(int client, int args) { Get5Team team = GetClientMatchTeam(client); if (!IsReadyGameState() || team == Get5Team_None || IsTeamReady(team)) { return Plugin_Handled; @@ -255,13 +255,13 @@ static void HandleReadyMessage(Get5Team team) { } } -public void MissingPlayerInfoMessage() { +void MissingPlayerInfoMessage() { MissingPlayerInfoMessageTeam(Get5Team_1); MissingPlayerInfoMessageTeam(Get5Team_2); MissingPlayerInfoMessageTeam(Get5Team_Spec); } -public void MissingPlayerInfoMessageTeam(Get5Team team) { +static void MissingPlayerInfoMessageTeam(Get5Team team) { if (IsTeamForcedReady(team)) { return; } @@ -284,7 +284,7 @@ public void MissingPlayerInfoMessageTeam(Get5Team team) { // Helpers -public void UpdateClanTags() { +void UpdateClanTags() { if (!g_SetClientClanTagCvar.BoolValue) { LogDebug("Not setting client clan tags because get5_set_client_clan_tags is 0"); return; diff --git a/scripting/get5/recording.sp b/scripting/get5/recording.sp index db4f66b6b..9e873f8b7 100644 --- a/scripting/get5/recording.sp +++ b/scripting/get5/recording.sp @@ -74,7 +74,7 @@ static void ReadDemoDataPack(DataPack pack, char[] matchId, const int matchIdLen delete pack; } -public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { +static Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { char matchId[MATCH_ID_LENGTH]; char demoFileName[PLATFORM_MAX_PATH]; int mapNumber; @@ -83,7 +83,7 @@ public Action Timer_StopGoTVRecording(Handle timer, DataPack pack) { return Plugin_Handled; } -public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) { +static Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) { char matchId[MATCH_ID_LENGTH]; char demoFileName[PLATFORM_MAX_PATH]; int mapNumber; @@ -98,7 +98,7 @@ public Action Timer_FireStopRecordingEvent(Handle timer, DataPack pack) { return Plugin_Handled; } -bool IsTVEnabled() { +static bool IsTVEnabled() { ConVar tvEnabledCvar = FindConVar("tv_enable"); if (tvEnabledCvar == null) { LogError("Failed to get tv_enable cvar"); diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index eee7c18f3..df9e4a1f8 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -1,4 +1,4 @@ -public void Stats_PluginStart() { +void Stats_PluginStart() { HookEvent("bomb_defused", Stats_BombDefusedEvent); HookEvent("bomb_exploded", Stats_BombExplodedEvent); HookEvent("bomb_planted", Stats_BombPlantedEvent); @@ -16,7 +16,7 @@ public void Stats_PluginStart() { HookEvent("smokegrenade_detonate", Stats_SmokeGrenadeDetonateEvent); } -public Action HandlePlayerDamage(int victim, int &attacker, int &inflictor, float &damage, +static Action HandlePlayerDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; @@ -121,7 +121,7 @@ public Action HandlePlayerDamage(int victim, int &attacker, int &inflictor, floa return Plugin_Continue; } -public Get5Player GetPlayerObject(int client) { +Get5Player GetPlayerObject(int client) { if (client == 0) { return new Get5Player(0, "", view_as(CS_TEAM_NONE), "Console", false); } @@ -151,19 +151,19 @@ public Get5Player GetPlayerObject(int client) { } } -public void Stats_HookDamageForClient(int client) { +void Stats_HookDamageForClient(int client) { SDKHook(client, SDKHook_OnTakeDamageAlive, HandlePlayerDamage); LogDebug("Hooked client %d to SDKHook_OnTakeDamageAlive", client); } -public void Stats_Reset() { +void Stats_Reset() { if (g_StatsKv != null) { delete g_StatsKv; } g_StatsKv = new KeyValues("Stats"); } -public void Stats_InitSeries() { +void Stats_InitSeries() { Stats_Reset(); char seriesType[32]; Format(seriesType, sizeof(seriesType), "bo%d", g_NumberOfMapsInSeries); @@ -173,7 +173,7 @@ public void Stats_InitSeries() { DumpToFile(); } -public void Stats_ResetRoundValues() { +void Stats_ResetRoundValues() { g_FirstKillDone = false; g_FirstDeathDone = false; g_SetTeamClutching[CS_TEAM_CT] = false; @@ -184,7 +184,7 @@ public void Stats_ResetRoundValues() { } } -public void Stats_ResetClientRoundValues(int client) { +void Stats_ResetClientRoundValues(int client) { g_RoundKills[client] = 0; g_RoundClutchingEnemyCount[client] = 0; g_PlayerKilledBy[client] = -1; @@ -201,7 +201,7 @@ public void Stats_ResetClientRoundValues(int client) { } } -public void Stats_ResetGrenadeContainers() { +void Stats_ResetGrenadeContainers() { LogDebug("Clearing out any lingering events in grenade StringMaps..."); // If any molotovs were active on the previous round when it ended (or on halftime/game end), we @@ -247,7 +247,7 @@ public void Stats_ResetGrenadeContainers() { g_LatestMolotovToExtinguishBySmoke = 0; } -public void Stats_RoundStart() { +void Stats_RoundStart() { LOOP_CLIENTS(i) { if (IsPlayer(i)) { // Ensures that each player has zero-filled stats on freeze-time end. @@ -269,7 +269,7 @@ public void Stats_RoundStart() { } } -public void Stats_RoundEnd(int csTeamWinner) { +void Stats_RoundEnd(int csTeamWinner) { // Update team scores. GoToMap(); char mapName[PLATFORM_MAX_PATH]; @@ -335,7 +335,7 @@ public void Stats_RoundEnd(int csTeamWinner) { } } -public void Stats_UpdateMapScore(Get5Team winner) { +void Stats_UpdateMapScore(Get5Team winner) { GoToMap(); char winnerString[16]; GetTeamString(winner, winnerString, sizeof(winnerString)); @@ -351,18 +351,18 @@ void Stats_SetDemoName(const char[] demoFileName) { DumpToFile(); } -public void Stats_Forfeit() { +void Stats_Forfeit() { g_StatsKv.SetNum(STAT_SERIES_FORFEIT, 1); } -public void Stats_SeriesEnd(Get5Team winner) { +void Stats_SeriesEnd(Get5Team winner) { char winnerString[16]; GetTeamString(winner, winnerString, sizeof(winnerString)); g_StatsKv.SetString(STAT_SERIESWINNER, winnerString); DumpToFile(); } -public void EndMolotovEvent(const char[] molotovKey) { +static void EndMolotovEvent(const char[] molotovKey) { // Since a molotov can be active when the round is ending, we need to grab the information from it // on both RoundStart // **and** on its expire event. @@ -383,7 +383,7 @@ public void EndMolotovEvent(const char[] molotovKey) { } } -public void EndHEEvent(const char[] grenadeKey) { +static void EndHEEvent(const char[] grenadeKey) { Get5HEDetonatedEvent heObject; if (g_HEGrenadeContainer.GetValue(grenadeKey, heObject)) { if (IsDoingRestoreOrMapChange()) { @@ -399,7 +399,7 @@ public void EndHEEvent(const char[] grenadeKey) { } } -public void EndFlashbangEvent(const char[] flashKey) { +static void EndFlashbangEvent(const char[] flashKey) { Get5FlashbangDetonatedEvent flashEvent; if (g_FlashbangContainer.GetValue(flashKey, flashEvent)) { if (IsDoingRestoreOrMapChange()) { @@ -415,7 +415,7 @@ public void EndFlashbangEvent(const char[] flashKey) { } } -public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -438,7 +438,7 @@ public Action Stats_DecoyStartedEvent(Event event, const char[] name, bool dontB EventLogger_LogAndDeleteEvent(decoyObject); } -public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -464,7 +464,7 @@ public Action Stats_SmokeGrenadeDetonateEvent(Event event, const char[] name, bo g_LatestMolotovToExtinguishBySmoke = 0; } -public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -490,7 +490,7 @@ public Action Stats_MolotovStartBurnEvent(Event event, const char[] name, bool d true); } -public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -504,7 +504,7 @@ public Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, boo LogDebug("Molotov Event: %s, %d", name, entityId); } -public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontBroadcast) { // No backup check; the event is deleted in EndMolotovEvent to prevent leaks, as this function works like the // the HE/flash timer callbacks which also do not check for backup state. if (g_GameState != Get5State_Live) { @@ -521,7 +521,7 @@ public Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontB EndMolotovEvent(molotovKey); } -public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -539,7 +539,7 @@ public Action Stats_MolotovDetonateEvent(Event event, const char[] name, bool do g_LatestUserIdToDetonateMolotov = attacker; } -public Action Stats_FlashbangDetonateEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_FlashbangDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -562,7 +562,7 @@ public Action Stats_FlashbangDetonateEvent(Event event, const char[] name, bool CreateTimer(0.001, Timer_HandleFlashbang, entityId, TIMER_FLAG_NO_MAPCHANGE); } -public Action Timer_HandleFlashbang(Handle timer, int entityId) { +static Action Timer_HandleFlashbang(Handle timer, int entityId) { char flashKey[16]; IntToString(entityId, flashKey, sizeof(flashKey)); @@ -571,7 +571,7 @@ public Action Timer_HandleFlashbang(Handle timer, int entityId) { return Plugin_Handled; } -public Action Stats_HEGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_HEGrenadeDetonateEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -594,7 +594,7 @@ public Action Stats_HEGrenadeDetonateEvent(Event event, const char[] name, bool CreateTimer(0.001, Timer_HandleHEGrenade, entityId, TIMER_FLAG_NO_MAPCHANGE); } -public Action Timer_HandleHEGrenade(Handle timer, int entityId) { +static Action Timer_HandleHEGrenade(Handle timer, int entityId) { char grenadeKey[16]; IntToString(entityId, grenadeKey, sizeof(grenadeKey)); @@ -603,7 +603,7 @@ public Action Timer_HandleHEGrenade(Handle timer, int entityId) { return Plugin_Handled; } -public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -630,7 +630,7 @@ public Action Stats_GrenadeThrownEvent(Event event, const char[] name, bool dont EventLogger_LogAndDeleteEvent(grenadeEvent); } -public Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_PlayerDeathEvent(Event event, const char[] name, bool dontBroadcast) { if (IsDoingRestoreOrMapChange()) { return; } @@ -794,7 +794,7 @@ static void UpdateTradeStat(int attacker, int victim) { } } -public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -821,7 +821,7 @@ public Action Stats_BombPlantedEvent(Event event, const char[] name, bool dontBr } } -public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -852,7 +852,7 @@ public Action Stats_BombDefusedEvent(Event event, const char[] name, bool dontBr } } -public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -869,7 +869,7 @@ public Action Stats_BombExplodedEvent(Event event, const char[] name, bool dontB EventLogger_LogAndDeleteEvent(bombExplodedEvent); } -public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -909,7 +909,7 @@ public Action Stats_PlayerBlindEvent(Event event, const char[] name, bool dontBr } } -public Action Stats_RoundMVPEvent(Event event, const char[] name, bool dontBroadcast) { +static Action Stats_RoundMVPEvent(Event event, const char[] name, bool dontBroadcast) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return; } @@ -1009,7 +1009,7 @@ static void InitPlayerStats(int client, Get5Side side) { GoBackFromPlayer(); } -public int AddToPlayerStat(int client, const char[] field, int delta) { +int AddToPlayerStat(int client, const char[] field, int delta) { if (IsFakeClient(client)) { return 0; } @@ -1086,18 +1086,18 @@ static int GetClutchingClient(int csTeam) { } } -public void DumpToFile() { +static void DumpToFile() { char path[PLATFORM_MAX_PATH + 1]; if (FormatCvarString(g_StatsPathFormatCvar, path, sizeof(path))) { DumpToFilePath(path); } } -public bool DumpToFilePath(const char[] path) { +bool DumpToFilePath(const char[] path) { return IsJSONPath(path) ? DumpToJSONFile(path) : g_StatsKv.ExportToFile(path); } -public bool DumpToJSONFile(const char[] path) { +static bool DumpToJSONFile(const char[] path) { g_StatsKv.Rewind(); g_StatsKv.GotoFirstSubKey(false); JSON_Object stats = EncodeKeyValue(g_StatsKv); @@ -1122,7 +1122,7 @@ public bool DumpToJSONFile(const char[] path) { return true; } -JSON_Object EncodeKeyValue(KeyValues kv) { +static JSON_Object EncodeKeyValue(KeyValues kv) { char keyBuffer[256]; char valBuffer[256]; char sectionName[256]; @@ -1155,7 +1155,7 @@ JSON_Object EncodeKeyValue(KeyValues kv) { return json_kv; } -public void PrintDamageInfo(int client) { +void PrintDamageInfo(int client) { if (!IsPlayer(client)) { return; } diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index f2831a546..aec0d57f4 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -105,7 +105,7 @@ void SetClientCoaching(int client, Get5Side side) { Get5_MessageToAll("%t", "PlayerIsCoachingTeam", formattedPlayerName, g_FormattedTeamNames[team]); } -public void CoachingChangedHook(ConVar convar, const char[] oldValue, const char[] newValue) { +void CoachingChangedHook(ConVar convar, const char[] oldValue, const char[] newValue) { if (g_GameState == Get5State_None) { return; } @@ -120,7 +120,7 @@ public void CoachingChangedHook(ConVar convar, const char[] oldValue, const char } } -public Action Command_SmCoach(int client, int args) { +Action Command_SmCoach(int client, int args) { if (g_GameState == Get5State_None) { return Plugin_Continue; } @@ -213,7 +213,7 @@ static void MoveCoachToPlayerInConfig(const int client, const Get5Team team) { } } -public Action Command_Coach(int client, const char[] command, int argc) { +Action Command_Coach(int client, const char[] command, int argc) { if (g_GameState == Get5State_None) { return Plugin_Continue; } @@ -316,7 +316,11 @@ int CountPlayersOnTeam(Get5Team team, int exclude = -1) { return count; } -Get5Side GetClientCoachingSide(int client) { +bool IsClientCoaching(int client) { + return GetClientCoachingSide(client) != Get5Side_None; +} + +static Get5Side GetClientCoachingSide(int client) { if (GetClientTeam(client) != CS_TEAM_SPECTATOR) { return Get5Side_None; } @@ -369,11 +373,11 @@ ArrayList GetTeamCoaches(Get5Team team) { return g_TeamCoaches[team]; } -bool IsAuthOnTeam(const char[] auth, Get5Team team) { +static bool IsAuthOnTeam(const char[] auth, Get5Team team) { return GetTeamAuths(team).FindString(auth) >= 0; } -bool IsAuthOnTeamCoach(const char[] auth, Get5Team team) { +static bool IsAuthOnTeamCoach(const char[] auth, Get5Team team) { return GetTeamCoaches(team).FindString(auth) >= 0; } diff --git a/scripting/get5/tests.sp b/scripting/get5/tests.sp index e0a0cadd6..778c53faf 100644 --- a/scripting/get5/tests.sp +++ b/scripting/get5/tests.sp @@ -1,9 +1,9 @@ -public Action Command_Test(int args) { +Action Command_Test(int args) { Get5_Test(); return Plugin_Handled; } -public void Get5_Test() { +static void Get5_Test() { if (g_GameState != Get5State_None) { g_GameState = Get5State_None; } diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 7d391f6af..858428f5d 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -204,10 +204,6 @@ stock void RestartGame(int delay = 1) { ServerCommand("mp_restartgame %d", delay); } -stock bool IsClientCoaching(int client) { - return GetClientCoachingSide(client) != Get5Side_None; -} - stock void SetTeamInfo(int csTeam, const char[] name, const char[] flag = "", const char[] logo = "", const char[] matchstat = "", int series_score = 0) { int team_int = (csTeam == CS_TEAM_CT) ? 1 : 2; @@ -468,7 +464,7 @@ stock bool IsPlayerTeam(Get5Team team) { return team == Get5Team_1 || team == Get5Team_2; } -public Get5Team VetoFirstFromString(const char[] str) { +stock Get5Team VetoFirstFromString(const char[] str) { if (StrEqual(str, "random", false)) { return view_as(GetRandomInt(0, 1)); } else if (StrEqual(str, "team2", false)) { @@ -530,7 +526,7 @@ stock void GetTeamString(Get5Team team, char[] buffer, int len) { } } -public MatchSideType MatchSideTypeFromString(const char[] str) { +stock MatchSideType MatchSideTypeFromString(const char[] str) { if (StrEqual(str, "normal", false) || StrEqual(str, "standard", false)) { return MatchSideType_Standard; } else if (StrEqual(str, "never_knife", false)) { @@ -540,7 +536,7 @@ public MatchSideType MatchSideTypeFromString(const char[] str) { } } -public void MatchSideTypeToString(MatchSideType type, char[] str, int len) { +stock void MatchSideTypeToString(MatchSideType type, char[] str, int len) { if (type == MatchSideType_Standard) { Format(str, len, "standard"); } else if (type == MatchSideType_NeverKnife) { @@ -648,7 +644,7 @@ stock SideChoice SideTypeFromString(const char[] input) { // Deletes a file if it exists. Returns true if the // file existed AND there was an error deleting it. -public bool DeleteFileIfExists(const char[] path) { +stock bool DeleteFileIfExists(const char[] path) { if (FileExists(path)) { if (!DeleteFile(path)) { LogError("Failed to delete file %s", path); @@ -659,7 +655,7 @@ public bool DeleteFileIfExists(const char[] path) { return true; } -public bool IsJSONPath(const char[] path) { +stock bool IsJSONPath(const char[] path) { int length = strlen(path); if (length >= 5) { return strcmp(path[length - 5], ".json", false) == 0; @@ -668,11 +664,11 @@ public bool IsJSONPath(const char[] path) { } } -public int GetMilliSecondsPassedSince(float timestamp) { +stock int GetMilliSecondsPassedSince(float timestamp) { return RoundToFloor((GetEngineTime() - timestamp) * 1000); } -public int GetRoundsPlayed() { +stock int GetRoundsPlayed() { return GameRules_GetProp("m_totalRoundsPlayed"); } diff --git a/scripting/get5_apistats.sp b/scripting/get5_apistats.sp index b7bcfc2ff..2741445da 100644 --- a/scripting/get5_apistats.sp +++ b/scripting/get5_apistats.sp @@ -72,7 +72,7 @@ public void OnPluginStart() { RegConsoleCmd("get5_web_available", Command_Available); } -public Action Command_Available(int client, int args) { +static Action Command_Available(int client, int args) { char versionString[64] = "unknown"; ConVar versionCvar = FindConVar("get5_version"); if (versionCvar != null) { @@ -94,11 +94,11 @@ public Action Command_Available(int client, int args) { return Plugin_Handled; } -public void LogoBasePathChanged(ConVar convar, const char[] oldValue, const char[] newValue) { +void LogoBasePathChanged(ConVar convar, const char[] oldValue, const char[] newValue) { g_LogoBasePath = g_UseSVGCvar.BoolValue ? LOGO_DIR : LEGACY_LOGO_DIR; } -public void ApiInfoChanged(ConVar convar, const char[] oldValue, const char[] newValue) { +void ApiInfoChanged(ConVar convar, const char[] oldValue, const char[] newValue) { g_APIKeyCvar.GetString(g_APIKey, sizeof(g_APIKey)); g_APIURLCvar.GetString(g_APIURL, sizeof(g_APIURL)); @@ -136,7 +136,7 @@ static Handle CreateRequest(EHTTPMethod httpMethod, const char[] apiMethod, any: } } -public int RequestCallback(Handle request, bool failure, bool requestSuccessful, +int RequestCallback(Handle request, bool failure, bool requestSuccessful, EHTTPStatusCode statusCode) { if (failure || !requestSuccessful) { LogError("API request failed, HTTP status code = %d", statusCode); @@ -163,7 +163,7 @@ public void Get5_OnSeriesInit(const Get5SeriesStartedEvent event) { CheckForLogo(logo2); } -public void CheckForLogo(const char[] logo) { +static void CheckForLogo(const char[] logo) { if (StrEqual(logo, "")) { return; } @@ -196,7 +196,7 @@ public void CheckForLogo(const char[] logo) { } } -public int LogoCallback(Handle request, bool failure, bool successful, EHTTPStatusCode status, int data) { +static int LogoCallback(Handle request, bool failure, bool successful, EHTTPStatusCode status, int data) { if (failure || !successful) { LogError("Logo request failed, status code = %d", status); return; @@ -235,7 +235,7 @@ public void Get5_OnGoingLive(const Get5GoingLiveEvent event) { Get5_AddLiveCvar("get5_web_api_url", g_APIURL); } -public void UpdateRoundStats(const char[] matchId, const int mapNumber) { +static void UpdateRoundStats(const char[] matchId, const int mapNumber) { int t1score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_1)); int t2score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_2)); @@ -284,7 +284,7 @@ static void AddIntStat(Handle req, KeyValues kv, const char[] field) { AddIntParam(req, field, kv.GetNum(field)); } -public void UpdatePlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, +static void UpdatePlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; diff --git a/scripting/get5_mysqlstats.sp b/scripting/get5_mysqlstats.sp index 5a9f255a0..91f9e4acf 100644 --- a/scripting/get5_mysqlstats.sp +++ b/scripting/get5_mysqlstats.sp @@ -115,7 +115,7 @@ public void Get5_OnSeriesInit(const Get5SeriesStartedEvent event) { } } -public void MatchInitCallback(Database dbObj, DBResultSet results, const char[] error, any data) { +static void MatchInitCallback(Database dbObj, DBResultSet results, const char[] error, any data) { if (results == null) { LogError("Failed to get Match ID from match init query: %s.", error); g_DisableStats = true; @@ -157,7 +157,7 @@ public void Get5_OnGoingLive(const Get5GoingLiveEvent event) { db.Query(SQLErrorCheckCallback, queryBuffer); } -public void UpdateRoundStats(const char[] matchId, const int mapNumber) { +static void UpdateRoundStats(const char[] matchId, const int mapNumber) { // Update team scores int t1score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_1)); int t2score = CS_GetTeamScore(Get5_Get5TeamToCSTeam(Get5Team_2)); @@ -223,7 +223,7 @@ public void Get5_OnMapResult(const Get5MapResultEvent event) { db.Query(SQLErrorCheckCallback, queryBuffer); } -public void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, +static void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; @@ -374,7 +374,7 @@ public void Get5_OnSeriesResult(const Get5SeriesResultEvent event) { db.Query(SQLErrorCheckCallback, queryBuffer); } -public int SQLErrorCheckCallback(Handle owner, Handle hndl, const char[] error, int data) { +static int SQLErrorCheckCallback(Handle owner, Handle hndl, const char[] error, int data) { if (!StrEqual("", error)) { LogError("Last Connect SQL Error: %s", error); } From f4101aa304a5519d6e33ed33b51d83128f91c83e Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Fri, 2 Sep 2022 15:11:32 +0200 Subject: [PATCH 103/104] Run formatter --- scripting/get5.sp | 102 ++++++++++++++++++---------------- scripting/get5/backups.sp | 61 +++++++++++--------- scripting/get5/goinglive.sp | 10 ++-- scripting/get5/kniferounds.sp | 5 +- scripting/get5/mapveto.sp | 7 ++- scripting/get5/matchconfig.sp | 94 ++++++++++++++++++------------- scripting/get5/stats.sp | 13 +++-- scripting/get5/teamlogic.sp | 54 ++++++++++-------- scripting/get5/util.sp | 27 +++++---- scripting/get5_apistats.sp | 5 +- scripting/get5_mysqlstats.sp | 4 +- 11 files changed, 217 insertions(+), 165 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 7a9ae0b98..384a5747c 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -118,8 +118,8 @@ bool g_SeriesCanClinch = true; int g_RoundNumber = -1; // The round number, 0-indexed. -1 if the match is not live. // The active map number, used by stats. Required as the calculated round number changes immediately // as a map ends, but before the map changes to the next. -int g_MapNumber = 0; // the current map number, starting at 0. -int g_NumberOfMapsInSeries = 0; // the number of maps to play in the series. +int g_MapNumber = 0; // the current map number, starting at 0. +int g_NumberOfMapsInSeries = 0; // the number of maps to play in the series. char g_MatchID[MATCH_ID_LENGTH]; ArrayList g_MapPoolList = null; ArrayList g_TeamAuths[MATCHTEAM_COUNT]; @@ -352,8 +352,9 @@ public void OnPluginStart() { g_CheckAuthsCvar = CreateConVar("get5_check_auths", "1", "If set to 0, get5 will not force players to the correct team based on steamid"); - g_DemoNameFormatCvar = CreateConVar("get5_demo_name_format", "{TIME}_{MATCHID}_map{MAPNUMBER}_{MAPNAME}", - "Format for demo file names, use \"\" to disable. Do not remove the {TIME} placeholder if you use the backup system."); + g_DemoNameFormatCvar = CreateConVar( + "get5_demo_name_format", "{TIME}_{MATCHID}_map{MAPNUMBER}_{MAPNAME}", + "Format for demo file names, use \"\" to disable. Do not remove the {TIME} placeholder if you use the backup system."); g_DisplayGotvVetoCvar = CreateConVar("get5_display_gotv_veto", "0", "Whether to wait for map vetos to be printed to GOTV before changing map"); @@ -459,7 +460,8 @@ public void OnPluginStart() { g_VersionCvar.SetString(PLUGIN_VERSION); g_CoachingEnabledCvar = FindConVar("sv_coaching_enabled"); - g_CoachingEnabledCvar.AddChangeHook(CoachingChangedHook); // used to move people off coaching if it gets disabled. + g_CoachingEnabledCvar.AddChangeHook( + CoachingChangedHook); // used to move people off coaching if it gets disabled. /** Client commands **/ g_ChatAliases = new ArrayList(ByteCountToCells(ALIAS_LENGTH)); @@ -693,8 +695,9 @@ public void OnClientAuthorized(int client, const char[] auth) { Get5Team team = GetClientMatchTeam(client); if (team == Get5Team_None) { RememberAndKickClient(client, "%t", "YouAreNotAPlayerInfoMessage"); - } else if (CountPlayersOnTeam(team, client) >= g_PlayersPerTeam - && (!g_CoachingEnabledCvar.BoolValue || CountCoachesOnTeam(team, client) >= g_CoachesPerTeam)) { + } else if (CountPlayersOnTeam(team, client) >= g_PlayersPerTeam && + (!g_CoachingEnabledCvar.BoolValue || + CountCoachesOnTeam(team, client) >= g_CoachesPerTeam)) { KickClient(client, "%t", "TeamIsFullInfoMessage"); } } @@ -707,15 +710,15 @@ void RememberAndKickClient(int client, const char[] format, const char[] transla public void OnClientPutInServer(int client) { LogDebug("OnClientPutInServer"); - Stats_HookDamageForClient(client); // Also needed for bots! + Stats_HookDamageForClient(client); // Also needed for bots! if (IsFakeClient(client)) { return; } - // If a player joins during freezetime, ensure their round stats are 0, as there will be no round-start event to do it. - // Maybe this could just be freezetime end? + // If a player joins during freezetime, ensure their round stats are 0, as there will be no + // round-start event to do it. Maybe this could just be freezetime end? Stats_ResetClientRoundValues(client); - // Because OnConfigsExecuted may run before a client is on the server, we have to repeat the start-logic here when the - // first client connects. + // Because OnConfigsExecuted may run before a client is on the server, we have to repeat the + // start-logic here when the first client connects. SetServerStateOnStartup(false); } @@ -806,10 +809,10 @@ static Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBr // This runs every time a map starts *or* when the plugin is reloaded. public void OnConfigsExecuted() { LogDebug("OnConfigsExecuted"); - // If the server has hibernation enabled, running this without a delay will cause it to frequently fail with - // "Gamerules lookup failed" probably due to some odd internal race-condition where the game is not yet running - // when we attempt to determine its "is paused" or "is in warmup" state. Putting it on a 1 second callback seems - // to solve this problem. + // If the server has hibernation enabled, running this without a delay will cause it to frequently + // fail with "Gamerules lookup failed" probably due to some odd internal race-condition where the + // game is not yet running when we attempt to determine its "is paused" or "is in warmup" state. + // Putting it on a 1 second callback seems to solve this problem. CreateTimer(1.0, Timer_ConfigsExecutedCallback); } @@ -830,7 +833,8 @@ static Action Timer_ConfigsExecutedCallback(Handle timer) { ResetReadyStatus(); if (CheckAutoLoadConfig()) { - // If gamestate is none and a config was autoloaded, a match config will set all of the below state. + // If gamestate is none and a config was autoloaded, a match config will set all of the below + // state. return; } @@ -845,10 +849,11 @@ static Action Timer_ConfigsExecutedCallback(Handle timer) { } // On map start, always put the game in warmup mode. - // When executing a backup load, the live config is loaded and warmup ends after players ready-up again. + // When executing a backup load, the live config is loaded and warmup ends after players ready-up + // again. SetServerStateOnStartup(true); - // This must not be called when waiting for a backup, as it will set the sides incorrectly if the team swapped in - // knife or if the backup target is the second half. + // This must not be called when waiting for a backup, as it will set the sides incorrectly if the + // team swapped in knife or if the backup target is the second half. if (!g_WaitingForRoundBackup) { SetStartingTeams(); } @@ -962,7 +967,7 @@ bool CheckAutoLoadConfig() { char autoloadConfig[PLATFORM_MAX_PATH]; g_AutoLoadConfigCvar.GetString(autoloadConfig, sizeof(autoloadConfig)); if (!StrEqual(autoloadConfig, "")) { - bool loaded = LoadMatchConfig(autoloadConfig); // return false if match config load fails! + bool loaded = LoadMatchConfig(autoloadConfig); // return false if match config load fails! if (loaded) { LogMessage("Match configuration was loaded via get5_autoload_config."); } @@ -1016,7 +1021,7 @@ static Action Command_EndMatch(int client, int args) { Call_Finish(); EventLogger_LogAndDeleteEvent(mapResultEvent); - StopRecording(1.0); // must go before EndSeries as it depends on g_MatchID. + StopRecording(1.0); // must go before EndSeries as it depends on g_MatchID. // No delay required when not kicking players. EndSeries(winningTeam, false, 0.0, false); @@ -1135,13 +1140,11 @@ static Action Command_Stop(int client, int args) { char stopCommandFormatted[64]; FormatChatCommand(stopCommandFormatted, sizeof(stopCommandFormatted), "!stop"); if (g_TeamGivenStopCommand[Get5Team_1] && !g_TeamGivenStopCommand[Get5Team_2]) { - Get5_MessageToAll("%t", "TeamWantsToReloadCurrentRound", - g_FormattedTeamNames[Get5Team_1], g_FormattedTeamNames[Get5Team_2], - stopCommandFormatted); + Get5_MessageToAll("%t", "TeamWantsToReloadCurrentRound", g_FormattedTeamNames[Get5Team_1], + g_FormattedTeamNames[Get5Team_2], stopCommandFormatted); } else if (!g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { - Get5_MessageToAll("%t", "TeamWantsToReloadCurrentRound", - g_FormattedTeamNames[Get5Team_2], g_FormattedTeamNames[Get5Team_1], - stopCommandFormatted); + Get5_MessageToAll("%t", "TeamWantsToReloadCurrentRound", g_FormattedTeamNames[Get5Team_2], + g_FormattedTeamNames[Get5Team_1], stopCommandFormatted); } else if (g_TeamGivenStopCommand[Get5Team_1] && g_TeamGivenStopCommand[Get5Team_2]) { RestoreLastRound(client); } @@ -1219,8 +1222,8 @@ static Action Event_MatchOver(Event event, const char[] name, bool dontBroadcast winningTeam = Get5Team_2; } - // If the round ends because the match is over, we clear the grenade container immediately as they will not fire - // on their own if the game state is not live. + // If the round ends because the match is over, we clear the grenade container immediately as + // they will not fire on their own if the game state is not live. Stats_ResetGrenadeContainers(); // Update series scores @@ -1311,7 +1314,7 @@ Action Timer_NextMatchMap(Handle timer) { } static void EndSeries(Get5Team winningTeam, bool printWinnerMessage, float restoreDelay, - bool kickPlayers = true) { + bool kickPlayers = true) { Stats_SeriesEnd(winningTeam); if (printWinnerMessage) { @@ -1451,10 +1454,10 @@ static Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas return; } - // We cannot do this during warmup, as sending users into warmup post-knife triggers a round start event. - // We add an extra restart to clear lingering state from the knife round, such as the round - // indicator in the middle of the scoreboard not being reset. This also tightly couples the live-announcement to - // the actual live start. + // We cannot do this during warmup, as sending users into warmup post-knife triggers a round start + // event. We add an extra restart to clear lingering state from the knife round, such as the round + // indicator in the middle of the scoreboard not being reset. This also tightly couples the + // live-announcement to the actual live start. if (!InWarmup()) { if (g_GameState == Get5State_WaitingForKnifeRoundDecision) { // Ensures that round end after knife sends players directly into warmup. @@ -1470,14 +1473,15 @@ static Action Event_RoundStart(Event event, const char[] name, bool dontBroadcas ChangeState(Get5State_Live); RestartGame(); CreateTimer(3.0, Timer_MatchLive, _, TIMER_FLAG_NO_MAPCHANGE); - return; // Next round start will take care of below, such as writing backup. + return; // Next round start will take care of below, such as writing backup. } } - // Ensures that players who connect during halftime/team swap are placed in their correct slots as soon as the - // following round starts. Otherwise they could be left on the "no team" screen and potentially - // ghost, depending on where the camera drops them. Especially important for coaches. - // We do this step *before* we write the backup, so we don't have any lingering players in case of a restore. + // Ensures that players who connect during halftime/team swap are placed in their correct slots as + // soon as the following round starts. Otherwise they could be left on the "no team" screen and + // potentially ghost, depending on where the camera drops them. Especially important for coaches. + // We do this step *before* we write the backup, so we don't have any lingering players in case of + // a restore. LOOP_CLIENTS(i) { if (IsPlayer(i) && GetClientTeam(i) == CS_TEAM_NONE) { CheckClientTeam(i); @@ -1658,9 +1662,9 @@ static Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) EventLogger_LogAndDeleteEvent(roundEndEvent); - // Reset this when a round ends, as voting has no reference to which round the teams wanted to restore to, so - // votes to restore during one round should not carry over into the next round, as it would just restore that round - // instead. + // Reset this when a round ends, as voting has no reference to which round the teams wanted to + // restore to, so votes to restore during one round should not carry over into the next round, + // as it would just restore that round instead. LOOP_TEAMS(t) { if (g_TeamGivenStopCommand[t]) { Get5_MessageToAll("%t", "StopCommandVotingReset", g_FormattedTeamNames[t]); @@ -1701,7 +1705,7 @@ static void StartGame(bool knifeRound) { LogDebug("StartGame"); if (knifeRound) { - ExecCfg(g_LiveCfgCvar); // live first, then apply and save knife cvars below + ExecCfg(g_LiveCfgCvar); // live first, then apply and save knife cvars below LogDebug("StartGame: about to begin knife round"); ChangeState(Get5State_KnifeRound); if (g_KnifeChangedCvars != INVALID_HANDLE) { @@ -1712,7 +1716,8 @@ static void StartGame(bool knifeRound) { g_KnifeChangedCvars = ExecuteAndSaveCvars(knifeConfig); CreateTimer(1.0, StartKnifeRound); } else { - // If there is no knife round, we go directly to live, which loads the live config etc. on its own. + // If there is no knife round, we go directly to live, which loads the live config etc. on its + // own. StartGoingLive(); } } @@ -1726,8 +1731,9 @@ static void SetServerStateOnStartup(bool force) { return; } if (g_GameState <= Get5State_Warmup || g_WaitingForRoundBackup) { - // If the server is in veto/preveto when someone joins or the configs exec, it should remain in that state. - // This would happen if the a config with veto is loaded before someone joins the server. + // If the server is in veto/preveto when someone joins or the configs exec, it should remain in + // that state. This would happen if the a config with veto is loaded before someone joins the + // server. if (g_GameState != Get5State_Veto && g_GameState != Get5State_PreVeto) { ChangeState(Get5State_Warmup); } @@ -1902,7 +1908,7 @@ release version to remove this message."); } static int VersionCheckRequestCallback(Handle request, bool failure, bool requestSuccessful, - EHTTPStatusCode statusCode) { + EHTTPStatusCode statusCode) { if (failure || !requestSuccessful) { LogError("Failed to check for Get5 update. HTTP error code: %d.", statusCode); delete request; diff --git a/scripting/get5/backups.sp b/scripting/get5/backups.sp index 76237a73f..fd8cbca73 100644 --- a/scripting/get5/backups.sp +++ b/scripting/get5/backups.sp @@ -156,11 +156,10 @@ void WriteBackup() { return; } - if (g_GameState != Get5State_Warmup - && g_GameState != Get5State_KnifeRound - && g_GameState != Get5State_Live) { + if (g_GameState != Get5State_Warmup && g_GameState != Get5State_KnifeRound && + g_GameState != Get5State_Live) { LogDebug("Not writing backup for game state %d.", g_GameState); - return; // Only backup post-veto warmup, knife and live. + return; // Only backup post-veto warmup, knife and live. } char folder[PLATFORM_MAX_PATH]; @@ -296,8 +295,8 @@ static void WriteBackupStructure(const char[] path) { kv.GoBack(); if (g_GameState == Get5State_Live) { - // Write valve's backup format into the file. This only applies to live rounds, as any pre-live backups should - // just restart the game to the knife round. + // Write valve's backup format into the file. This only applies to live rounds, as any pre-live + // backups should just restart the game to the knife round. char lastBackup[PLATFORM_MAX_PATH]; ConVar lastBackupCvar = FindConVar("mp_backup_round_file_last"); if (lastBackupCvar != null) { @@ -330,8 +329,9 @@ bool RestoreFromBackup(const char[] path, bool restartRecording = true) { } if (restartRecording) { - // We must stop recording when loading a backup, and we must do it before we load the match config, or the g_MatchID - // variable will be incorrect. This is suppressed when using the !stop command. + // We must stop recording when loading a backup, and we must do it before we load the match + // config, or the g_MatchID variable will be incorrect. This is suppressed when using the !stop + // command. StopRecording(); } @@ -352,8 +352,9 @@ bool RestoreFromBackup(const char[] path, bool restartRecording = true) { } if (g_GameState != Get5State_Live) { - // This isn't perfect, but it's better than resetting all pauses used to zero in cases of restore on a new server. - // If restoring while live, we just retain the current pauses used, as they should be the "most correct". + // This isn't perfect, but it's better than resetting all pauses used to zero in cases of + // restore on a new server. If restoring while live, we just retain the current pauses used, as + // they should be the "most correct". g_TacticalPausesUsed[Get5Team_1] = kv.GetNum("team1_tac_pauses_used", 0); g_TacticalPausesUsed[Get5Team_2] = kv.GetNum("team2_tac_pauses_used", 0); g_TechnicalPausesUsed[Get5Team_1] = kv.GetNum("team1_tech_pauses_used", 0); @@ -440,22 +441,25 @@ bool RestoreFromBackup(const char[] path, bool restartRecording = true) { if (!StrEqual(currentMap, currentSeriesMap)) { // We don't need to assign players if changing map; this will be done when the players rejoin. - // If a map is to be changed, we want to suppress all stats events immediately, as the Get5_OnBackupRestore is - // called now and we don't want events firing after this until the game is live again. + // If a map is to be changed, we want to suppress all stats events immediately, as the + // Get5_OnBackupRestore is called now and we don't want events firing after this until the game + // is live again. ChangeMap(currentSeriesMap, 3.0); } else { - // We must assign players to their teams. This is normally done inside LoadMatchConfig, but since we need - // the team sides to be applied from the backup, we skip it then and do it here. + // We must assign players to their teams. This is normally done inside LoadMatchConfig, but + // since we need the team sides to be applied from the backup, we skip it then and do it here. LOOP_CLIENTS(i) { if (IsPlayer(i)) { CheckClientTeam(i); } } if (g_WaitingForRoundBackup) { - // Same map, but round restore with a Valve backup; do normal restore immediately with no ready-up. + // Same map, but round restore with a Valve backup; do normal restore immediately with no + // ready-up. RestoreGet5Backup(restartRecording); } else { - // We are restarting to the same map for prelive; just go back into warmup and let players ready-up again. + // We are restarting to the same map for prelive; just go back into warmup and let players + // ready-up again. ResetReadyStatus(); UnpauseGame(Get5Team_None); ChangeState(Get5State_Warmup); @@ -481,21 +485,23 @@ bool RestoreFromBackup(const char[] path, bool restartRecording = true) { } void RestoreGet5Backup(bool restartRecording = true) { - // If you load a backup during a live round, the game might get stuck if there are only bots remaining and no - // players are alive. Other stuff will probably also go wrong, so we just reset the game before loading the - // backup to avoid any weird edge-cases. + // If you load a backup during a live round, the game might get stuck if there are only bots + // remaining and no players are alive. Other stuff will probably also go wrong, so we just reset + // the game before loading the backup to avoid any weird edge-cases. if (!InWarmup()) { - RestartGame(); + RestartGame(); } ExecCfg(g_LiveCfgCvar); PauseGame(Get5Team_None, Get5PauseType_Backup); - g_DoingBackupRestoreNow = true; // reset after the backup has completed, suppresses various events and hooks until then. + g_DoingBackupRestoreNow = true; // reset after the backup has completed, suppresses various + // events and hooks until then. g_WaitingForRoundBackup = false; CreateTimer(1.5, Timer_StartRestore); if (restartRecording) { - // Since a backup command forces the recording to stop, we restart it here once the backup has completed. - // We have to do this on a delay, as when loading from a live game, the backup will already be recording and must - // flush before a new record command can be issued. This is suppressed when using the !stop command! + // Since a backup command forces the recording to stop, we restart it here once the backup has + // completed. We have to do this on a delay, as when loading from a live game, the backup will + // already be recording and must flush before a new record command can be issued. This is + // suppressed when using the !stop command! CreateTimer(3.0, Timer_StartRecordingAfterBackup, _, TIMER_FLAG_NO_MAPCHANGE); } } @@ -514,11 +520,12 @@ static Action Timer_StartRestore(Handle timer) { ServerCommand("mp_backup_restore_load_file \"%s\"", tempValveBackup); CreateTimer(0.5, Timer_FinishBackup); - // We need to fire the OnRoundStarted event manually, as it will be suppressed during backups and won't fire while - // g_DoingBackupRestoreNow is true. + // We need to fire the OnRoundStarted event manually, as it will be suppressed during backups and + // won't fire while g_DoingBackupRestoreNow is true. KeyValues kv = new KeyValues("Backup"); if (kv.ImportFromFile(tempValveBackup)) { - Get5RoundStartedEvent startEvent = new Get5RoundStartedEvent(g_MatchID, g_MapNumber, kv.GetNum("round", 0)); + Get5RoundStartedEvent startEvent = + new Get5RoundStartedEvent(g_MatchID, g_MapNumber, kv.GetNum("round", 0)); LogDebug("Calling Get5_OnRoundStart() via backup."); Call_StartForward(g_OnRoundStart); Call_PushCell(startEvent); diff --git a/scripting/get5/goinglive.sp b/scripting/get5/goinglive.sp index a8e7aee06..b808b7776 100644 --- a/scripting/get5/goinglive.sp +++ b/scripting/get5/goinglive.sp @@ -14,21 +14,23 @@ void StartGoingLive() { ChangeState(Get5State_GoingLive); - // This ensures that we can send send the game to warmup and count down *even if* someone had put "mp_warmup_end", or - // something else that would mess up warmup, in their live config, which they shouldn't. But we can't be sure. + // This ensures that we can send send the game to warmup and count down *even if* someone had put + // "mp_warmup_end", or something else that would mess up warmup, in their live config, which they + // shouldn't. But we can't be sure. CreateTimer(1.0, Timer_GoToLiveAfterWarmupCountdown, _, TIMER_FLAG_NO_MAPCHANGE); } static Action Timer_GoToLiveAfterWarmupCountdown(Handle timer) { if (g_GameState != Get5State_GoingLive) { - return Plugin_Handled; // super defensive race-condition check. + return Plugin_Handled; // super defensive race-condition check. } // Always disable sv_cheats! ServerCommand("sv_cheats 0"); // Ensure we're in warmup and counting down to live. Round_PreStart handles the rest. int countdown = g_LiveCountdownTimeCvar.IntValue; if (countdown < 5) { - countdown = 5; // ensures that a cvar countdown value of 0 does not leave the game forever in warmup. + countdown = + 5; // ensures that a cvar countdown value of 0 does not leave the game forever in warmup. } Get5_MessageToAll("%t", "MatchBeginInSecondsInfoMessage", countdown); StartWarmup(countdown); diff --git a/scripting/get5/kniferounds.sp b/scripting/get5/kniferounds.sp index f8c008a87..be5848144 100644 --- a/scripting/get5/kniferounds.sp +++ b/scripting/get5/kniferounds.sp @@ -42,8 +42,9 @@ static void PerformSideSwap(bool swap) { LOOP_CLIENTS(i) { if (IsValidClient(i) && !IsClientSourceTV(i)) { if (IsFakeClient(i)) { - // Because bots never have an assigned team, they won't be moved around by CheckClientTeam. We kick them to - // prevent one team from having too many players. They will rejoin if defined in the live config. + // Because bots never have an assigned team, they won't be moved around by + // CheckClientTeam. We kick them to prevent one team from having too many players. They + // will rejoin if defined in the live config. KickClient(i); } else { CheckClientTeam(i, false); diff --git a/scripting/get5/mapveto.sp b/scripting/get5/mapveto.sp index 0cc8e9950..7bbf72ac8 100644 --- a/scripting/get5/mapveto.sp +++ b/scripting/get5/mapveto.sp @@ -82,7 +82,7 @@ static void VetoFinished() { } // Always end recording here; ensures that we can successfully start one after veto. StopRecording(delay); - WriteBackup(); // Write first pre-live backup after veto. + WriteBackup(); // Write first pre-live backup after veto. } // Main Veto Controller @@ -210,7 +210,7 @@ static void VetoController(int client) { // Confirmations static void GiveConfirmationMenu(int client, MenuHandler handler, const char[] title, - const char[] confirmChoice) { + const char[] confirmChoice) { // Figure out text for positive and negative values char positiveBuffer[1024], negativeBuffer[1024]; Format(positiveBuffer, sizeof(positiveBuffer), "%T", "ConfirmPositiveOptionText", client); @@ -314,7 +314,8 @@ static int MapVetoMenuHandler(Menu menu, MenuAction action, int param1, int para FormatMapName(mapName, formattedMapName, sizeof(formattedMapName), true, false); // Add color here as FormatMapName would make the color green. Format(formattedMapName, sizeof(formattedMapName), "{LIGHT_RED}%s{NORMAL}", formattedMapName); - Get5_MessageToAll("%t", "TeamVetoedMapInfoMessage", g_FormattedTeamNames[team], formattedMapName); + Get5_MessageToAll("%t", "TeamVetoedMapInfoMessage", g_FormattedTeamNames[team], + formattedMapName); Get5MapVetoedEvent event = new Get5MapVetoedEvent(g_MatchID, team, mapName); diff --git a/scripting/get5/matchconfig.sp b/scripting/get5/matchconfig.sp index ac2a415cb..4d428c851 100644 --- a/scripting/get5/matchconfig.sp +++ b/scripting/get5/matchconfig.sp @@ -73,7 +73,8 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } if (g_NumberOfMapsInSeries > g_MapPoolList.Length) { - MatchConfigFail("Cannot play a series of %d maps with a maplist of %d maps", g_NumberOfMapsInSeries, g_MapPoolList.Length); + MatchConfigFail("Cannot play a series of %d maps with a maplist of %d maps", + g_NumberOfMapsInSeries, g_MapPoolList.Length); return false; } @@ -85,7 +86,8 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { // Push a map side if one hasn't been set yet. if (g_MapSides.Length < g_MapsToPlay.Length) { - if (g_MatchSideType == MatchSideType_Standard || g_MatchSideType == MatchSideType_AlwaysKnife) { + if (g_MatchSideType == MatchSideType_Standard || + g_MatchSideType == MatchSideType_AlwaysKnife) { g_MapSides.Push(SideChoice_KnifeRound); } else { g_MapSides.Push(SideChoice_Team1CT); @@ -108,16 +110,17 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { } if (g_GameState == Get5State_None) { - // Make sure here that we don't run the code below in game state none, but also not overriding PreVeto. - // Currently, this could happen if you restored a backup with skip_veto:false. + // Make sure here that we don't run the code below in game state none, but also not overriding + // PreVeto. Currently, this could happen if you restored a backup with skip_veto:false. ChangeState(Get5State_Warmup); } - // Before we run the Get5_OnSeriesInit forward, we want to ensure that as much game state is set as possible, - // so that any implementation reacting to that event/forward will have all the natives return proper data. - // ExecuteMatchConfigCvars gets called twice because ExecCfg(g_WarmupCfgCvar) also does it async, but we need it here - // as the team assigment below depends on it. We set this one first as the others may depend on something changed in - // the match cvars section. + // Before we run the Get5_OnSeriesInit forward, we want to ensure that as much game state is set + // as possible, so that any implementation reacting to that event/forward will have all the + // natives return proper data. ExecuteMatchConfigCvars gets called twice because + // ExecCfg(g_WarmupCfgCvar) also does it async, but we need it here as the team assigment below + // depends on it. We set this one first as the others may depend on something changed in the match + // cvars section. ExecuteMatchConfigCvars(); SetMatchTeamCvars(); LoadPlayerNames(); @@ -146,23 +149,23 @@ bool LoadMatchConfig(const char[] config, bool restoreBackup = false) { EventLogger_LogAndDeleteEvent(startEvent); if (!g_CheckAuthsCvar.BoolValue && - (GetTeamAuths(Get5Team_1).Length != 0 - || GetTeamAuths(Get5Team_2).Length != 0 - || GetTeamCoaches(Get5Team_1).Length != 0 - || GetTeamCoaches(Get5Team_2).Length != 0)) { - LogError("Setting player auths in the \"players\" or \"coaches\" section has no impact with get5_check_auths 0"); + (GetTeamAuths(Get5Team_1).Length != 0 || GetTeamAuths(Get5Team_2).Length != 0 || + GetTeamCoaches(Get5Team_1).Length != 0 || GetTeamCoaches(Get5Team_2).Length != 0)) { + LogError( + "Setting player auths in the \"players\" or \"coaches\" section has no impact with get5_check_auths 0"); } - // ExecuteMatchConfigCvars must be executed before we place players, as it might have get5_check_auths 1. - // We must also have called SetStartingTeams to get the sides right. - // When restoring from backup, assigning to teams is done after loading the match config as it depends on the sides - // being set correctly by the backup, so we put it inside this "if" here. - // When the match is loaded, we do not want to assign players on no team, as they may be in the process of joining - // the server, which is the reason for the timer callback. This has caused problems with players getting stuck on - // no team when using match config autoload, essentially recreating the "coaching bug". Adding a second seems to - // solve this problem. We cannot just skip team none, as players may also just be on the team selection menu when - // the match is loaded, meaning they will never have a joingame hook, as it already happened, and we still want - // those players placed. + // ExecuteMatchConfigCvars must be executed before we place players, as it might have + // get5_check_auths 1. We must also have called SetStartingTeams to get the sides right. When + // restoring from backup, assigning to teams is done after loading the match config as it + // depends on the sides being set correctly by the backup, so we put it inside this "if" here. + // When the match is loaded, we do not want to assign players on no team, as they may be in the + // process of joining the server, which is the reason for the timer callback. This has caused + // problems with players getting stuck on no team when using match config autoload, essentially + // recreating the "coaching bug". Adding a second seems to solve this problem. We cannot just + // skip team none, as players may also just be on the team selection menu when the match is + // loaded, meaning they will never have a joingame hook, as it already happened, and we still + // want those players placed. LOOP_CLIENTS(i) { if (IsPlayer(i)) { if (GetClientTeam(i) == CS_TEAM_NONE) { @@ -298,7 +301,7 @@ stock bool LoadMatchFromUrl(const char[] url, ArrayList paramNames = null, // SteamWorks HTTP callback for fetching a workshop collection static int SteamWorks_OnMatchConfigReceived(Handle request, bool failure, bool requestSuccessful, - EHTTPStatusCode statusCode, Handle data) { + EHTTPStatusCode statusCode, Handle data) { if (failure || !requestSuccessful) { MatchConfigFail("Steamworks GET request failed, HTTP status code = %d", statusCode); return; @@ -408,7 +411,8 @@ static bool LoadMatchFromKv(KeyValues kv) { g_NumberOfMapsInSeries = kv.GetNum("num_maps", CONFIG_NUM_MAPSDEFAULT); g_MapsToWin = MapsToWin(g_NumberOfMapsInSeries); if (g_NumberOfMapsInSeries != 2 && g_NumberOfMapsInSeries % 2 == 0) { - MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); + MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", + g_NumberOfMapsInSeries); return false; } @@ -506,7 +510,8 @@ static bool LoadMatchFromJson(JSON_Object json) { g_NumberOfMapsInSeries = json_object_get_int_safe(json, "num_maps", CONFIG_NUM_MAPSDEFAULT); g_MapsToWin = MapsToWin(g_NumberOfMapsInSeries); if (g_NumberOfMapsInSeries != 2 && g_NumberOfMapsInSeries % 2 == 0) { - MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", g_NumberOfMapsInSeries); + MatchConfigFail("Cannot create a series of %d maps. Use an odd number or 2.", + g_NumberOfMapsInSeries); return false; } @@ -802,13 +807,16 @@ Action Command_AddPlayer(int client, int args) { ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; } else if (g_InScrimMode) { - ReplyToCommand(client, "Cannot use get5_addplayer in scrim mode. Use get5_ringer to swap a player's team."); + ReplyToCommand( + client, + "Cannot use get5_addplayer in scrim mode. Use get5_ringer to swap a player's team."); return Plugin_Handled; } else if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { ReplyToCommand(client, "Cannot add players while waiting for round backup."); return Plugin_Handled; } else if (g_PendingSideSwap || InHalftimePhase()) { - ReplyToCommand(client, "Cannot add players during halftime. Please wait until the next round starts."); + ReplyToCommand(client, + "Cannot add players during halftime. Please wait until the next round starts."); return Plugin_Handled; } @@ -856,13 +864,15 @@ Action Command_AddCoach(int client, int args) { ReplyToCommand(client, "Coaching is not enabled."); return Plugin_Handled; } else if (g_InScrimMode) { - ReplyToCommand(client, "Coaches cannot be added in scrim mode. Use the !coach command in chat."); + ReplyToCommand(client, + "Coaches cannot be added in scrim mode. Use the !coach command in chat."); return Plugin_Handled; } else if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { ReplyToCommand(client, "Cannot add coaches while waiting for round backup."); return Plugin_Handled; } else if (g_PendingSideSwap || InHalftimePhase()) { - ReplyToCommand(client, "Cannot add coaches during halftime. Please wait until the next round starts."); + ReplyToCommand(client, + "Cannot add coaches during halftime. Please wait until the next round starts."); return Plugin_Handled; } @@ -891,7 +901,8 @@ Action Command_AddCoach(int client, int args) { } if (AddCoachToTeam(auth, team, name)) { - // If the player is already on the team as a regular player, remove them when adding to coaches. + // If the player is already on the team as a regular player, remove them when adding to + // coaches. int index = GetTeamAuths(team).FindString(auth); if (index >= 0) { GetTeamAuths(team).Erase(index); @@ -904,7 +915,9 @@ Action Command_AddCoach(int client, int args) { if (addedClient > 0 && IsClientConnected(addedClient)) { Get5Side side = view_as(Get5TeamToCSTeam(team)); if (side != Get5Side_None) { - LogDebug("Player %s was present on the server when added as coach; moving them to coach for %d.", auth, team); + LogDebug( + "Player %s was present on the server when added as coach; moving them to coach for %d.", + auth, team); SetClientCoaching(addedClient, side); } } @@ -925,13 +938,16 @@ Action Command_AddKickedPlayer(int client, int args) { ReplyToCommand(client, "No match configuration was loaded."); return Plugin_Handled; } else if (g_InScrimMode) { - ReplyToCommand(client, "Cannot use get5_addkickedplayer in scrim mode. Use get5_ringer to swap a player's team."); + ReplyToCommand( + client, + "Cannot use get5_addkickedplayer in scrim mode. Use get5_ringer to swap a player's team."); return Plugin_Handled; } else if (g_DoingBackupRestoreNow || g_WaitingForRoundBackup) { ReplyToCommand(client, "Cannot add players while waiting for round backup."); return Plugin_Handled; } else if (g_PendingSideSwap || InHalftimePhase()) { - ReplyToCommand(client, "Cannot add players during halftime. Please wait until the next round starts."); + ReplyToCommand(client, + "Cannot add players during halftime. Please wait until the next round starts."); return Plugin_Handled; } @@ -1140,8 +1156,8 @@ Action Command_CreateScrim(int client, int args) { MatchConfigFail("Failed to read scrim template in %s", templateFile); return Plugin_Handled; } - // Because we read the field and write it again, then load it as a match config, we have to make sure empty - // strings are not being skipped. + // Because we read the field and write it again, then load it as a match config, we have to make + // sure empty strings are not being skipped. if (kv.JumpToKey("team1") && kv.JumpToKey("players") && kv.GotoFirstSubKey(false)) { char name[MAX_NAME_LENGTH]; do { @@ -1301,8 +1317,8 @@ void ExecCfg(ConVar cvar) { } static Action Timer_ExecMatchConfig(Handle timer) { - // When we load config files using ServerCommand("exec") above, which is async, we want match config cvars to always - // override. + // When we load config files using ServerCommand("exec") above, which is async, we want match + // config cvars to always override. ExecuteMatchConfigCvars(); SetMatchTeamCvars(); return Plugin_Handled; diff --git a/scripting/get5/stats.sp b/scripting/get5/stats.sp index df9e4a1f8..c92e51734 100644 --- a/scripting/get5/stats.sp +++ b/scripting/get5/stats.sp @@ -17,7 +17,7 @@ void Stats_PluginStart() { } static Action HandlePlayerDamage(int victim, int &attacker, int &inflictor, float &damage, - int &damagetype) { + int &damagetype) { if (g_GameState != Get5State_Live || IsDoingRestoreOrMapChange()) { return Plugin_Continue; } @@ -255,13 +255,13 @@ void Stats_RoundStart() { // until next round. Get5Side side = view_as(GetClientTeam(i)); if (side == Get5Side_None) { - continue; // Don't do anything to players pending team join. + continue; // Don't do anything to players pending team join. } Get5Team team = GetClientMatchTeam(i); if (team == Get5Team_1 || team == Get5Team_2) { InitPlayerStats(i, side); if (side == Get5Side_Spec) { - continue; // exclude coaches from STAT_ROUNDSPLAYED. + continue; // exclude coaches from STAT_ROUNDSPLAYED. } IncrementPlayerStat(i, STAT_ROUNDSPLAYED); } @@ -505,8 +505,8 @@ static Action Stats_MolotovExtinguishedEvent(Event event, const char[] name, boo } static Action Stats_MolotovEndedEvent(Event event, const char[] name, bool dontBroadcast) { - // No backup check; the event is deleted in EndMolotovEvent to prevent leaks, as this function works like the - // the HE/flash timer callbacks which also do not check for backup state. + // No backup check; the event is deleted in EndMolotovEvent to prevent leaks, as this function + // works like the the HE/flash timer callbacks which also do not check for backup state. if (g_GameState != Get5State_Live) { return; } @@ -956,7 +956,8 @@ static void InitPlayerStats(int client, Get5Side side) { GetClientName(client, name, sizeof(name)); g_StatsKv.SetString(STAT_NAME, name); - // Update if client is coaching. Spectators are excluded as their match team is spec; this checks side only. + // Update if client is coaching. Spectators are excluded as their match team is spec; this checks + // side only. g_StatsKv.SetNum(STAT_COACHING, side == Get5Side_Spec); // If the player already had their stats set, don't override them. diff --git a/scripting/get5/teamlogic.sp b/scripting/get5/teamlogic.sp index aec0d57f4..2bfbf502a 100644 --- a/scripting/get5/teamlogic.sp +++ b/scripting/get5/teamlogic.sp @@ -24,7 +24,8 @@ void CheckClientTeam(int client, bool useDefaultTeamSelection = true) { Get5Side correctSide = view_as(Get5TeamToCSTeam(correctTeam)); if (correctSide == Get5Side_None) { // This should not be possible. - LogError("Client %d belongs to no side. This is an unexpected error and should be reported.", client); + LogError("Client %d belongs to no side. This is an unexpected error and should be reported.", + client); return; } @@ -40,20 +41,21 @@ void CheckClientTeam(int client, bool useDefaultTeamSelection = true) { return; } - // If player was not locked to coaching, check if their team's current size -self is less than the max. + // If player was not locked to coaching, check if their team's current size -self is less than the + // max. if (CountPlayersOnTeam(correctTeam, client) < g_PlayersPerTeam) { SwitchPlayerTeam(client, correctSide, useDefaultTeamSelection); return; } - // We end here if a player was not a predefined coach while there was no space as a regular player. If coaching is - // enabled, we drop the player in coach, and if not, they must be kicked. + // We end here if a player was not a predefined coach while there was no space as a regular + // player. If coaching is enabled, we drop the player in coach, and if not, they must be kicked. if (g_CoachingEnabledCvar.BoolValue && coachesOnTeam < g_CoachesPerTeam) { Get5_Message(client, "%t", "MoveToCoachInfoMessage"); - // In scrim mode, we don't put coaches or players of the "away" team into any auth arrays; they default to the - // opposite of the home team. If a full team's coach disconnects or leaves and rejoins, they should be placed on the - // coach team if their team is full. In a regular match, they will have called .coach before the map starts and will - // be placed by auth above. + // In scrim mode, we don't put coaches or players of the "away" team into any auth arrays; they + // default to the opposite of the home team. If a full team's coach disconnects or leaves and + // rejoins, they should be placed on the coach team if their team is full. In a regular match, + // they will have called .coach before the map starts and will be placed by auth above. if (!g_InScrimMode) { MovePlayerToCoachInConfig(client, correctTeam); } @@ -75,9 +77,10 @@ Action Command_JoinTeam(int client, const char[] command, int argc) { if (g_GameState == Get5State_None || !g_CheckAuthsCvar.BoolValue) { return Plugin_Continue; } - // If, in some odd case, a player should find themselves on no team while g_CheckAuthsCvar is true, we want to let - // them trigger the PlacePlayerOnTeam logic when clicking any team. In any other case, we just block. - // Blocking ensures that coaches in scrim-mode will not stop coaching if they select a team in the menu. + // If, in some odd case, a player should find themselves on no team while g_CheckAuthsCvar is + // true, we want to let them trigger the PlacePlayerOnTeam logic when clicking any team. In any + // other case, we just block. Blocking ensures that coaches in scrim-mode will not stop coaching + // if they select a team in the menu. if (IsAuthedPlayer(client) && GetClientTeam(client) == CS_TEAM_NONE) { PlacePlayerOnTeam(client); } @@ -97,7 +100,8 @@ void SetClientCoaching(int client, Get5Side side) { SwitchPlayerTeam(client, Get5Side_Spec); SetEntProp(client, Prop_Send, "m_iCoachingTeam", side); SetEntProp(client, Prop_Send, "m_iObserverMode", 4); - SetEntProp(client, Prop_Send, "m_iAccount", 0); // Ensures coaches have no money if they were to rejoin the game. + SetEntProp(client, Prop_Send, "m_iAccount", + 0); // Ensures coaches have no money if they were to rejoin the game. char formattedPlayerName[MAX_NAME_LENGTH]; Get5Team team = GetClientMatchTeam(client); @@ -109,7 +113,8 @@ void CoachingChangedHook(ConVar convar, const char[] oldValue, const char[] newV if (g_GameState == Get5State_None) { return; } - // If disabling coaching, make sure we swap coaches to team or kick them, as they are now regular spectators. + // If disabling coaching, make sure we swap coaches to team or kick them, as they are now regular + // spectators. if (StringToInt(oldValue) != 0 && !convar.BoolValue) { LogDebug("Detected sv_coaching_enabled was disabled. Checking for coaches."); LOOP_CLIENTS(i) { @@ -154,8 +159,8 @@ Action Command_SmCoach(int client, int args) { Get5_Message(client, "%t", "CannotLeaveCoachingTeamIsFull"); return Plugin_Continue; } - // Fall-through to CheckClientTeam(i) below, which moves the player back on the team because they are not - // defined as a coach in auth. + // Fall-through to CheckClientTeam(i) below, which moves the player back on the team because + // they are not defined as a coach in auth. } else { if (coachSlotsFull) { Get5_Message(client, "%t", "AllCoachSlotsFilledForTeam", g_CoachesPerTeam); @@ -203,9 +208,10 @@ static void MoveCoachToPlayerInConfig(const int client, const Get5Team team) { char auth[AUTH_LENGTH]; GetAuth(client, auth, sizeof(auth)); AddPlayerToTeam(auth, team, ""); - // This differs from MovePlayerToCoachInConfig because being in coach array + player array will make coaching - // take precedence, so if you're being added from coach to player, and you're already defined as a player, the above - // function will return false, so we always remove from the coach array when moving from coach to player. + // This differs from MovePlayerToCoachInConfig because being in coach array + player array will + // make coaching take precedence, so if you're being added from coach to player, and you're + // already defined as a player, the above function will return false, so we always remove from the + // coach array when moving from coach to player. int index = GetTeamCoaches(team).FindString(auth); if (index >= 0) { LogDebug("Removing client %d from coach team auth array for team %d", client, team); @@ -217,7 +223,9 @@ Action Command_Coach(int client, const char[] command, int argc) { if (g_GameState == Get5State_None) { return Plugin_Continue; } - ReplyToCommand(client, "Please use .coach in chat or sm_coach instead of the built-in console coach command."); + ReplyToCommand( + client, + "Please use .coach in chat or sm_coach instead of the built-in console coach command."); return Plugin_Stop; } @@ -298,7 +306,8 @@ int CountCoachesOnTeam(Get5Team team, int exclude = -1) { int count = 0; Get5Side side = view_as(Get5TeamToCSTeam(team)); LOOP_CLIENTS(i) { - if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team && GetClientCoachingSide(i) == side) { + if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team && + GetClientCoachingSide(i) == side) { count++; } } @@ -309,7 +318,8 @@ int CountPlayersOnTeam(Get5Team team, int exclude = -1) { int count = 0; Get5Side side = view_as(Get5TeamToCSTeam(team)); LOOP_CLIENTS(i) { - if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team && view_as(GetClientTeam(i)) == side) { + if (i != exclude && IsAuthedPlayer(i) && GetClientMatchTeam(i) == team && + view_as(GetClientTeam(i)) == side) { count++; } } @@ -322,7 +332,7 @@ bool IsClientCoaching(int client) { static Get5Side GetClientCoachingSide(int client) { if (GetClientTeam(client) != CS_TEAM_SPECTATOR) { - return Get5Side_None; + return Get5Side_None; } int side = GetEntProp(client, Prop_Send, "m_iCoachingTeam"); if (side == CS_TEAM_CT) { diff --git a/scripting/get5/util.sp b/scripting/get5/util.sp index 858428f5d..922ba4a37 100644 --- a/scripting/get5/util.sp +++ b/scripting/get5/util.sp @@ -4,8 +4,9 @@ #define MAX_FLOAT_STRING_LENGTH 32 #define AUTH_LENGTH 64 -// Dummy value for when we need to write a KeyValue string, but we don't care about the value *or* when the value is an -// empty string. Trying to write an empty string results in the KeyValue not being written, so we use this. +// Dummy value for when we need to write a KeyValue string, but we don't care about the value *or* +// when the value is an empty string. Trying to write an empty string results in the KeyValue not +// being written, so we use this. #define KEYVALUE_STRING_PLACEHOLDER "__placeholder" static char _colorNames[][] = {"{NORMAL}", "{DARK_RED}", "{PINK}", "{GREEN}", "{YELLOW}", @@ -75,7 +76,8 @@ stock void SwitchPlayerTeam(int client, Get5Side side, bool useDefaultTeamSelect if (useDefaultTeamSelection || team == CS_TEAM_SPECTATOR) { ChangeClientTeam(client, team); } else { - // When doing side-swap in knife-rounds, we do this to prevent the score from going -1 for everyone. + // When doing side-swap in knife-rounds, we do this to prevent the score from going -1 for + // everyone. CS_SwitchTeam(client, team); CS_UpdateClientModel(client); CS_RespawnPlayer(client); @@ -128,7 +130,8 @@ stock void FormatCvarName(char[] buffer, const int bufferLength, const char[] cV Format(buffer, bufferLength, "{GRAY}%s{NORMAL}", cVar); } -stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client, const Get5Team team) { +stock void FormatPlayerName(char[] buffer, const int bufferLength, const int client, + const Get5Team team) { // Used when injecting the team for coaching players, who are always on team spectator. Get5Side side = view_as(Get5_Get5TeamToCSTeam(team)); if (side == Get5Side_CT) { @@ -178,7 +181,8 @@ stock void StartWarmup(int warmupTime = 0) { if (warmupTime < 1) { LogDebug("Setting indefinite warmup."); // Setting mp_warmuptime to anything less than 7 triggers the countdown to restart regardless of - // mp_warmup_pausetimer 1, and this might be tick-related, so we set it to 10 just for good measure. + // mp_warmup_pausetimer 1, and this might be tick-related, so we set it to 10 just for good + // measure. ServerCommand("mp_warmuptime 10"); ServerCommand("mp_warmup_pausetimer 1"); } else { @@ -296,7 +300,8 @@ stock int GetCvarIntSafe(const char[] cvarName) { } } -stock void FormatMapName(const char[] mapName, char[] buffer, int len, bool cleanName = false, bool color = false) { +stock void FormatMapName(const char[] mapName, char[] buffer, int len, bool cleanName = false, + bool color = false) { // explode map by '/' so we can remove any directory prefixes (e.g. workshop stuff) char buffers[4][PLATFORM_MAX_PATH]; int numSplits = ExplodeString(mapName, "/", buffers, sizeof(buffers), PLATFORM_MAX_PATH); @@ -420,9 +425,10 @@ stock bool RemoveStringFromArray(ArrayList list, const char[] str) { return false; } -// Because KeyValue cannot write empty strings, we use this to consistently read empty strings and replace -// our empty-string-placeholder with actual empty string. -stock bool ReadEmptyStringInsteadOfPlaceholder(const KeyValues kv, char[] buffer, const int bufferSize) { +// Because KeyValue cannot write empty strings, we use this to consistently read empty strings and +// replace our empty-string-placeholder with actual empty string. +stock bool ReadEmptyStringInsteadOfPlaceholder(const KeyValues kv, char[] buffer, + const int bufferSize) { kv.GetString(NULL_STRING, buffer, bufferSize); if (StrEqual(KEYVALUE_STRING_PLACEHOLDER, buffer)) { Format(buffer, bufferSize, ""); @@ -431,7 +437,8 @@ stock bool ReadEmptyStringInsteadOfPlaceholder(const KeyValues kv, char[] buffer return false; } -stock bool WritePlaceholderInsteadOfEmptyString(const KeyValues kv, char[] buffer, const int bufferSize) { +stock bool WritePlaceholderInsteadOfEmptyString(const KeyValues kv, char[] buffer, + const int bufferSize) { kv.GetString(NULL_STRING, buffer, bufferSize); if (StrEqual("", buffer)) { kv.SetString(NULL_STRING, KEYVALUE_STRING_PLACEHOLDER); diff --git a/scripting/get5_apistats.sp b/scripting/get5_apistats.sp index 2741445da..a3f5522f3 100644 --- a/scripting/get5_apistats.sp +++ b/scripting/get5_apistats.sp @@ -196,7 +196,8 @@ static void CheckForLogo(const char[] logo) { } } -static int LogoCallback(Handle request, bool failure, bool successful, EHTTPStatusCode status, int data) { +static int LogoCallback(Handle request, bool failure, bool successful, EHTTPStatusCode status, + int data) { if (failure || !successful) { LogError("Logo request failed, status code = %d", status); return; @@ -285,7 +286,7 @@ static void AddIntStat(Handle req, KeyValues kv, const char[] field) { } static void UpdatePlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, - const Get5Team team) { + const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; diff --git a/scripting/get5_mysqlstats.sp b/scripting/get5_mysqlstats.sp index 91f9e4acf..ddd063eb0 100644 --- a/scripting/get5_mysqlstats.sp +++ b/scripting/get5_mysqlstats.sp @@ -224,7 +224,7 @@ public void Get5_OnMapResult(const Get5MapResultEvent event) { } static void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyValues kv, - const Get5Team team) { + const Get5Team team) { char name[MAX_NAME_LENGTH]; char auth[AUTH_LENGTH]; char nameSz[MAX_NAME_LENGTH * 2 + 1]; @@ -236,7 +236,7 @@ static void AddPlayerStats(const char[] matchId, const int mapNumber, const KeyV if (kv.GotoFirstSubKey()) { do { if (kv.GetNum(STAT_COACHING, 0) > 0) { - continue; // Don't update stats for coaches. + continue; // Don't update stats for coaches. } kv.GetSectionName(auth, sizeof(auth)); kv.GetString("name", name, sizeof(name)); From c51adc9deb8133f85503d4097f5d72dd03a34af3 Mon Sep 17 00:00:00 2001 From: Nicolai Cornelis Date: Sat, 3 Sep 2022 14:25:52 +0200 Subject: [PATCH 104/104] Always put server into warmup, otherwise a boX will never go from postgame back into warmup on map change --- scripting/get5.sp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripting/get5.sp b/scripting/get5.sp index 384a5747c..de01988d8 100644 --- a/scripting/get5.sp +++ b/scripting/get5.sp @@ -1730,16 +1730,20 @@ static void SetServerStateOnStartup(bool force) { // Only run on first client connect or if forced (during OnConfigsExecuted). return; } - if (g_GameState <= Get5State_Warmup || g_WaitingForRoundBackup) { - // If the server is in veto/preveto when someone joins or the configs exec, it should remain in - // that state. This would happen if the a config with veto is loaded before someone joins the - // server. - if (g_GameState != Get5State_Veto && g_GameState != Get5State_PreVeto) { - ChangeState(Get5State_Warmup); - } - ExecCfg(g_WarmupCfgCvar); - StartWarmup(); + // It shouldn't really be possible to end up here, as the server *should* reload the map anyway when first player + // joins, but as a safeguard we don't want to move a live game that's not pending a backup or map change into warmup + // on player connect. + if (!force && g_GameState == Get5State_Live && !g_WaitingForRoundBackup && !g_MapChangePending) { + return; + } + // If the server is in preveto when someone joins or the configs exec, it should remain in + // that state. This would happen if the a config with veto is loaded before someone joins the + // server. + if (g_GameState != Get5State_PreVeto) { + ChangeState(Get5State_Warmup); } + ExecCfg(g_WarmupCfgCvar); + StartWarmup(); } void ChangeState(Get5State state) {