diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..cc83d7cd3 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,9 @@ +# Add 'needs code review' label to any changes within the entire repository +needs code review: +- changed-files: + - any-glob-to-any-file: '**' + +# Add 'needs testing' label to any changes within the entire repository +needs testing: +- changed-files: + - any-glob-to-any-file: '**' diff --git a/.github/nativefuncs.json b/.github/nativefuncs.json index 1148d3e58..110429031 100644 --- a/.github/nativefuncs.json +++ b/.github/nativefuncs.json @@ -18,6 +18,12 @@ "returnTypeString":"void", "argTypes":"string modName, bool enabled" }, + { + "name":"NSIsModRemote", + "helpText":"", + "returnTypeString":"bool", + "argTypes":"string modName" + }, { "name":"NSGetModDescriptionByModName", "helpText":"", @@ -266,6 +272,12 @@ "returnTypeString":"void", "argTypes":"string modName, bool enabled" }, + { + "name":"NSIsModRemote", + "helpText":"", + "returnTypeString":"bool", + "argTypes":"string modName" + }, { "name":"NSGetModDescriptionByModName", "helpText":"", @@ -466,6 +478,12 @@ "returnTypeString":"void", "argTypes":"string modName, bool enabled" }, + { + "name":"NSIsModRemote", + "helpText":"", + "returnTypeString":"bool", + "argTypes":"string modName" + }, { "name":"NSGetModDescriptionByModName", "helpText":"", @@ -502,6 +520,13 @@ "returnTypeString":"array", "argTypes":"string modName" }, + { + "name": "NSFetchVerifiedModsManifesto", + "helpText": "Retrieves the verified mods list from the central authority (GitHub).", + "returnTypeString": "void", + "argTypes": "" + + }, { "name": "NSIsModDownloadable", "helpText": "checks whether a mod is verified and can be auto-downloaded", diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8cda06a32..e4dd3515d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,3 +16,11 @@ Note that commit messages in PRs will generally be squashed to keep commit histo --> Replace this line with a description of your change (and screenshots/screenrecordings if applicable). + +### Code review: + +Replace this line with anything specific to look out for during code reviews. + +### Testing: + +Replace this line with instructions on how to test your pull request. The more detailed, the easier it is for reviewers to test, the faster your PR gets merged. diff --git a/.github/workflows/auto-label-pr.yml b/.github/workflows/auto-label-pr.yml new file mode 100644 index 000000000..659ff351d --- /dev/null +++ b/.github/workflows/auto-label-pr.yml @@ -0,0 +1,14 @@ +name: Auto-Labeler +on: + pull_request_target: + types: + - opened + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 diff --git a/.github/workflows/compile-check.yml b/.github/workflows/compile-check.yml index cb7ab1d08..8803f4f4c 100644 --- a/.github/workflows/compile-check.yml +++ b/.github/workflows/compile-check.yml @@ -8,7 +8,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: "mods" diff --git a/.github/workflows/encoding.yml b/.github/workflows/encoding.yml index a88e3961a..b1d851a14 100644 --- a/.github/workflows/encoding.yml +++ b/.github/workflows/encoding.yml @@ -3,19 +3,19 @@ on: [push, pull_request] jobs: check-loc-encoding: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check localization files encoding run: | files=$(ls Northstar.Client/mod/resource/northstar_client_localisation_*.txt) IFS=$'\n'; files=($files); unset IFS; ! file --mime "${files[@]}" | grep -v "charset=utf-16le" check-missing-translations: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Look out for missing translations run: node .github/build/find-missing-translations.js continue-on-error: true diff --git a/.github/workflows/merge-conflict-auto-label.yml b/.github/workflows/merge-conflict-auto-label.yml index e237726af..abb7cabde 100644 --- a/.github/workflows/merge-conflict-auto-label.yml +++ b/.github/workflows/merge-conflict-auto-label.yml @@ -1,8 +1,11 @@ name: Merge Conflict Auto Label on: + workflow_dispatch: # Manual run push: branches: - main + schedule: + - cron: "10 21 * * *" # Runs at 21:10; time was chosen based on contributor activity and low GitHub Actions cron load. jobs: triage: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..64b8c0c3c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing +> NOTE: This is the first iteration of this file. You're welcome to pull request changes + +### Contents +- [Making issues](#Making-issues) +- [Making pull requests](#Making-pull-requests) +- [Formatting code](#Formatting-code) + +## Making issues +When creating issues, whether to track a bug or suggest a feature, please try to follow this set of rules: +1. When filing a bug report issue, please attach a log file ( Located in `R2Northstar/logs/` ). +2. **Short, consise.** No-one wants to read an essay on why x should be added. +3. When applicable attach a short video / screen shots to better convey what the issue is about. + +## Making pull requests +When creating a pull request please follow this set of rules: +1. **1 Fix/Feature should equal to 1 Pull Request.** The more you do in 1 PR the longer it'll take to merge. +2. Mark your Pull Request as draft if it isnt finished just yet. +3. Properly format your code. As we currently don't have a formatter we're very lax on this. That doesn't mean you don't have to try to format your code. +4. **Mention how to test your changes / add a test mod to make it easier to test** + +## Formatting code +A basic set of rules you should follow when creating a Pull Request + +### Comment your code +- If you're adding a new file you should add a doc comment noting what the file does and its origin + ```cpp + ///----------------------------------------------------------------------------- + /// Origin: Northstar + /// Purpose: handles server-side rui + ///----------------------------------------------------------------------------- + ``` + Alternative to `Origin: Northstar` would be `Origin: Respawn` +- Each function should have a header doc comment + ```cpp + ///----------------------------------------------------------------------------- + /// Sends a string message to player + /// Returns true if it succeeded + ///----------------------------------------------------------------------------- + bool function NSSendInfoMessageToPlayer( entity player, string text ) + ``` +### Functions +- Functions should have spaces in the parentheses + ```cpp + bool function NSSendInfoMessageToPlayer( entity player, string text ) + ``` +- If a function need to be threaded off using `thread` it should have a `_Threaded` suffix + +### File +- Files should use tabs for indentation diff --git a/Northstar.Client/keyvalues/resource/fontfiletable.txt b/Northstar.Client/keyvalues/resource/fontfiletable.txt new file mode 100644 index 000000000..f0b91c07e --- /dev/null +++ b/Northstar.Client/keyvalues/resource/fontfiletable.txt @@ -0,0 +1,10 @@ +FontFileTable +{ + "arial unicode ms" "resource/Lato-Regular.ttf" + + "lucida console" "resource/NorthstarMono.ttf" [$PC] + + "arial" "resource/Lato-Regular.ttf" + "arial bold" "resource/Lato-Regular.ttf" + "arial narrow" "resource/Lato-Regular.ttf" +} diff --git a/Northstar.Client/mod.json b/Northstar.Client/mod.json index 44937a2b0..0d0cfc169 100644 --- a/Northstar.Client/mod.json +++ b/Northstar.Client/mod.json @@ -46,6 +46,10 @@ "Name": "modlist_reverse", "DefaultValue": "0", "Flags": "ARCHIVE_PLAYERPROFILE" + }, + { + "Name": "modemenu_mode_filter", + "DefaultValue": "-1" } ], "Scripts": [ diff --git a/Northstar.Client/mod/resource/fontfiletable.txt b/Northstar.Client/mod/resource/fontfiletable.txt deleted file mode 100644 index b42381341..000000000 --- a/Northstar.Client/mod/resource/fontfiletable.txt +++ /dev/null @@ -1,40 +0,0 @@ -FontFileTable -{ - "Default" "resource/MetronicPro-Regular.vfont" [!$JAPANESE && !$TCHINESE] - "Default" "resource/NotoSansJP-Regular.vfont" [$JAPANESE] - "Default" "resource/NotoSansTC-Regular.vfont" [$TCHINESE] - - "DefaultBold" "resource/MetronicPro-SemiBold.vfont" [!$JAPANESE && !$TCHINESE] - "DefaultBold" "resource/NotoSansJP-Regular.vfont" [$JAPANESE] - "DefaultBold" "resource/NotoSansTC-Regular.vfont" [$TCHINESE] - - "Titanfall" "resource/Titanfall-Regular.vfont" [!$JAPANESE && !$TCHINESE && !$RUSSIAN] - "Titanfall" "resource/NotoSansJP-Regular.vfont" [$JAPANESE] - "Titanfall" "resource/NotoSansTC-Regular.vfont" [$TCHINESE] - "Titanfall" "resource/MetronicPro-SemiBold.vfont" [$RUSSIAN] - - "marlett" "vgui/fonts/marlett.ttf" - - // Everything below is DONOTSHIP / Dev only - - "arial unicode ms" "resource/Lato-Regular.ttf" - - "lucida console" "resource/NorthstarMono.ttf" [$PC] - "lucida console" "resource/MetronicPro-Regular.vfont" [$GAMECONSOLE] - - "tahoma" "fonts\\tahoma.ttf" [$PC] - "tahoma" "resource/MetronicPro-Regular.vfont" [$GAMECONSOLE] - - "tahoma bold" "fonts\\tahomabd.ttf" [$PC] - "tahoma bold" "resource/MetronicPro-SemiBold.vfont" [$GAMECONSOLE] - - "courier new" "fonts\\cour.ttf" [$PC] - "courier new" "vgui/fonts/cour.ttf" [$GAMECONSOLE] - - "times new roman" "fonts\\times.ttf" [$PC] - "times new roman" "vgui/fonts/times.ttf" [$GAMECONSOLE] - - "arial" "resource/Lato-Regular.ttf" - "arial bold" "resource/Lato-Regular.ttf" - "arial narrow" "resource/Lato-Regular.ttf" -} diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt index e6518febb..f7c5ee2d1 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt @@ -320,10 +320,22 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a "UNAUTHORIZED_PWD" "Wrong password" "STRYDER_RESPONSE" "Couldn't parse stryder response" "PLAYER_NOT_FOUND" "Couldn't find player account" - "INVALID_MASTERSERVER_TOKEN" "Invalid or expired masterserver token" + "INVALID_MASTERSERVER_TOKEN" "Invalid or expired masterserver token, try restarting EA App." "JSON_PARSE_ERROR" "Error parsing json response" "UNSUPPORTED_VERSION" "The version you are using is no longer supported" + // Mode menu + "MODE_MENU_PVPVE" "PvPvE" + "MODE_MENU_PVE" "PvE" + "MODE_MENU_PVP" "PvP" + "MODE_MENU_FFA" "FFA" + "MODE_MENU_TITAN_ONLY" "Titan Only" + "MODE_MENU_OTHER" "Other" + "MODE_MENU_CUSTOM" "Custom" + "MODE_MENU_ALL" "All" + "MODE_MENU_UNKNOWN" "Unknown" + "MODE_MENU_SWITCH" "Filter" + "AUTHENTICATION_FAILED_HEADER" "Authentication Failed" "AUTHENTICATION_FAILED_BODY" "Failed to authenticate with Atlas!" "AUTHENTICATION_FAILED_ERROR_CODE" "Error code: ^DB6F2C00%s1^" @@ -373,12 +385,14 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a "WRONG_MOD_VERSION" "Server has mod \"%s1\" v%s2 while you have v%s3" "MOD_NOT_VERIFIED" "(mod is not verified, and couldn't be downloaded automatically)" "MOD_DL_DISABLED" "(automatic mod downloading is disabled)" + "MANIFESTO_FETCHING_TITLE" "Setting up mod download" + "MANIFESTO_FETCHING_TEXT" "Retrieving the list of verified mods..." "DOWNLOADING_MOD_TITLE" "Downloading mod" "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Downloading mod (%s1%)" "DOWNLOADING_MOD_TEXT" "Downloading %s1 v%s2..." "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Downloading %s1 v%s2...\n(%s3/%s4 MB)" - "CHECKSUMING_TITLE" "Checksuming mod" - "CHECKSUMING_TEXT" "Verifying contents of %s1 v%s2..." + "CHECKSUMING_TITLE" "Verifying mod integrity" + "CHECKSUMING_TEXT" "Validating files of %s1 v%s2..." "EXTRACTING_MOD_TITLE" "Extracting mod (%s1%)" "EXTRACTING_MOD_TEXT" "Extracting %s1 v%s2...\n(%s3/%s4 MB)" "FAILED_DOWNLOADING" "Failed downloading mod" diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_french.txt b/Northstar.Client/mod/resource/northstar_client_localisation_french.txt index b0e579cff..38d601b0c 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_french.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_french.txt @@ -316,7 +316,7 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst "UNAUTHORIZED_PWD" "Mot de passe incorrect" "STRYDER_RESPONSE" "Impossible d'analyser la réponse de Stryder" "PLAYER_NOT_FOUND" "Impossible de trouver le compte du joueur" - "INVALID_MASTERSERVER_TOKEN" "Jeton du server maître invalide ou expiré" + "INVALID_MASTERSERVER_TOKEN" "Token du server maître invalide ou expiré, veuillez relancer l'application EA." "JSON_PARSE_ERROR" "Une erreur est survenue durant l'analyse JSON" "UNSUPPORTED_VERSION" "La version que vous utilisez n'est plus supportée" @@ -348,7 +348,7 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst "PROGRESSION_ENABLED_HEADER" "Progression activée !" "PROGRESSION_DISABLED_HEADER" "Progression désactivée !" "PROGRESSION_DISABLED_BODY" "^CCCC0000La progression a été désactivée.^\n\nLes Titans, Armes, Factions, Skins, et autres seront débloqués et utilisables en tout temps.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs." - "PROGRESSION_TOGGLE_DISABLED_BODY" "Les Titans, Armes, Factions, Skins et autres seront débloqués par la monté en niveau ou par leur achats en mérites.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs.\n\n^CC000000Warning : Si vous équiper des objets que vous n'avez pas encore débloqués, ils seront déséquipés !" + "PROGRESSION_TOGGLE_DISABLED_BODY" "Les Titans, Armes, Factions, Skins et autres seront débloqués par la monté en niveau ou par leur achats en mérites.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs.\n\n^CC000000Warning : Si vous équipez des objets que vous n'avez pas encore débloqués, ils seront déséquipés !" "PROGRESSION_ENABLED_BODY" "^CCCC0000La progression a été activée.^\n\nLes Titans, Armes, Factions, Skins et autres seront débloqués par la monté en niveau ou par leur achats en mérites.\n\nPeut être changé à n'importe que moment dans le salon multijoueurs." "TOGGLE_PROGRESSION" "Activer la progression" "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Activer la progression" @@ -357,5 +357,37 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst "AUTHENTICATION_FAILED_HELP" "Aide" "AUTHENTICATION_FAILED_ERROR_CODE" "Code d'erreur : ^DB6F2C00%s1^" "AUTHENTICATION_FAILED_BODY" "L'authentification avec Atlas a échoué." + "MISSING_MOD" "Mod manquant \"%s1\" v%s2" + "MOD_REQUIRED_WARNING" " : Ce mod peut être (dé)chargé automatiquement en rejoignant un serveur" + "EXTRACTING_MOD_TITLE" "Extraction du mod (%s1%)" + "MOD_NOT_VERIFIED" "(ce mod n'est pas vérifié, et n'a donc pas pu être automatiquement téléchargé)" + "MOD_DL_DISABLED" "(le téléchargement automatique de mods est désactivé)" + "DOWNLOADING_MOD_TITLE" "Téléchargement du mod" + "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Téléchargement du mod (%s1%)" + "DOWNLOADING_MOD_TEXT" "Téléchargement de %s1 v%s2..." + "WRONG_MOD_VERSION" "Le serveur requiert la version v%s2 du mod \"%s1\" (vous avez la version v%s3)" + "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Téléchargement de %s1 v%s2...\n(%s3/%s4 Mo)" + "CHECKSUMING_TITLE" "Vérification de l'intégrité du mod" + "CHECKSUMING_TEXT" "Vérification du contenu de %s1 v%s2..." + "EXTRACTING_MOD_TEXT" "Extraction de %s1 v%s2...\n(%s3/%s4 Mo)" + "FAILED_DOWNLOADING" "Echec du téléchargement du mod" + "FAILED_READING_ARCHIVE" "Une erreur est survenue lors de la lecture de l'archive." + "FAILED_WRITING_TO_DISK" "Une erreur est survenue lors de l'extraction des fichiers." + "MOD_FETCHING_FAILED" "L'archive n'a pas pu être téléchargée depuis Thunderstore." + "MOD_CORRUPTED" "La somme de contrôle de l'archive ne correspond pas à la signature vérifiée." + "NO_DISK_SPACE_AVAILABLE" "L'espace restant sur votre disque est insuffisant." + "MOD_FETCHING_FAILED_GENERAL" "L'extraction du mod a échoué. Consultez le journal pour plus d'informations." + "MANIFESTO_FETCHING_TITLE" "Préparation du téléchargement du mod" + "MANIFESTO_FETCHING_TEXT" "Récupération de la liste des mods vérifiés..." + "MODE_MENU_PVPVE" "JcJcE" + "MODE_MENU_PVE" "JcE" + "MODE_MENU_PVP" "JcJ" + "MODE_MENU_FFA" "Mêlée générale" + "MODE_MENU_OTHER" "Autre" + "MODE_MENU_CUSTOM" "Personnalisé" + "MODE_MENU_ALL" "Tout" + "MODE_MENU_UNKNOWN" "Inconnu" + "MODE_MENU_SWITCH" "Filtre" + "MODE_MENU_TITAN_ONLY" "Titan Uniquement" } } diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_german.txt b/Northstar.Client/mod/resource/northstar_client_localisation_german.txt index 996a3e2ba..726ad6ac8 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_german.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_german.txt @@ -17,9 +17,9 @@ Drücke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im Modmenü ändern." "BACK_AUTHENTICATION_AGREEMENT" "Authentifizierungs-Einwilligung" "AUTHENTICATION_AGREEMENT" "Authentifizierungs-Einwilligung" - "AUTHENTICATION_AGREEMENT_RESTART" "Ein Neustart ist notwendig, um diese Änderung zu übernehmen" + "AUTHENTICATION_AGREEMENT_RESTART" "Ein Neustart ist notwendig, um diese Änderung zu übernehmen." - "DIALOG_AUTHENTICATING_MASTERSERVER" "Authentifizierung mit Master Server" + "DIALOG_AUTHENTICATING_MASTERSERVER" "Authentifizierung mit Master Server." "AUTHENTICATIONAGREEMENT_NO" "Du hast dich gegen die Authentifizierung mit Northstar entschieden. Du kannst die Authentifizierungs-Einwilligung im Modmenü ansehen." "MENU_TITLE_SERVER_BROWSER" "Server Browser" @@ -320,7 +320,7 @@ Drücke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im Modmenü "SHOW_ONLY_NOT_REQUIRED" "Nur optionale Mods" "SHOW_ONLY_REQUIRED" "Nur notwendige Mods" "PROGRESSION_TOGGLE_DISABLED_HEADER" "Fortschritt aktivieren?" - "TOGGLE_PROGRESSION" "Fortschritt zuschalten" + "TOGGLE_PROGRESSION" "Fortschritt umschalten" "PROGRESSION_TOGGLE_ENABLED_HEADER" "Fortschritt deaktivieren?" "PROGRESSION_TOGGLE_ENABLED_BODY" "Titans, Waffen, Fraktionen, Skins, usw werden freigeschaltet und sind zu jeder Zeit verfügbar .\n\nDies kann in der Mehrspielerlobby zu jedem Zeitpunkt geändert werden." "MATCH_COUNTDOWN_LENGTH" "Countdown für privates Match" @@ -357,7 +357,7 @@ Drücke Ja, um zuzustimmen. Du kannst diese Entscheidung jederzeit im Modmenü "PROGRESSION_DISABLED_HEADER" "Fortschritt deaktiviert!" "WILL_RESET_ALL_SETTINGS" "Dadurch werden ALLE Einstellungen, die zu dieser Kategorie gehören, zurückgesetzt.\n\nDies kann nicht rückgängig gemacht werden." "WILL_RESET_SETTING" "Dies setzten die Einstellungen %s1 auf deren Ursprungeswert zurück.\n\nDies kann nicht rückgängig gemacht werden." - "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Fortschritt zuschalten." + "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Fortschritt umschalten" "PROGRESSION_TOGGLE_DISABLED_BODY" "Titans, Waffen, Fraktionen, Skins usw. müssen durch Levelaufstieg freigeschaltet oder mit Verdiensten gekauft werden.\n\nDies kann jederzeit in der Mehrspieler-Lobby geändert werden.\n\n^CC000000Warnung: Wenn Sie derzeit ausgerüstete Gegenstände besitzen, die Sie nicht freigeschaltet haben, werden diese zurückgesetzt!" "PROGRESSION_ENABLED_BODY" "^CCCC0000Fortschritt wurde aktiviert.^\n\nTitans, Waffen, Fraktionen, Skins usw. müssen durch Levelaufstieg freigeschaltet oder mit Verdiensten gekauft werden.\n\nDies kann jederzeit in der Mehrspieler-Lobby geändert werden." "PROGRESSION_DISABLED_BODY" "^CCCC0000Fortschritt wurde deaktiviert.^\n\nTitans, Waffen, Fraktionen, Skins usw. werden alle freigeschaltet und jederzeit nutzbar sein.\n\nDies kann jederzeit in der Mehrspieler-Lobby geändert werden." diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt b/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt index 38e67dea4..089edf35b 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_italian.txt @@ -316,7 +316,7 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo "UNAUTHORIZED_PWD" "Password errata" "STRYDER_RESPONSE" "Non è stato possibile analizzare la risposta di Stryder" "PLAYER_NOT_FOUND" "Non è stato trovato l'account player" - "INVALID_MASTERSERVER_TOKEN" "Token Masterserver invalido o scaduto" + "INVALID_MASTERSERVER_TOKEN" "Token Masterserver invalido o scaduto, prova a riavviare l'App EA" "JSON_PARSE_ERROR" "Errore nell'analisi della risposta json" "UNSUPPORTED_VERSION" "La versione che stai usando non è più supportata" @@ -380,5 +380,42 @@ Premi Sì se sei d'accordo. Questa scelta può essere modificata in qualsiasi mo "sns_wme_kill_value" "Valore per uccisione Wingman d'Elite" "sns_reset_kill_value" "Valore per uccisione Lama Impulsi/Esecuzione" "PL_tffa_desc" "Ogni pilota per sè, distruggi tutti i titan nemici." + "player_force_respawn" "Respawn Forzato" + "PROGRESSION_TOGGLE_DISABLED_HEADER" "Attivare Progressione?" + "TOGGLE_PROGRESSION" "Attiva/Disattiva Progressione" + "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Attiva/Disattiva Progressione" + "PROGRESSION_TOGGLE_ENABLED_HEADER" "Disattivare la Progressione?" + "AUTHENTICATION_FAILED_BODY" "Fallimento nell'autenticare con Atlas" + "AUTHENTICATION_FAILED_ERROR_CODE" "Codice di errore: ^DB6F2C00%s1^" + "AUTHENTICATION_FAILED_HELP" "Aiuto" + "AUTHENTICATION_FAILED_HEADER" "Autenticazione Fallita" + "MISSING_MOD" "Mod mancante \"%s1\" v%s2" + "MOD_NOT_VERIFIED" "(Mod non verificata, non è stato possibile il download automatico)" + "MOD_DL_DISABLED" "(Il download automatico delle mod è disabilitato)" + "DOWNLOADING_MOD_TEXT" "Download %s1 v%s2..." + "CHECKSUMING_TEXT" "Verifica contenuti %s1 v%s2..." + "EXTRACTING_MOD_TITLE" "Estrazione mod (%s1%)" + "FAILED_DOWNLOADING" "Download della mod fallito" + "NO_DISK_SPACE_AVAILABLE" "Non c'è abbastanza spazio sul disco." + "MOD_FETCHING_FAILED_GENERAL" "Estrazione mod fallita. Controlla i file di log per più dettagli." + "DOWNLOADING_MOD_TITLE" "Download mod in corso" + "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Download in corso della mod (%s1%)" + "FAILED_READING_ARCHIVE" "C'è stato un errore durante la lettura dell'archivio della mod." + "FAILED_WRITING_TO_DISK" "C'è stato un errore durante l'estrazione della mod al filesystem." + "MOD_FETCHING_FAILED" "Impossibile scaricare l'archivio mod da Thunderstore." + "MOD_CORRUPTED" "La firma dell'archivio scaricato non corrisponde con quella verificata." + "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Download %s1 v%s2...\n(%s3/%s4 MB)" + "EXTRACTING_MOD_TEXT" "Estraendo %s1 v%s2...\n(%s3/%s4 MB)" + "MOD_REQUIRED_WARNING" " : Questa mod potrebbe venire (non)caricata entrando in un server" + "PROGRESSION_ENABLED_HEADER" "Progressione Attivata!" + "PROGRESSION_ENABLED_BODY" "^CCCC0000La progessione è stata abilitata.^\n\nTitan, Armi, Fazioni, Skin, etc. dovranno essere sbloccate livellando, o comprate con i Meriti.\n\nQuesto può essere cambiato in qualsiasi momento nella lobby multigiocatore." + "PROGRESSION_DISABLED_HEADER" "Progressione Disabilitata!" + "PROGRESSION_DISABLED_BODY" "^CCCC0000La progressione è stata disabilitata.^\n\nTitan, Armi, Fazioni, Skin, etc. saranno sbloccate e utilizzabili in qualsiasi momento.\n\nQuesto può essere cambiato in qualsiasi momento nella lobby multigiocatore." + "WRONG_MOD_VERSION" "Il server ha la mod \"%s1\" v%s2 mentre tu hai v%s3" + "MANIFESTO_FETCHING_TITLE" "Iniziando download mod" + "MANIFESTO_FETCHING_TEXT" "Recuperando lista delle mod verificate..." + "PROGRESSION_TOGGLE_ENABLED_BODY" "Titan, Armi, Fazioni, Skin, etc. saranno tutti sbloccati e utilizzabili in ogni momento.\n\nQuesto può essere cambiato in qualsiasi momento nella lobby Multigiocatore." + "PROGRESSION_TOGGLE_DISABLED_BODY" "Titan, Armi, Factions, Skin, etc. avranno bisogno di essere sbloccate livellando, o comprate con i Meriti.\n\nQuesto può essere cambiato in ogni momento nella lobby Multiplayer.\n\n^CC000000Attenzione: se al momento hai equipaggiato degli item che non hai sbloccato, verranno resettati!" + "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000La progressione può essere abilitata ora!^\n\nNorthstar ora supporta la progressione vanilla, ciò significa che puoi scegliere di sbloccare Armi, Skin, Titan, etc. attraverso i livelli e le sfide.\n\nPuoi abilitare la progressione cliccando il pulsante in basso nella schermata lobby.\n\nQuesto può essere cambiato in qualsiasi momento." } } diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt b/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt index b7fadeaff..798d603e0 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_japanese.txt @@ -233,8 +233,8 @@ "GAMEMODE_fastball" "ファストボール" "PL_fastball" "ファストボール" "PL_fastball_lobby" "ファストボール ロビー" - "PL_fastball_desc" "ライブファイア。パネルをハックし、味方を蘇生できる" - "PL_fastball_hint" "ライブファイア。パネルをハックし、味方を蘇生できる" + "PL_fastball_desc" "ライブファイア。パネルをハックし、味方を蘇生できる。" + "PL_fastball_hint" "ライブファイア。パネルをハックし、味方を蘇生できる。" "PL_fastball_abbr" "FB" "FASTBALL_PANEL_CAPTURED" "%s1 がパネル%s2を制圧した" "SCOREBOARD_FASTBALL_HACKS" "制圧パネル" @@ -265,10 +265,10 @@ "player_bleedout_aiBleedingPlayerMissChance" "ダウン時のAI命中率" // coop stuff - "PL_sp_coop" "(UNFINISHED) Singleplayer Coop" - "PL_sp_coop_lobby" "Singleplayer Coop Lobby" - "PL_sp_coop_desc" "Play through the singleplayer campaign with friends" - "PL_sp_coop_hint" "Play through the singleplayer campaign with friends" + "PL_sp_coop" "(未完成) シングルプレイヤー 協力モード" + "PL_sp_coop_lobby" "シングルプレイヤー 協力モード ロビー" + "PL_sp_coop_desc" "シングルプレイヤーのキャンペーンモードをフレンドと一緒にプレイできる" + "PL_sp_coop_hint" "シングルプレイヤーのキャンペーンモードをフレンドと一緒にプレイできる" "PL_sp_coop_abbr" "SP" "SP_TRAINING" "パイロット・ガントレット" @@ -340,13 +340,70 @@ "NO_GAMESERVER_RESPONSE" "ゲームサーバーに接続できません\n(Couldn't reach game server)" "BAD_GAMESERVER_RESPONSE" "ゲームサーバーが不明なレスポンスを返しました\n(Game server gave an invalid response)" "UNAUTHORIZED_GAMESERVER" "ゲームサーバーにそのリクエストを作成する許可がありません\n(Game server is not authorized to make that request)" - "UNAUTHORIZED_GAME" "StryderはこのアカウントがTitanfall 2を所持しているかどうかを確認できませんでした\nStryder couldn't confirm that this account owns Titanfall 2" + "UNAUTHORIZED_GAME" "StryderはこのアカウントがTitanfall 2を所持しているかどうかを確認できませんでした\n(Stryder couldn't confirm that this account owns Titanfall 2)" "UNAUTHORIZED_PWD" "パスワードが間違っています\n(Wrong password)" "STRYDER_RESPONSE" "Stryderからのレスポンスの処理に失敗しました\n(Couldn't parse stryder response)" "PLAYER_NOT_FOUND" "プレイヤーのアカウントが見つかりません\n(Couldn't find player account)" - "INVALID_MASTERSERVER_TOKEN" "マスターサーバーのトークンが不明か期限切れです\n(Invalid or expired masterserver token)" + "INVALID_MASTERSERVER_TOKEN" "マスターサーバーのトークンが不明か期限切れです。EAアプリの再起動をお試しください。\n(Invalid or expired masterserver token, try restarting EA App.)" "JSON_PARSE_ERROR" "JSONレスポンスの処理に失敗しました\n(Error parsing json response)" "UNSUPPORTED_VERSION" "現在使用しているバージョンはサポートされていません\n(The version you are using is no longer supported)" + "player_force_respawn" "強制リスポーン" + "SHOW_ONLY_REQUIRED" "必須のModのみ" + "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% 進行システム切り替え" + "TOGGLE_PROGRESSION" "進行システム切り替え" + "AUTHENTICATION_FAILED_HEADER" "認証に失敗" + "DOWNLOADING_MOD_TITLE" "Modをダウンロード中" + "PROGRESSION_TOGGLE_ENABLED_BODY" "タイタン、武器、勢力、スキンなどが全てアンロックされ、いつでも使えるようになる。\n\nこの設定は、マルチプレイヤーロビーでいつでも変更可能だ。" + "PROGRESSION_TOGGLE_DISABLED_HEADER" "進行システムを有効にしますか?" + "PROGRESSION_TOGGLE_ENABLED_HEADER" "進行システムを無効にしますか?" + "PROGRESSION_DISABLED_HEADER" "進行システムが無効になりました!" + "PROGRESSION_ENABLED_HEADER" "進行システムが有効になりました!" + "PROGRESSION_DISABLED_BODY" "^CCCC0000進行システムが無効化された。^\n\nタイタン、武器、勢力、スキンなどが全てアンロックされ、いつでも使えるようになる。\n\nこの設定は、マルチプレイヤーロビーでいつでも変更可能だ。" + "SHOULD_RETURN_TO_LOBBY" "マッチ終了後にロビーへ戻る" + "REPLACEMENT_WEAPON" "武器の置き換え" + "TACTICAL_REPLACEMENT" "戦術の置き換え" + "DISALLOWED_TACTICALS" "戦術の禁止" + "DISALLOWED_WEAPONS" "武器の禁止" + "ONLY_HOST_CAN_START_MATCH" "マッチを開始できるのはホストのみ" + "ONLY_HOST_MATCH_SETTINGS" "プライベートマッチの設定を変更できるのはホストのみ" + "AUTHENTICATION_FAILED_HELP" "ヘルプ" + "MATCH_COUNTDOWN_LENGTH" "プライベートマッチのカウントダウン時間" + "ARE_YOU_SURE" "よろしいですか?" + "MOD_SETTINGS_SERVER" "サーバー" + "MOD_SETTINGS_RESET" "リセット" + "MOD_SETTINGS_RESET_ALL" "全てリセット" + "NO_RESULTS" "リザルトなし。" + "MOD_SETTINGS" "Modの設定" + "NORTHSTAR_BASE_SETTINGS" "Northstarの基本設定" + "NO_DISK_SPACE_AVAILABLE" "ディスクに十分な領域がありません。" + "FAILED_DOWNLOADING" "Modのダウンロードに失敗" + "FAILED_READING_ARCHIVE" "Modアーカイブの読込中にエラーが発生しました。" + "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Modをダウンロード中 (%s1%)" + "DOWNLOADING_MOD_TEXT" "ダウンロード中 %s1 v%s2..." + "DOWNLOADING_MOD_TEXT_W_PROGRESS" "ダウンロード中 %s1 v%s2...\n(%s3/%s4 MB)" + "MOD_FETCHING_FAILED" "ThunderstoreからModアーカイブをダウンロードできませんでした。" + "EXTRACTING_MOD_TITLE" "Modを抽出中 (%s1%)" + "EXTRACTING_MOD_TEXT" "抽出中 %s1 v%s2...\n(%s3/%s4 MB)" + "FAILED_WRITING_TO_DISK" "Modファイルをファイルシステムに抽出する際にエラーが発生しました。" + "MOD_FETCHING_FAILED_GENERAL" "Modの抽出に失敗。詳細はログを確認してください。" + "MOD_CORRUPTED" "ダウンロードしたアーカイブのチェックサムが検証済み署名と一致しませんでした。" + "CHECKSUMING_TITLE" "Modをチェックサム中" + "CHECKSUMING_TEXT" "コンテンツを検証中 %s1 v%s2..." + "MOD_DL_DISABLED" "(自動Modダウンロードは無効です)" + "PROGRESSION_TOGGLE_DISABLED_BODY" "タイタン、武器、勢力、スキンなどのアンロックにレベル上げやメリットが必要になる。\n\nこの設定は、マルチプレイヤーロビーでいつでも変更可能だ。\n\n^CC000000警告: 現在装備しているアイテムがロック中の場合、装備状況は初期化されます!" + "PROGRESSION_ENABLED_BODY" "^CCCC0000進行システムが有効化された。^\n\nタイタン、武器、勢力、スキンなどのアンロックにレベル上げやメリットが必要になる。\n\nこの設定は、マルチプレイヤーロビーでいつでも変更可能だ。" + "LOG_UNKNOWN_CLIENTCOMMANDS" "不明なクライアントコマンドを記録する" + "SHOW_ONLY_NOT_REQUIRED" "任意のModのみ" + "AUTHENTICATION_FAILED_ERROR_CODE" "エラーコード: ^DB6F2C00%s1^" + "AUTHENTICATION_FAILED_BODY" "Atlasの認証に失敗しました!\n(Failed to authenticate with Atlas!)" + "MISSING_MOD" "Mod消失 \"%s1\" v%s2" + "MOD_REQUIRED_WARNING" " :このModはサーバーに参加した際に読み込まる(または読み込まれない)ことがあります" + "WILL_RESET_ALL_SETTINGS" "カテゴリー内の全設定をリセットしようとしています。\n\nこの操作は取り消せません。" + "WILL_RESET_SETTING" "%s1 の設定を初期値に戻そうとしています。\n\nこの操作は取り消せません。" + "NO_MODS" "設定が利用できません!Modのインストールはこちらから: ^5588FF00northstar.thunderstore.io^0" + "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000進行システムが開放されました!^\n\nNorthstarはバニラの進行システム、つまり武器、スキン、タイタン等をレベルアップやチャレンジ達成で解除する機能をサポートしています。\n\nこの進行システムは、ロビー画面下のボタンから有効にできます。\n\nこの設定はいつでも変更可能です。" + "MOD_NOT_VERIFIED" "(Modが検証されていないため、自動ダウンロードできませんでした)" + "WRONG_MOD_VERSION" "サーバーはMod\"%s1\"v%s2 を要求していますが、あなたのModバージョンは%s3です" // Translation done by Zetryox and CYakigasi } diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_polish.txt b/Northstar.Client/mod/resource/northstar_client_localisation_polish.txt new file mode 100644 index 000000000..9b570cebe --- /dev/null +++ b/Northstar.Client/mod/resource/northstar_client_localisation_polish.txt @@ -0,0 +1,394 @@ +"lang" +{ + "Language" "polish" + "Tokens" + { + // This file needs to be encoded as UTF-16 LE + + "MENU_LAUNCH_NORTHSTAR" "Uruchom Northstar" + "MENU_TITLE_MODS" "Mody" + "RELOAD_MODS" "Przeładuj Mody" + "WARNING" "Ostrzeżenie" + "CORE_MOD_DISABLE_WARNING" "Wyłączenie kluczowych Modów może zepsuć Northstar!" + "DISABLE" "Wyłącz" + + "DIALOG_TITLE_INSTALLED_NORTHSTAR" "Dzięki za instalację Northstar!" + "AUTHENTICATION_AGREEMENT_DIALOG_TEXT" "Dla poprawnego działania Northstar potrzebne jest uwierzytelnienie z głównym serwerem. Potrzebne będzie wysłanie twojego tokena Origin do głównego serwera, nie będzie on przechowywany ani użyty do żadnych innych celów. +Naciśnij Tak jeżeli wyrażasz zgodę. Wybór może zostać zmieniony w menu Modów." + "BACK_AUTHENTICATION_AGREEMENT" "Zgoda na uwierzytelnianie" + "AUTHENTICATION_AGREEMENT" "Zgoda na uwierzytelnianie" + "AUTHENTICATION_AGREEMENT_RESTART" "Potrzebne jest ponowne odpalenie Titanfall 2 by zmiany dały efekt." + + "DIALOG_AUTHENTICATING_MASTERSERVER" "Uwierzytelnianie z głównym serwerem." + "AUTHENTICATIONAGREEMENT_NO" "Zdecydowałeś się nie uwierzytelniać się z Northstar. Zgodę można wyrazić w menu z modami." + + "MENU_TITLE_SERVER_BROWSER" "Wyszukiwarka serwerów" + "NS_SERVERBROWSER_NOSERVERS" "Nie znaleziono serwerów" + "NS_SERVERBROWSER_UNKNOWNMODE" "Nieznany tryb" + "NS_SERVERBROWSER_WAITINGFORSERVERS" "Oczekiwanie na serwery..." + "NS_SERVERBROWSER_CONNECTIONFAILED" "Połączenie nieudane!" + "REFRESH_SERVERS" "Odśwież" + + "MENU_TITLE_CONNECT_PASSWORD" "Dołącz z hasłem" + "MENU_CONNECT_MENU_CONNECT" "Dołącz" + + "PRIVATE_MATCH_PAGE_PREV" "Poprzednia strona" + "PRIVATE_MATCH_PAGE_NEXT" "Następna strona" + + "MENU_MATCH_SETTINGS" "Ustawienia meczu" + "MENU_MATCH_SETTINGS_SUBMENU" "%s1 Ustawienia trybu" + + "PRIVATE_MATCH_SINGLEPLAYER_LEVEL" "%s1 (Tryb Jednoosobowy)" + + // fra hint for private match menu, because fra only has PL_fra_desc in vanilla + "PL_fra_hint" "Każdy na każdego. Zbierz 3 baterie, aby otrzymać Tytana." + + // mode settings + "MODE_SETTING_CATEGORY_PILOT" "Pilot" + "MODE_SETTING_CATEGORY_TITAN" "Tytan" + "MODE_SETTING_CATEGORY_RIFF" "Modyfikatory rozgrywki" + "MODE_SETTING_CATEGORY_MATCH" "Mecz" + + "classic_mp" "Klasyczny tryb wieloosobowy" + "run_epilogue" "Epilog" + "scorelimit" "Limit wyniku" + "roundscorelimit" "Limit wyniku (na rundę)" + "timelimit" "Limit Czasu" + "roundtimelimit" "Limit Czasu (na rundę)" + "respawnprotection" "Ochrona po odrodzeniu" + + "pilot_health_multiplier" "Mnożnik życia" + "respawn_delay" "Opóźnienie odrodzeń" + "boosts_enabled" "Wzmocnienia" + "earn_meter_pilot_overdrive" "Doładowanie wskaźnika Tytana/wzmocnienia" + "earn_meter_pilot_multiplier" "Mnożnik wsk. Tytana/wzm." + + "earn_meter_titan_multiplier" "Mnożnik wskaźnika rdzenia" + "aegis_upgrades" "POSTĘPY DOTYCZĄCE RANGI EGIDY" + "infinite_doomed_state" "Stan tytana spisany na straty jest nieskończony" + "titan_shield_regen" "Regeneracja tarczy Tytanów" + + "riff_floorislava" "Deadly ground" + "featured_mode_all_holopilot" "The Great Bamboozle" + "featured_mode_all_grapple" "Attack on Titanfall" + "featured_mode_all_phase" "The Otherside" + "featured_mode_all_ticks" "Spicy" + "featured_mode_tactikill" "Tactikill" + "featured_mode_amped_tacticals" "Amped Tacticals" + "featured_mode_rocket_arena" "Rocket Arena" + "featured_mode_shotguns_snipers" "Armed and Dangerous" + "iron_rules" "Iron Titan Rules" + + "cp_amped_capture_points" "Obrona umocnień" + "coliseum_loadouts_enabled" "Uzbrojenie Koloseum" + + "aitdm_archer_grunts" "Piechurzy z łucznikami" + + // northstar.custom localisation is just deciding not to work, so putting it here for now + "PL_sbox" "Piaskownica" + "PL_sbox_lobby" "Piaskownica Lobby" + "PL_sbox_desc" "jak gmod tylko gorsze" + "PL_sbox_abbr" "Piaskownica" + "GAMEMODE_SBOX" "Piaskownica" + + "PL_gg" "Gun Game" + "PL_gg_lobby" "Gun Game" + "PL_gg_desc" "Zdobądź zabójstwo każdą bronią by wygrać." + "PL_gg_hint" "Zdobądź zabójstwo każdą bronią by wygrać." + "PL_gg_abbr" "GG" + "GAMEMODE_GG" "Gun Game" + "gg_kill_reward" "Procentowa nagroda za zabójstwo" + "gg_assist_reward" "Procentowa nagroda za asystę" + "gg_execution_reward" "Procentowa nagroda za egzekucję" + + "PL_tt" "Titan Tag" + "PL_tt_lobby" "Titan Tag Lobby" + "PL_tt_desc" "Zdobywaj punkty będąc w Tytanie. Zniszcz Tytana by otrzymać własnego." + "PL_tt_hint" "Zdobywaj punkty będąc w Tytanie. Zniszcz Tytana by otrzymać własnego." + "PL_tt_abbr" "TT" + "GAMEMODE_TT" "Titan Tag" + + "PL_chamber" "One in the Chamber" + "PL_chamber_lobby" "One in the Chamber Lobby" + "PL_chamber_desc" "Jeden strzał, Jedno zabójstwo. Zdobądź kolejny pocisk w magazynku zabijając przeciwnika." + "PL_chamber_hint" "Jeden strzał, Jedno zabójstwo. Zdobądź kolejny pocisk w magazynku zabijając przeciwnika." + "PL_chamber_abbr" "CHAMBER" + "GAMEMODE_CHAMBER" "One in the Chamber Lobby" + + "PL_hidden" "The Hidden" + "PL_hidden_lobby" "The Hidden Lobby" + "PL_hidden_desc" "Jeden gracz jest niewidzialny i poluję na resztę." + "PL_hidden_hint" "Jeden gracz jest niewidzialny i poluję na resztę." + "PL_hidden_abbr" "HIDDEN" + "GAMEMODE_HIDDEN" "The Hidden" + "HIDDEN_YOU_ARE_HIDDEN" "Jesteś The Hidden!" + "HIDDEN_KILL_SURVIVORS" "Zabij wszystkich ocaleńców." + "HIDDEN_FIRST_HIDDEN" "%s1 jest The Hidden." + + "PL_sns" "Sticks and Stones" + "PL_sns_lobby" "Sticks and Stones Lobby" + "PL_sns_desc" "Każdy na każdego. Zabij przeciwnika ostrzem pulsacyjnym lub wykonaj egzekucję by zresetować jego wynik" + "PL_sns_abbr" "SNS" + "GAMEMODE_SNS" "Sticks and Stones" + "SCOREBOARD_BANKRUPTS" "Zabójstwa resetujące" + "SNS_LEADER_BANKRUPT" "Wynik Lidera został zresetowany!" + "SNS_LEADER_BANKRUPT_SUB" "Wynik %s1 został zresetowany przez %s2" + "SNS_BANKRUPT" "Reset!" + "SNS_BANKRUPT_SUB" "Twój wynik został zresetowany przez %s1" + "sns_wme_kill_value" "Wartość zabójstwa przy użyciu Elitarny skrzydłowy" + "sns_softball_kill_value" "Wartość zabójstwa przy użyciu Softball" + "sns_offhand_kill_value" "Wartość zabójstwa przy użyciu Offhand " + "sns_reset_kill_value" "Wartość zabójstwa przy użyciu Ostrza pulsacyjnego/egzekucji" + "sns_melee_kill_value" "Wartość zabójstwa wręcz" + "sns_reset_pulse_blade_cooldown_on_pulse_blade_kill" "Zabójstwa regenerują zdolność taktyczną - Ostrze pulsacyjne" + "sns_softball_enabled" "Softball Włączony" + + "PL_inf" "Infekcja" + "PL_inf_lobby" "Infekcja Lobby" + "PL_inf_desc" "Przeżyj zarazę. Ocaleńcy zostają zainfekowani gdy zabici." + "PL_inf_hint" "Przeżyj zarazę. Ocaleńcy zostają zainfekowani gdy zabici.." + "PL_inf_abbr" "INF" + "GAMEMODE_INF" "Infekcja" + "INFECTION_YOU_ARE_INFECTED" "Zostałeś zainfekowany!" + "INFECTION_KILL_SURVIVORS" "Zainfekuj pozostałych ocaleńców." + "INFECTION_FIRST_INFECTED" "%s1 jest pierwszym zainfekowanym." + "INFECTION_LAST_SURVIVOR" "%s1 jest ostatnim ocaleńcem!" + "INFECTION_KILL_LAST_SURVIVOR" "Zainfekuj ich przed końcem czasu!" + "INFECTION_YOU_ARE_LAST_SURVIVOR" "Jesteś ostatnim ocaleńcem!" + "INFECTION_SURVIVE_LAST_SURVIVOR" "Przeżyj..." + + "PL_tffa" "Każdy na każdego Tytany" + "PL_tffa_lobby" "Każdy na każdego Tytany" + "PL_tffa_desc" "Każdy na każdego, zniszcz tytany przeciwników." + "PL_tffa_hint" "Każdy na każdego, zniszcz tytany przeciwników." + "PL_tffa_abbr" "TFFA" + "GAMEMODE_TFFA" "Każdy na każdego Tytany" + + "PL_hs" "Chowany" + "PL_hs_lobby" "Chowany Lobby" + "PL_hs_desc" "Szukający szukają chowających się." + "PL_hs_hint" "Szukający szukają chowających się." + "PL_hs_abbr" "HS" + "GAMEMODE_hs" "Chowany" + "HIDEANDSEEK_YOU_ARE_SEEKER" "JESTEŚ SZUKAJĄCYM" + "HIDEANDSEEK_SEEKER_DESC" "Znajdź chowających się i przyłóż im wręcz.\nOdrodzisz się w %s1 sekund" + "HIDEANDSEEK_YOU_ARE_HIDER" "JESTEŚ CHOWAJĄCYM SIĘ" + "HIDEANDSEEK_HIDER_DESC" "Schowaj się." + "HIDEANDSEEK_SEEKERS_INCOMING" "Szukający nadchodzi" + "HIDEANDSEEK_DONT_GET_FOUND" "Nie daj się znaleźć!" + "HIDEANDSEEK_GET_LAST_HIDER" "%s1 JEST OSTATNIM CHOWAJĄCYM SIĘ" + "HIDEANDSEEK_YOU_ARE_LAST_HIDER" "JESTEŚ OSTATNIM CHOWAJĄCYM SIĘ" + "HIDEANDSEEK_GOT_STIM" "Masz Stymulant! Nie daj się złapać!" + "hideandseek_balance_teams" "Automatyczny balans Chowający się/Szukający" + "hideandseek_hiding_time" "Czas na ukrycie się dla Chowających się" + + // these are defined in r1_english but titan war is a shit name so i'm changing it to another one that was referenced in development + "GAMEMODE_fw" "Wojna Kresów" + "PL_fw" "Wojna Kresów" + "PL_fw_lobby" "Wojna Kresów Lobby" + "PL_fw_desc" "Zniszcz Zbieracz przeciwnika i ochroń swój" + "PL_fw_abbr" "FW" + + "GAMEMODE_kr" "Seria Zabójstw" + "PL_kr" "Seria Zabójstw" + "PL_kr_lobby" "Seria Zabójstw Lobby" + "PL_kr_desc" "Zabijaj przeciwników by ustanowić rekord serii i wygrać. Zbierz flagę by ją aktywować." + "PL_kr_hint" "Zabijaj przeciwników by ustanowić rekord serii i wygrać. Zbierz flagę by ją aktywować." + "PL_kr_abbr" "KR" + "SCOREBOARD_KR_RECORD" "Rekord Zabójstw" + "KR_NEW_RACER" "%s1 aktywował Serię" + "KR_YOU_ARE_NEW_RACER" "Aktywowałeś Serię" + "KR_YOU_SET_NEW_RECORD" "Ustaw nowy rekord zabójstw!" + "KR_FLAG_INCOMING" "Niedługo pojawi się flaga" + "KR_COLLECT_FLAG" "Zdobądź ją by aktywować Serię!" + "KR_ENEMY_KILLRACE_OVER" "Seria %s1 się skończyła" + "KR_YOUR_KILLRACE_OVER" "Twoja Seria się skończyła" + "KR_YOUR_KILLRACE_SCORE" "Zdobyłeś %s1 zabójstw." + + "GAMEMODE_fastball" "Fastball" + "PL_fastball" "Fastball" + "PL_fastball_lobby" "Fastball Lobby" + "PL_fastball_desc" "Śmierć pernamentna. Hackuj panele kotrolne by wygrywać rundy i odradzać swoich osoby z swojej drużyny." + "PL_fastball_hint" "Śmierć pernamentna. Hackuj panele kotrolne by wygrywać rundy i odradzać swoich osoby z swojej drużyny." + "PL_fastball_abbr" "FB" + "FASTBALL_PANEL_CAPTURED" "%s1 przęjeli panel %s2" + "SCOREBOARD_FASTBALL_HACKS" "Przejęte panele" + + "GAMEMODE_ctf_comp" "Kompetetywne - Walka o flagę" + + // mode settings + "MODE_SETTING_CATEGORY_PROMODE" "Tryb pro" + "MODE_SETTING_CATEGORY_BLEEDOUT" "Krwawienie Pilotów" + + "custom_air_accel_pilot" "Air Acceleration" + "no_pilot_collision" "Kolidowanie Pilotów" + "promode_enable" "Bronie trybu pro" + "fp_embark_enabled" "Pierwszoosobowe wsiadanie/egzekucje" + "classic_rodeo" "Klasyczne Rodeo" + "oob_timer_enabled" "Licznik czasu poza granicami mapy" + "riff_instagib" "Instagib Mode" + "player_force_respawn" "Wymuszone odrodzenie" + + "riff_player_bleedout" "Krwawienie Pilotów" + "player_bleedout_forceHolster" "Schowaj bronie gdy powalonym" + "player_bleedout_forceDeathOnTeamBleedout" "Umrzyj przy wykrawieniu się drużyny" + "player_bleedout_bleedoutTime" "Czas do wykrwawienia się" + "player_bleedout_firstAidTime" "Czas potrzebny do udzielenia pierwszej pomocy" + "player_bleedout_firstAidTimeSelf" "Czas potrzebny do samo-reanimacji" + "player_bleedout_firstAidHealPercent" "Procent życia po otrzymaniu pierwszej pomocy" + "player_bleedout_aiBleedingPlayerMissChance" "Szanasa na nietrafienie przez powalone SI" + + // coop stuff + "PL_sp_coop" "(NIE UKOŃCZONE) Kampania w trybie Kooperacji" + "PL_sp_coop_lobby" "Kampania w trybie Kooperacji Lobby" + "PL_sp_coop_desc" "Zagraj kampanie z przyjaciółmi" + "PL_sp_coop_hint" "Zagraj kampanie z przyjaciółmi" + "PL_sp_coop_abbr" "SP" + + "SP_TRAINING" "Tor przeszkód" + "SP_TRAINING_CLASSIC_DESC" "Symulacja kapitana Lastimosy." + + "SP_CRASHSITE" "BT-7274" + "SP_CRASHSITE_CLASSIC_DESC" "Jack Cooper spotyka BT-7274." + + "SP_SEWERS1" "Krew i rdza" + "SP_SEWERS1_CLASSIC_DESC" "Cooper i BT ruszają spotkać się z majorem Andersonem." + + "SP_BOOMTOWN_START" "W paszczę otchłani" + "SP_BOOMTOWN_START_CLASSIC_DESC" "Przeprawa podziemnym skrótem przynosi nieoczekiwane skutki." + + "SP_HUB_TIMESHIFT" "Skutek i przyczyna" + "SP_HUB_TIMESHIFT_CLASSIC_DESC" "W miejscu, w którym przebywa major Anderson zaobserwowano osobliwe zjawisko." + + "SP_BEACON" "Nadajnik" + "SP_BEACON_CLASSIC_DESC" "Cooper i BT podejmują próbę nawiązania kontaktu z pozostałymi siłami by przekazać im plany IMC." + + "SP_TDAY" "Próba ognia" + "SP_TDAY_CLASSIC_DESC" "Zdolności pilotażu Coopera zostają wystawione na próbę w ostatecznym starciu o Arkę." + + "SP_S2S" "Arka" + "SP_S2S_CLASSIC_DESC" "Cooper i BT ruszają statkiem w pościg za Arką." + + "SP_SKYWAY_V1" "Zakrzywiacz" + "SP_SKYWAY_V1_CLASSIC_DESC" "Cooper i BT zostają pojmani przez Kubena Bliska." + + // Better.Serverbrowser + "SERVERS_COLUMN" "Serwery" + "PLAYERS_COLUMN" "Gracze" + "MAP_COLUMN" "Mapa" + "GAMEMODE_COLUMN" "Tryb gry" + "REGION_COLUMN" "Region" + "SEARCHBAR_LABEL" "Wyszukiwanie:" + "MAP_FILTER" "Mapa" + "GAMEMODE_FILTER" "Tryb gry" + "HIDE_FULL_FILTER" "Ukryj Pełne Serwery" + "HIDE_EMPTY_FILTER" "Ukryj Puste Serwery" + "HIDE_PROT_FILTER" "Ukryj Chronione Hasłem Serwery" + "SERVER_DESCRIPTION" "Opis" + "SERVER_MODS" "Mody" + "CLEAR_FILTERS" "WYCZYŚĆ" + "JOIN_BUTTON" "DOŁĄCZ" + + "SWITCH_YES" "Tak" + "SWITCH_NO" "Nie" + "SWITCH_ANY" "Dowolnie" + + "CONNECTING" "Łączenie..." + "INGAME_PLAYERS" "Gracze: ^6BA6C400%s1" + "TOTAL_SERVERS" "Serwery: ^C46C6C00%s1" + + // Mods menu + "SHOW" "Pokaż" + "SHOW_ALL" "Wszystko" + "SHOW_ONLY_ENABLED" "Tylko Włączone" + "SHOW_ONLY_DISABLED" "Tylko Wyłączone" + "SHOW_ONLY_NOT_REQUIRED" "Tylko Mody Opcjonalne" + "SHOW_ONLY_REQUIRED" "Tylko Mody Wymagane" + "MOD_REQUIRED_WARNING" " : Ten mod może zostać wyłączony po dołączeniu na serwer" + + // Maps menu + "HIDE_LOCKED" "Ukryj zablokowane" + + // In-game chat + "HUD_CHAT_WHISPER_PREFIX" "[SZEPT]" + "HUD_CHAT_SERVER_PREFIX" "[SERWER]" + + "NO_GAMESERVER_RESPONSE" "Nie udało się osiągnąć serwera" + "BAD_GAMESERVER_RESPONSE" "Serwer dał nieprawidłową odpowiedź" + "UNAUTHORIZED_GAMESERVER" "Serwer nie ma autoryzacji by przedstawić takie zapytanie" + "UNAUTHORIZED_GAME" "Stryder nie może potwierdzić że to konto ma Titanfall 2" + "UNAUTHORIZED_PWD" "Nieprawidłowe hasło" + "STRYDER_RESPONSE" "Nieprawidłowa odpowiedź od Stryder" + "PLAYER_NOT_FOUND" "Nie udało się znaleźć konta gracza" + "INVALID_MASTERSERVER_TOKEN" "Token głównego serwera jest nieprawidłowy lub wygasł, spróbuj odpalić ponownie aplikację EA" + "JSON_PARSE_ERROR" "Wystąpił błąd przy sprawdzaniu pliku json" + "UNSUPPORTED_VERSION" "Wersja której używasz nie jest już wspierana" + + "AUTHENTICATION_FAILED_HEADER" "Uwierzytelnianie się nie powiodło" + "AUTHENTICATION_FAILED_BODY" "Uwierzytelnianie z Atlas się nie powiodło!" + "AUTHENTICATION_FAILED_ERROR_CODE" "Kod błędu: ^DB6F2C00%s1^" + "AUTHENTICATION_FAILED_HELP" "Pomoc" + + // Mod Settings + "MOD_SETTINGS" "Ustawienia Modów" + "NORTHSTAR_BASE_SETTINGS" "Główne ustawienia Northstar" + "ONLY_HOST_MATCH_SETTINGS" "Tylko Host może zmienić ustawienia meczu prywatnego" + "ONLY_HOST_CAN_START_MATCH" "Tylko Host może rozpocząć mecz" + "MATCH_COUNTDOWN_LENGTH" "Długość odliczana do rozpoczęcia meczu prywatnego" + "LOG_UNKNOWN_CLIENTCOMMANDS" "Loguj nieznane komendy klienta" + "DISALLOWED_TACTICALS" "Zabroniona umiejętność taktyczna" + "TACTICAL_REPLACEMENT" "Zastępująca umiejętność taktyczna" + "DISALLOWED_WEAPONS" "Zabroniona broń" + "REPLACEMENT_WEAPON" "Zastępująca broń" + "SHOULD_RETURN_TO_LOBBY" "Powróć do lobby po zakończeniu meczu" + "ARE_YOU_SURE" "Jesteś pewien?" + "WILL_RESET_ALL_SETTINGS" "Zresetujesz WSZYSTKIE ustawienia tej kategorii.\n\nAkcja jest nieodwracalna." + "WILL_RESET_SETTING" "Zresetujesz %s1 do startowej wartości.\n\nAkcja jest nieodwracalna." + "MOD_SETTINGS_SERVER" "Serwer" + "MOD_SETTINGS_RESET" "Zresetuj" + "MOD_SETTINGS_RESET_ALL" "Zresetuj WSZYSTKO" + "NO_RESULTS" "Brak rezultatu." + "NO_MODS" "Brak dostępnych ustawień, zainstaluj więcej modów w ^5588FF00northstar.thunderstore.io^0." + + // Toggleable progression + "TOGGLE_PROGRESSION" "Przełącz Progresję" + "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Przełącz Progresję" + + "PROGRESSION_TOGGLE_ENABLED_HEADER" "Wyłączyć Progresję?" + "PROGRESSION_TOGGLE_ENABLED_BODY" "Tytany, Bronie, Fakcje, Skórki, itd. zostaną wszytskie odblokowane.\n\nDecyzję możesz zmienić kiedykolwiek w Lobby trybu wieloosobowego." + + "PROGRESSION_TOGGLE_DISABLED_HEADER" "Włączyć Progresję?" + "PROGRESSION_TOGGLE_DISABLED_BODY" "Tytany, Bronie, Fakcje, Skórki, itd. będą musiały być odblokowane poprzez zwiększanie swojego poziomu lub zakupione.\n\nDecyzję możesz zmienić kiedykolwiek w Lobby trybu wieloosobowego.\n\n^CC000000Ostrzeżenie: jeżeli masz wyekwipowane uzbrojenie której nie jest odblokowane zostenie ono zresetowane!" + + "PROGRESSION_ENABLED_HEADER" "Progresja została Włączona!" + "PROGRESSION_ENABLED_BODY" "^CCCC0000Progresja została Włączona.^\n\nTytany, Bronie, Fakcje, Skórki, itd. będą musiały być odblokowane poprzez zwiększanie swojego poziomu lub zakupione.\n\nDecyzję możesz zmienić kiedykolwiek w Lobby trybu wieloosobowego." + + "PROGRESSION_DISABLED_HEADER" "Progresja została Wyłączona!" + "PROGRESSION_DISABLED_BODY" "^CCCC0000Progresja została Wyłączona.^\n\nTytany, Bronie, Fakcje, Skórki, itd. zostaną wszytskie odblokowane.\n\nDecyzję możesz zmienić kiedykolwiek w Lobby trybu wieloosobowego." + + "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Jest już możliwość włączenia Progresjii!^\n\nNorthstar teraz wspiera klasyczną Progresję, Dzięki niej możesz wybrać odblokowawanie Broni, Tytanów, Skórek, itd. poprzez zwiększanie swojego poziomu lub ukańczanie wyzwań.\n\nProgresja może zostać włączona poprzez przycisk na dole ekranu Lobby.\n\nDecyzję możesz zmienić kiedykolwiek." + + // Mod downloading + "MISSING_MOD" "Brakujący Mod \"%s1\" v%s2" + "WRONG_MOD_VERSION" "Serwer ma Moda \"%s1\" v%s2 podczas gdy ty masz v%s3" + "MOD_NOT_VERIFIED" "(Mod nie został zweryfikowany i nie mógł być pobrany automatycznie)" + "MOD_DL_DISABLED" "(Automatyczne pobieranie modów jest wyłączone)" + "MANIFESTO_FETCHING_TITLE" "Przygotowywanie do pobierania Moda" + "MANIFESTO_FETCHING_TEXT" "Zdobywanie listy zweryfikowanych modów..." + "DOWNLOADING_MOD_TITLE" "Pobieranie" + "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Pobieranie (%s1%)" + "DOWNLOADING_MOD_TEXT" "Pobieranie %s1 v%s2..." + "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Pobieranie %s1 v%s2...\n(%s3/%s4 MB)" + "CHECKSUMING_TITLE" "Sprawdzanie wartości kontrolnej Moda" + "CHECKSUMING_TEXT" "Weryfikowanie zawartości %s1 v%s2..." + "EXTRACTING_MOD_TITLE" "Rozpakowywanie Moda (%s1%)" + "EXTRACTING_MOD_TEXT" "Rozpakowywanie %s1 v%s2...\n(%s3/%s4 MB)" + "FAILED_DOWNLOADING" "Pobieranie Moda się nie powiodło" + "FAILED_READING_ARCHIVE" "Wystąpił błąd podczas czytania archiwum Moda." + "FAILED_WRITING_TO_DISK" "Wystąpił błąd podczas rozpakowywania Moda." + "MOD_FETCHING_FAILED" "Pobieranie archiwum Moda się nie powiodło." + "MOD_CORRUPTED" "Wartość kontrolna pobranego archiwum/moda nie zgadza się z zweryfikowaną wartośćią kontrolną." + "NO_DISK_SPACE_AVAILABLE" "Nie ma wystarczająco miejsca na dysku." + "MOD_FETCHING_FAILED_GENERAL" "Rozpakowywanie Moda się nie powiodło. Sprawdź Logi po więcej detali." + } +} diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt b/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt index 5570b0471..79f3272de 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_portuguese.txt @@ -304,8 +304,8 @@ Clique em Sim se você concorda. Esta escolha pode ser alterada a qualquer momen "HIDE_LOCKED" "Esconder desativados" // In-game chat - "HUD_CHAT_WHISPER_PREFIX" "[WHISPER]" - "HUD_CHAT_SERVER_PREFIX" "[SERVER]" + "HUD_CHAT_WHISPER_PREFIX" "[SUSSURRO]" + "HUD_CHAT_SERVER_PREFIX" "[SERVIDOR]" "ONLY_HOST_MATCH_SETTINGS" "Somente o Host pode mudar as configurações da Partida Privada" "ONLY_HOST_CAN_START_MATCH" "Somente o Host pode Iniciar a Partida" "LOG_UNKNOWN_CLIENTCOMMANDS" "Registrar Comandos desconhecidos de Clientes" @@ -326,7 +326,7 @@ Clique em Sim se você concorda. Esta escolha pode ser alterada a qualquer momen "UNAUTHORIZED_GAMESERVER" "Servidor da partida não está autorizado a fazer tal requisição" "UNAUTHORIZED_PWD" "Senha inválida" "PLAYER_NOT_FOUND" "Não foi possível encontrar conta do jogador" - "INVALID_MASTERSERVER_TOKEN" "Token do servidor mestre inválido ou vencido" + "INVALID_MASTERSERVER_TOKEN" "Token do servidor mestre inválido ou vencido, tente reiniciar o EA App." "JSON_PARSE_ERROR" "Erro ao ler a resposta json" "SHOW_ONLY_REQUIRED" "Somente Mods Mandatórios" "UNAUTHORIZED_GAME" "Stryder não pode confirmar que esta conta possui Titanfall 2" @@ -355,8 +355,8 @@ Clique em Sim se você concorda. Esta escolha pode ser alterada a qualquer momen "MOD_DL_DISABLED" "(download automático de mod está desabilitado)" "DOWNLOADING_MOD_TITLE" "Baixando mod" "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Baixando %s1 v%s2...\n(%s3/%s4 MB)" - "CHECKSUMING_TITLE" "Verificando o mod" - "CHECKSUMING_TEXT" "Verificando conteúdo de %s1 v%s2..." + "CHECKSUMING_TITLE" "Verificando a integridade do mod" + "CHECKSUMING_TEXT" "Validando arquivos de %s1 v%s2..." "EXTRACTING_MOD_TITLE" "Extraíndo mod (%s1%)" "MOD_REQUIRED_WARNING" " Este mod pode ser desativado quando entrar em um servidor" "AUTHENTICATION_FAILED_HEADER" "Autenticação Falhou" @@ -374,5 +374,17 @@ Clique em Sim se você concorda. Esta escolha pode ser alterada a qualquer momen "WRONG_MOD_VERSION" "O servidor tem o mod \"%s1\" v%s2 enquanto você possui v%s3" "FAILED_WRITING_TO_DISK" "Um erro ocorreu enquanto se extraía o conteúdo do mod para o sistema." "MOD_FETCHING_FAILED_GENERAL" "Extração do mod falhou. Verifique os logs para mais detalhes." + "MANIFESTO_FETCHING_TEXT" "Retornando a lista de mods verificados..." + "MANIFESTO_FETCHING_TITLE" "Preparando o download do mod" + "MODE_MENU_PVPVE" "JcJcA" + "MODE_MENU_PVE" "JcA" + "MODE_MENU_PVP" "JcJ" + "MODE_MENU_FFA" "TcT" + "MODE_MENU_OTHER" "Outros" + "MODE_MENU_CUSTOM" "Personalizado" + "MODE_MENU_ALL" "Todos" + "MODE_MENU_UNKNOWN" "Desconhecido" + "MODE_MENU_SWITCH" "Filtrar" + "MODE_MENU_TITAN_ONLY" "Somente Titãs" } } diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt b/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt index cf410ff29..a0f751ff1 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_russian.txt @@ -250,7 +250,7 @@ "UNAUTHORIZED_PWD" "Неправильный пароль" "STRYDER_RESPONSE" "Не удалось разобрать ответ Stryder" "PLAYER_NOT_FOUND" "Не удалось найти аккаунт игрока" - "INVALID_MASTERSERVER_TOKEN" "Некорректный или истёкший токен главного сервера" + "INVALID_MASTERSERVER_TOKEN" "Некорректный или истёкший токен главного сервера. Попробуйте перезапустить EA App." "JSON_PARSE_ERROR" "Ошибка разбора json-ответа" "UNSUPPORTED_VERSION" "Используемая вами версия больше не поддерживается" "DISABLE" "Выключить" @@ -345,5 +345,34 @@ "AUTHENTICATION_FAILED_ERROR_CODE" "Код ошибки: ^DB6F2C00%s1^" "AUTHENTICATION_FAILED_HELP" "Справка" "AUTHENTICATION_FAILED_HEADER" "Ошибка аутентификации" + "MISSING_MOD" "Отсутствует мод \"%s1\" v%s2" + "MOD_NOT_VERIFIED" "(мод не был загружен автоматически, т.к. он не проверенный)" + "MOD_DL_DISABLED" "(автоматическая загрузка модов не включена)" + "DOWNLOADING_MOD_TITLE" "Загрузка мода" + "DOWNLOADING_MOD_TEXT" "Загружаем %s1 v%s2..." + "DOWNLOADING_MOD_TEXT_W_PROGRESS" "Загружаем %s1 v%s2...\n(%s3/%s4 МБ)" + "DOWNLOADING_MOD_TITLE_W_PROGRESS" "Загрузка мода (%s1%)" + "CHECKSUMING_TITLE" "Проверка целостности" + "MOD_REQUIRED_WARNING" " : Этот мод может автоматически включиться / отключиться при подключении к серверу" + "WRONG_MOD_VERSION" "Версии мода \"%s1\" не совпадают. На сервере: v%s2. У вас: v%s3" + "CHECKSUMING_TEXT" "Проверяем целостность %s1 v%s2..." + "EXTRACTING_MOD_TITLE" "Распаковка мода (%s1%)" + "EXTRACTING_MOD_TEXT" "Распаковываем %s1 v%s2...\n(%s3/%s4 МБ)" + "FAILED_DOWNLOADING" "Ошибка загрузки мода" + "FAILED_READING_ARCHIVE" "Ошибка чтения архива мода." + "FAILED_WRITING_TO_DISK" "Ошибка распаковки файлов мода." + "MOD_FETCHING_FAILED" "Ошибка скачивания мода с Thunderstore." + "MOD_CORRUPTED" "Файл архива мода повреждён: контрольная сумма не совпадает." + "NO_DISK_SPACE_AVAILABLE" "Недостаточно места на диске." + "MOD_FETCHING_FAILED_GENERAL" "Ошибка распаковки мода. Проверьте файл лога, чтобы узнать подробности." + "MANIFESTO_FETCHING_TEXT" "Скачиваем список проверенных модов..." + "MANIFESTO_FETCHING_TITLE" "Начало загрузки модов" + "MODE_MENU_FFA" "Все против всех" + "MODE_MENU_OTHER" "Другое" + "MODE_MENU_CUSTOM" "Свой" + "MODE_MENU_ALL" "Все" + "MODE_MENU_UNKNOWN" "Неизвестный" + "MODE_MENU_SWITCH" "Фильтр" + "MODE_MENU_TITAN_ONLY" "Только титаны" } } diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt b/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt index 500f8a969..fd649cdb6 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_tchinese.txt @@ -320,7 +320,7 @@ "UNAUTHORIZED_PWD" "密碼錯誤" "STRYDER_RESPONSE" "無法讀取Stryder回應" "PLAYER_NOT_FOUND" "找不到玩家賬戶" - "INVALID_MASTERSERVER_TOKEN" "主伺服器token過期或無效" + "INVALID_MASTERSERVER_TOKEN" "主伺服器token過期或無效。" "JSON_PARSE_ERROR" "讀取json回應時發生錯誤" "UNSUPPORTED_VERSION" "您的遊戲版本過低" "NORTHSTAR_BASE_SETTINGS" "北极星基础设置" @@ -356,5 +356,31 @@ "PROGRESSION_DISABLED_BODY" "^CCCC0000個人進度已停用^\n\n泰坦,武器,陣營,皮膚及所有其他一切需要解鎖的物品將隨時可以進行解鎖並使用。\n\n您可以隨時在多人大廳中更改此項。" "PROGRESSION_ENABLED_BODY" "^CCCC0000個人進度已啟用^\n\n泰坦,武器, 陣營,皮膚以及其他一切需要解鎖的物品將通過升級或是使用點數購買來進行解鎖。.\n\n您可以隨時在多人大廳中更改此項。" "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000現在可以隨時開啟個人進度!^\n\nNorthstar 現在支持類似官服的進度系統, 这意味着你可以选择通过升级和完成挑战来解锁武器、皮肤、泰坦等。\n\n您可以通過多人大廳底部的“遊戲進度”按鈕來進行開啟。\n\n您可以隨時在多人大廳中更改此項。" + "AUTHENTICATION_FAILED_HEADER" "認證失敗" + "AUTHENTICATION_FAILED_BODY" "無法與Atlas驗證!" + "AUTHENTICATION_FAILED_ERROR_CODE" "錯誤代碼:^DB6F2C00%s1^" + "AUTHENTICATION_FAILED_HELP" "幫助" + "MISSING_MOD" "缺少Mod \"%s1\" v%s2" + "MOD_NOT_VERIFIED" "(Mod未經驗證并且無法自動下載)" + "MOD_REQUIRED_WARNING" " : 加入一個服務器時,這個Mod可能加載或不加載" + "DOWNLOADING_MOD_TEXT" "正在下載Mod %s1 %s2..." + "DOWNLOADING_MOD_TEXT_W_PROGRESS" "正在下載Mod %s1 %s2...\n(%s3/%s4 MB)" + "CHECKSUMING_TITLE" "正在校驗Mod" + "WRONG_MOD_VERSION" "服務器的 \"%s1\" Mod版本為 %s2 而你的版本是 %s3" + "MOD_DL_DISABLED" "(Mod自動下載已禁用)" + "DOWNLOADING_MOD_TITLE" "下載Mod中" + "DOWNLOADING_MOD_TITLE_W_PROGRESS" "下載Mod中(%s1%)" + "CHECKSUMING_TEXT" "驗證内容 %s1 %s2……" + "EXTRACTING_MOD_TITLE" "解壓Mod中 (%s1%)" + "EXTRACTING_MOD_TEXT" "解壓Mod中 %s1 %s2...\n(%s3/%s4 MB)" + "FAILED_DOWNLOADING" "下載Mod失敗" + "FAILED_READING_ARCHIVE" "在讀取Mod檔案時發生錯誤。" + "FAILED_WRITING_TO_DISK" "在解壓Mod文件到文件系統時發生錯誤。" + "MOD_FETCHING_FAILED" "從Thunderstore下載Mod檔案失敗。" + "MOD_CORRUPTED" "已下載檔案的校驗和無法與驗證簽名匹配。" + "NO_DISK_SPACE_AVAILABLE" "你的存儲設備沒有足夠空間。" + "MOD_FETCHING_FAILED_GENERAL" "Mod解壓失敗。在日志中查看更多詳情。" + "MANIFESTO_FETCHING_TITLE" "設置Mod下載" + "MANIFESTO_FETCHING_TEXT" "獲取已驗證Mod列表……" } } diff --git a/Northstar.Client/mod/resource/ui/menus/mode_select.menu b/Northstar.Client/mod/resource/ui/menus/mode_select.menu new file mode 100644 index 000000000..bf07164e7 --- /dev/null +++ b/Northstar.Client/mod/resource/ui/menus/mode_select.menu @@ -0,0 +1,608 @@ +resource/ui/menus/mode_select.menu +{ + menu + { + ControlName Frame + xpos 0 + ypos 0 + zpos 3 + wide f0 + tall f0 + autoResize 0 + pinCorner 0 + visible 1 + enabled 1 + PaintBackgroundType 0 + infocus_bgcolor_override "0 0 0 0" + outoffocus_bgcolor_override "0 0 0 0" + + MenuCommon + { + ControlName CNestedPanel + xpos 0 + ypos 0 + wide f0 + tall f0 + visible 1 + controlSettingsFile "resource/ui/menus/panels/menu_common.res" + } + + MatchmakingStatus + { + ControlName CNestedPanel + xpos 0 + ypos 0 + wide f0 + tall f0 + visible 1 + controlSettingsFile "resource/ui/menus/panels/matchmaking_status.res" + } + + MenuTitle + { + ControlName Label + InheritProperties MenuTitle + labelText "#SELECT_GAME_MODE" + } + + ButtonRowAnchor + { + ControlName Label + labelText "" + + xpos 96 + ypos 140 + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// NEXT MODE PANEL +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + NextModeImageFrame + { + ControlName RuiPanel + xpos 740 + ypos 160 + wide 860 + tall 520 + labelText "" + visible 1 + bgcolor_override "0 0 0 0" + paintbackground 1 + rui "ui/control_options_description.rpak" + } + + NextModeImage + { + ControlName RuiPanel + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner TOP_LEFT + xpos 0 + ypos 14 + wide 480 + tall 240 + visible 1 + scaleImage 1 + rui "ui/basic_menu_image.rpak" + zpos 2 + } + + ModeIconImage + { + ControlName RuiPanel + pin_to_sibling NextModeImage + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner TOP_LEFT + xpos 0 + ypos -16 + wide 72 + tall 72 + visible 1 + scaleImage 1 + rui "ui/basic_image_add.rpak" + zpos 2 + } + + NextModeName + { + ControlName Label + pin_to_sibling NextModeImage + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + ypos -10 + xpos -10 + wide 840 + auto_tall_tocontents 1 + visible 1 + labelText "Foo" + //textAlignment center + //centerWrap 1 + font Default_43_DropShadow + allcaps 1 + fgcolor_override "255 255 255 255" + } + + NextModeDesc + { + ControlName Label + pin_to_sibling NextModeName + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + xpos 0 + ypos 10 + wide 840 + wrap 1 + auto_tall_tocontents 1 + visible 1 + labelText "Bar" + //textAlignment center + //centerWrap 1 + font Default_27 + allcaps 0 + fgcolor_override "255 255 255 255" + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// FILTERS PANEL +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + FiltersPanel + { + ControlName RuiPanel + xpos 740 + ypos 682 + wide 860 + tall 156 + zpos -1 + + rui "ui/control_options_description.rpak" + } + + BtnModeLabel + { + ControlName RuiButton + InheritProperties RuiSmallButton + labelText "#SEARCHBAR_LABEL" + textAlignment west + classname FilterPanelChild + + wide 500 + xpos -18 + ypos -16 + + pin_to_sibling FiltersPanel + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner TOP_LEFT + } + + BtnModeSearch + { + ControlName TextEntry + classname FilterPanelChild + zpos 100 // This works around input weirdness when the control is constructed by code instead of VGUI blackbox. + xpos -400 + ypos -5 + wide 390 + tall 30 + textHidden 0 + editable 1 + font Default_21 + allowRightClickMenu 0 + allowSpecialCharacters 0 + unicode 1 + + pin_to_sibling BtnModeLabel + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner TOP_RIGHT + } + + SwtModeLabel + { + ControlName RuiButton + InheritProperties SwitchButton + labelText "#MODE_MENU_FILTER" + ConVar "modemenu_mode_filter" + classname FilterPanelChild + wide 500 + ypos 2 + + pin_to_sibling BtnModeLabel + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + BtnModeFiltersClear + { + ControlName RuiButton + InheritProperties RuiSmallButton + labelText "#CLEAR_FILTERS" + textAlignment west + classname FilterPanelChild + + wide 100 + ypos 2 + + pin_to_sibling SwtModeLabel + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PANELS LIST +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Panel1 + { + ControlName CNestedPanel + classname ModeSelectorPanel + scriptID 1 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling ButtonRowAnchor + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel2 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 2 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel1 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel3 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 3 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel2 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel4 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 4 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel3 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel5 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 5 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel4 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel6 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 6 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel5 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel7 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 7 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel6 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel8 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 8 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel7 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel9 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 9 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel8 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel10 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 10 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel9 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel11 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 11 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel10 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel12 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 12 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel11 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel13 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 13 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel12 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel14 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 14 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel13 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + + Panel15 + { + ControlName "CNestedPanel" + classname ModeSelectorPanel + scriptID 15 + + controlSettingsFile "resource/ui/menus/panels/mode_select_button.res" + wide %100 + tall 45 + + pin_to_sibling Panel14 + pin_corner_to_sibling TOP_LEFT + pin_to_sibling_corner BOTTOM_LEFT + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SLIDER +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + BtnModeListUpArrow + { + ControlName RuiButton + InheritProperties RuiSmallButton + //labelText "A" + wide 40 + tall 40 + xpos 2 + ypos 2 + + image "vgui/hud/white" + drawColor "255 255 255 128" + + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_RIGHT + pin_to_sibling_corner TOP_LEFT + } + + BtnModeListUpArrowPanel + { + ControlName RuiPanel + wide 40 + tall 40 + xpos 2 + ypos 2 + + rui "ui/control_options_description.rpak" + + visible 1 + zpos -1 + + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_RIGHT + pin_to_sibling_corner TOP_LEFT + } + + BtnModeListDownArrow + { + ControlName RuiButton + InheritProperties RuiSmallButton + //labelText "V" + wide 40 + tall 40 + xpos 2 + ypos -639 + + image "vgui/hud/white" + drawColor "255 255 255 128" + + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_RIGHT + pin_to_sibling_corner TOP_LEFT + } + + BtnModeListDownArrowPanel + { + ControlName RuiPanel + wide 40 + tall 40 + xpos 2 + ypos -639 + + rui "ui/control_options_description.rpak" + + visible 1 + zpos -1 + + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_RIGHT + pin_to_sibling_corner TOP_LEFT + } + + BtnModeListSlider + { + ControlName RuiButton + InheritProperties RuiSmallButton + //labelText "V" + wide 40 + tall 599 + xpos 2 + ypos -42 + zpos 0 + + image "vgui/hud/white" + drawColor "255 255 255 128" + + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_RIGHT + pin_to_sibling_corner TOP_LEFT + } + + BtnModeListSliderPanel + { + ControlName RuiPanel + wide 40 + tall 599 + xpos 2 + ypos -42 + + rui "ui/control_options_description.rpak" + + visible 1 + zpos -1 + + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_RIGHT + pin_to_sibling_corner TOP_LEFT + } + + // sh_menu_models.gnut has a global function which gets called when + // left mouse button gets called while hovering and has mouse + // deltaX; deltaY which we can yoink for ourselfes + MouseMovementCapture + { + ControlName CMouseMovementCapturePanel + wide 40 + tall 562 + xpos 2 + ypos -42 + zpos 1 + + pin_to_sibling NextModeImageFrame + pin_corner_to_sibling TOP_RIGHT + pin_to_sibling_corner TOP_LEFT + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ButtonTooltip + { + ControlName CNestedPanel + InheritProperties ButtonTooltip + } + + FooterButtons + { + ControlName CNestedPanel + xpos 0 + ypos r119 + wide f0 + tall 36 + visible 1 + controlSettingsFile "resource/ui/menus/panels/footer_buttons.res" + } + } +} diff --git a/Northstar.Client/mod/resource/ui/menus/panels/mode_select_button.res b/Northstar.Client/mod/resource/ui/menus/panels/mode_select_button.res new file mode 100644 index 000000000..b361e4faa --- /dev/null +++ b/Northstar.Client/mod/resource/ui/menus/panels/mode_select_button.res @@ -0,0 +1,31 @@ +resource/ui/menus/panels/mode_select_button.res +{ + BtnMode + { + ControlName RuiButton + InheritProperties RuiSmallButton + classname ModButton + labelText "please show up" + wide 600 + tall 45 + + pin_to_sibling ControlBox + pin_corner_to_sibling LEFT + pin_to_sibling_corner RIGHT + } + + Header + { + ControlName Label + InheritProperties RuiSmallButton + wide 600 + labelText "labelText" + font Default_41 + fgcolor_override "255 255 255 255" + tall 45 + + pin_to_sibling ControlBox + pin_corner_to_sibling LEFT + pin_to_sibling_corner RIGHT + } +} diff --git a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut index 3560fd562..9e683a869 100644 --- a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut +++ b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut @@ -20,6 +20,9 @@ global struct GameStateStruct { int otherHighestScore int maxScore float timeEnd + int serverGameState + int fd_waveNumber + int fd_totalWaves } global struct UIPresenceStruct { diff --git a/Northstar.Client/mod/scripts/vscripts/mp/levels/cl_mp_glitch.nut b/Northstar.Client/mod/scripts/vscripts/mp/levels/cl_mp_glitch.nut new file mode 100644 index 000000000..4ca323919 --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/mp/levels/cl_mp_glitch.nut @@ -0,0 +1,171 @@ +global function ClientCodeCallback_MapInit +global function AddInWorldMinimapObject + +// someday, move this to in world minimap + +struct +{ + array minimapBGTopos + array minimapTopos + array screens + float mapCornerX + float mapCornerY + float mapScale + float threatMaxDist +} file + +void function ClientCodeCallback_MapInit() +{ + AddCallback_EntitiesDidLoad( EntitiesDidLoad ) + AddCallback_MinimapEntSpawned( AddInWorldMinimapObject ) + AddCallback_LocalViewPlayerSpawned( AddInWorldMinimapObject ) +} + +void function EntitiesDidLoad() +{ + InitMinimapScreens() +} + +var function AddInWorldMinimapTopo( entity ent, float width, float height ) +{ + vector ang = ent.GetAngles() + vector right = ( (AnglesToRight( ang )*-1) * width * 0.5 ) + vector down = ( (AnglesToUp( ang )*-1) * height * 0.5 ) + + vector org = ent.GetOrigin() + + org = ent.GetOrigin() - right*0.5 - down*0.5 + + var topo = RuiTopology_CreatePlane( org, right, down, true ) + return topo +} + +void function InitMinimapScreens() +{ + array screens = GetEntArrayByScriptName( "inworld_minimap" ) + foreach ( screen in screens ) + { + file.minimapTopos.append( AddInWorldMinimapTopo( screen, 350, 350 ) ) + file.minimapBGTopos.append( AddInWorldMinimapTopo( screen, 450, 450 ) ) + } + + asset mapImage = Minimap_GetAssetForKey( "minimap" ) + file.mapCornerX = Minimap_GetFloatForKey( "pos_x" ) + file.mapCornerY = Minimap_GetFloatForKey( "pos_y" ) + float displayDist = Minimap_GetFloatForKey( "displayDist" ) + float threatDistNear = Minimap_GetFloatForKey( "threatNearDist" ) + float threatDistFar = Minimap_GetFloatForKey( "threatFarDist" ) + file.mapScale = Minimap_GetFloatForKey( "scale" ) + + file.threatMaxDist = Minimap_GetFloatForKey( "threatMaxDist" ) + + foreach ( screen in file.minimapBGTopos ) + { + entity player = GetLocalViewPlayer() + var rui = RuiCreate( $"ui/in_world_minimap_border.rpak", screen, RUI_DRAW_WORLD, 0 ) + string factionChoice = GetFactionChoice( player ) + ItemDisplayData displayData = GetItemDisplayData( factionChoice ) + asset factionLogo = displayData.image + RuiSetImage( rui, "logo", factionLogo ) + RuiSetImage( rui, "basicImage", $"overviews/mp_glitch_wallmap_bracket" ) + } + foreach ( screen in file.minimapTopos ) + { + var rui = RuiCreate( $"ui/in_world_minimap_base.rpak", screen, RUI_DRAW_WORLD, 0 ) + RuiSetImage( rui, "mapImage", $"overviews/mp_glitch_wallmap" ) + RuiSetFloat3( rui, "mapCorner", ) + RuiSetFloat( rui, "displayDist", max( file.threatMaxDist, 2200 ) ) + RuiSetFloat( rui, "mapScale", file.mapScale ) + file.screens.append( rui ) + } + + foreach ( player in GetPlayerArray() ) + { + if ( IsValid( player ) ) + AddInWorldMinimapObject( player ) + } +} + +void function AddInWorldMinimapObject( entity ent ) //TODO: If we want radar jammer boost to hide friendly players we need to be able to get the rui handles back. +{ + Assert( IsValid( ent ) ) + + if ( !ent.IsPlayer() && !ent.IsTitan() ) + return + + ent.SetDoDestroyCallback( true ) + + foreach ( screen in file.minimapTopos ) + thread AddInWorldMinimapObjectInternal( ent, screen ) +} + +void function AddInWorldMinimapObjectInternal( entity ent, var screen ) +{ + bool isNPCTitan = ent.IsNPC() && ent.IsTitan() + bool isPetTitan = ent == GetLocalViewPlayer().GetPetTitan() + bool isLocalPlayer = ent == GetLocalViewPlayer() + int customState = ent.Minimap_GetCustomState() + asset minimapAsset = $"ui/in_world_minimap_player.rpak" + if ( isNPCTitan ) + { + minimapAsset = $"ui/in_world_minimap_object.rpak" + } + + int zOrder = ent.Minimap_GetZOrder() + entity viewPlayer = GetLocalViewPlayer() + + var rui = RuiCreate( minimapAsset, screen, RUI_DRAW_WORLD, MINIMAP_Z_BASE + zOrder ) + + //RuiTrackGameTime( rui, "lastFireTime", ent, RUI_TRACK_LAST_FIRED_TIME ) + + RuiSetFloat3( rui, "mapCorner", ) + RuiSetFloat( rui, "mapScale", file.mapScale ) + + RuiTrackFloat3( rui, "objectPos", ent, RUI_TRACK_ABSORIGIN_FOLLOW ) + RuiTrackFloat3( rui, "objectAngles", ent, RUI_TRACK_EYEANGLES_FOLLOW ) + RuiTrackInt( rui, "objectFlags", ent, RUI_TRACK_MINIMAP_FLAGS ) + RuiTrackInt( rui, "customState", ent, RUI_TRACK_MINIMAP_CUSTOM_STATE ) + RuiSetFloat( rui, "displayDist", max( file.threatMaxDist, 2200 ) ) + + if ( isLocalPlayer ) + RuiSetBool( rui, "isLocalPlayer", isLocalPlayer ) + + // MinimapPackage_PlayerInit( ent, rui ) + + if ( isPetTitan ) + { + RuiSetBool( rui, "useTeamColor", false ) + RuiSetFloat3( rui, "iconColor", TEAM_COLOR_YOU / 255.0 ) + } + + OnThreadEnd( + function() : ( rui ) + { + RuiDestroy( rui ) + } + ) + + ent.EndSignal( "OnDestroy" ) + + if ( ent.IsPlayer() ) + { + while ( IsValid( ent ) ) + { + WaitSignal( ent, "SettingsChanged", "OnDeath" ) + } + } + else + { + ent.WaitSignal( "OnDestroy" ) + } +} + +void function MinimapPackage_PlayerInit( entity ent, var rui ) +{ + RuiTrackGameTime( rui, "lastFireTime", ent, RUI_TRACK_LAST_FIRED_TIME ) + if ( !IsFFAGame() ) //JFS: Too much work to get FFA to work correctly with Minimap logic, so disabling it for FFA + { + RuiTrackFloat( rui, "sonarDetectedFrac", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.sonar_detected ) + RuiTrackFloat( rui, "maphackDetectedFrac", ent, RUI_TRACK_STATUS_EFFECT_SEVERITY, eStatusEffect.maphack_detected ) + } +} \ No newline at end of file diff --git a/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut b/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut index f17216fbc..191ef1444 100644 --- a/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut +++ b/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut @@ -22,14 +22,37 @@ GameStateStruct function DiscordRPC_GenerateGameState( GameStateStruct gs ) gs.mapDisplayname = Localize(GetMapDisplayName(GetMapName())) gs.playlist = GetCurrentPlaylistName() - gs.playlistDisplayname = Localize(GetCurrentPlaylistVarString("name", GetCurrentPlaylistName())) + gs.playlistDisplayname = Localize( GetCurrentPlaylistVarString( "name", GetCurrentPlaylistName() ) ) - gs.currentPlayers = GetPlayerArray().len() - gs.maxPlayers = GetCurrentPlaylistVarInt( "maxPlayers", -1 ) + int reservedCount = GetTotalPendingPlayersReserved() + int connectingCount = GetTotalPendingPlayersConnecting() + int loadingCount = GetTotalPendingPlayersLoading() + int connectedCount = GetPlayerArray().len() + int allKnownPlayersCount = reservedCount + connectingCount + loadingCount + connectedCount + + gs.currentPlayers = allKnownPlayersCount + gs.maxPlayers = GetCurrentPlaylistVarInt( "max_players", 16 ) if ( IsValid( GetLocalClientPlayer() ) ) gs.ownScore = GameRules_GetTeamScore( GetLocalClientPlayer().GetTeam() ) + #if MP + if ( GameRules_GetGameMode() == FD ) + { + gs.playlist = "fd" // So it returns only one thing to the plugin side instead of the 5 separate difficulties FD have + if ( GetGlobalNetInt( "FD_waveState" ) == WAVE_STATE_INCOMING || GetGlobalNetInt( "FD_waveState" ) == WAVE_STATE_IN_PROGRESS ) + { + gs.fd_waveNumber = GetGlobalNetInt( "FD_currentWave" ) + 1 + gs.fd_totalWaves = GetGlobalNetInt( "FD_totalWaves" ) + } + else + gs.fd_waveNumber = -1 // Tells plugin it's on Wave Break + } + #else + gs.fd_waveNumber = -1 // Unecessary for campaign so return -1 + #endif + + gs.serverGameState = GetGameState() == -1 ? 0 : GetGameState() gs.otherHighestScore = gs.ownScore == highestScore ? secondHighest : highestScore gs.maxScore = IsRoundBased() ? GetCurrentPlaylistVarInt( "roundscorelimit", 0 ) : GetCurrentPlaylistVarInt( "scorelimit", 0 ) diff --git a/Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut b/Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut index 89b7f7196..966686025 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/atlas_auth.nut @@ -37,7 +37,7 @@ void function AtlasAuthDialog_Threaded() if ( res.errorCode != "" ) dialogData.message += format( "\n\n%s", Localize( "#AUTHENTICATION_FAILED_ERROR_CODE", res.errorCode ) ) - string link = "https://r2northstar.gitbook.io/r2northstar-wiki/installing-northstar/troubleshooting" + string link = "https://docs.northstar.tf/Wiki/installing-northstar/troubleshooting/" // link to generic troubleshooting page if we don't have an error code from Atlas if ( res.errorCode != "" ) link = format( "%s#%s", link, res.errorCode ) diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut index 605af3832..109eed129 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut @@ -1,81 +1,551 @@ +untyped global function InitModesMenu +global function NSSetModeCategory + +global enum eModeMenuModeCategory +{ + UNKNOWN = 0, + PVPVE = 1, + PVE = 2, + PVP = 3, + FFA = 4, + TITAN = 5, + OTHER = 6, + CUSTOM = 7 + + SIZE +} + +// List of blocked modes due to them being unfinished +const array blockedModes = +[ + "fd_easy", + "fd_normal", + "fd_hard", + "fd_master", + "fd_insane" +] + +struct ListEntry_t { + string mode + int category +} + +// Slider mouse delta buffer +struct { + int deltaX = 0 + int deltaY = 0 +} mouseDeltaBuffer struct { - int currentModePage + int scrollOffset + var menu + + string searchString + int searchEnum + + // Table of category overrides + table categoryOverrides + + // List of all modes we know + array modes + + // Sorted list of modes we want to show with categories included + array sortedModes } file const int MODES_PER_PAGE = 15 void function InitModesMenu() { - var menu = GetMenu( "ModesMenu" ) + file.menu = GetMenu( "ModesMenu" ) + + AddMouseMovementCaptureHandler( Hud_GetChild( file.menu, "MouseMovementCapture"), UpdateMouseDeltaBuffer ) + + AddMenuEventHandler( file.menu, eUIEvent.MENU_CLOSE, OnCloseModesMenu ) + AddMenuEventHandler( file.menu, eUIEvent.MENU_OPEN, OnOpenModesMenu ) + AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModeListUpArrow"), UIE_CLICK, OnUpArrowSelected ) + AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModeListDownArrow"), UIE_CLICK, OnDownArrowSelected ) - AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnOpenModesMenu ) + AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModeLabel"), UIE_CHANGE, FilterAndUpdateList ) + AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModeSearch"), UIE_CHANGE, FilterAndUpdateList ) + AddButtonEventHandler( Hud_GetChild( file.menu, "SwtModeLabel"), UIE_CHANGE, FilterAndUpdateList ) - AddEventHandlerToButtonClass( menu, "ModeButton", UIE_GET_FOCUS, ModeButton_GetFocus ) - AddEventHandlerToButtonClass( menu, "ModeButton", UIE_CLICK, ModeButton_Click ) + AddButtonEventHandler( Hud_GetChild( file.menu, "BtnModeFiltersClear"), UIE_CLICK, OnBtnFiltersClear_Activate ) + + array buttons = GetElementsByClassname( file.menu, "ModeSelectorPanel" ) + foreach ( var panel in buttons ) + { + AddEventHandlerToButton( panel, "BtnMode", UIE_GET_FOCUS, ModeButton_GetFocus ) + AddEventHandlerToButton( panel, "BtnMode", UIE_CLICK, ModeButton_Click ) + } - AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" ) - AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" ) - - AddMenuFooterOption( menu, BUTTON_SHOULDER_LEFT, "#PRIVATE_MATCH_PAGE_PREV", "#PRIVATE_MATCH_PAGE_PREV", CycleModesBack ) - AddMenuFooterOption( menu, BUTTON_SHOULDER_RIGHT, "#PRIVATE_MATCH_PAGE_NEXT", "#PRIVATE_MATCH_PAGE_NEXT", CycleModesForward ) + Hud_SetText( Hud_GetChild( file.menu, "SwtModeLabel" ), "#MODE_MENU_SWITCH" ) + SetButtonRuiText( Hud_GetChild( file.menu, "SwtModeLabel" ), "" ) + Hud_DialogList_AddListItem( Hud_GetChild( file.menu, "SwtModeLabel" ) , "#MODE_MENU_ALL", "-1" ) + for( int i = 0; i < eModeMenuModeCategory.SIZE; i++ ) + { + Hud_DialogList_AddListItem( Hud_GetChild( file.menu, "SwtModeLabel" ) , GetCategoryStringFromEnum(i), string(i) ) + } + + AddMenuFooterOption( file.menu, BUTTON_A, "#A_BUTTON_SELECT" ) + AddMenuFooterOption( file.menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" ) +} + +void function NSSetModeCategory( string mode, int category ) +{ + if( mode in file.categoryOverrides ) + { + file.categoryOverrides[mode] = category + printt( "Overwriting category for mode:", mode ) + return + } + + file.categoryOverrides[mode] <- category +} + +void function OnBtnFiltersClear_Activate( var b ) +{ + file.searchString = "" + file.searchEnum = -1 + + SetConVarInt( "modemenu_mode_filter", -1 ) + Hud_SetText( Hud_GetChild( file.menu, "BtnModeSearch"), "" ) + + file.scrollOffset = 0 + + BuildSortedModesArray() + UpdateListSliderHeight(float(file.sortedModes.len())) + UpdateListSliderPosition(file.sortedModes.len()) + UpdateVisibleModes() +} + +void function FilterAndUpdateList( var n ) +{ + file.searchString = Hud_GetUTF8Text( Hud_GetChild( file.menu, "BtnModeSearch" ) ) + file.searchEnum = GetConVarInt( "modemenu_mode_filter" ) + + file.scrollOffset = 0 + + BuildSortedModesArray() + UpdateListSliderHeight(float(file.sortedModes.len())) + UpdateListSliderPosition(file.sortedModes.len()) + UpdateVisibleModes() } void function OnOpenModesMenu() { + RegisterButtonPressedCallback( MOUSE_WHEEL_UP , OnScrollUp ) + RegisterButtonPressedCallback( MOUSE_WHEEL_DOWN , OnScrollDown ) + + // Reset filters + file.searchString = "" + file.searchEnum = -1 + + // We rebuild the modes array on open menu to make sure + // all modes get listed + BuildModesArray() + BuildSortedModesArray() + + UpdateListSliderHeight(float(file.sortedModes.len())) + UpdateListSliderPosition(file.sortedModes.len()) UpdateVisibleModes() - - if ( level.ui.privatematch_mode == 0 ) // set to the first mode if there's no mode focused - Hud_SetFocused( GetElementsByClassname( GetMenu( "ModesMenu" ), "ModeButton" )[ 0 ] ) + + // Set to the first mode if there's no mode focused + if ( level.ui.privatematch_mode == 0 ) + { + array panels = GetElementsByClassname( file.menu, "ModeSelectorPanel" ) + foreach( var panel in panels ) + { + if( Hud_IsEnabled( Hud_GetChild( panel, "BtnMode") ) ) + { + Hud_SetFocused( Hud_GetChild( panel, "BtnMode") ) + break + } + } + } +} + +void function OnCloseModesMenu() +{ + try + { + DeregisterButtonPressedCallback( MOUSE_WHEEL_UP , OnScrollUp ) + DeregisterButtonPressedCallback( MOUSE_WHEEL_DOWN , OnScrollDown ) + } + catch ( ex ) {} } +string function GetCategoryStringFromEnum( int category ) +{ + switch( category ) + { + case eModeMenuModeCategory.PVPVE: return "#MODE_MENU_PVPVE" + case eModeMenuModeCategory.PVE: return "#MODE_MENU_PVE" + case eModeMenuModeCategory.PVP: return "#MODE_MENU_PVP" + case eModeMenuModeCategory.FFA: return "#MODE_MENU_FFA" + case eModeMenuModeCategory.TITAN: return "#MODE_MENU_TITAN_ONLY" + case eModeMenuModeCategory.OTHER: return "#MODE_MENU_OTHER" + case eModeMenuModeCategory.CUSTOM: return "#MODE_MENU_CUSTOM" + } + + return "#MODE_MENU_UNKNOWN" +} + +void function BuildModesArray() +{ + file.modes.clear() + + foreach( string mode in GetPrivateMatchModes() ) + { + ListEntry_t entry + entry.mode = mode + entry.category = eModeMenuModeCategory.UNKNOWN + + switch( mode ) + { + case "aitdm": + case "at": + entry.category = eModeMenuModeCategory.PVPVE + break + case "fd_easy": + case "fd_normal": + case "fd_hard": + case "fd_master": + case "fd_insane": + entry.category = eModeMenuModeCategory.PVE + break + case "tdm": + case "ctf": + case "mfd": + case "ps": + case "cp": + case "speedball": + case "rocket_lf": + case "holopilot_lf": + entry.category = eModeMenuModeCategory.PVP + break + case "ffa": + case "fra": + entry.category = eModeMenuModeCategory.FFA + break + case "lts": + case "ttdm": + case "attdm": + case "turbo_ttdm": + case "alts": + case "turbo_lts": + entry.category = eModeMenuModeCategory.TITAN + break + case "coliseum": + case "sp_coop": + entry.category = eModeMenuModeCategory.OTHER + break + case "chamber": + case "hidden": + case "sns": + case "fw": + case "gg": + case "tt": + case "inf": + case "kr": + case "fastball": + case "hs": + case "ctf_comp": + case "tffa": + entry.category = eModeMenuModeCategory.CUSTOM + break + } + + file.modes.append(entry) + } +} + +int function SortModesAlphabetize( string a, string b ) +{ + a = Localize( GetGameModeDisplayName( a ) ) + b = Localize( GetGameModeDisplayName( b ) ) + + if ( a > b ) + return 1 + + if ( a < b ) + return -1 + + return 0 +} + +void function BuildSortedModesArray() +{ + file.sortedModes.clear() + + // Build sorted list of categories + array categories + for( int i = 0; i < eModeMenuModeCategory.SIZE; i++ ) + { + if( file.searchEnum != -1 && file.searchEnum != i ) + continue + + categories.append( GetCategoryStringFromEnum( i ) ) + } + + categories.sort( SortStringAlphabetize ) + + // Build final list of mixed modes and categories + foreach( string category in categories ) + { + // Build sorted list of modes in category + array modes + foreach( ListEntry_t entry in file.modes ) + { + int iCategory = entry.category + if( entry.mode in file.categoryOverrides ) + iCategory = file.categoryOverrides[entry.mode] + + if( GetCategoryStringFromEnum( iCategory ) != category ) + continue + + string mode = entry.mode + + if( file.searchString != "" && Localize(GetGameModeDisplayName(mode)).tolower().find(file.searchString.tolower()) == null ) + continue + + if( !modes.contains(mode) ) + modes.append( mode ) + } + + modes.sort( SortModesAlphabetize ) + + if( modes.len() == 0 ) + continue + + // Add to final list we then display + file.sortedModes.append( category ) + foreach( string mode in modes ) + file.sortedModes.append( mode ) + } +} + +//////////////////////////// +// Slider +//////////////////////////// +void function UpdateMouseDeltaBuffer( int x, int y ) +{ + mouseDeltaBuffer.deltaX += x + mouseDeltaBuffer.deltaY += y + + SliderBarUpdate() +} + +void function FlushMouseDeltaBuffer() +{ + mouseDeltaBuffer.deltaX = 0 + mouseDeltaBuffer.deltaY = 0 +} + + +void function SliderBarUpdate() +{ + if( file.sortedModes.len() < MODES_PER_PAGE ) + return + + var sliderButton = Hud_GetChild( file.menu , "BtnModeListSlider" ) + var sliderPanel = Hud_GetChild( file.menu , "BtnModeListSliderPanel" ) + var movementCapture = Hud_GetChild( file.menu , "MouseMovementCapture" ) + + Hud_SetFocused( sliderButton ) + + int[2] screenSize = GetScreenSize() + float minYPos = -40.0 * ( screenSize[1] / 1080.0 ) + float maxHeight = 596.0 * ( screenSize[1] / 1080.0 ) + float maxYPos = minYPos - ( maxHeight - Hud_GetHeight( sliderPanel ) ) + float useableSpace = maxHeight - Hud_GetHeight( sliderPanel ) + + float jump = minYPos - ( useableSpace / ( float( file.sortedModes.len() ) ) ) + + // got local from official respaw scripts, without untyped throws an error + local pos = Hud_GetPos( sliderButton )[1] + local newPos = pos - mouseDeltaBuffer.deltaY + FlushMouseDeltaBuffer() + + if ( newPos < maxYPos ) newPos = maxYPos + if ( newPos > minYPos ) newPos = minYPos + + Hud_SetPos( sliderButton , 2, newPos ) + Hud_SetPos( sliderPanel , 2, newPos ) + Hud_SetPos( movementCapture , 2, newPos ) + + file.scrollOffset = -int( ( ( newPos - minYPos ) / useableSpace ) * ( file.sortedModes.len() - MODES_PER_PAGE ) ) + UpdateVisibleModes() +} + +void function UpdateListSliderHeight( float modes ) +{ + var sliderButton = Hud_GetChild( file.menu , "BtnModeListSlider" ) + var sliderPanel = Hud_GetChild( file.menu , "BtnModeListSliderPanel" ) + var movementCapture = Hud_GetChild( file.menu , "MouseMovementCapture" ) + + int[2] screenSize = GetScreenSize() + float maxHeight = 596.0 * ( screenSize[1] / 1080.0 ) + float minHeight = 80.0 * ( screenSize[1] / 1080.0 ) + + float height = maxHeight * ( MODES_PER_PAGE / modes ) + + if ( height > maxHeight ) height = maxHeight + if ( height < minHeight ) height = minHeight + + Hud_SetHeight( sliderButton, height ) + Hud_SetHeight( sliderPanel, height ) + Hud_SetHeight( movementCapture, height ) +} + + +void function UpdateListSliderPosition( int modes ) +{ + if( modes < MODES_PER_PAGE ) + return + + var sliderButton = Hud_GetChild( file.menu, "BtnModeListSlider" ) + var sliderPanel = Hud_GetChild( file.menu, "BtnModeListSliderPanel" ) + var movementCapture = Hud_GetChild( file.menu, "MouseMovementCapture" ) + + float minYPos = -40.0 * ( GetScreenSize()[1] / 1080.0 ) + float useableSpace = (596.0 * ( GetScreenSize()[1] / 1080.0 ) - Hud_GetHeight( sliderPanel ) ) + + float jump = minYPos - ( useableSpace / ( float( modes ) - MODES_PER_PAGE ) * file.scrollOffset ) + + if ( jump > minYPos ) jump = minYPos + + Hud_SetPos( sliderButton, 2, jump ) + Hud_SetPos( sliderPanel, 2, jump ) + Hud_SetPos( movementCapture, 2, jump ) +} + +void function OnScrollDown( var button ) +{ + if (file.sortedModes.len() <= MODES_PER_PAGE) return + file.scrollOffset += 5 + if (file.scrollOffset + MODES_PER_PAGE > file.sortedModes.len()) { + file.scrollOffset = file.sortedModes.len() - MODES_PER_PAGE + } + UpdateVisibleModes() + UpdateListSliderPosition( file.sortedModes.len() ) +} + +void function OnScrollUp( var button ) +{ + file.scrollOffset -= 5 + if ( file.scrollOffset < 0 ) { + file.scrollOffset = 0 + } + UpdateVisibleModes() + UpdateListSliderPosition( file.sortedModes.len() ) +} + +void function OnDownArrowSelected( var button ) +{ + if ( file.sortedModes.len() <= MODES_PER_PAGE ) return + file.scrollOffset += 1 + if ( file.scrollOffset + MODES_PER_PAGE > file.sortedModes.len() ) + { + file.scrollOffset = file.sortedModes.len() - MODES_PER_PAGE + } + + UpdateVisibleModes() + UpdateListSliderPosition( file.sortedModes.len() ) +} + + +void function OnUpArrowSelected( var button ) +{ + file.scrollOffset -= 1 + if ( file.scrollOffset < 0 ) + { + file.scrollOffset = 0 + } + + UpdateVisibleModes() + UpdateListSliderPosition( file.sortedModes.len() ) +} + +bool function IsStringCategory( string str ) +{ + return GetGameModeDisplayName( str ) == "" +} + +///////////////////////////// +// LIST +///////////////////////////// + void function UpdateVisibleModes() -{ +{ // ensures that we only ever show enough buttons for the number of modes we have - array buttons = GetElementsByClassname( GetMenu( "ModesMenu" ), "ModeButton" ) - foreach ( var button in buttons ) + array buttons = GetElementsByClassname( GetMenu( "ModesMenu" ), "ModeSelectorPanel" ) + foreach ( var panel in buttons ) { - Hud_SetEnabled( button, false ) - Hud_SetVisible( button, false ) + Hud_SetEnabled( panel, false ) + Hud_SetVisible( panel, false ) + Hud_SetText( Hud_GetChild( panel, "Header" ), "" ) + Hud_SetText( Hud_GetChild( panel, "BtnMode" ), "" ) + SetButtonRuiText( Hud_GetChild( panel, "BtnMode" ), "" ) } - - array modesArray = GetPrivateMatchModes() + for ( int i = 0; i < MODES_PER_PAGE; i++ ) { - if ( i + ( file.currentModePage * MODES_PER_PAGE ) >= modesArray.len() ) + if ( i + file.scrollOffset >= file.sortedModes.len() ) break - - int modeIndex = i + ( file.currentModePage * MODES_PER_PAGE ) - SetButtonRuiText( buttons[ i ], GetGameModeDisplayName( modesArray[ modeIndex ] ) ) - - Hud_SetEnabled( buttons[ i ], true ) - Hud_SetVisible( buttons[ i ], true ) - - // This check is refactored in the new mode menu so we can just ignore this atrocity - if ( !ModeSettings_RequiresAI( modesArray[ modeIndex ] ) || modesArray[ modeIndex ] == "aitdm" || modesArray[ modeIndex ] == "at" ) - Hud_SetLocked( buttons[ i ], false ) + + // Setup locals + var panel = buttons[i] + var button = Hud_GetChild( panel, "BtnMode" ) + var header = Hud_GetChild( panel, "Header" ) + + int modeIndex = i + file.scrollOffset + string mode = file.sortedModes[ modeIndex ] + + bool bIsCategory = IsStringCategory( mode ) + mode = bIsCategory ? mode : GetGameModeDisplayName( mode ) + + // Show the panel + Hud_SetEnabled( panel, true ) + Hud_SetVisible( panel, true ) + Hud_SetLocked( button, false ) + + if( bIsCategory ) + { + Hud_SetText( header, mode ) + Hud_SetEnabled( button, false ) + } else - Hud_SetLocked( buttons[ i ], true ) + { + Hud_SetEnabled( button, true ) + SetButtonRuiText( button, mode ) + + if( blockedModes.contains( file.sortedModes[ modeIndex ] ) ) + Hud_SetLocked( button, true ) + + if ( PrivateMatch_IsValidMapModeCombo( PrivateMatch_GetSelectedMap(), mode ) ) + { + Hud_SetLocked( button, true ) + SetButtonRuiText( button, mode ) + } + } } } void function ModeButton_GetFocus( var button ) { - int modeId = int( Hud_GetScriptID( button ) ) + ( file.currentModePage * MODES_PER_PAGE ) - - var menu = GetMenu( "ModesMenu" ) - var nextModeImage = Hud_GetChild( menu, "NextModeImage" ) - var nextModeIcon = Hud_GetChild( menu, "ModeIconImage" ) - var nextModeName = Hud_GetChild( menu, "NextModeName" ) - var nextModeDesc = Hud_GetChild( menu, "NextModeDesc" ) + int modeId = int( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset - 1 - array modesArray = GetPrivateMatchModes() + var nextModeImage = Hud_GetChild( file.menu, "NextModeImage" ) + var nextModeIcon = Hud_GetChild( file.menu, "ModeIconImage" ) + var nextModeName = Hud_GetChild( file.menu, "NextModeName" ) + var nextModeDesc = Hud_GetChild( file.menu, "NextModeDesc" ) - if ( modeId > modesArray.len() ) + if ( modeId > file.sortedModes.len() ) return - string modeName = modesArray[modeId] + string modeName = file.sortedModes[modeId] asset playlistImage = GetPlaylistImage( modeName ) RuiSetImage( Hud_GetRui( nextModeImage ), "basicImage", playlistImage ) @@ -99,35 +569,17 @@ void function ModeButton_Click( var button ) if ( Hud_IsLocked( button ) ) return - int modeID = int( Hud_GetScriptID( button ) ) + ( file.currentModePage * MODES_PER_PAGE ) + int modeID = int( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset - 1 - array modesArray = GetPrivateMatchModes() - string modeName = modesArray[ modeID ] + string modeName = file.sortedModes[ modeID ] // on modded servers set us to the first map for that mode automatically // need this for coliseum mainly which is literally impossible to select without this - if ( !PrivateMatch_IsValidMapModeCombo( PrivateMatch_GetSelectedMap(), modesArray[ modeID ] ) ) + if ( !PrivateMatch_IsValidMapModeCombo( PrivateMatch_GetSelectedMap(), modeName ) ) + { ClientCommand( "SetCustomMap " + GetPrivateMatchMapsForMode( modeName )[ 0 ] ) - + } // set it ClientCommand( "PrivateMatchSetMode " + modeName ) CloseActiveMenu() } - -void function CycleModesBack( var button ) -{ - if ( file.currentModePage == 0 ) - return - - file.currentModePage-- - UpdateVisibleModes() -} - -void function CycleModesForward( var button ) -{ - if ( ( file.currentModePage + 1 ) * MODES_PER_PAGE >= GetPrivateMatchModes().len() ) - return - - file.currentModePage++ - UpdateVisibleModes() -} \ No newline at end of file diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut index 4d299362d..4968714c7 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut @@ -1,8 +1,10 @@ global function DownloadMod global function DisplayModDownloadErrorDialog +global function FetchVerifiedModsManifesto global enum eModInstallStatus { + MANIFESTO_FETCHING, DOWNLOADING, CHECKSUMING, EXTRACTING, @@ -18,6 +20,35 @@ global enum eModInstallStatus const int MB = 1024*1000; + +void function FetchVerifiedModsManifesto() +{ + print("Start fetching verified mods manifesto from the Internet") + + // Fetching UI + DialogData dialogData + dialogData.header = Localize( "#MANIFESTO_FETCHING_TITLE" ) + dialogData.message = Localize( "#MANIFESTO_FETCHING_TEXT" ) + dialogData.showSpinner = true; + + // Prevent user from closing dialog + dialogData.forceChoice = true; + OpenDialog( dialogData ) + + // Do the actual fetching + NSFetchVerifiedModsManifesto() + + ModInstallState state = NSGetModInstallState() + while ( state.status == eModInstallStatus.MANIFESTO_FETCHING ) + { + state = NSGetModInstallState() + WaitFrame() + } + + // Close dialog when manifesto has been received + CloseActiveMenu() +} + bool function DownloadMod( RequiredModInfo mod ) { // Downloading mod UI @@ -58,6 +89,10 @@ void function UpdateModDownloadDialog( RequiredModInfo mod, ModInstallState stat { switch ( state.status ) { + case eModInstallStatus.MANIFESTO_FETCHING: + Hud_SetText( header, Localize( "#MANIFESTO_FETCHING_TITLE" ) ) + Hud_SetText( body, Localize( "#MANIFESTO_FETCHING_TEXT" ) ) + break case eModInstallStatus.DOWNLOADING: Hud_SetText( header, Localize( "#DOWNLOADING_MOD_TITLE_W_PROGRESS", string( state.ratio ) ) ) Hud_SetText( body, Localize( "#DOWNLOADING_MOD_TEXT_W_PROGRESS", mod.name, mod.version, floor( state.progress / MB ), floor( state.total / MB ) ) ) diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut index 3f643aa3d..f08d69a72 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut @@ -338,6 +338,10 @@ void function RefreshMods() { string mod = modNames[i] + // Do not display remote mods + if ( NSIsModRemote( mod ) ) + continue + if ( searchTerm.len() && mod.tolower().find( searchTerm ) == null ) continue diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut index 4f17dedf2..cdeb8b3e0 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut @@ -967,49 +967,93 @@ void function OnServerSelected_Threaded( var button ) bool autoDownloadAllowed = GetConVarBool( "allow_mod_auto_download" ) int downloadedMods = 0; + // Check out if there's any server-required mod that is not locally installed + array modNames = NSGetModNames() + bool uninstalledModFound = false + foreach ( requiredModInfo in server.requiredMods ) + { + // Tolerate core mods having different versions + if ( requiredModInfo.name.len() > 10 && requiredModInfo.name.slice(0, 10) == "Northstar." ) + continue + + if ( !modNames.contains( requiredModInfo.name ) ) + { + print( format ( "\"%s\" was not found locally, triggering manifesto fetching.", requiredModInfo.name ) ) + uninstalledModFound = true + break + } else if ( NSGetModVersionByModName( requiredModInfo.name ) != requiredModInfo.version ) { + print( format ( "\"%s\" was found locally but has version \"%s\" while server requires \"%s\", triggering manifesto fetching.", requiredModInfo.name, NSGetModVersionByModName( requiredModInfo.name ), requiredModInfo.version ) ) + uninstalledModFound = true + break + } + } + + // If yes, we fetch the verified mods manifesto, to check whether uninstalled + // mods can be installed through auto-download + if ( uninstalledModFound && autoDownloadAllowed ) + { + print("Auto-download is allowed, checking if missing mods can be installed automatically.") + FetchVerifiedModsManifesto() + } + foreach ( RequiredModInfo mod in server.requiredMods ) { - if ( !NSGetModNames().contains( mod.name ) ) + // Tolerate core mods having different versions + if ( mod.name.len() > 10 && mod.name.slice(0, 10) == "Northstar." ) + continue + + if ( !NSGetModNames().contains( mod.name ) || NSGetModVersionByModName( mod.name ) != mod.version ) { - // Check if mod can be auto-downloaded - bool modIsVerified = NSIsModDownloadable( mod.name, mod.version ) + // Auto-download mod + if ( autoDownloadAllowed ) + { + bool modIsVerified = NSIsModDownloadable( mod.name, mod.version ) + + // Display error message if mod is not verified + if ( !modIsVerified ) + { + DialogData dialogData + dialogData.header = "#ERROR" + dialogData.message = Localize( "#MISSING_MOD", mod.name, mod.version ) + dialogData.message += "\n" + Localize( "#MOD_NOT_VERIFIED" ) + dialogData.image = $"ui/menu/common/dialog_error" - // Display an error message if not - if ( !modIsVerified || !autoDownloadAllowed ) + AddDialogButton( dialogData, "#DISMISS" ) + AddDialogFooter( dialogData, "#A_BUTTON_SELECT" ) + AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" ) + + OpenDialog( dialogData ) + return + } + else + { + if ( DownloadMod( mod ) ) + { + downloadedMods++ + } + else + { + DisplayModDownloadErrorDialog( mod.name ) + return + } + } + } + + // Mod not found, display error message + else { DialogData dialogData dialogData.header = "#ERROR" dialogData.message = Localize( "#MISSING_MOD", mod.name, mod.version ) dialogData.image = $"ui/menu/common/dialog_error" - // Specify error (only if autoDownloadAllowed is set) - if ( autoDownloadAllowed ) - { - dialogData.message += "\n" + Localize( "#MOD_NOT_VERIFIED" ) - } - AddDialogButton( dialogData, "#DISMISS" ) - AddDialogFooter( dialogData, "#A_BUTTON_SELECT" ) AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" ) OpenDialog( dialogData ) - return } - - else // Launch download - { - if ( DownloadMod( mod ) ) - { - downloadedMods++ - } - else - { - DisplayModDownloadErrorDialog( mod.name ) - return - } - } } else { diff --git a/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl index 2fcc34390..82b58becc 100644 Binary files a/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl and b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/ptpov_shotgun_doublebarrel.mdl differ diff --git a/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl index 137d985d6..9e6603745 100644 Binary files a/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl and b/Northstar.Custom/mod/models/weapons/shotgun_doublebarrel/w_shotgun_doublebarrel.mdl differ diff --git a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut index 37d4356f0..10bca342c 100644 --- a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut +++ b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut @@ -207,7 +207,7 @@ string function GetSelectedBurnCardRef( entity player ) #if SERVER if ( GetItemDisplayData( ref ).hidden ) - ClientCommand( player, "disconnect" ) + NSDisconnectPlayer( player, "" ) #endif #if SERVER || CLIENT diff --git a/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut b/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut index 16908362c..3971d2bec 100644 --- a/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut +++ b/Northstar.Custom/mod/scripts/vscripts/earn_meter/cl_earn_meter.gnut @@ -335,7 +335,11 @@ void function EarnMeter_Update() break entity soul = player.GetTitanSoul() - entity core = player.GetOffhandWeapons()[3] + entity core = player.GetOffhandWeapon( OFFHAND_EQUIPMENT ) + + if ( !IsValid( core ) ) + break + string coreName = core.GetWeaponClassName() array coreMods = core.GetMods() diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut b/Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut index 4987ee015..bae0116ef 100644 --- a/Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut +++ b/Northstar.Custom/mod/scripts/vscripts/sh_damage_types.nut @@ -727,7 +727,7 @@ bool function RegisterWeaponDamageSourceInternal( int id, string newVal, string damageSourceID[ newVal ] <- id file.damageSourceIDToString[ id ] <- newVal file.damageSourceIDToName[ id ] <- stringVal - file.customDamageSourceIDList.extend( [ id.tostring(), newVal, StringReplace( stringVal, " ", MESSAGE_SPACE_PADDING ) ] ) + file.customDamageSourceIDList.extend( [ id.tostring(), newVal, StringReplace( stringVal, " ", MESSAGE_SPACE_PADDING, true ) ] ) return true } @@ -773,6 +773,6 @@ void function ReceiveNewDamageSourceIDs( array args ) { // IDs are inserted to the custom list in triplets, so we can trust these indices exist and the loop will end properly for ( int i = 0; i < args.len(); i += 3 ) - RegisterWeaponDamageSourceInternal( args[ i ].tointeger(), args[ i + 1 ], StringReplace( args[ i + 2 ], MESSAGE_SPACE_PADDING, " " ) ) + RegisterWeaponDamageSourceInternal( args[ i ].tointeger(), args[ i + 1 ], StringReplace( args[ i + 2 ], MESSAGE_SPACE_PADDING, " ", true ) ) } #endif diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut index defb1a56f..e198f7265 100644 --- a/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut @@ -85,28 +85,30 @@ global const ARC_CANNON_BEAM_EFFECT_MOD = $"wpn_arc_cannon_beam_mod" global const ARC_CANNON_FX_TABLE = "exp_arc_cannon" global const ArcCannonTargetClassnames = { - [ "npc_drone" ] = true, - [ "npc_dropship" ] = true, - [ "npc_marvin" ] = true, - [ "npc_prowler" ] = true, - [ "npc_soldier" ] = true, - [ "npc_soldier_heavy" ] = true, - [ "npc_soldier_shield" ] = true, - [ "npc_spectre" ] = true, - [ "npc_stalker" ] = true, - [ "npc_super_spectre" ] = true, - [ "npc_titan" ] = true, - [ "npc_turret_floor" ] = true, - [ "npc_turret_mega" ] = true, - [ "npc_turret_sentry" ] = true, - [ "npc_frag_drone" ] = true, - [ "player" ] = true, - [ "prop_dynamic" ] = true, - [ "prop_script" ] = true, - [ "grenade_frag" ] = true, - [ "rpg_missile" ] = true, - [ "script_mover" ] = true, - [ "turret" ] = true, + [ "npc_drone" ] = true, + [ "npc_dropship" ] = true, + [ "npc_gunship" ] = true, + [ "npc_marvin" ] = true, + [ "npc_prowler" ] = true, + [ "npc_soldier" ] = true, + [ "npc_soldier_heavy" ] = true, + [ "npc_soldier_shield" ] = true, + [ "npc_pilot_elite" ] = true, + [ "npc_spectre" ] = true, + [ "npc_stalker" ] = true, + [ "npc_super_spectre" ] = true, + [ "npc_titan" ] = true, + [ "npc_turret_floor" ] = true, + [ "npc_turret_mega" ] = true, + [ "npc_turret_sentry" ] = true, + [ "npc_frag_drone" ] = true, + [ "player" ] = true, + [ "prop_dynamic" ] = true, + [ "prop_script" ] = true, + [ "grenade_frag" ] = true, + [ "rpg_missile" ] = true, + [ "script_mover" ] = true, + [ "turret" ] = true, } struct { diff --git a/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpak b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpak index 3a714d3b6..440f16aef 100644 Binary files a/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpak and b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.rpak differ diff --git a/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpak b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpak index 3bd344679..93e59c892 100644 Binary files a/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpak and b/Northstar.Custom/paks/mp_weapon_shotgun_doublebarrel.starpak differ diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut index 2b95d1a8c..97d993e65 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_xp.gnut @@ -16,7 +16,10 @@ void function SetupPlayerPreviousXPValues( entity player ) player.SetPersistentVar( "previousFactionXP[" + xpFaction + "]", FactionGetXP( player, xpFaction ) ) foreach ( string xpTitan in shTitanXP.titanClasses ) + { player.SetPersistentVar( "previousTitanXP[" + xpTitan + "]", TitanGetXP( player, xpTitan ) ) + player.SetPersistentVar( "fdPreviousTitanXP[" + xpTitan + "]", FD_TitanGetXP( player, xpTitan ) ) + } foreach ( string xpWeapon in shWeaponXP.weaponClassNames ) player.SetPersistentVar( GetItemPersistenceStruct( xpWeapon ) + ".previousWeaponXP", WeaponGetXP( player, xpWeapon ) ) @@ -35,6 +38,14 @@ void function HandleXPGainForScoreEvent( entity player, ScoreEvent event ) int weaponXp = ScoreEvent_GetXPValueWeapon( event ) int titanXp = ScoreEvent_GetXPValueTitan( event ) int factionXp = ScoreEvent_GetXPValueFaction( event ) + + if ( player.GetPlayerNetInt( "xpMultiplier" ) > 0 || GetCurrentPlaylistVarInt( "double_xp_enabled", 0 ) == 1 ) + { + xpValue *= 2 + weaponXp *= 2 + titanXp *= 2 + factionXp *= 2 + } entity weapon = player.GetActiveWeapon() if ( IsValid( weapon ) && ShouldTrackXPForWeapon( weapon.GetWeaponClassName() ) && weaponXp != 0 ) @@ -63,5 +74,10 @@ void function AddXP( entity player, int amount ) int newXp = player.GetPersistentVarAsInt( "xp" ) int newLevel = GetLevelForXP( newXp ) if ( newLevel != oldLevel ) + { Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerLeveledUp", player.GetPersistentVarAsInt( "gen" ), newLevel ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForPlayerLevels( player, player.GetPersistentVarAsInt( "gen" ), newLevel ) + } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut index a20e7aa05..21723ab5d 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/earn_meter/sv_earn_meter_mp.gnut @@ -91,6 +91,8 @@ void function EarnMeterMP_PlayerLifeThink( entity player ) EarnObject pilotReward = PlayerEarnMeter_GetReward( player ) float pilotRewardFrac = PlayerEarnMeter_GetRewardFrac( player ) int lastEarnMeterMode = PlayerEarnMeter_GetMode( player ) + bool saidTitanSoon = false + bool titanReadyMsg = false float lastPassiveGainTime = Time() OnThreadEnd( @@ -148,8 +150,22 @@ void function EarnMeterMP_PlayerLifeThink( entity player ) if ( lastEarnMeterMode == eEarnMeterMode.DEFAULT ) { + if ( Riff_TitanAvailability() != eTitanAvailability.Never ) + { + if ( PlayerEarnMeter_GetOwnedFrac( player ) >= 0.75 && PlayerEarnMeter_GetOwnedFrac( player ) < 0.95 && !saidTitanSoon ) + { + PlayFactionDialogueToPlayer( "mp_titanSoon", player ) + saidTitanSoon = true + } + else if( PlayerEarnMeter_GetOwnedFrac( player ) < 0.75 ) + saidTitanSoon = false + } + if ( PlayerEarnMeter_GetOwnedFrac( player ) < 1.0 ) + { PlayerEarnMeter_DisableGoal( player ) + titanReadyMsg = false + } else if ( player.GetPlayerNetInt( "goalState" ) != eRewardState.UNAVAILABLE ) { // if goal is enabled then the client will show "titan ready" alerts even if it isn't @@ -157,6 +173,11 @@ void function EarnMeterMP_PlayerLifeThink( entity player ) // so unfortunately we have to do this manually player.SetPlayerNetInt( "goalState", eRewardState.AVAILABLE ) PlayerEarnMeter_RefreshGoal( player ) + if( !titanReadyMsg ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_TitanReadyMessage" ) + titanReadyMsg = true + } } if ( Time() - lastPassiveGainTime > 4.0 && file.passiveMeterGainEnabled ) // this might be 5.0 diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut index af074689c..760daef0b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut @@ -246,6 +246,9 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa SetTeamActiveObjective( evacTeam, "EG_DropshipExtract", Time() + arrivalTime, file.evacIcon ) SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_StopExtract", Time() + arrivalTime, file.evacIcon ) + foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) ) //Show the Evac Match Goal for players of the team that lost the match + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 0 ) + // would've liked to use cd_dropship_rescue_side_start length here, but can't since this is done before dropship spawn, can't wait arrivalTime - 4.33333 @@ -280,6 +283,9 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa { SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipDestroyed" ) SetTeamActiveObjective( GetOtherTeam( evacTeam ), "EG_DropshipExtractDropshipDestroyed" ) + + foreach( entity player in GetPlayerArrayOfTeam( evacTeam ) ) + SetPlayerChallengeEvacState( player, 0 ) } foreach ( entity player in dropship.s.evacSlots ) @@ -402,7 +408,10 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa if ( !PlayerInDropship( player, dropship ) ) { if ( player.GetTeam() == dropship.GetTeam() ) + { SetPlayerActiveObjective( player, "EG_DropshipExtractFailedEscape" ) + SetPlayerChallengeEvacState( player, 2 ) + } continue } @@ -417,6 +426,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa Remote_CallFunction_NonReplay( player, "ServerCallback_DisableHudForEvac" ) Remote_CallFunction_NonReplay( player, "ServerCallback_SetClassicSkyScale", dropship.GetEncodedEHandle(), 0.7 ) Remote_CallFunction_NonReplay( player, "ServerCallback_SetMapSettings", 4.0, false, 0.4, 0.125 ) + SetPlayerChallengeEvacState( player, 1 ) // display player [Evacuated] in killfeed foreach ( entity otherPlayer in GetPlayerArray() ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut index 6555c875b..5aee11044 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/faction_xp.gnut @@ -4,10 +4,19 @@ void function AddFactionXP( entity player, int amount ) { string faction = GetFactionChoice( player ) int oldLevel = FactionGetLevel( player, faction ) + int FactionXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]" ) + // increment xp player.SetPersistentVar( "factionXP[" + faction + "]", min( FactionGetXP( player, faction ) + amount, FactionGetMaxXP( faction ) ) ) // note: no notif for faction level up if ( FactionGetLevel( player, faction ) != oldLevel ) + { AddPlayerScore( player, "FactionLevelUp" ) + IncrementPlayerChallengeFactionLeveledUp( player ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.FACTION_LEVELED + "]", FactionXPMatch + 1 ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForFactionLevels( player, faction, oldLevel, FactionGetLevel( player, faction ) ) + } } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut index f47ee90f9..cacb54cf1 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut @@ -60,6 +60,7 @@ void function GamemodeAITdm_Init() } ScoreEvent_SetupEarnMeterValuesForMixedModes() + SetupGenericTDMChallenge() } // add settings @@ -434,9 +435,12 @@ void function SquadHandler( array guys ) // Setup AI, first assault point foreach ( guy in guys ) { - guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) - guy.AssaultPoint( point ) - guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others + if ( IsAlive( guy ) ) + { + guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) + guy.AssaultPoint( point ) + guy.AssaultSetGoalRadius( 1600 ) // 1600 is minimum for npc_stalker, works fine for others + } //thread AITdm_CleanupBoredNPCThread( guy ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut index 93a3aa168..9cf0021d7 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut @@ -178,6 +178,12 @@ void function AT_PlayerTitleThink( entity player ) { // Set player money count player.SetTitle( "$" + string( AT_GetPlayerBonusPoints( player ) ) ) + + if( AT_GetPlayerBonusPoints( player ) >= 600 && !HasPlayerCompletedMeritScore( player ) ) //Challenge is: "Earn $600." + { + AddPlayerScore( player, "ChallengeATAssault" ) + SetPlayerChallengeMeritScore( player ) + } } else if ( GetGameState() >= eGameState.WinnerDetermined ) { diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut index c5765300f..d8b0c9bdd 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_cp.nut @@ -29,6 +29,8 @@ struct { array hardpoints array players + table playerAssaultPoints + table playerDefensePoints } file void function GamemodeCP_Init() @@ -112,11 +114,13 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama { AddPlayerScore( attacker , "HardpointDefense", victim ) attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_DEFENSE) + UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_DEFENSE) } else if((victimCP.hardpoint.GetTeam()==victim.GetTeam())||(GetHardpointCappingTeam(victimCP)==victim.GetTeam())) { AddPlayerScore( attacker, "HardpointAssault", victim ) attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_ASSAULT) + UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_ASSAULT,0) } } } @@ -127,10 +131,12 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama { AddPlayerScore( attacker , "HardpointSnipe", victim ) attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SNIPE) + UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SNIPE,0) } else{ AddPlayerScore( attacker , "HardpointSiege", victim ) attacker.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_SIEGE) + UpdatePlayerScoreForChallenge(attacker,POINTVALUE_HARDPOINT_SIEGE,0) } } else if(attackerCP.hardpoint!=null)//Perimeter Defense @@ -138,6 +144,7 @@ void function GamemodeCP_OnPlayerKilled(entity victim, entity attacker, var dama if(attackerCP.hardpoint.GetTeam()==attacker.GetTeam()) AddPlayerScore( attacker , "HardpointPerimeterDefense", victim) attacker.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE) + UpdatePlayerScoreForChallenge(attacker,0,POINTVALUE_HARDPOINT_PERIMETER_DEFENSE) } foreach(CP_PlayerStruct player in file.players) //Reset Victim Holdtime Counter @@ -308,6 +315,7 @@ void function CapturePointForTeam(HardpointStruct hardpoint, int Team) if(player.IsPlayer()){ AddPlayerScore(player,"ControlPointCapture") player.AddToPlayerGameStat(PGS_ASSAULT_SCORE,POINTVALUE_HARDPOINT_CAPTURE) + UpdatePlayerScoreForChallenge(player,POINTVALUE_HARDPOINT_CAPTURE,0) } } } @@ -319,12 +327,17 @@ void function GamemodeCP_InitPlayer(entity player) playerStruct.timeOnPoints = [0.0,0.0,0.0] playerStruct.isOnHardpoint = false file.players.append(playerStruct) + file.playerAssaultPoints[player] <- 0 + file.playerDefensePoints[player] <- 0 thread PlayerThink(playerStruct) } void function GamemodeCP_RemovePlayer(entity player) { - + if(player in file.playerAssaultPoints) + delete file.playerAssaultPoints[player] + if(player in file.playerDefensePoints) + delete file.playerDefensePoints[player] foreach(index,CP_PlayerStruct playerStruct in file.players) { if(playerStruct.player==player) @@ -376,11 +389,13 @@ void function PlayerThink(CP_PlayerStruct player) { AddPlayerScore(player.player,"ControlPointAmpedHold") player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_AMPED_HOLD ) + UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_AMPED_HOLD) } else { AddPlayerScore(player.player,"ControlPointHold") player.player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, POINTVALUE_HARDPOINT_HOLD ) + UpdatePlayerScoreForChallenge(player.player,0,POINTVALUE_HARDPOINT_HOLD) } } break @@ -574,6 +589,7 @@ void function HardpointThink( HardpointStruct hardpoint ) { AddPlayerScore(player,"ControlPointAmped") player.AddToPlayerGameStat(PGS_DEFENSE_SCORE,POINTVALUE_HARDPOINT_AMPED) + UpdatePlayerScoreForChallenge(player,0,POINTVALUE_HARDPOINT_AMPED) } } } @@ -714,3 +730,26 @@ string function GetHardpointGroup(entity hardpoint) //Hardpoint Entity B on Home return string(hardpoint.kv.hardpointGroup) } + +void function UpdatePlayerScoreForChallenge(entity player,int assaultpoints = 0,int defensepoints = 0) +{ + if(player in file.playerAssaultPoints) + { + file.playerAssaultPoints[player] += assaultpoints + if( file.playerAssaultPoints[player] >= 1000 && !HasPlayerCompletedMeritScore(player) ) + { + AddPlayerScore(player,"ChallengeCPAssault") + SetPlayerChallengeMeritScore(player) + } + } + + if(player in file.playerDefensePoints) + { + file.playerDefensePoints[player] += defensepoints + if( file.playerDefensePoints[player] >= 500 && !HasPlayerCompletedMeritScore(player) ) + { + AddPlayerScore(player,"ChallengeCPDefense") + SetPlayerChallengeMeritScore(player) + } + } +} diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut index 9b05c3d42..85b80d744 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ctf.nut @@ -1,5 +1,4 @@ untyped -// this needs a refactor lol global function CaptureTheFlag_Init global function RateSpawnpoints_CTF @@ -12,16 +11,31 @@ const array SWAP_FLAG_MAPS = [ struct { entity imcFlagSpawn entity imcFlag - entity imcFlagReturnTrigger entity militiaFlagSpawn entity militiaFlag - entity militiaFlagReturnTrigger array imcCaptureAssistList array militiaCaptureAssistList } file + + + + + + + + + +/* + ██████ █████ ██████ ████████ ██ ██ ██████ ███████ ████████ ██ ██ ███████ ███████ ██ █████ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██████ ██ ██ ██ ██████ █████ ██ ███████ █████ █████ ██ ███████ ██ ███ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██ ██ ██ ██ ██████ ██ ██ ███████ ██ ██ ██ ███████ ██ ███████ ██ ██ ██████ +*/ + void function CaptureTheFlag_Init() { PrecacheModel( CTF_FLAG_MODEL ) @@ -30,30 +44,31 @@ void function CaptureTheFlag_Init() PrecacheParticleSystem( FLAG_FX_ENEMY ) CaptureTheFlagShared_Init() + SetSwitchSidesBased( true ) SetSuddenDeathBased( true ) + SetShouldUseRoundWinningKillReplay( true ) - SetRoundWinningKillReplayKillClasses( false, false ) // make these fully manual + SetRoundWinningKillReplayKillClasses( false, false ) AddCallback_OnClientConnected( CTFInitPlayer ) - + AddCallback_OnClientDisconnected( CTFPlayerDisconnected ) + AddCallback_GameStateEnter( eGameState.Prematch, CreateFlags ) AddCallback_GameStateEnter( eGameState.Epilogue, RemoveFlags ) + AddCallback_GameStateEnter( eGameState.Playing, OnPlaying ) + AddCallback_OnTouchHealthKit( "item_flag", OnFlagCollected ) + AddCallback_OnPlayerKilled( OnPlayerKilled ) AddCallback_OnPilotBecomesTitan( DropFlagForBecomingTitan ) - SetSpawnZoneRatingFunc( DecideSpawnZone_CTF ) AddSpawnpointValidationRule( VerifyCTFSpawnpoint ) - RegisterSignal( "FlagReturnEnded" ) RegisterSignal( "ResetDropTimeout" ) - // setup stuff for the functions in sh_gamemode_ctf - // don't really like using level for stuff but just how it be level.teamFlags <- {} - // setup score event earnmeter values ScoreEvent_SetEarnMeterValues( "KillPilot", 0.05, 0.20 ) ScoreEvent_SetEarnMeterValues( "Headshot", 0.0, 0.02 ) ScoreEvent_SetEarnMeterValues( "FirstStrike", 0.0, 0.05 ) @@ -67,61 +82,21 @@ void function CaptureTheFlag_Init() ScoreEvent_SetEarnMeterValues( "FlagReturn", 0.0, 0.20 ) } -void function RateSpawnpoints_CTF( int checkClass, array spawnpoints, int team, entity player ) -{ - RateSpawnpoints_SpawnZones( checkClass, spawnpoints, team, player ) -} -bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) -{ - // ensure spawnpoints aren't too close to enemy base - - if ( HasSwitchedSides() ) - team = GetOtherTeam( team ) - - array startSpawns = SpawnPoints_GetPilotStart( team ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) - - vector averageFriendlySpawns - vector averageEnemySpawns - - foreach ( entity spawn in startSpawns ) - averageFriendlySpawns += spawn.GetOrigin() - - averageFriendlySpawns /= startSpawns.len() - - foreach ( entity spawn in enemyStartSpawns ) - averageEnemySpawns += spawn.GetOrigin() - - averageEnemySpawns /= startSpawns.len() - - return Distance2D( spawnpoint.GetOrigin(), averageEnemySpawns ) / Distance2D( averageFriendlySpawns, averageEnemySpawns ) > 0.35 -} -void function CTFInitPlayer( entity player ) -{ - if ( !IsValid( file.imcFlagSpawn ) ) - return - - vector imcSpawn = file.imcFlagSpawn.GetOrigin() - Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z ) - - vector militiaSpawn = file.militiaFlagSpawn.GetOrigin() - Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) -} -void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) -{ - if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk - return - if ( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ).GetParent() == victim ) - { - if ( victim != attacker && attacker.IsPlayer() ) - AddPlayerScore( attacker, "FlagCarrierKill", victim ) - - DropFlag( victim ) - } -} + + + + + +/* +███████ ██████ █████ ██ ██ ███ ██ ██ ██████ ██████ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ +███████ ██████ ███████ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ + ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ███ ███ ██ ████ ███████ ██████ ██████ ██ ██████ +*/ void function CreateFlags() { @@ -129,25 +104,20 @@ void function CreateFlags() { file.imcFlagSpawn.Destroy() file.imcFlag.Destroy() - file.imcFlagReturnTrigger.Destroy() - + } + if ( IsValid( file.militiaFlagSpawn ) ) + { file.militiaFlagSpawn.Destroy() file.militiaFlag.Destroy() - file.militiaFlagReturnTrigger.Destroy() } foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) { - // on some maps flags are on the opposite side from what they should be - // likely this is because respawn uses distance checks from spawns to check this in official - // but i don't like doing that so just using a list of maps to swap them on lol bool switchedSides = HasSwitchedSides() == 1 - - // i dont know why this works and whatever we had before didn't, but yeah + bool shouldSwap = switchedSides - if (!shouldSwap && SWAP_FLAG_MAPS.contains( GetMapName() )) + if ( !shouldSwap && SWAP_FLAG_MAPS.contains( GetMapName() ) ) shouldSwap = !shouldSwap - int flagTeam = spawn.GetTeam() if ( shouldSwap ) @@ -155,48 +125,33 @@ void function CreateFlags() flagTeam = GetOtherTeam( flagTeam ) SetTeam( spawn, flagTeam ) } - - // create flag base + entity base = CreatePropDynamic( CTF_FLAG_BASE_MODEL, spawn.GetOrigin(), spawn.GetAngles(), 0 ) SetTeam( base, spawn.GetTeam() ) svGlobal.flagSpawnPoints[ flagTeam ] = base - // create flag entity flag = CreateEntity( "item_flag" ) flag.SetValueForModelKey( CTF_FLAG_MODEL ) SetTeam( flag, flagTeam ) flag.MarkAsNonMovingAttachment() - flag.Minimap_AlwaysShow( TEAM_IMC, null ) // show flag icon on minimap + flag.Minimap_AlwaysShow( TEAM_IMC, null ) flag.Minimap_AlwaysShow( TEAM_MILITIA, null ) flag.Minimap_SetAlignUpright( true ) DispatchSpawn( flag ) flag.SetModel( CTF_FLAG_MODEL ) - flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > ) // ensure flag doesn't spawn clipped into geometry + flag.SetOrigin( spawn.GetOrigin() + < 0, 0, base.GetBoundingMaxs().z * 2 > ) flag.SetVelocity( < 0, 0, 1 > ) flag.s.canTake <- true - flag.s.playersReturning <- [] level.teamFlags[ flag.GetTeam() ] <- flag - - entity returnTrigger = CreateEntity( "trigger_cylinder" ) - SetTeam( returnTrigger, flagTeam ) - returnTrigger.SetRadius( CTF_GetFlagReturnRadius() ) - returnTrigger.SetAboveHeight( CTF_GetFlagReturnRadius() ) - returnTrigger.SetBelowHeight( CTF_GetFlagReturnRadius() ) - - returnTrigger.SetEnterCallback( OnPlayerEntersFlagReturnTrigger ) - returnTrigger.SetLeaveCallback( OnPlayerExitsFlagReturnTrigger ) - DispatchSpawn( returnTrigger ) - - thread TrackFlagReturnTrigger( flag, returnTrigger ) + thread FlagProximityTracker( flag ) if ( flagTeam == TEAM_IMC ) { file.imcFlagSpawn = base file.imcFlag = flag - file.imcFlagReturnTrigger = returnTrigger SetGlobalNetEnt( "imcFlag", file.imcFlag ) SetGlobalNetEnt( "imcFlagHome", file.imcFlagSpawn ) @@ -205,76 +160,185 @@ void function CreateFlags() { file.militiaFlagSpawn = base file.militiaFlag = flag - file.militiaFlagReturnTrigger = returnTrigger SetGlobalNetEnt( "milFlag", file.militiaFlag ) SetGlobalNetEnt( "milFlagHome", file.militiaFlagSpawn ) } } - // reset the flag states, prevents issues where flag is home but doesnt think it's home when halftime goes SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None ) SetFlagStateForTeam( TEAM_IMC, eFlagState.None ) - - foreach ( entity player in GetPlayerArray() ) - CTFInitPlayer( player ) } void function RemoveFlags() { - // destroy all the flag related things if ( IsValid( file.imcFlagSpawn ) ) { + PlayFX( $"P_phase_shift_main", file.imcFlagSpawn.GetOrigin() ) file.imcFlagSpawn.Destroy() file.imcFlag.Destroy() - file.imcFlagReturnTrigger.Destroy() } + if ( IsValid( file.militiaFlagSpawn ) ) { + PlayFX( $"P_phase_shift_main", file.militiaFlagSpawn.GetOrigin() ) file.militiaFlagSpawn.Destroy() file.militiaFlag.Destroy() - file.militiaFlagReturnTrigger.Destroy() } - - // unsure if this is needed, since the flags are destroyed? idk + SetFlagStateForTeam( TEAM_MILITIA, eFlagState.None ) SetFlagStateForTeam( TEAM_IMC, eFlagState.None ) } -void function TrackFlagReturnTrigger( entity flag, entity returnTrigger ) +void function RateSpawnpoints_CTF( int checkClass, array spawnpoints, int team, entity player ) { - // this is a bit of a hack, it seems parenting the return trigger to the flag actually sets the pickup radius of the flag to be the same as the trigger - // this isn't wanted since only pickups should use that additional radius - flag.EndSignal( "OnDestroy" ) + vector allyFlagSpot + vector enemyFlagSpot + foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) + { + if( spawn.GetTeam() == team ) + allyFlagSpot = spawn.GetOrigin() + else + enemyFlagSpot = spawn.GetOrigin() + } + + foreach ( entity spawn in spawnpoints ) + { + float rating = 0.0 + float allyFlagDistance = Distance2D( spawn.GetOrigin(), allyFlagSpot ) + float enemyFlagDistance = Distance2D( spawn.GetOrigin(), enemyFlagSpot ) + + if( enemyFlagDistance > allyFlagDistance ) + { + rating += spawn.NearbyAllyScore( team, "ai" ) + rating += spawn.NearbyAllyScore( team, "titan" ) + rating += spawn.NearbyAllyScore( team, "pilot" ) + + rating += spawn.NearbyEnemyScore( team, "ai" ) + rating += spawn.NearbyEnemyScore( team, "titan" ) + rating += spawn.NearbyEnemyScore( team, "pilot" ) + + rating = rating / allyFlagDistance + } + + if ( spawn == player.p.lastSpawnPoint ) + rating += GetConVarFloat( "spawnpoint_last_spawn_rating" ) - while ( true ) + spawn.CalculateRating( checkClass, team, rating, rating * 0.25 ) + } +} + +bool function VerifyCTFSpawnpoint( entity spawnpoint, int team ) +{ + vector allyFlagSpot + vector enemyFlagSpot + foreach ( entity spawn in GetEntArrayByClass_Expensive( "info_spawnpoint_flag" ) ) { - returnTrigger.SetOrigin( flag.GetOrigin() ) - WaitFrame() + if( spawn.GetTeam() == team ) + allyFlagSpot = spawn.GetOrigin() + else + enemyFlagSpot = spawn.GetOrigin() } + + if( Distance2D( spawnpoint.GetOrigin(), allyFlagSpot ) > Distance2D( spawnpoint.GetOrigin(), enemyFlagSpot ) ) + return false + + return true } -void function SetFlagStateForTeam( int team, int state ) + + + + + + + + +/* +██████ ██ █████ ██ ██ ███████ ██████ ██ ██████ ██████ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██████ ██ ███████ ████ █████ ██████ ██ ██ ██ ██ ███ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██ ██ ██ ███████ ██ ██ ███████ ██████ ██████ ██ ██████ +*/ + +void function OnPlaying() { - if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it - SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() ) - else - SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag ) + foreach ( entity player in GetPlayerArray() ) + CTFInitPlayer( player ) +} - SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state ) +void function CTFInitPlayer( entity player ) +{ + if ( !GamePlaying() ) + return + + if ( IsValid( file.imcFlagSpawn ) ) + { + vector imcSpawn = file.imcFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_IMC, imcSpawn.x, imcSpawn.y, imcSpawn.z ) + } + + if ( IsValid( file.militiaFlagSpawn ) ) + { + vector militiaSpawn = file.militiaFlagSpawn.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_SetFlagHomeOrigin", TEAM_MILITIA, militiaSpawn.x, militiaSpawn.y, militiaSpawn.z ) + } +} + +void function CTFPlayerDisconnected( entity player ) +{ + // This has no validity checks on the player because the disconnection callback happens in the exact last frame the player entity still exists + if( !GamePlaying() ) + return + + if ( PlayerHasEnemyFlag( player ) ) + DropFlag( player, false ) } +void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( !IsValid( GetFlagForTeam( GetOtherTeam( victim.GetTeam() ) ) ) ) // getting a crash idk + return + + if ( PlayerHasEnemyFlag( victim ) ) + { + if ( victim != attacker && attacker.IsPlayer() ) + AddPlayerScore( attacker, "FlagCarrierKill", victim ) + + DropFlag( victim ) + } +} + + + + + + + + + + + +/* +███████ ██ █████ ██████ ██ ██████ ██████ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +█████ ██ ███████ ██ ███ ██ ██ ██ ██ ███ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██ ██ ██████ ███████ ██████ ██████ ██ ██████ +*/ + bool function OnFlagCollected( entity player, entity flag ) { if ( !IsAlive( player ) || flag.GetParent() != null || player.IsTitan() || player.IsPhaseShifted() ) return false if ( player.GetTeam() != flag.GetTeam() && flag.s.canTake ) - GiveFlag( player, flag ) // pickup enemy flag + GiveFlag( player, flag ) else if ( player.GetTeam() == flag.GetTeam() && IsFlagHome( flag ) && PlayerHasEnemyFlag( player ) ) - CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) ) // cap the flag + CaptureFlag( player, GetFlagForTeam( GetOtherTeam( flag.GetTeam() ) ) ) - return false // don't wanna delete the flag entity + return false // Don't delete the flag } void function GiveFlag( entity player, entity flag ) @@ -283,7 +347,8 @@ void function GiveFlag( entity player, entity flag ) flag.Signal( "ResetDropTimeout" ) flag.SetParent( player, "FLAG" ) - thread DropFlagIfPhased( player, flag ) + if ( GetCurrentPlaylistVarInt( "phase_shift_drop_flag", 0 ) == 1 ) + thread DropFlagIfPhased( player, flag ) // do notifications MessageToPlayer( player, eEventNotifications.YouHaveTheEnemyFlag ) @@ -301,103 +366,10 @@ void function GiveFlag( entity player, entity flag ) SetFlagStateForTeam( flag.GetTeam(), eFlagState.Away ) // used for held } -void function DropFlagIfPhased( entity player, entity flag ) -{ - player.EndSignal( "StartPhaseShift" ) - player.EndSignal( "OnDestroy" ) - - OnThreadEnd( function() : ( player ) - { - if (GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath) - DropFlag( player, true ) - }) - // the IsValid check is purely to prevent a crash due to a destroyed flag (epilogue) - while( IsValid(flag) && flag.GetParent() == player ) - WaitFrame() -} - -void function DropFlagForBecomingTitan( entity pilot, entity titan ) -{ - DropFlag( pilot, true ) -} - -void function DropFlag( entity player, bool realDrop = true ) -{ - entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) ) - - if ( flag.GetParent() != player ) - return - - print( player + " dropped the flag!" ) - - flag.ClearParent() - flag.SetAngles( < 0, 0, 0 > ) - flag.SetVelocity( < 0, 0, 0 > ) - - if ( realDrop ) - { - // start drop timeout countdown - thread TrackFlagDropTimeout( flag ) - - // add to capture assists - if ( player.GetTeam() == TEAM_IMC ) - file.imcCaptureAssistList.append( player ) - else - file.militiaCaptureAssistList.append( player ) - - // do notifications - MessageToPlayer( player, eEventNotifications.YouDroppedTheEnemyFlag ) - EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagDrop" ) - - MessageToTeam( player.GetTeam(), eEventNotifications.PlayerDroppedEnemyFlag, player, player ) - // todo need a sound here maybe - MessageToTeam( GetOtherTeam( player.GetTeam() ), eEventNotifications.PlayerDroppedFriendlyFlag, player, player ) - // todo need a sound here maybe - } - - SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) // used for return prompt -} - -void function TrackFlagDropTimeout( entity flag ) -{ - flag.EndSignal( "ResetDropTimeout" ) - - wait CTF_GetDropTimeout() - - ResetFlag( flag ) -} - -void function ResetFlag( entity flag ) -{ - // prevents crash when flag is reset after it's been destroyed due to epilogue - if (!IsValid(flag)) - return - // ensure we can't pickup the flag after it's been dropped but before it's been reset - flag.s.canTake = false - - if ( flag.GetParent() != null ) - DropFlag( flag.GetParent(), false ) - - entity spawn - if ( flag.GetTeam() == TEAM_IMC ) - spawn = file.imcFlagSpawn - else - spawn = file.militiaFlagSpawn - - flag.SetOrigin( spawn.GetOrigin() + < 0, 0, spawn.GetBoundingMaxs().z + 1 > ) - - // we can take it again now - flag.s.canTake = true - - SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) // used for home - - flag.Signal( "ResetDropTimeout" ) -} - void function CaptureFlag( entity player, entity flag ) { // can only capture flags during normal play or sudden death - if (GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath) + if ( GetGameState() != eGameState.Playing && GetGameState() != eGameState.SuddenDeath ) { printt( player + " tried to capture the flag, but the game state was " + GetGameState() + " not " + eGameState.Playing + " or " + eGameState.SuddenDeath) return @@ -421,8 +393,18 @@ void function CaptureFlag( entity player, entity flag ) assistList = file.militiaCaptureAssistList foreach( entity assistPlayer in assistList ) - if ( player != assistPlayer ) - AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) + { + if ( IsValidPlayer( assistPlayer ) ) + { + if ( player != assistPlayer ) + AddPlayerScore( assistPlayer, "FlagCaptureAssist", player ) + if( !HasPlayerCompletedMeritScore( assistPlayer ) ) + { + AddPlayerScore( assistPlayer, "ChallengeCTFCapAssist" ) + SetPlayerChallengeMeritScore( assistPlayer ) + } + } + } assistList.clear() @@ -430,84 +412,207 @@ void function CaptureFlag( entity player, entity flag ) MessageToPlayer( player, eEventNotifications.YouCapturedTheEnemyFlag ) EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_PlayerScore" ) + if( !HasPlayerCompletedMeritScore( player ) ) + SetPlayerChallengeMeritScore( player ) + MessageToTeam( team, eEventNotifications.PlayerCapturedEnemyFlag, player, player ) EmitSoundOnEntityToTeamExceptPlayer( flag, "UI_CTF_3P_TeamScore", player.GetTeam(), player ) MessageToTeam( GetOtherTeam( team ), eEventNotifications.PlayerCapturedFriendlyFlag, player, player ) EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyScores", flag.GetTeam() ) - if ( GameRules_GetTeamScore( team ) == GameMode_GetRoundScoreLimit( GAMETYPE ) - 1 ) + if ( GameRules_GetTeamScore( team ) == GetScoreLimit_FromPlaylist() - 1 ) { PlayFactionDialogueToTeam( "ctf_notifyWin1more", team ) PlayFactionDialogueToTeam( "ctf_notifyLose1more", GetOtherTeam( team ) ) + foreach( entity otherPlayer in GetPlayerArray() ) + Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_CTF_PlayMatchNearEndMusic" ) } } -void function OnPlayerEntersFlagReturnTrigger( entity trigger, entity player ) +void function DropFlag( entity player, bool realDrop = true ) { - entity flag - if ( trigger.GetTeam() == TEAM_IMC ) - flag = file.imcFlag - else - flag = file.militiaFlag - - if( !IsValid( flag ) || !IsValid( player ) ) - return + entity flag = GetFlagForTeam( GetOtherTeam( player.GetTeam() ) ) - if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) + if( !IsValid( flag ) || flag.GetParent() != player ) return - thread TryReturnFlag( player, flag ) + print( player + " dropped the flag!" ) + + flag.ClearParent() + flag.SetAngles( < 0, 0, 0 > ) + flag.SetVelocity( < 0, 0, 0 > ) + + if ( realDrop ) + { + if ( player.GetTeam() == TEAM_IMC && !file.imcCaptureAssistList.contains( player ) ) + file.imcCaptureAssistList.append( player ) + + else if( !file.militiaCaptureAssistList.contains( player ) ) + file.militiaCaptureAssistList.append( player ) + + MessageToPlayer( player, eEventNotifications.YouDroppedTheEnemyFlag ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagDrop" ) + + MessageToTeam( player.GetTeam(), eEventNotifications.PlayerDroppedEnemyFlag, player, player ) + MessageToTeam( GetOtherTeam( player.GetTeam() ), eEventNotifications.PlayerDroppedFriendlyFlag, player, player ) + } + + thread TrackFlagDropTimeout( flag ) + SetFlagStateForTeam( flag.GetTeam(), eFlagState.Home ) } -void function OnPlayerExitsFlagReturnTrigger( entity trigger, entity player ) +void function ResetFlag( entity flag ) { - entity flag - if ( trigger.GetTeam() == TEAM_IMC ) - flag = file.imcFlag + flag.s.canTake = false + + if ( flag.GetParent() != null ) + DropFlag( flag.GetParent(), false ) + + entity flagBase + if ( flag.GetTeam() == TEAM_IMC ) + flagBase = file.imcFlagSpawn else - flag = file.militiaFlag + flagBase = file.militiaFlagSpawn - if ( !player.IsPlayer() || player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) - return + flag.SetOrigin( flagBase.GetOrigin() + < 0, 0, flagBase.GetBoundingMaxs().z + 1 > ) + + flag.s.canTake = true - player.Signal( "FlagReturnEnded" ) -} + SetFlagStateForTeam( flag.GetTeam(), eFlagState.None ) + + flag.Signal( "ResetDropTimeout" ) +} + +//----------------------------------------------------------------------------- +// Purpose: Check proximity for flag returns +// Input : flag - The flag entity +//----------------------------------------------------------------------------- +void function FlagProximityTracker( entity flag ) +{ + flag.EndSignal( "OnDestroy" ) + + array < entity > playerInsidePerimeter + while( true ) + { + if( !playerInsidePerimeter.len() ) + ArrayRemoveDead( playerInsidePerimeter ) + + foreach ( player in GetPlayerArrayOfTeam_Alive( flag.GetTeam() ) ) + { + if ( Distance( player.GetOrigin(), flag.GetOrigin() ) < CTF_GetFlagReturnRadius() ) + { + if ( player.IsTitan() || player.GetTeam() != flag.GetTeam() || IsFlagHome( flag ) || flag.GetParent() != null ) + continue + + if( playerInsidePerimeter.contains( player ) ) + continue + + playerInsidePerimeter.append( player ) + thread TryReturnFlag( player, flag ) + } + else + { + if( playerInsidePerimeter.contains( player ) ) + { + player.Signal( "CTF_LeftReturnTriggerArea" ) // Cut the progress if outside range + playerInsidePerimeter.removebyvalue( player ) + } + } + } + + WaitFrame() + } +} void function TryReturnFlag( entity player, entity flag ) { - // start return progress bar Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StartReturnFlagProgressBar", Time() + CTF_GetFlagReturnTime() ) EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_FlagReturnMeter" ) - OnThreadEnd( function() : ( player ) + OnThreadEnd( function() : ( flag, player ) { - // cleanup - Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" ) - StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" ) + if ( IsValidPlayer( player ) ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_CTF_StopReturnFlagProgressBar" ) + StopSoundOnEntity( player, "UI_CTF_1P_FlagReturnMeter" ) + } }) - player.EndSignal( "FlagReturnEnded" ) - flag.EndSignal( "FlagReturnEnded" ) // avoid multiple players to return one flag at once + flag.EndSignal( "CTF_ReturnedFlag" ) + flag.EndSignal( "OnDestroy" ) + + player.EndSignal( "CTF_LeftReturnTriggerArea" ) player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) wait CTF_GetFlagReturnTime() - // flag return succeeded - // return flag ResetFlag( flag ) - flag.Signal( "FlagReturnEnded" ) - - // do notifications for return - MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag ) - AddPlayerScore( player, "FlagReturn", player ) - player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 ) MessageToTeam( flag.GetTeam(), eEventNotifications.PlayerReturnedFriendlyFlag, null, player ) EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_TeamReturnsFlag", flag.GetTeam() ) PlayFactionDialogueToTeam( "ctf_flagReturnedFriendly", flag.GetTeam() ) + MessageToPlayer( player, eEventNotifications.YouReturnedFriendlyFlag ) + AddPlayerScore( player, "FlagReturn", player ) + player.AddToPlayerGameStat( PGS_DEFENSE_SCORE, 1 ) + + if ( !HasPlayerCompletedMeritScore( player ) ) + { + AddPlayerScore( player, "ChallengeCTFRetAssist" ) + SetPlayerChallengeMeritScore( player ) + } + MessageToTeam( GetOtherTeam( flag.GetTeam() ), eEventNotifications.PlayerReturnedEnemyFlag, null, player ) EmitSoundOnEntityToTeam( flag, "UI_CTF_3P_EnemyReturnsFlag", GetOtherTeam( flag.GetTeam() ) ) + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_CTF_1P_ReturnsFlag" ) PlayFactionDialogueToTeam( "ctf_flagReturnedEnemy", GetOtherTeam( flag.GetTeam() ) ) -} \ No newline at end of file + + flag.Signal( "CTF_ReturnedFlag" ) +} + +void function SetFlagStateForTeam( int team, int state ) +{ + if ( state == eFlagState.Away ) // we tell the client the flag is the player carrying it if they're carrying it + SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", ( team == TEAM_IMC ? file.imcFlag : file.militiaFlag ).GetParent() ) + else + SetGlobalNetEnt( team == TEAM_IMC ? "imcFlag" : "milFlag", team == TEAM_IMC ? file.imcFlag : file.militiaFlag ) + + SetGlobalNetInt( team == TEAM_IMC ? "imcFlagState" : "milFlagState", state ) +} + +void function DropFlagIfPhased( entity player, entity flag ) +{ + player.EndSignal( "StartPhaseShift" ) + player.EndSignal( "OnDestroy" ) + flag.EndSignal( "OnDestroy" ) + + OnThreadEnd( function() : ( player ) + { + if ( IsValidPlayer( player ) ) + { + if ( GetGameState() == eGameState.Playing || GetGameState() == eGameState.SuddenDeath ) + DropFlag( player, true ) + } + }) + + while( flag.GetParent() == player ) + WaitFrame() +} + +void function DropFlagForBecomingTitan( entity pilot, entity titan ) +{ + DropFlag( pilot, true ) +} + +void function TrackFlagDropTimeout( entity flag ) +{ + flag.EndSignal( "CTF_ReturnedFlag" ) + flag.EndSignal( "ResetDropTimeout" ) + flag.EndSignal( "OnDestroy" ) + + wait CTF_GetDropTimeout() + + ResetFlag( flag ) +} diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut index 9d8f84b5c..6d0fd3c7b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_fra.nut @@ -17,6 +17,7 @@ void function GamemodeFRA_Init() ScoreEvent_SetEarnMeterValues( "PilotBatteryPickup", 0.0, 0.34 ) EarnMeterMP_SetPassiveMeterGainEnabled( false ) PilotBattery_SetMaxCount( 3 ) + SetupGenericFFAChallenge() AddCallback_OnPlayerKilled( FRARemoveEarnMeter ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut index 31c85a573..8999231d3 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_lts.nut @@ -8,6 +8,8 @@ struct { float lastDamageInfoTime bool shouldDoHighlights + + table< entity, int > pilotstreak } file void function GamemodeLts_Init() @@ -34,6 +36,37 @@ void function GamemodeLts_Init() ClassicMP_SetCustomIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() ) ClassicMP_ForceDisableEpilogue( true ) AddCallback_GameStateEnter( eGameState.Playing, WaitForThirtySecondsLeft ) + + AddCallback_OnClientConnected( SetupPlayerLTSChallenges ) //Just to make up the Match Goals tracking + AddCallback_OnClientDisconnected( RemovePlayerLTSChallenges ) //Safety removal of data to prevent crashes + AddCallback_OnPlayerKilled( LTSChallengeForPlayerKilled ) +} + +void function SetupPlayerLTSChallenges( entity player ) +{ + file.pilotstreak[ player ] <- 0 +} + +void function RemovePlayerLTSChallenges( entity player ) +{ + if( player in file.pilotstreak ) + delete file.pilotstreak[ player ] +} + +void function LTSChallengeForPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( victim.IsPlayer() && attacker in file.pilotstreak ) + { + file.pilotstreak[attacker]++ + if( file.pilotstreak[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeLTS" ) + SetPlayerChallengeMeritScore( attacker ) + } + } } void function WaitForThirtySecondsLeft() diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut index d36045a61..768bbde11 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut @@ -180,6 +180,12 @@ void function MarkPlayers( entity imcMark, entity militiaMark ) entity livingMark = GetMarked( GetOtherTeam( deadMark.GetTeam() ) ) livingMark.SetPlayerGameStat( PGS_DEFENSE_SCORE, livingMark.GetPlayerGameStat( PGS_DEFENSE_SCORE ) + 1 ) + if( !HasPlayerCompletedMeritScore( livingMark ) ) + { + AddPlayerScore( livingMark, "ChallengeMFD" ) + SetPlayerChallengeMeritScore( livingMark ) + } + // thread this so we don't kill our own thread thread AddTeamScore( livingMark.GetTeam(), 1 ) } @@ -195,8 +201,15 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo MessageToAll( eEventNotifications.MarkedForDeathKill, null, victim, actualAttacker.GetEncodedEHandle() ) svGlobal.levelEnt.Signal( "MarkKilled", { mark = victim } ) - + if ( !isSuicide && attacker.IsPlayer() ) + { attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 ) + if( !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeMFD" ) + SetPlayerChallengeMeritScore( attacker ) + } + } } } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut index 57355ad8b..fb84cc82b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ps.nut @@ -19,12 +19,7 @@ void function GamemodePs_Init() AddCallback_OnPlayerKilled( GiveScoreForPlayerKill ) ScoreEvent_SetupEarnMeterValuesForMixedModes() SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) - - // spawnzone stuff - SetShouldCreateMinimapSpawnZones( true ) - - //AddCallback_OnPlayerKilled( CheckSpawnzoneSuspiciousDeaths ) - //AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", SpawnzoneTriggerInit ) + SetupGenericFFAChallenge() file.militiaPreviousSpawnZones = [ null, null, null ] file.imcPreviousSpawnZones = [ null, null, null ] diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut index cb277b004..4617476eb 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_speedball.nut @@ -18,6 +18,7 @@ void function GamemodeSpeedball_Init() Riff_ForceTitanAvailability( eTitanAvailability.Never ) Riff_ForceSetEliminationMode( eEliminationMode.Pilots ) ScoreEvent_SetupEarnMeterValuesForMixedModes() + SetupGenericFFAChallenge() AddSpawnCallbackEditorClass( "script_ref", "info_speedball_flag", CreateFlag ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut index 5c0e6feca..61ede2d44 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_tdm.nut @@ -6,6 +6,7 @@ void function GamemodeTdm_Init() AddCallback_OnPlayerKilled( GiveScoreForPlayerKill ) ScoreEvent_SetupEarnMeterValuesForMixedModes() SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) + SetupGenericTDMChallenge() } void function GiveScoreForPlayerKill( entity victim, entity attacker, var damageInfo ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut index 6b30a3990..3ba843945 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_ttdm.nut @@ -2,6 +2,11 @@ global function GamemodeTTDM_Init const float TTDMIntroLength = 15.0 +struct +{ + table< entity, int > challengeCount +} file + void function GamemodeTTDM_Init() { Riff_ForceSetSpawnAsTitan( eSpawnAsTitan.Always ) @@ -14,6 +19,8 @@ void function GamemodeTTDM_Init() ClassicMP_ForceDisableEpilogue( true ) SetTimeoutWinnerDecisionFunc( CheckScoreForDraw ) + AddCallback_OnClientConnected( SetupPlayerTTDMChallenges ) //Just to make up the Match Goals tracking + AddCallback_OnClientDisconnected( RemovePlayerTTDMChallenges ) //Safety removal of data to prevent crashes AddCallback_OnPlayerKilled( AddTeamScoreForPlayerKilled ) // dont have to track autotitan kills since you cant leave your titan in this mode // probably needs scoreevent earnmeter values @@ -56,6 +63,17 @@ void function TTDMIntroShowIntermissionCam( entity player ) thread PlayerWatchesTTDMIntroIntermissionCam( player ) } +void function SetupPlayerTTDMChallenges( entity player ) +{ + file.challengeCount[ player ] <- 0 +} + +void function RemovePlayerTTDMChallenges( entity player ) +{ + if( player in file.challengeCount ) + delete file.challengeCount[ player ] +} + void function PlayerWatchesTTDMIntroIntermissionCam( entity player ) { player.EndSignal( "OnDestroy" ) @@ -79,6 +97,19 @@ void function AddTeamScoreForPlayerKilled( entity victim, entity attacker, var d if ( victim == attacker || !victim.IsPlayer() || !attacker.IsPlayer() && GetGameState() == eGameState.Playing ) return + if( victim in file.challengeCount ) + file.challengeCount[victim] = 0 + + if( attacker in file.challengeCount ) + { + file.challengeCount[attacker]++ + if( file.challengeCount[attacker] >= 2 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeTTDM" ) + SetPlayerChallengeMeritScore( attacker ) + } + } + AddTeamScore( GetOtherTeam( victim.GetTeam() ), 1 ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_spawnpoints.gnut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_spawnpoints.gnut deleted file mode 100644 index e69de29bb..000000000 diff --git a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut index 9057f7d8b..3814126ba 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/item_inventory/sv_item_inventory.gnut @@ -32,7 +32,7 @@ void function PrematchClearInventory() // vanilla behavior { foreach( entity player in GetPlayerArray() ) { - PlayerInventory_TakeAllInventoryItems( player ) + thread PlayerInventory_TakeAllInventoryItems( player ) } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut index 466a50425..016097f20 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_challenges.gnut @@ -1,6 +1,276 @@ global function InitChallenges +global function SetPlayerChallengeEvacState //Hooked in _evac.gnut +global function SetPlayerChallengeMatchWon //Hooked in _score.nut +global function SetPlayerChallengeMatchComplete //Hooked in _score.nut +global function SetPlayerChallengeMeritScore //Up to gamemodes to use this directly if needed +global function IncrementPlayerChallengeTitanLeveledUp //Hooked in titan_xp.gnut +global function IncrementPlayerChallengeWeaponLeveledUp //Hooked in weapon_xp.gnut +global function IncrementPlayerChallengeFactionLeveledUp //Hooked in faction_xp.gnut (invisible but necessary for post-summary menu) +global function RegisterChallenges_OnMatchEnd //Hooked in _gamestate_mp.gnut + +global function HasPlayerCompletedMeritScore //Check from gamemodes to not reapply SetPlayerChallengeMeritScore +global function SetupGenericTDMChallenge //Used by gamemodes which simply adopts the: "Kill 3 Pilots without dying." Challenge +global function SetupGenericFFAChallenge //Used by gamemodes which simply adopts the: "Kill 5 Pilots." Challenge + +struct +{ + table< entity, int > playerTotalMeritCount + table< entity, bool > playerChallenge + table< entity, int > pilotstreak + bool isHappyHourActive +} file + + + + + + +/*============================================================================================================= + __ __ _ _ ____ _ _ _ + | \/ | __ _ | |_ ___ | |__ / ___|| |__ __ _ | || | ___ _ __ __ _ ___ ___ + | |\/| | / _` || __|/ __|| '_ \ | | | '_ \ / _` || || | / _ \| '_ \ / _` | / _ \/ __| + | | | || (_| || |_| (__ | | | | | |___ | | | || (_| || || || __/| | | || (_| || __/\__ \ + |_| |_| \__,_| \__|\___||_| |_| \____||_| |_| \__,_||_||_| \___||_| |_| \__, | \___||___/ + |___/ +=============================================================================================================*/ void function InitChallenges() { +#if (UI && CLIENT) + + SCB_SetCompleteMeritState( 4 ) + SCB_SetEvacMeritState( 4 ) + SCB_SetMeritCount( 4 ) + SCB_SetScoreMeritState( 4 ) + SCB_SetWinMeritState( 4 ) + SCB_SetWeaponMeritCount( -1 ) + SCB_SetTitanMeritCount( -1 ) + +#elseif (SERVER && MP) + + AddCallback_OnClientConnected( SetupPlayerMenuChallenges ) + AddCallback_OnClientDisconnected( RemovePlayerFromChallengePool ) + +#endif +} + +void function SetupPlayerMenuChallenges( entity player ) +{ + file.playerTotalMeritCount[ player ] <- 0 + file.pilotstreak[ player ] <- 0 + file.playerChallenge[ player ] <- false + + thread SetupChallenges_Threaded( player ) +} +void function SetupChallenges_Threaded( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + WaitFrame() + + Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 0 ) + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 4 ) //4 tells RUI to hide it + Remote_CallFunction_UI( player, "SCB_SetMeritCount", 0 ) + Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 0 ) + Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 0 ) + Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", 0 ) + Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", 0 ) +} + +void function SetupGenericTDMChallenge() +{ + AddCallback_OnPlayerKilled( TDMChallenges_OnPlayerKilled ) +} + +void function SetupGenericFFAChallenge() +{ + AddCallback_OnPlayerKilled( FFAChallenges_OnPlayerKilled ) +} + +void function RemovePlayerFromChallengePool( entity player ) +{ + if( player in file.playerChallenge ) + delete file.playerChallenge[ player ] + if( player in file.playerTotalMeritCount ) + delete file.playerTotalMeritCount[ player ] + if( player in file.pilotstreak ) + delete file.pilotstreak[ player ] +} + +void function RegisterChallenges_OnMatchEnd() +{ + bool eliteWarpaintRNG = false + + if( RandomIntRange( 0, 100 ) <= 30 ) //30% Chance to trigger akin to vanilla, apply always since all players have paid cosmetics unlocked + eliteWarpaintRNG = true + + foreach( player in GetPlayerArray() ) + { + player.SetPersistentVar( "isPostGameScoreboardValid", true ) + player.SetPersistentVar( "isFDPostGameScoreboardValid", false ) //FD itself overrides this right after when match ends + SetUIVar( level, "showGameSummary", true ) + + if( eliteWarpaintRNG ) + SetPlayerChallengeSquadLeader( player ) + + if( ShouldAwardHappyHourBonus( player ) ) + { + AddPlayerScore( player, "HappyHourBonus" ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.HAPPY_HOUR + "]", 5 ) //The XP Given from Happy Hour Score is 5 merits + } + } +} + +void function TDMChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( victim.IsPlayer() ) + { + if( victim in file.pilotstreak ) + file.pilotstreak[victim] = 0 + if( attacker in file.pilotstreak ) + { + file.pilotstreak[attacker]++ + if( file.pilotstreak[attacker] >= 3 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengeTDM" ) + SetPlayerChallengeMeritScore( attacker ) + } + } + } +} + +void function FFAChallenges_OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( victim == attacker || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( victim.IsPlayer() && attacker in file.pilotstreak ) + { + file.pilotstreak[attacker]++ + if( file.pilotstreak[attacker] >= 5 && !HasPlayerCompletedMeritScore( attacker ) ) + { + AddPlayerScore( attacker, "ChallengePVPKillCount" ) + SetPlayerChallengeMeritScore( attacker ) + } + } +} + +bool function HasPlayerCompletedMeritScore( entity player ) +{ + Assert( player in file.playerChallenge, player + " is not registered in the challenge pool hooks." ) + return file.playerChallenge[ player ] +} + + + + + + + +/*============================================================================================================= + ____ _ _ _ _ + / ___| __ _ _ __ ___ ___ _ __ ___ ___ __| | ___ | | | | ___ ___ | | __ ___ + | | _ / _` || '_ ` _ \ / _ \| '_ ` _ \ / _ \ / _` | / _ \ | |_| | / _ \ / _ \ | |/ // __| + | |_| || (_| || | | | | || __/| | | | | || (_) || (_| || __/ | _ || (_) || (_) || < \__ \ + \____| \__,_||_| |_| |_| \___||_| |_| |_| \___/ \__,_| \___| |_| |_| \___/ \___/ |_|\_\|___/ + +=============================================================================================================*/ + +void function SetPlayerChallengeEvacState( entity player, int successEvac = 0 ) +{ + if( successEvac == 0 ) //Evac Ship destroyed + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 2 ) + + else if( successEvac == 1 ) //Player itself managed to evac + { + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.EVAC + "]", 1 ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) + } + + else if( successEvac == 2 ) //Team managed to evac + Remote_CallFunction_UI( player, "SCB_SetEvacMeritState", 3 ) +} + +void function SetPlayerChallengeMatchWon( entity player, bool playerWon ) +{ + if( playerWon ) + { + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetWinMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_VICTORY + "]", 1 ) + player.SetPersistentVar( "matchWin", true ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) + } + else + Remote_CallFunction_UI( player, "SCB_SetWinMeritState", -1 ) +} + +void function SetPlayerChallengeMatchComplete( entity player ) +{ + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetCompleteMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.MATCH_COMPLETED + "]", 1 ) + player.SetPersistentVar( "matchComplete", true ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) +} + +void function SetPlayerChallengeSquadLeader( entity player ) +{ + if( !ProgressionEnabledForPlayer( player ) ) + return + + Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderDoubleXP" ) + Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", player.GetEncodedEHandle() ) + player.SetPersistentVar( "matchSquadBonus", true ) + Player_GiveDoubleXP( player, 1 ) + foreach( entity teamplayer in GetPlayerArrayOfTeam( player.GetTeam() ) ) + { + if( teamplayer == player ) + continue + + Remote_CallFunction_NonReplay( player, "ServerCallback_SquadLeaderBonus", teamplayer.GetEncodedEHandle() ) + } +} + +void function SetPlayerChallengeMeritScore( entity player ) +{ + if( !HasPlayerCompletedMeritScore( player ) ) + { + file.playerChallenge[ player ] = true + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetScoreMeritState", 1 ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.SCORE_MILESTONE + "]", 1 ) + player.SetPersistentVar( "matchScoreEvent", true ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) + } +} + +void function IncrementPlayerChallengeTitanLeveledUp( entity player ) +{ + player.p.meritData.titanMerits++ + file.playerTotalMeritCount[ player ]++ + + Remote_CallFunction_UI( player, "SCB_SetTitanMeritCount", player.p.meritData.titanMerits++ ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) +} + +void function IncrementPlayerChallengeWeaponLeveledUp( entity player ) +{ + player.p.meritData.weaponMerits++ + file.playerTotalMeritCount[ player ]++ + + Remote_CallFunction_UI( player, "SCB_SetWeaponMeritCount", player.p.meritData.weaponMerits ) + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) +} + +void function IncrementPlayerChallengeFactionLeveledUp( entity player ) +{ + file.playerTotalMeritCount[ player ]++ + Remote_CallFunction_UI( player, "SCB_SetMeritCount", file.playerTotalMeritCount[ player ] ) } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut index 23ae37a17..c3bdf01c6 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_classic_mp_dropship_intro.gnut @@ -26,18 +26,9 @@ const int MAX_DROPSHIP_PLAYERS = 4 global const float DROPSHIP_INTRO_LENGTH = 15.0 // TODO tweak this -struct IntroDropship -{ - entity dropship - - int playersInDropship - entity[MAX_DROPSHIP_PLAYERS] players -} - struct { - // these used to be IntroDropship[2]s but i wanted to be able to use array.getrandom so they have to be actual arrays - array militiaDropships - array imcDropships + table< entity, array > militiaDropships + table< entity, array > imcDropships float introStartTime } file @@ -52,7 +43,12 @@ void function ClassicMP_DefaultDropshipIntro_Setup() void function DropshipIntro_OnClientConnected( entity player ) { if ( GetGameState() == eGameState.Prematch ) - thread SpawnPlayerIntoDropship( player ) + { + if( PlayerCanSpawn( player ) ) + DoRespawnPlayer( player, null ) + + PutPlayerInDropship( player ) + } } void function OnPrematchStart() @@ -62,11 +58,11 @@ void function OnPrematchStart() print( "starting dropship intro!" ) file.introStartTime = Time() - // make 2 empty dropship structs per team - IntroDropship emptyDropship + // Clear Dropship arrays of Teams for Match Restarts (i.e Half-Times) file.militiaDropships.clear() file.imcDropships.clear() + // Try to gather all possible Dropship spawn points for Team array validDropshipSpawns array dropshipSpawns = GetEntArrayByClass_Expensive( "info_spawnpoint_dropship_start" ) foreach ( entity dropshipSpawn in dropshipSpawns ) @@ -78,47 +74,47 @@ void function OnPrematchStart() validDropshipSpawns.append( dropshipSpawn ) } - // if no dropship spawns for this mode, just allow any dropship spawns + // Use any spawn point if not enough valid for this Gamemode exists if ( validDropshipSpawns.len() < 2 ) validDropshipSpawns = dropshipSpawns // spawn dropships foreach ( entity dropshipSpawn in validDropshipSpawns ) { - // todo: possibly make this only spawn dropships if we've got enough players to need them int createTeam = HasSwitchedSides() ? GetOtherTeam( dropshipSpawn.GetTeam() ) : dropshipSpawn.GetTeam() - array teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships + table< entity, array > teamDropships = createTeam == TEAM_MILITIA ? file.militiaDropships : file.imcDropships if ( teamDropships.len() >= 2 ) - continue + break - // create entity entity dropship = CreateDropship( createTeam, dropshipSpawn.GetOrigin(), dropshipSpawn.GetAngles() ) - - teamDropships.append( clone emptyDropship ) - teamDropships[ teamDropships.len() - 1 ].dropship = dropship - AddAnimEvent( dropship, "dropship_warpout", WarpoutEffect ) + dropship.SetValueForModelKey( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) + if ( dropshipSpawn.GetTeam() == TEAM_IMC ) + dropship.SetValueForModelKey( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" ) DispatchSpawn( dropship ) - // have to do this after dispatch otherwise it won't work for some reason - // weirdly enough, tf2 actually does use different dropships for imc and militia, despite these concepts not really being a thing for players in tf2 - // probably was just missed by devs, but keeping it in for accuracy + dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) if ( dropshipSpawn.GetTeam() == TEAM_IMC ) dropship.SetModel( $"models/vehicle/goblin_dropship/goblin_dropship_hero.mdl" ) - else - dropship.SetModel( $"models/vehicle/crow_dropship/crow_dropship_hero.mdl" ) + + teamDropships[ dropship ] <- [ null, null, null, null ] thread PlayAnim( dropship, "dropship_classic_mp_flyin" ) } + // Populate Dropships foreach ( entity player in GetPlayerArray() ) { if ( !IsPrivateMatchSpectator( player ) ) - thread SpawnPlayerIntoDropship( player ) + { + if( PlayerCanSpawn( player ) ) + DoRespawnPlayer( player, null ) + + PutPlayerInDropship( player ) + } else RespawnPrivateMatchSpectator( player ) } @@ -128,76 +124,79 @@ void function OnPrematchStart() void function EndIntroWhenFinished() { - wait 15.0 + wait DROPSHIP_INTRO_LENGTH ClassicMP_OnIntroFinished() } -void function SpawnPlayerIntoDropship( entity player ) +void function PutPlayerInDropship( entity player ) { - player.EndSignal( "OnDestroy" ) + //Find the player's dropship and seat + table< entity, array > teamDropships + if ( player.GetTeam() == TEAM_MILITIA ) + teamDropships = file.militiaDropships + else + teamDropships = file.imcDropships + + entity playerDropship + array< int > availableShipSlots + array< entity > introDropships + int playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS ) + foreach( dropship, playerslot in teamDropships ) + { + introDropships.append( dropship ) + for ( int i = 0; i < MAX_DROPSHIP_PLAYERS; i++ ) + { + if ( !IsValidPlayer( playerslot[i] ) ) + availableShipSlots.append( i ) + } + + if( !availableShipSlots.len() ) + continue + + int slotPick = availableShipSlots.getrandom() + playerslot[slotPick] = player + playerDropship = dropship + playerDropshipIndex = slotPick + break + } + + if( !IsAlive( playerDropship ) ) //If we're at this point, we have more players than we do dropships, so just pick a random one + playerDropship = introDropships.getrandom() + + thread SpawnPlayerIntoDropship( player, playerDropshipIndex, playerDropship ) +} - if ( IsAlive( player ) ) - player.Die() // kill them so we don't have any issues respawning them later +void function SpawnPlayerIntoDropship( entity player, int playerDropshipIndex, entity playerDropship ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) - player.s.dropshipIntroIsJumping <- false - OnThreadEnd( function() : ( player ) + OnThreadEnd( function() : ( player, playerDropshipIndex, playerDropship ) { if ( IsValid( player ) ) { player.ClearParent() ClearPlayerAnimViewEntity( player ) - - if ( !player.s.dropshipIntroIsJumping ) - { - player.MovementEnable() - player.EnableWeaponViewModel() - RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) - } + } + if( IsAlive( playerDropship ) ) + { + if ( playerDropship.GetTeam() == TEAM_MILITIA ) + file.militiaDropships[ playerDropship ][ playerDropshipIndex ] = null + else + file.imcDropships[ playerDropship ][ playerDropshipIndex ] = null } }) - WaitFrame() - - player.EndSignal( "OnDeath" ) - - // find the player's dropship and seat - array teamDropships - if ( player.GetTeam() == TEAM_MILITIA ) - teamDropships = file.militiaDropships - else - teamDropships = file.imcDropships - - IntroDropship playerDropship - int playerDropshipIndex = -1 - foreach ( IntroDropship dropship in teamDropships ) - for ( int i = 0; i < dropship.players.len(); i++ ) - if ( dropship.players[ i ] == null ) - { - playerDropship = dropship - playerDropshipIndex = i - - dropship.players[ i ] = player - break - } - - if ( playerDropship.dropship == null ) - { - // if we're at this point, we have more players than we do dropships, so just pick a random one - playerDropship = teamDropships.getrandom() - playerDropshipIndex = RandomInt( MAX_DROPSHIP_PLAYERS ) - } - - // respawn player and holster their weapons so they aren't out - if ( !IsAlive( player ) ) - player.RespawnPlayer( null ) - HolsterAndDisableWeapons(player) + HolsterAndDisableWeapons( player ) player.DisableWeaponViewModel() + UnMuteAll( player ) + StopSoundOnEntity( player, "Duck_For_FrontierDefenseTitanSelectScreen" ) // hide hud and fade screen out from black AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) ScreenFadeFromBlack( player, 0.5, 0.5 ) // faction leaders are done clientside, spawn them here - Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.dropship.GetEncodedEHandle(), file.introStartTime ) + Remote_CallFunction_NonReplay( player, "ServerCallback_SpawnFactionCommanderInDropship", playerDropship.GetEncodedEHandle(), file.introStartTime ) // do firstperson sequence FirstPersonSequenceStruct idleSequence @@ -208,9 +207,7 @@ void function SpawnPlayerIntoDropship( entity player ) idleSequence.viewConeFunction = ViewConeRampFree idleSequence.hideProxy = true idleSequence.setInitialTime = Time() - file.introStartTime - thread FirstPersonSequence( idleSequence, player, playerDropship.dropship ) - WaittillAnimDone( player ) - + waitthread FirstPersonSequence( idleSequence, player, playerDropship ) // todo: possibly rework this to actually get the time the idle anim takes and start the starttime of the jump sequence for very late joiners using that // jump sequence @@ -218,13 +215,17 @@ void function SpawnPlayerIntoDropship( entity player ) jumpSequence.firstPersonAnim = DROPSHIP_JUMP_ANIMS_POV[ playerDropshipIndex ] jumpSequence.thirdPersonAnim = DROPSHIP_JUMP_ANIMS[ playerDropshipIndex ] jumpSequence.attachment = "ORIGIN" + jumpSequence.viewConeFunction = ViewConeFree jumpSequence.setInitialTime = max( 0.0, Time() - ( file.introStartTime + 11.0 ) ) // pretty sure you should do this with GetScriptedAnimEventCycleFrac? // idk unsure how to use that, all i know is getsequenceduration > the length it actually should be - thread FirstPersonSequence( jumpSequence, player, playerDropship.dropship ) - WaittillAnimDone( player ) // somehow this is better than just waiting for the blocking FirstPersonSequence call? + #if BATTLECHATTER_ENABLED + if( playerDropshipIndex == 0 ) + PlayBattleChatterLine( player, "bc_pIntroChat" ) + #endif + + waitthread FirstPersonSequence( jumpSequence, player, playerDropship ) - player.s.dropshipIntroIsJumping <- true thread PlayerJumpsFromDropship( player ) } @@ -240,20 +241,21 @@ void function PlayerJumpsFromDropship( entity player ) // show weapon viewmodel and hud and let them move again player.MovementEnable() player.EnableWeaponViewModel() - DeployAndEnableWeapons(player) + DeployAndEnableWeapons( player ) RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) } }) - - // wait for intro timer to be fully done - wait ( file.introStartTime + DROPSHIP_INTRO_LENGTH ) - Time() - player.MovementDisable() // disable all movement but let them look around still - player.ConsumeDoubleJump() // movementdisable doesn't prevent double jumps // wait for player to hit the ground - wait 0.1 // assume players will never actually hit ground before this + player.ClearParent() + WaitFrame() + player.SetVelocity( < 0, 0, -100 > ) // Toss players a bit down so it makes a smoother transition when jumping off the Dropship + player.MovementDisable() // Disable all movement but let them look around still + player.ConsumeDoubleJump() // MovementDisable doesn't prevent double jumps + WaitFrame() while ( !player.IsOnGround() && !player.IsWallRunning() && !player.IsWallHanging() ) // todo this needs tweaking WaitFrame() - TryGameModeAnnouncement( player ) + if ( GetRoundsPlayed() == 0 ) //Intro is announced only for the first round in Vanilla as certain gamemodes have different announcements for rounds restarts + TryGameModeAnnouncement( player ) } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index 5cc096f29..17323c38b 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -59,18 +59,21 @@ struct { bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) shouldTryUseProjectileReplayCallback } file -void function SetCallback_TryUseProjectileReplay( bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) callback ) -{ - file.shouldTryUseProjectileReplayCallback = callback -} -bool function ShouldTryUseProjectileReplay( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) -{ - if ( file.shouldTryUseProjectileReplayCallback != null ) - return file.shouldTryUseProjectileReplayCallback( victim, attacker, damageInfo, isRoundEnd ) - // default to true (vanilla behaviour) - return true -} + + + + + + + +/* + ██████ █████ ███ ███ ███████ ███████ ████████ █████ ████████ ███████ ███████ +██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███ ███████ ██ ████ ██ █████ ███████ ██ ███████ ██ █████ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ███████ ███████ +*/ void function PIN_GameStart() { @@ -101,6 +104,61 @@ void function PIN_GameStart() RegisterSignal( "CleanUpEntitiesForRoundEnd" ) } +void function GameState_EntitiesDidLoad() +{ + if ( GetClassicMPMode() || ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() ) + ClassicMP_SetupIntro() +} + +void function WaittillGameStateOrHigher( int gameState ) +{ + while ( GetGameState() < gameState ) + svGlobal.levelEnt.WaitSignal( "GameStateChanged" ) +} + +bool function ShouldTryUseProjectileReplay( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) +{ + if ( file.shouldTryUseProjectileReplayCallback != null ) + return file.shouldTryUseProjectileReplayCallback( victim, attacker, damageInfo, isRoundEnd ) + // default to true (vanilla behaviour) + return true +} + +/// This is to move all NPCs that a player owns from one team to the other during a match +/// Auto-Titans, Turrets, Ticks and Hacked Spectres will all move along together with the player to the new Team +/// Also possibly prevents mods that spawns other types of NPCs that players can own from breaking when switching (i.e Drones, Hacked Reapers) +void function OnPlayerChangedTeam( entity player ) +{ + if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams + return + + if( IsIMCOrMilitiaTeam( player.GetTeam() ) ) + NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() ) + + foreach( npc in GetNPCArray() ) + { + entity bossPlayer = npc.GetBossPlayer() + if ( IsValidPlayer( bossPlayer ) && bossPlayer == player && IsAlive( npc ) ) + SetTeam( npc, player.GetTeam() ) + } +} + + + + + + + + + +/* + ██████ █████ ███ ███ ███████ ███████ ███████ ████████ ██ ██ ██████ +██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███ ███████ ██ ████ ██ █████ ███████ █████ ██ ██ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██ ██ ██ ██ ███████ ███████ ███████ ██ ██████ ██ +*/ + void function SetGameState( int newState ) { if ( newState == GetGameState() ) @@ -115,23 +173,136 @@ void function SetGameState( int newState ) callbackFunc() } -void function GameState_EntitiesDidLoad() +void function AddTeamScore( int team, int amount ) { - if ( GetClassicMPMode() || ClassicMP_ShouldTryIntroAndEpilogueWithoutClassicMP() ) - ClassicMP_SetupIntro() + GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + amount ) + GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + amount ) + + int scoreLimit + if ( IsRoundBased() ) + scoreLimit = GameMode_GetRoundScoreLimit( GAMETYPE ) + else + scoreLimit = GameMode_GetScoreLimit( GAMETYPE ) + + int score = GameRules_GetTeamScore( team ) + if ( score >= scoreLimit || GetGameState() == eGameState.SuddenDeath ) + SetWinner( team ) + else if ( ( file.switchSidesBased && !file.hasSwitchedSides ) && score >= ( scoreLimit.tofloat() / 2.0 ) ) + SetGameState( eGameState.SwitchingSides ) } -void function WaittillGameStateOrHigher( int gameState ) +void function SetWinner( int team, string winningReason = "", string losingReason = "" ) +{ + SetServerVar( "winningTeam", team ) + + file.gameWonThisFrame = true + thread UpdateGameWonThisFrameNextFrame() + + if ( winningReason.len() == 0 ) + file.announceRoundWinnerWinningSubstr = 0 + else + file.announceRoundWinnerWinningSubstr = GetStringID( winningReason ) + + if ( losingReason.len() == 0 ) + file.announceRoundWinnerLosingSubstr = 0 + else + file.announceRoundWinnerLosingSubstr = GetStringID( losingReason ) + + if ( GamePlayingOrSuddenDeath() ) + { + if ( IsRoundBased() ) + { + if ( team != TEAM_UNASSIGNED ) + { + GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + 1 ) + GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + 1 ) + } + + SetGameState( eGameState.WinnerDetermined ) + ScoreEvent_RoundComplete( team ) + } + else + { + SetGameState( eGameState.WinnerDetermined ) + ScoreEvent_MatchComplete( team ) + + RegisterMatchStats_OnMatchComplete() + } + } +} + +void function SetTimeoutWinnerDecisionFunc( int functionref() callback ) { - while ( GetGameState() < gameState ) - svGlobal.levelEnt.WaitSignal( "GameStateChanged" ) + file.timeoutWinnerDecisionFunc = callback } +void function SetCallback_TryUseProjectileReplay( bool functionref( entity victim, entity attacker, var damageInfo, bool isRoundEnd ) callback ) +{ + file.shouldTryUseProjectileReplayCallback = callback +} -// logic for individual gamestates: +void function AddCallback_OnRoundEndCleanup( void functionref() callback ) +{ + file.roundEndCleanupCallbacks.append( callback ) +} +void function SetShouldUsePickLoadoutScreen( bool shouldUse ) +{ + file.usePickLoadoutScreen = shouldUse +} + +void function SetSwitchSidesBased( bool switchSides ) +{ + file.switchSidesBased = switchSides +} + +void function SetSuddenDeathBased( bool suddenDeathBased ) +{ + file.suddenDeathBased = suddenDeathBased +} + +void function SetTimerBased( bool timerBased ) +{ + file.timerBased = timerBased +} + +void function SetShouldUseRoundWinningKillReplay( bool shouldUse ) +{ + SetServerVar( "roundWinningKillReplayEnabled", shouldUse ) +} + +void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan ) +{ + file.roundWinningKillReplayTrackPilotKills = pilot + file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this +} + +void function SetRoundWinningKillReplayAttacker( entity attacker, int inflictorEHandle = -1 ) +{ + file.roundWinningKillReplayTime = Time() + file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker ) + file.roundWinningKillReplayAttacker = attacker + file.roundWinningKillReplayInflictorEHandle = inflictorEHandle == -1 ? attacker.GetEncodedEHandle() : inflictorEHandle + file.roundWinningKillReplayTimeOfDeath = Time() +} + + + + + + + + + + +/* + ██████ ██ ██ ███████ ████████ ██████ ███ ███ ███████ ████████ █████ ██████ ████████ +██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ███████ ██ ██ ██ ██ ████ ██ ███████ ██ ███████ ██████ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██████ ██████ ███████ ██ ██████ ██ ██ ███████ ██ ██ ██ ██ ██ ██ +*/ -// eGameState.WaitingForCustomStart void function GameStateEnter_WaitingForCustomStart() { // unused in release, comments indicate this was supposed to be used for an e3 demo @@ -139,7 +310,22 @@ void function GameStateEnter_WaitingForCustomStart() } -// eGameState.WaitingForPlayers + + + + + + + + +/* +██ ██ █████ ██ ████████ ██ ███ ██ ██████ ███████ ██████ ██████ ██████ ██ █████ ██ ██ ███████ ██████ ███████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ █ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ███ █████ ██ ██ ██████ ██████ ██ ███████ ████ █████ ██████ ███████ +██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ███ ███ ██ ██ ██ ██ ██ ██ ████ ██████ ██ ██████ ██ ██ ██ ███████ ██ ██ ██ ███████ ██ ██ ███████ +*/ + void function GameStateEnter_WaitingForPlayers() { foreach ( entity player in GetPlayerArray() ) @@ -171,7 +357,22 @@ void function WaitingForPlayers_ClientConnected( entity player ) ScreenFadeToBlackForever( player, 0.0 ) } -// eGameState.PickLoadout + + + + + + + + +/* +██████ ██ ██████ ██ ██ ██ ██████ █████ ██████ ██████ ██ ██ ████████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██████ ██ ██ █████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██████ ██ ██ ███████ ██████ ██ ██ ██████ ██████ ██████ ██ +*/ + void function GameStateEnter_PickLoadout() { thread GameStateEnter_PickLoadout_Threaded() @@ -190,7 +391,22 @@ void function GameStateEnter_PickLoadout_Threaded() } -// eGameState.Prematch + + + + + + + + +/* +██████ ██████ ███████ ███ ███ █████ ████████ ██████ ██ ██ +██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ +██████ ██████ █████ ██ ████ ██ ███████ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ███████ ██ ██ ██ ██ ██ ██████ ██ ██ +*/ + void function GameStateEnter_Prematch() { int timeLimit = GameMode_GetTimeLimit( GAMETYPE ) * 60 @@ -236,7 +452,22 @@ void function StartGameWithoutClassicMP() } -// eGameState.Playing + + + + + + + + +/* +██████ ██ █████ ██ ██ ██ ███ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +██████ ██ ███████ ████ ██ ██ ██ ██ ██ ███ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ███████ ██ ██ ██ ██ ██ ████ ██████ +*/ + void function GameStateEnter_Playing() { thread GameStateEnter_Playing_Threaded() @@ -279,7 +510,22 @@ void function GameStateEnter_Playing_Threaded() } -// eGameState.WinnerDetermined + + + + + + + + +/* +██ ██ ██ ███ ██ ███ ██ ███████ ██████ ██████ ███████ ████████ ███████ ██████ ███ ███ ██ ███ ██ ███████ ██████ +██ ██ ██ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ +██ █ ██ ██ ██ ██ ██ ██ ██ ██ █████ ██████ ██ ██ █████ ██ █████ ██████ ██ ████ ██ ██ ██ ██ ██ █████ ██ ██ +██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ███ ███ ██ ██ ████ ██ ████ ███████ ██ ██ ██████ ███████ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ███████ ██████ +*/ + // these are likely innacurate const float ROUND_END_FADE_KILLREPLAY = 1.0 const float ROUND_END_DELAY_KILLREPLAY = 3.0 @@ -396,6 +642,7 @@ void function GameStateEnter_WinnerDetermined_Threaded() } else { + RegisterChallenges_OnMatchEnd() if ( ClassicMP_ShouldRunEpilogue() ) { ClassicMP_SetupEpilogue() @@ -445,7 +692,22 @@ void function PlayerWatchesRoundWinningKillReplay( entity player, float replayLe } -// eGameState.SwitchingSides + + + + + + + + +/* +███████ ██ ██ ██ ████████ ██████ ██ ██ ██ ███ ██ ██████ ███████ ██ ██████ ███████ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ █ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ███ ███████ ██ ██ ██ █████ ███████ + ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ███ ███ ██ ██ ██████ ██ ██ ██ ██ ████ ██████ ███████ ██ ██████ ███████ ███████ +*/ + void function GameStateEnter_SwitchingSides() { thread GameStateEnter_SwitchingSides_Threaded() @@ -536,7 +798,22 @@ void function PlayerWatchesSwitchingSidesKillReplay( entity player, bool doRepla } -// eGameState.SuddenDeath + + + + + + + + +/* +███████ ██ ██ ██████ ██████ ███████ ███ ██ ██████ ███████ █████ ████████ ██ ██ +██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ █████ ███████ ██ ███████ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██████ ██████ ██████ ███████ ██ ████ ██████ ███████ ██ ██ ██ ██ ██ +*/ + void function GameStateEnter_SuddenDeath() { // disable respawns, suddendeath calling is done on a kill callback @@ -558,7 +835,22 @@ void function GameStateEnter_SuddenDeath() } -// eGameState.Postmatch + + + + + + + + +/* +██████ ██████ ███████ ████████ ███ ███ █████ ████████ ██████ ██ ██ +██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ +██████ ██ ██ ███████ ██ ██ ████ ██ ███████ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██████ ███████ ██ ██ ██ ██ ██ ██ ██████ ██ ██ +*/ + void function GameStateEnter_Postmatch() { foreach ( entity player in GetPlayerArray() ) @@ -591,7 +883,21 @@ void function ForceFadeToBlack( entity player ) } -// shared across multiple gamestates + + + + + + + + +/* +██ ██ ██ ██ ██ ██████ █████ ██ ██ ██████ █████ ██████ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +█████ ██ ██ ██ ██ ███████ ██ ██ ██████ ███████ ██ █████ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ███████ ███████ ██████ ██ ██ ███████ ███████ ██████ ██ ██ ██████ ██ ██ ███████ +*/ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) { @@ -721,10 +1027,22 @@ void function OnTitanKilled( entity victim, var damageInfo ) } } -void function AddCallback_OnRoundEndCleanup( void functionref() callback ) -{ - file.roundEndCleanupCallbacks.append( callback ) -} + + + + + + + + + +/* +████████ ██████ ██████ ██ ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██ + ██ ██ ██ ██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + ██ ██████ ██████ ███████ ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████ +*/ void function CleanUpEntitiesForRoundEnd() { @@ -762,86 +1080,6 @@ void function CleanUpEntitiesForRoundEnd() SetPlayerDeathsHidden( false ) } - - -// stuff for gamemodes to call - -void function SetShouldUsePickLoadoutScreen( bool shouldUse ) -{ - file.usePickLoadoutScreen = shouldUse -} - -void function SetSwitchSidesBased( bool switchSides ) -{ - file.switchSidesBased = switchSides -} - -void function SetSuddenDeathBased( bool suddenDeathBased ) -{ - file.suddenDeathBased = suddenDeathBased -} - -void function SetTimerBased( bool timerBased ) -{ - file.timerBased = timerBased -} - -void function SetShouldUseRoundWinningKillReplay( bool shouldUse ) -{ - SetServerVar( "roundWinningKillReplayEnabled", shouldUse ) -} - -void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan ) -{ - file.roundWinningKillReplayTrackPilotKills = pilot - file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this -} - -void function SetRoundWinningKillReplayAttacker( entity attacker, int inflictorEHandle = -1 ) -{ - file.roundWinningKillReplayTime = Time() - file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker ) - file.roundWinningKillReplayAttacker = attacker - file.roundWinningKillReplayInflictorEHandle = inflictorEHandle == -1 ? attacker.GetEncodedEHandle() : inflictorEHandle - file.roundWinningKillReplayTimeOfDeath = Time() -} - -void function SetWinner( int team, string winningReason = "", string losingReason = "" ) -{ - SetServerVar( "winningTeam", team ) - - file.gameWonThisFrame = true - thread UpdateGameWonThisFrameNextFrame() - - if ( winningReason.len() == 0 ) - file.announceRoundWinnerWinningSubstr = 0 - else - file.announceRoundWinnerWinningSubstr = GetStringID( winningReason ) - - if ( losingReason.len() == 0 ) - file.announceRoundWinnerLosingSubstr = 0 - else - file.announceRoundWinnerLosingSubstr = GetStringID( losingReason ) - - if ( GamePlayingOrSuddenDeath() ) - { - if ( IsRoundBased() ) - { - if ( team != TEAM_UNASSIGNED ) - { - GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + 1 ) - GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + 1 ) - } - - SetGameState( eGameState.WinnerDetermined ) - } - else - SetGameState( eGameState.WinnerDetermined ) - - ScoreEvent_MatchComplete( team ) - } -} - void function UpdateGameWonThisFrameNextFrame() { WaitFrame() @@ -849,29 +1087,6 @@ void function UpdateGameWonThisFrameNextFrame() file.hasKillForGameWonThisFrame = false } -void function AddTeamScore( int team, int amount ) -{ - GameRules_SetTeamScore( team, GameRules_GetTeamScore( team ) + amount ) - GameRules_SetTeamScore2( team, GameRules_GetTeamScore2( team ) + amount ) - - int scoreLimit - if ( IsRoundBased() ) - scoreLimit = GameMode_GetRoundScoreLimit( GAMETYPE ) - else - scoreLimit = GameMode_GetScoreLimit( GAMETYPE ) - - int score = GameRules_GetTeamScore( team ) - if ( score >= scoreLimit || GetGameState() == eGameState.SuddenDeath ) - SetWinner( team ) - else if ( ( file.switchSidesBased && !file.hasSwitchedSides ) && score >= ( scoreLimit.tofloat() / 2.0 ) ) - SetGameState( eGameState.SwitchingSides ) -} - -void function SetTimeoutWinnerDecisionFunc( int functionref() callback ) -{ - file.timeoutWinnerDecisionFunc = callback -} - int function GetWinningTeamWithFFASupport() { if ( !IsFFAGame() ) @@ -901,8 +1116,6 @@ int function GetWinningTeamWithFFASupport() unreachable } -// idk - float function GameState_GetTimeLimitOverride() { return 100 @@ -932,8 +1145,6 @@ float function GetTimeLimit_ForGameMode() return GetCurrentPlaylistVarFloat( playlistString, 10 ) } -// faction dialogue - void function DialoguePlayNormal() { int totalScore = GameMode_GetScoreLimit( GameRules_GetGameMode() ) @@ -1017,22 +1228,4 @@ void function DialoguePlayWinnerDetermined() PlayFactionDialogueToTeam( "scoring_won", winningTeam ) PlayFactionDialogueToTeam( "scoring_lost", losingTeam ) } -} - -/// This is to move all NPCs that a player owns from one team to the other during a match -/// Auto-Titans, Turrets, Ticks and Hacked Spectres will all move along together with the player to the new Team -/// Also possibly prevents mods that spawns other types of NPCs that players can own from breaking when switching (i.e Drones, Hacked Reapers) -void function OnPlayerChangedTeam( entity player ) -{ - if ( !player.hasConnected ) // Prevents players who just joined to trigger below code, as server always pre setups their teams - return - - NotifyClientsOfTeamChange( player, GetOtherTeam( player.GetTeam() ), player.GetTeam() ) - - foreach( npc in GetNPCArray() ) - { - entity bossPlayer = npc.GetBossPlayer() - if ( IsValidPlayer( bossPlayer ) && bossPlayer == player && IsAlive( npc ) ) - SetTeam( npc, player.GetTeam() ) - } -} +} \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut index aba1d5401..2a4c4282c 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut @@ -8,6 +8,7 @@ global function ScoreEvent_TitanDoomed global function ScoreEvent_TitanKilled global function ScoreEvent_NPCKilled global function ScoreEvent_MatchComplete +global function ScoreEvent_RoundComplete global function ScoreEvent_SetEarnMeterValues global function ScoreEvent_SetupEarnMeterValuesForMixedModes @@ -287,8 +288,24 @@ void function ScoreEvent_MatchComplete( int winningTeam ) foreach( entity player in GetPlayerArray() ) { AddPlayerScore( player, "MatchComplete" ) + SetPlayerChallengeMatchComplete( player ) if ( player.GetTeam() == winningTeam ) + { AddPlayerScore( player, "MatchVictory" ) + SetPlayerChallengeMatchWon( player, true ) + } + else + SetPlayerChallengeMatchWon( player, false ) + } +} + +void function ScoreEvent_RoundComplete( int winningTeam ) +{ + foreach( entity player in GetPlayerArray() ) + { + AddPlayerScore( player, "RoundComplete" ) + if ( player.GetTeam() == winningTeam ) + AddPlayerScore( player, "RoundVictory" ) } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut index 101d5e4ef..84b09ec8d 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut @@ -13,6 +13,7 @@ global function UpdateTitanCoreEarnedStat global function PreScoreEventUpdateStats global function PostScoreEventUpdateStats global function Stats_OnPlayerDidDamage +global function RegisterMatchStats_OnMatchComplete struct { table< string, array > refs @@ -36,7 +37,6 @@ void function Stats_Init() AddCallback_OnPlayerRespawned( OnPlayerRespawned ) AddCallback_OnClientConnected( OnClientConnected ) AddCallback_OnClientDisconnected( OnClientDisconnected ) - AddCallback_GameStateEnter( eGameState.WinnerDetermined, OnWinnerDetermined ) thread HandleDistanceAndTimeStats_Threaded() thread SaveStatsPeriodically_Threaded() @@ -334,6 +334,10 @@ void function OnPlayerOrNPCKilled( entity victim, entity attacker, var damageInf thread SetLastPosForDistanceStatValid_Threaded( victim, false ) HandleDeathStats( victim, attacker, damageInfo ) + + if( victim == attacker ) //Suicides are registering stats, afaik vanilla ignores them + return + HandleKillStats( victim, attacker, damageInfo ) HandleWeaponKillStats( victim, attacker, damageInfo ) HandleTitanStats( victim, attacker, damageInfo ) @@ -489,23 +493,32 @@ void function HandleKillStats( entity victim, entity attacker, var damageInfo ) // get the player and it's pet titan entity player entity playerPetTitan - if ( attacker.IsPlayer() ) + entity inflictor = DamageInfo_GetInflictor( damageInfo ) + + if ( IsValid( inflictor ) ) { - // the player is just the attacker - player = attacker - playerPetTitan = player.GetPetTitan() + if ( inflictor.IsProjectile() && IsValid( inflictor.GetOwner() ) ) // Attackers are always the final entity in the owning hierarchy, projectile owners though migh be a player's NPC minion (i.e Auto-Titans) + attacker = inflictor.GetOwner() + + else if ( inflictor.IsNPC() ) // NPCs are bypassed as Attackers if they are owned by players, instead they become just inflictors + attacker = inflictor } - else if ( attacker.IsTitan() && IsPetTitan( attacker ) ) + + if ( attacker.IsNPC() ) { - // the attacker is the player's auto titan + if ( !attacker.IsTitan() ) // Normal NPCs case + return + + if ( !IsPetTitan( attacker ) ) // NPC Titans case + return + player = attacker.GetTitanSoul().GetBossPlayer() playerPetTitan = attacker } + else if ( attacker.IsPlayer() ) // Still checks this because worldspawn might be the attacker + player = attacker else - { - // attacker could be something like an NPC, or worldspawn return - } // check things once, for performance int damageSource = DamageInfo_GetDamageSourceIdentifier( damageInfo ) @@ -800,7 +813,7 @@ void function OnPlayerRespawned( entity player ) thread SetLastPosForDistanceStatValid_Threaded( player, true ) } -void function OnWinnerDetermined() +void function RegisterMatchStats_OnMatchComplete() { // award players for match completed, wins, and losses foreach ( entity player in GetPlayerArray() ) @@ -875,30 +888,28 @@ void function OnWinnerDetermined() player.SetPersistentVar( "kdratio_lifetime_pvp", kdratio_lifetimepvp ) } - // award mvp and top 3 in each team - if ( !IsFFAGame() ) + array players = GetPlayerArray() + players.sort( GetScoreboardCompareFunc() ) + int playerCount = players.len() + int currentPlace = 1 + for ( int i = 0; i < 3; i++ ) { - string gamemode = GameRules_GetGameMode() - int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( gamemode ) - - for( int team = 0; team < MAX_TEAMS; team++ ) + if ( i >= playerCount ) + continue + + int functionref( entity, entity ) compareFunc = GetScoreboardCompareFunc() + if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 ) + currentPlace += 1 + switch( currentPlace ) { - array players = GetPlayerArrayOfTeam( team ) - if ( compareFunc == null ) - { - printt( "gamemode doesn't have a compare func to get the top 3" ) - return - } - players.sort( compareFunc ) - int maxAwards = int( min( players.len(), 3 ) ) - for ( int i = 0; i < maxAwards; i++ ) - { - if ( i == 0 ) - Stats_IncrementStat( players[ i ], "game_stats", "mvp", "", 1.0 ) - Stats_IncrementStat( players[ i ], "game_stats", "top3OnTeam", "", 1.0 ) - } + case 1: // MVP have two parallel stats which one registers MVP for the map played and the other goes to the player's stats menu as a total MVP times + UpdatePlayerStat( players[i], "game_stats", "mvp" ) + UpdatePlayerStat( players[i], "game_stats", "mvp_total" ) + case 2: + case 3: + UpdatePlayerStat( players[i], "game_stats", "top3OnTeam" ) // Ingame this is the "Times Top 3" for the whole match, not per team + break } - } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut index 8d859ba63..376c5b7c3 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/mp_wargames.nut @@ -231,6 +231,8 @@ void function OnPrematchStart() void function PlayerWatchesWargamesIntro( entity player ) { + player.EndSignal( "OnDestroy" ) + if ( IsAlive( player ) ) player.Die() @@ -253,8 +255,6 @@ void function PlayerWatchesWargamesIntro( entity player ) // we need to wait a frame if we killed ourselves to spawn into this, so just easier to do it all the time to remove any weirdness WaitFrame() - - player.EndSignal( "OnDestroy" ) player.EndSignal( "OnDeath" ) int factionTeam = ConvertPlayerFactionToIMCOrMilitiaTeam( player ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut index 4956375bd..c47552b3e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut @@ -1,27 +1,30 @@ untyped -global function InitRatings // temp for testing - global function Spawn_Init -global function SetRespawnsEnabled -global function RespawnsEnabled +global function FindSpawnPoint + global function SetSpawnpointGamemodeOverride global function GetSpawnpointGamemodeOverride global function AddSpawnpointValidationRule + +global function SetRespawnsEnabled +global function RespawnsEnabled global function CreateNoSpawnArea global function DeleteNoSpawnArea - -global function FindSpawnPoint +global function SpawnPointInNoSpawnArea global function RateSpawnpoints_Generic global function RateSpawnpoints_Frontline - -global function SetSpawnZoneRatingFunc -global function SetShouldCreateMinimapSpawnZones -global function CreateTeamSpawnZoneEntity global function RateSpawnpoints_SpawnZones global function DecideSpawnZone_Generic -global function DecideSpawnZone_CTF + +global struct spawnZoneProperties{ + int controllingTeam = TEAM_UNASSIGNED + entity minimapEnt = null + float zoneRating = 0.0 +} + +global table< entity, spawnZoneProperties > mapSpawnZones // Global so other scripts can access this for custom ratings if needed struct NoSpawnArea { @@ -35,30 +38,61 @@ struct NoSpawnArea struct { bool respawnsEnabled = true + array noSpawnAreas string spawnpointGamemodeOverride array< bool functionref( entity, int ) > customSpawnpointValidationRules - - table noSpawnAreas + bool shouldCreateMinimapSpawnzones } file + + + + + + + + + + +/* +██████ █████ ███████ ███████ ███████ ██ ██ ███ ██ ██████ ████████ ██ ██████ ███ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ██ ██ +██████ ███████ ███████ █████ █████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██████ ██ ██ ███████ ███████ ██ ██████ ██ ████ ██████ ██ ██ ██████ ██ ████ ███████ +*/ + void function Spawn_Init() -{ +{ + // callbacks for generic spawns AddSpawnCallback( "info_spawnpoint_human", InitSpawnpoint ) - AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint ) AddSpawnCallback( "info_spawnpoint_titan", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_droppod", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_dropship", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint ) AddSpawnCallback( "info_spawnpoint_titan_start", InitSpawnpoint ) - - // callbacks for generic spawns - AddCallback_EntitiesDidLoad( InitPreferSpawnNodes ) + AddSpawnCallback( "info_spawnpoint_droppod_start", InitSpawnpoint ) + AddSpawnCallback( "info_spawnpoint_dropship_start", InitSpawnpoint ) // callbacks for spawnzone spawns AddCallback_GameStateEnter( eGameState.Prematch, ResetSpawnzones ) AddSpawnCallbackEditorClass( "trigger_multiple", "trigger_mp_spawn_zone", AddSpawnZoneTrigger ) -} - -void function InitSpawnpoint( entity spawnpoint ) -{ - spawnpoint.s.lastUsedTime <- -999 + + float friendlyAIValue = 1.75 + if ( GameModeHasCapturePoints() ) + friendlyAIValue = 0.75 + + SpawnPoints_SetRatingMultipliers_Enemy( TD_TITAN, -10.0, -6.0, -1.0 ) + SpawnPoints_SetRatingMultipliers_Enemy( TD_PILOT, -10.0, -6.0, -1.0 ) + SpawnPoints_SetRatingMultipliers_Enemy( TD_AI, -2.0, -0.25, 0.0 ) + + SpawnPoints_SetRatingMultipliers_Friendly( TD_TITAN, 0.25, 1.75, friendlyAIValue ) + SpawnPoints_SetRatingMultipliers_Friendly( TD_PILOT, 0.25, 1.75, friendlyAIValue ) + SpawnPoints_SetRatingMultipliers_Friendly( TD_AI, 0.5, 0.25, 0.0 ) + + SpawnPoints_SetRatingMultiplier_PetTitan( 2.0 ) + + file.shouldCreateMinimapSpawnzones = GetCurrentPlaylistVarInt( "spawn_zone_enabled", 1 ) != 0 } void function SetRespawnsEnabled( bool enabled ) @@ -71,9 +105,10 @@ bool function RespawnsEnabled() return file.respawnsEnabled } -void function AddSpawnpointValidationRule( bool functionref( entity spawn, int team ) rule ) +void function InitSpawnpoint( entity spawnpoint ) { - file.customSpawnpointValidationRules.append( rule ) + spawnpoint.s.lastUsedTime <- -999 + spawnpoint.s.inUse <- false } string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam, vector position, float lifetime, float radius ) @@ -85,11 +120,12 @@ string function CreateNoSpawnArea( int blockSpecificTeam, int blockEnemiesOfTeam noSpawnArea.lifetime = lifetime noSpawnArea.radius = radius - // generate an id noSpawnArea.id = UniqueString( "noSpawnArea" ) - thread NoSpawnAreaLifetime( noSpawnArea ) + if ( lifetime > 0 ) + thread NoSpawnAreaLifetime( noSpawnArea ) + file.noSpawnAreas.append( noSpawnArea ) return noSpawnArea.id } @@ -101,8 +137,41 @@ void function NoSpawnAreaLifetime( NoSpawnArea noSpawnArea ) void function DeleteNoSpawnArea( string noSpawnIdx ) { - if ( noSpawnIdx in file.noSpawnAreas ) - delete file.noSpawnAreas[ noSpawnIdx ] + foreach ( noSpawnArea in file.noSpawnAreas ) + { + if ( noSpawnArea.id == noSpawnIdx ) + file.noSpawnAreas.removebyvalue( noSpawnArea ) + } +} + +bool function SpawnPointInNoSpawnArea( vector vec, int team ) +{ + foreach ( noSpawnArea in file.noSpawnAreas ) + { + if ( Distance( noSpawnArea.position, vec ) < noSpawnArea.radius ) + { + if ( noSpawnArea.blockedTeam != TEAM_INVALID && noSpawnArea.blockedTeam == team ) + return true + + if ( noSpawnArea.blockOtherTeams != TEAM_INVALID && noSpawnArea.blockOtherTeams != team ) + return true + } + } + + return false +} + +bool function IsSpawnpointValidDrop( entity spawnpoint, int team ) +{ + if ( spawnpoint.IsOccupied() || spawnpoint.s.inUse ) + return false + + return true +} + +void function AddSpawnpointValidationRule( bool functionref( entity spawn, int team ) rule ) +{ + file.customSpawnpointValidationRules.append( rule ) } void function SetSpawnpointGamemodeOverride( string gamemode ) @@ -114,35 +183,38 @@ string function GetSpawnpointGamemodeOverride() { if ( file.spawnpointGamemodeOverride != "" ) return file.spawnpointGamemodeOverride - else - return GAMETYPE - unreachable + return GAMETYPE } -void function InitRatings( entity player, int team ) -{ - if ( player != null ) - SpawnPoints_InitRatings( player, team ) // no idea what the second arg supposed to be lol -} + + + + + + + + + +/* +███████ ██████ █████ ██ ██ ███ ██ ██████ ██████ ██████ ███████ ██████ ██ ███ ██ ██████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +███████ ██████ ███████ ██ █ ██ ██ ██ ██ ██ ██ ██████ ██ ██ █████ ██████ ██ ██ ██ ██ ██ ███ + ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ███ ███ ██ ████ ██████ ██ ██ ██████ ███████ ██ ██ ██ ██ ████ ██████ +*/ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnpoint ) { int team = player.GetTeam() - if ( HasSwitchedSides() ) - team = GetOtherTeam( team ) - + array spawnpoints if ( useStartSpawnpoint ) spawnpoints = isTitan ? SpawnPoints_GetTitanStart( team ) : SpawnPoints_GetPilotStart( team ) else spawnpoints = isTitan ? SpawnPoints_GetTitan() : SpawnPoints_GetPilot() - InitRatings( player, player.GetTeam() ) - - // don't think this is necessary since we call discardratings - //foreach ( entity spawnpoint in spawnpoints ) - // spawnpoint.CalculateRating( isTitan ? TD_TITAN : TD_PILOT, team, 0.0, 0.0 ) + SpawnPoints_InitRatings( player, team ) void functionref( int, array, int, entity ) ratingFunc = isTitan ? GameMode_GetTitanSpawnpointsRatingFunc( GAMETYPE ) : GameMode_GetPilotSpawnpointsRatingFunc( GAMETYPE ) ratingFunc( isTitan ? TD_TITAN : TD_PILOT, spawnpoints, team, player ) @@ -166,46 +238,46 @@ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnp spawnpoints = useStartSpawnpoint ? SpawnPoints_GetPilotStart( team ) : SpawnPoints_GetPilot() } - entity spawnpoint = GetBestSpawnpoint( player, spawnpoints ) + entity spawnpoint = GetBestSpawnpoint( player, spawnpoints, isTitan ) spawnpoint.s.lastUsedTime = Time() player.SetLastSpawnPoint( spawnpoint ) + + //SpawnPoints_DiscardRatings() return spawnpoint } -entity function GetBestSpawnpoint( entity player, array spawnpoints ) +entity function GetBestSpawnpoint( entity player, array spawnpoints, bool isTitan ) { - // not really 100% sure on this randomisation, needs some thought array validSpawns + + // I know this looks hacky but the native funcs to get the spawns is returning null arrays for FFA idk why. + if ( IsFFAGame() ) + { + spawnpoints.clear() + if ( isTitan ) + spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_titan" ) + else + spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" ) + } + foreach ( entity spawnpoint in spawnpoints ) { if ( IsSpawnpointValid( spawnpoint, player.GetTeam() ) ) - { validSpawns.append( spawnpoint ) - - if ( validSpawns.len() == 3 ) // arbitrary small sample size - break - } } - if ( validSpawns.len() == 0 ) + if ( !validSpawns.len() ) // First validity check { - // no valid spawns, very bad, so dont care about spawns being valid anymore - print( "found no valid spawns! spawns may be subpar!" ) + CodeWarning( "Map has no valid spawn points for " + GAMETYPE + " gamemode, attempting any other possible spawn point" ) foreach ( entity spawnpoint in spawnpoints ) - { validSpawns.append( spawnpoint ) - - if ( validSpawns.len() == 3 ) // arbitrary small sample size - break - } } - // last resort - if ( validSpawns.len() == 0 ) + if ( !validSpawns.len() ) // On all validity check, just gather the most basic spawn { - print( "map has literally 0 spawnpoints, as such everything is fucked probably, attempting to use info_player_start if present" ) + CodeWarning( "Map has no proper spawn points, falling back to info_player_start" ) entity start = GetEnt( "info_player_start" ) if ( IsValid( start ) ) @@ -213,14 +285,19 @@ entity function GetBestSpawnpoint( entity player, array spawnpoints ) start.s.lastUsedTime <- -999 validSpawns.append( start ) } + else + throw( "Map has no player spawns at all" ) } - return validSpawns[ RandomInt( validSpawns.len() ) ] // slightly randomize it + if ( IsFFAGame() ) + return validSpawns.getrandom() + + return validSpawns[0] // Return first entry in the array because native have already sorted everything through the ratings, so first one is the best one } bool function IsSpawnpointValid( entity spawnpoint, int team ) { - if ( !spawnpoint.HasKey( "ignoreGamemode" ) || ( spawnpoint.HasKey( "ignoreGamemode" ) && spawnpoint.kv.ignoreGamemode == "0" ) ) // used by script-spawned spawnpoints + if ( !spawnpoint.HasKey( "ignoreGamemode" ) || spawnpoint.HasKey( "ignoreGamemode" ) && spawnpoint.kv.ignoreGamemode == "0" ) // used by script-spawned spawnpoints { if ( file.spawnpointGamemodeOverride != "" ) { @@ -232,223 +309,149 @@ bool function IsSpawnpointValid( entity spawnpoint, int team ) return false } - int compareTeam = spawnpoint.GetTeam() - if ( HasSwitchedSides() && ( compareTeam == TEAM_MILITIA || compareTeam == TEAM_IMC ) ) - compareTeam = GetOtherTeam( compareTeam ) - foreach ( bool functionref( entity, int ) customValidationRule in file.customSpawnpointValidationRules ) if ( !customValidationRule( spawnpoint, team ) ) return false - if ( spawnpoint.GetTeam() > 0 && compareTeam != team && !IsFFAGame() ) + if ( !IsSpawnpointValidDrop( spawnpoint, team ) || Time() - spawnpoint.s.lastUsedTime <= 10.0 ) return false - if ( spawnpoint.IsOccupied() ) + if ( SpawnPointInNoSpawnArea( spawnpoint.GetOrigin(), team ) ) return false - - if ( Time() - spawnpoint.s.lastUsedTime <= 10.0 ) - return false - - foreach ( k, NoSpawnArea noSpawnArea in file.noSpawnAreas ) + + // Line of Sight Check, could use IsVisibleToEnemies but apparently that considers only players, not NPCs + array< entity > enemyTitans = GetTitanArrayOfEnemies( team ) + if ( GetConVarBool( "spawnpoint_avoid_npc_titan_sight" ) ) { - if ( Distance( noSpawnArea.position, spawnpoint.GetOrigin() ) > noSpawnArea.radius ) - continue - - if ( noSpawnArea.blockedTeam != TEAM_INVALID && noSpawnArea.blockedTeam == team ) - return false - - if ( noSpawnArea.blockOtherTeams != TEAM_INVALID && noSpawnArea.blockOtherTeams != team ) - return false + foreach ( titan in enemyTitans ) + { + if ( IsAlive( titan ) && titan.IsNPC() && titan.CanSee( spawnpoint ) ) + return false + } } - - const minEnemyDist = 1000.0 // about 20 meters? - // in rsquirrel extend returns null unlike in vanilla squirrel - array< entity > spawnBlockers = GetPlayerArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist ) - spawnBlockers.extend( GetProjectileArrayEx( "any", TEAM_ANY, TEAM_ANY, spawnpoint.GetOrigin(), minEnemyDist ) ) - foreach ( entity blocker in spawnBlockers ) - if ( blocker.GetTeam() != team ) - return false - // los check return !spawnpoint.IsVisibleToEnemies( team ) } -// SPAWNPOINT RATING FUNCS BELOW -// generic -struct { - array preferSpawnNodes -} spawnStateGeneric -void function RateSpawnpoints_Generic( int checkClass, array spawnpoints, int team, entity player ) -{ - if ( !IsFFAGame() ) - { - // use frontline spawns in 2-team modes - RateSpawnpoints_Frontline( checkClass, spawnpoints, team, player ) - return - } - else - { - // todo: ffa spawns :terror: - } - // old algo: keeping until we have a better ffa spawn algo - // i'm not a fan of this func, but i really don't have a better way to do this rn, and it's surprisingly good with los checks implemented now - - // calculate ratings for preferred nodes - // this tries to prefer nodes with more teammates, then activity on them - // todo: in the future it might be good to have this prefer nodes with enemies up to a limit of some sort - // especially in ffa modes i could deffo see this falling apart a bit rn - // perhaps dead players could be used to calculate some sort of activity rating? so high-activity points with an even balance of friendly/unfriendly players are preferred - array preferSpawnNodeRatings - foreach ( vector preferSpawnNode in spawnStateGeneric.preferSpawnNodes ) + + + + +/* +██████ ██████ ██ ███ ██ ████████ ██████ █████ ████████ ██ ███ ██ ██████ +██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ +██████ ██ ██ ██ ██ ██ ██ ██ ██████ ███████ ██ ██ ██ ██ ██ ██ ███ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██████ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██████ +*/ + +void function RateSpawnpoints_Generic( int checkClass, array spawnpoints, int team, entity player ) +{ + foreach ( entity spawnpoint in spawnpoints ) { - float currentRating - - // this seems weird, not using rn - //Frontline currentFrontline = GetCurrentFrontline( team ) - //if ( !IsFFAGame() || currentFrontline.friendlyCenter != < 0, 0, 0 > ) - // currentRating += max( 0.0, ( 1000.0 - Distance2D( currentFrontline.origin, preferSpawnNode ) ) / 200 ) + float currentRating = 0.0 - foreach ( entity nodePlayer in GetPlayerArray() ) - { - float currentChange = 0.0 - - // the closer a player is to a node the more they matter - float dist = Distance2D( preferSpawnNode, nodePlayer.GetOrigin() ) - if ( dist > 600.0 ) - continue - - currentChange = ( 600.0 - dist ) / 5 - if ( player == nodePlayer ) - currentChange *= -3 // always try to stay away from places we've already spawned - else if ( !IsAlive( nodePlayer ) ) // dead players mean activity which is good, but they're also dead so they don't matter as much as living ones - currentChange *= 0.6 - if ( nodePlayer.GetTeam() != player.GetTeam() ) // if someone isn't on our team and alive they're probably bad - { - if ( IsFFAGame() ) // in ffa everyone is on different teams, so this isn't such a big deal - currentChange *= -0.2 - else - currentChange *= -0.6 - } - - currentRating += currentChange - } + // Gather friendly scoring first to give positive rating first + currentRating += spawnpoint.NearbyAllyScore( team, "ai" ) + currentRating += spawnpoint.NearbyAllyScore( team, "titan" ) + currentRating += spawnpoint.NearbyAllyScore( team, "pilot" ) - preferSpawnNodeRatings.append( currentRating ) - } - - foreach ( entity spawnpoint in spawnpoints ) - { - float currentRating - float petTitanModifier - // scale how much a given spawnpoint matters to us based on how far it is from each node - bool spawnHasRecievedInitialBonus = false - for ( int i = 0; i < spawnStateGeneric.preferSpawnNodes.len(); i++ ) - { - // bonus if autotitan is nearish - if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) < 1200.0 ) - petTitanModifier += 10.0 - - float dist = Distance2D( spawnpoint.GetOrigin(), spawnStateGeneric.preferSpawnNodes[ i ] ) - if ( dist > 750.0 ) - continue - - if ( dist < 600.0 && !spawnHasRecievedInitialBonus ) - { - currentRating += 10.0 - spawnHasRecievedInitialBonus = true // should only get a bonus for simply being by a node once to avoid over-rating - } + // Enemies then subtract that rating ( Values already returns negative, so no need to apply subtract again ) + currentRating += spawnpoint.NearbyEnemyScore( team, "ai" ) + currentRating += spawnpoint.NearbyEnemyScore( team, "titan" ) + currentRating += spawnpoint.NearbyEnemyScore( team, "pilot" ) - currentRating += ( preferSpawnNodeRatings[ i ] * ( ( 750.0 - dist ) / 75 ) ) + max( RandomFloat( 1.25 ), 0.9 ) - if ( dist < 250.0 ) // shouldn't get TOO close to an active node - currentRating *= 0.7 - - if ( spawnpoint.s.lastUsedTime < 10.0 ) - currentRating *= 0.7 - } - - float rating = spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating + petTitanModifier ) - //print( "spawnpoint at " + spawnpoint.GetOrigin() + " has rating: " + ) + if ( spawnpoint == player.p.lastSpawnPoint ) // Reduce the rating of the spawn point used previously + currentRating += GetConVarFloat( "spawnpoint_last_spawn_rating" ) - if ( rating != 0.0 || currentRating != 0.0 ) - print( "rating = " + rating + ", internal rating = " + currentRating ) - } -} - -void function InitPreferSpawnNodes() -{ - foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) ) - { - if ( !hardpoint.HasKey( "hardpointGroup" ) ) - continue - - if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" ) - continue - - spawnStateGeneric.preferSpawnNodes.append( hardpoint.GetOrigin() ) + spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating * 0.25 ) } - - //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) ) - // spawnStateGeneric.preferSpawnNodes.append( frontline.GetOrigin() ) } -// frontline void function RateSpawnpoints_Frontline( int checkClass, array spawnpoints, int team, entity player ) { + Frontline currentFrontline = GetFrontline( team ) + + vector inverseFrontlineDir = currentFrontline.combatDir * -1 + vector adjustedPosition = currentFrontline.origin + currentFrontline.combatDir * 8000 + + SpawnPoints_InitFrontlineData( adjustedPosition, currentFrontline.combatDir, currentFrontline.origin, currentFrontline.friendlyCenter, 4000 ) + foreach ( entity spawnpoint in spawnpoints ) { - float rating = spawnpoint.CalculateFrontlineRating() - spawnpoint.CalculateRating( checkClass, player.GetTeam(), rating, rating > 0 ? rating * 0.25 : rating ) + float frontlineRating = spawnpoint.CalculateFrontlineRating() + + spawnpoint.CalculateRating( checkClass, team, frontlineRating, frontlineRating * 0.25 ) } } -// spawnzones -struct { - array mapSpawnzoneTriggers - entity functionref( array, int ) spawnzoneRatingFunc - bool shouldCreateMinimapSpawnzones = false - - // for DecideSpawnZone_Generic - table activeTeamSpawnzones - table activeTeamSpawnzoneMinimapEnts -} spawnStateSpawnzones + + + + + + + + + +/* +███████ ██████ █████ ██ ██ ███ ██ ███████ ██████ ███ ██ ███████ ███████ +██ ██ ██ ██ ██ ██ ██ ████ ██ ███ ██ ██ ████ ██ ██ ██ +███████ ██████ ███████ ██ █ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ █████ ███████ + ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ +███████ ██ ██ ██ ███ ███ ██ ████ ███████ ██████ ██ ████ ███████ ███████ +*/ void function ResetSpawnzones() { - spawnStateSpawnzones.activeTeamSpawnzones.clear() - - foreach ( int team, entity minimapEnt in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) - if ( IsValid( minimapEnt ) ) - minimapEnt.Destroy() - - spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts.clear() + foreach ( zone, zoneProperties in mapSpawnZones ) + { + if ( IsValid( zoneProperties.minimapEnt ) ) + zoneProperties.minimapEnt.Destroy() + + zoneProperties.controllingTeam = TEAM_UNASSIGNED + zoneProperties.zoneRating = 0.0 + } } void function AddSpawnZoneTrigger( entity trigger ) { - trigger.s.spawnzoneRating <- 0.0 - spawnStateSpawnzones.mapSpawnzoneTriggers.append( trigger ) + spawnZoneProperties zoneProperties + mapSpawnZones[trigger] <- zoneProperties } -void function SetSpawnZoneRatingFunc( entity functionref( array, int ) ratingFunc ) +bool function TeamHasDirtySpawnzone( int team ) { - spawnStateSpawnzones.spawnzoneRatingFunc = ratingFunc -} - -void function SetShouldCreateMinimapSpawnZones( bool shouldCreateMinimapSpawnzones ) -{ - spawnStateSpawnzones.shouldCreateMinimapSpawnzones = shouldCreateMinimapSpawnzones + foreach ( zone, zoneProperties in mapSpawnZones ) + { + if ( zoneProperties.controllingTeam == team ) + { + int numDeadInZone = 0 + array teamPlayers = GetPlayerArrayOfTeam( team ) + foreach ( entity player in teamPlayers ) + { + if ( Time() - player.p.postDeathThreadStartTime < 20.0 && zone.ContainsPoint( player.p.deathOrigin ) ) + numDeadInZone++ + } + + if ( numDeadInZone < teamPlayers.len() ) + return false + } + } + + return true } -entity function CreateTeamSpawnZoneEntity( entity spawnzone, int team ) +void function CreateTeamSpawnZoneEntity( entity spawnzone, int team ) { entity minimapObj = CreatePropScript( $"models/dev/empty_model.mdl", spawnzone.GetOrigin() ) SetTeam( minimapObj, team ) - minimapObj.Minimap_SetObjectScale( 100.0 / Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) + minimapObj.Minimap_SetObjectScale( Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) / 16000 ) // 16000 cuz thats the total space Minimap uses minimapObj.Minimap_SetAlignUpright( true ) minimapObj.Minimap_AlwaysShow( TEAM_IMC, null ) minimapObj.Minimap_AlwaysShow( TEAM_MILITIA, null ) @@ -461,67 +464,58 @@ entity function CreateTeamSpawnZoneEntity( entity spawnzone, int team ) minimapObj.Minimap_SetCustomState( eMinimapObject_prop_script.SPAWNZONE_MIL ) minimapObj.DisableHibernation() - return minimapObj + mapSpawnZones[spawnzone].minimapEnt = minimapObj } void function RateSpawnpoints_SpawnZones( int checkClass, array spawnpoints, int team, entity player ) { - if ( spawnStateSpawnzones.spawnzoneRatingFunc == null ) - spawnStateSpawnzones.spawnzoneRatingFunc = DecideSpawnZone_Generic - - // don't use spawnzones if we're using start spawns if ( ShouldStartSpawn( player ) ) { RateSpawnpoints_Generic( checkClass, spawnpoints, team, player ) return } - - entity spawnzone = spawnStateSpawnzones.spawnzoneRatingFunc( spawnStateSpawnzones.mapSpawnzoneTriggers, player.GetTeam() ) - if ( !IsValid( spawnzone ) ) // no spawn zone, use generic algo + + array< entity > zoneTriggers + foreach ( zone, zoneProperties in mapSpawnZones ) + zoneTriggers.append( zone ) + + entity spawnzone = DecideSpawnZone_Generic( zoneTriggers, player.GetTeam() ) + if ( !IsValid( spawnzone ) ) { RateSpawnpoints_Generic( checkClass, spawnpoints, team, player ) return } - // rate spawnpoints foreach ( entity spawn in spawnpoints ) { float rating = 0.0 float distance = Distance2D( spawn.GetOrigin(), spawnzone.GetOrigin() ) if ( distance < Distance2D( < 0, 0, 0 >, spawnzone.GetBoundingMaxs() ) ) - rating = 100.0 - else // max 35 rating if not in zone, rate by closest - rating = 35.0 * ( 1 - ( distance / 5000.0 ) ) + rating = 10.0 + else + rating = 2.0 * ( 1 - ( distance / 3000.0 ) ) - spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) + spawn.CalculateRating( checkClass, team, rating, rating * 0.25 ) } } entity function DecideSpawnZone_Generic( array spawnzones, int team ) { - if ( spawnzones.len() == 0 ) + if ( !spawnzones.len() ) return null - // get average team startspawn positions - int spawnCompareTeam = team - if ( HasSwitchedSides() ) - spawnCompareTeam = GetOtherTeam( team ) - - array startSpawns = SpawnPoints_GetPilotStart( spawnCompareTeam ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( spawnCompareTeam ) ) + array startSpawns = SpawnPoints_GetPilotStart( team ) + array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) - if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash + if ( !startSpawns.len() || !enemyStartSpawns.len() ) return null - - // get average startspawn position and max dist between spawns - // could probably cache this, tbh, not like it should change outside of halftimes - vector averageFriendlySpawns + + vector averageFriendlySpawns foreach ( entity spawn in startSpawns ) averageFriendlySpawns += spawn.GetOrigin() averageFriendlySpawns /= startSpawns.len() - // get average enemy startspawn position vector averageEnemySpawns foreach ( entity spawn in enemyStartSpawns ) averageEnemySpawns += spawn.GetOrigin() @@ -530,250 +524,87 @@ entity function DecideSpawnZone_Generic( array spawnzones, int team ) float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns ) - bool needNewZone = true - if ( team in spawnStateSpawnzones.activeTeamSpawnzones ) - { - foreach ( entity player in GetPlayerArray() ) - { - // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this - if ( player.GetTeam() != team && spawnStateSpawnzones.activeTeamSpawnzones[ team ].ContainsPoint( player.GetOrigin() ) ) - break - } - - int numDeadInZone = 0 - array teamPlayers = GetPlayerArrayOfTeam( team ) - foreach ( entity player in teamPlayers ) - { - // check if they died in the zone recently, get a new zone if too many died - if ( Time() - player.p.postDeathThreadStartTime < 15.0 && spawnStateSpawnzones.activeTeamSpawnzones[ team ].ContainsPoint( player.p.deathOrigin ) ) - numDeadInZone++ - } - - // cast to float so result is float - if ( float( numDeadInZone ) / teamPlayers.len() <= 0.1 ) - needNewZone = false - } - - if ( needNewZone ) + if ( TeamHasDirtySpawnzone( team ) ) { - // find new zone array possibleZones - foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers ) + foreach ( zone, zoneProperties in mapSpawnZones ) { - // don't remember if you can do a "value in table.values" sorta thing in squirrel so doing manual lookup - bool spawnzoneTaken = false - foreach ( int otherTeam, entity otherSpawnzone in spawnStateSpawnzones.activeTeamSpawnzones ) - { - if ( otherSpawnzone == spawnzone ) - { - spawnzoneTaken = true - break - } - } - - if ( spawnzoneTaken ) + if ( zoneProperties.controllingTeam == GetOtherTeam( team ) ) continue - // check zone validity - bool spawnzoneEvil = false - foreach ( entity player in GetPlayerArray() ) + bool spawnzoneHasEnemies = false + foreach ( entity enemy in GetPlayerArrayOfEnemies_Alive( team ) ) { - // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this - if ( player.GetTeam() != team && spawnzone.ContainsPoint( player.GetOrigin() ) ) + if ( zone.ContainsPoint( enemy.GetOrigin() ) ) { - spawnzoneEvil = true + spawnzoneHasEnemies = true break } } - // don't choose spawnzones that are closer to enemy base than friendly base - // note: vanilla spawns might not necessarily require this, worth checking - if ( !spawnzoneEvil && Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) > Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) ) - spawnzoneEvil = true + if ( !spawnzoneHasEnemies && Distance2D( zone.GetOrigin(), averageFriendlySpawns ) > Distance2D( zone.GetOrigin(), averageEnemySpawns ) ) + spawnzoneHasEnemies = true - if ( spawnzoneEvil ) + if ( spawnzoneHasEnemies ) continue - // rate spawnzone based on distance to frontline Frontline frontline = GetFrontline( team ) - - // prefer spawns close to base pos - float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawnzone.GetOrigin() ) / baseDistance ) + float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, zone.GetOrigin() ) / baseDistance ) if ( frontline.friendlyCenter != < 0, 0, 0 > ) { - // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir - rating += rating * ( 1.0 - ( Distance2D( spawnzone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) - rating *= fabs( frontline.combatDir.y - Normalize( spawnzone.GetOrigin() - averageFriendlySpawns ).y ) + rating += rating * ( 1.0 - ( Distance2D( zone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) + rating *= fabs( frontline.combatDir.y - Normalize( zone.GetOrigin() - averageFriendlySpawns ).y ) } - spawnzone.s.spawnzoneRating = rating - possibleZones.append( spawnzone ) + zoneProperties.zoneRating = rating + possibleZones.append( zone ) } - if ( possibleZones.len() == 0 ) + if ( !possibleZones.len() ) return null - possibleZones.sort( int function( entity a, entity b ) - { - if ( a.s.spawnzoneRating > b.s.spawnzoneRating ) - return -1 - - if ( b.s.spawnzoneRating > a.s.spawnzoneRating ) - return 1 - - return 0 - } ) - entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ] + possibleZones.sort( SortPossibleZones ) - if ( spawnStateSpawnzones.shouldCreateMinimapSpawnzones ) - { - entity oldEnt - if ( team in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) - oldEnt = spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] - - spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] <- CreateTeamSpawnZoneEntity( chosenZone, team ) - if ( IsValid( oldEnt ) ) - oldEnt.Destroy() - } - - spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone - } - - return spawnStateSpawnzones.activeTeamSpawnzones[ team ] -} - -// ideally this should be in the gamemode_ctf file, but would need refactors to expose more stuff that's not available there rn -entity function DecideSpawnZone_CTF( array spawnzones, int team ) -{ - if ( spawnzones.len() == 0 ) - return null - - int otherTeam = GetOtherTeam( team ) - array enemyPlayers = GetPlayerArrayOfTeam( otherTeam ) - - // get average team startspawn positions - int spawnCompareTeam = team - if ( HasSwitchedSides() ) - spawnCompareTeam = GetOtherTeam( team ) - - array startSpawns = SpawnPoints_GetPilotStart( spawnCompareTeam ) - array enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( spawnCompareTeam ) ) - - if ( startSpawns.len() == 0 || enemyStartSpawns.len() == 0 ) // ensure we don't crash - return null - - // get average startspawn position and max dist between spawns - // could probably cache this, tbh, not like it should change outside of halftimes - vector averageFriendlySpawns - foreach ( entity spawn in startSpawns ) - averageFriendlySpawns += spawn.GetOrigin() - - averageFriendlySpawns /= startSpawns.len() - - // get average enemy startspawn position - vector averageEnemySpawns - foreach ( entity spawn in enemyStartSpawns ) - averageEnemySpawns += spawn.GetOrigin() - - averageEnemySpawns /= enemyStartSpawns.len() - - float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns ) - - // find new zone - array possibleZones - foreach ( entity spawnzone in spawnStateSpawnzones.mapSpawnzoneTriggers ) - { - // can't choose zone if another team has it - if ( otherTeam in spawnStateSpawnzones.activeTeamSpawnzones && spawnStateSpawnzones.activeTeamSpawnzones[ otherTeam ] == spawnzone ) - continue + entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ] - // check zone validity - bool spawnzoneEvil = false - foreach ( entity player in enemyPlayers ) + if ( file.shouldCreateMinimapSpawnzones ) { - // couldn't get IsTouching, GetTouchingEntities or enter callbacks to work in testing, so doing this - if ( spawnzone.ContainsPoint( player.GetOrigin() ) ) + foreach ( zone, zoneProperties in mapSpawnZones ) { - spawnzoneEvil = true - break + if ( chosenZone == zone ) + continue + + if ( IsValid( zoneProperties.minimapEnt ) && zoneProperties.controllingTeam == team ) + zoneProperties.minimapEnt.Destroy() } + + CreateTeamSpawnZoneEntity( chosenZone, team ) } - // don't choose spawnzones that are closer to enemy base than friendly base - if ( !spawnzoneEvil && Distance2D( spawnzone.GetOrigin(), averageFriendlySpawns ) > Distance2D( spawnzone.GetOrigin(), averageEnemySpawns ) ) - spawnzoneEvil = true - - if ( spawnzoneEvil ) - continue - - // rate spawnzone based on distance to frontline - Frontline frontline = GetFrontline( team ) - - // prefer spawns close to base pos - float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawnzone.GetOrigin() ) / baseDistance ) - - if ( frontline.friendlyCenter != < 0, 0, 0 > ) + foreach ( zone, zoneProperties in mapSpawnZones ) { - // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir - rating += rating * ( 1.0 - ( Distance2D( spawnzone.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) - rating *= fabs( frontline.combatDir.y - Normalize( spawnzone.GetOrigin() - averageFriendlySpawns ).y ) - - // reduce rating based on players that can currently see the zone - bool hasAppliedInitialLoss = false - foreach ( entity player in enemyPlayers ) - { - // don't trace here, just do an angle check - if ( PlayerCanSee( player, spawnzone, false, 65 ) && Distance2D( player.GetOrigin(), spawnzone.GetOrigin() ) <= 2000.0 ) - { - float distFrac = TraceLineSimple( player.GetOrigin(), spawnzone.GetOrigin(), player ) - - if ( distFrac >= 0.65 ) - { - // give a fairly large loss if literally anyone can see it - if ( !hasAppliedInitialLoss ) - { - rating *= 0.8 - hasAppliedInitialLoss = true - } - - rating *= ( 1.0 / enemyPlayers.len() ) * distFrac - } - } - } + if ( chosenZone == zone ) + continue + + if ( zoneProperties.controllingTeam == team ) + zoneProperties.controllingTeam = TEAM_UNASSIGNED } - spawnzone.s.spawnzoneRating = rating - possibleZones.append( spawnzone ) + mapSpawnZones[chosenZone].controllingTeam = team + return chosenZone } - if ( possibleZones.len() == 0 ) - return null - - possibleZones.sort( int function( entity a, entity b ) - { - if ( a.s.spawnzoneRating > b.s.spawnzoneRating ) - return -1 + return null +} + +int function SortPossibleZones( entity a, entity b ) +{ + if ( mapSpawnZones[a].zoneRating > mapSpawnZones[b].zoneRating ) + return -1 - if ( b.s.spawnzoneRating > a.s.spawnzoneRating ) - return 1 + if ( mapSpawnZones[b].zoneRating > mapSpawnZones[a].zoneRating ) + return 1 - return 0 - } ) - entity chosenZone = possibleZones[ minint( RandomInt( 3 ), possibleZones.len() - 1 ) ] - - if ( spawnStateSpawnzones.shouldCreateMinimapSpawnzones ) - { - entity oldEnt - if ( team in spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts ) - oldEnt = spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] - - spawnStateSpawnzones.activeTeamSpawnzoneMinimapEnts[ team ] <- CreateTeamSpawnZoneEntity( chosenZone, team ) - if ( IsValid( oldEnt ) ) - oldEnt.Destroy() - } - - spawnStateSpawnzones.activeTeamSpawnzones[ team ] <- chosenZone - - return spawnStateSpawnzones.activeTeamSpawnzones[ team ] + return 0 } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut index 7a7498b8c..4bf195b6d 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_loadouts.nut @@ -1509,7 +1509,7 @@ string function GetValidatedPersistentLoadoutValue( entity player, string loadou { printt( "Invalid Loadout Property: ", loadoutType, loadoutIndex, loadoutProperty, value ) value = ResetLoadoutPropertyToDefault( player, loadoutType, loadoutIndex, loadoutProperty ) //TODO: This will call player.SetPersistentVar() directly. Awkward to do this in a getter function - ClientCommand( player, "disconnect #RESETTING_LOADOUT", 0 ) //Kick player out with a "Resetting Invalid Loadout" message. Mainly necessary so UI/Client script don't crash out later with known, bad data from persistence + NSDisconnectPlayer( player, "#RESETTING_LOADOUT" ) // Kick player out with a "Resetting Invalid Loadout" message. Mainly necessary so UI/Client script don't crash out later with known, bad data from persistence } } @@ -1519,7 +1519,8 @@ string function GetValidatedPersistentLoadoutValue( entity player, string loadou { printt( "Invalid Loadout Property: ", loadoutType, loadoutIndex, loadoutProperty, value ) value = ResetLoadoutPropertyToDefault( player, loadoutType, loadoutIndex, loadoutProperty ) //TODO: This will call player.SetPersistentVar() directly. Awkward to do this in a getter function - ClientCommand( player, "disconnect #RESETTING_LOADOUT", 0 ) //Kick player out with a "Resetting Invalid Loadout" message. Mainly necessary so UI/Client script don't crash out later with known, bad data from persistence + NSDisconnectPlayer( player, "#RESETTING_LOADOUT" ) // Kick player out with a "Resetting Invalid Loadout" message. Mainly necessary so UI/Client script don't crash out later with known, bad data from persistence + } ValidateSkinAndCamoIndexesAsAPair( player, loadoutType, loadoutIndex, loadoutProperty, value ) //TODO: This is awkward, has the potential to call a SetPersistentLoadoutValue() if skinIndex and camoIndex are not correct as a pair diff --git a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut index 0436a393c..847881b58 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/titan_xp.gnut @@ -1,17 +1,39 @@ global function AddTitanXP +global function AddFDTitanXP void function AddTitanXP( entity player, int amount ) { string titan = GetActiveTitanLoadout( player ).titanClass int oldLevel = TitanGetLevel( player, titan ) + int TitanXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]" ) // increment xp player.SetPersistentVar( "titanXP[" + titan + "]", min( TitanGetXP( player, titan ) + amount, TitanGetMaxXP( titan ) ) ) + Remote_CallFunction_NonReplay( player, "ServerCallback_TitanXPAdded", shTitanXP.titanClasses.find( titan ), TitanGetXP( player, titan ), amount ) // level up notif if ( TitanGetLevel( player, titan ) != oldLevel ) { Remote_CallFunction_NonReplay( player, "ServerCallback_TitanLeveledUp", shTitanXP.titanClasses.find( titan ), TitanGetGen( player, titan ), TitanGetLevel( player, titan ) ) AddPlayerScore( player, "TitanLevelUp" ) + IncrementPlayerChallengeTitanLeveledUp( player ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.TITAN_LEVELED + "]", TitanXPMatch + 1 ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForTitanLevels( player, titan, oldLevel, TitanGetLevel( player, titan ) ) } +} + +void function AddFDTitanXP( entity player, int fdXPamount ) +{ + string titanRef = GetActiveTitanLoadout( player ).titanClass + + player.SetPersistentVar( "fdTitanXP[" + titanRef + "]", FD_TitanGetPreviousXP( player, titanRef ) + fdXPamount ) + int startingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetPreviousXP( player, titanRef ) ) + int endingLevel = FD_TitanGetLevelForXP( titanRef, FD_TitanGetXP( player, titanRef ) ) + + Player_GiveFDUnlockPoints( player, endingLevel - startingLevel ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForFDTitanLevels( player, titanRef, startingLevel, endingLevel ) } \ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut index 4e25e3019..0b0084b3c 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/weapon_xp.gnut @@ -6,15 +6,22 @@ void function AddWeaponXP( entity player, int amount ) entity activeWeapon = player.GetActiveWeapon() string weaponClassname = activeWeapon.GetWeaponClassName() int oldLevel = WeaponGetLevel( player, weaponClassname ) + int WeaponXPMatch = player.GetPersistentVarAsInt( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]" ) // increment xp player.SetPersistentVar( GetItemPersistenceStruct( weaponClassname ) + ".weaponXP", min( WeaponGetXP( player, weaponClassname ) + amount, WeaponGetMaxXP( weaponClassname ) ) ) + Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponXPAdded", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetXP( player, weaponClassname ), amount ) // level up notif if ( WeaponGetLevel( player, weaponClassname ) != oldLevel ) { Remote_CallFunction_NonReplay( player, "ServerCallback_WeaponLeveledUp", shWeaponXP.weaponClassNames.find( weaponClassname ), WeaponGetGen( player, weaponClassname ), WeaponGetLevel( player, weaponClassname ) ) AddPlayerScore( player, "WeaponLevelUp" ) + IncrementPlayerChallengeWeaponLeveledUp( player ) + player.SetPersistentVar( "xp_match[" + XP_TYPE.WEAPON_LEVELED + "]", WeaponXPMatch + 1 ) + + if( ProgressionEnabledForPlayer( player ) ) + AwardRandomItemsForWeaponLevels( player, weaponClassname, oldLevel, WeaponGetLevel( player, weaponClassname ) ) } // proscreen diff --git a/README.md b/README.md index 7b6dfaf02..3d7a9019c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Translation status -[Squirrel](http://www.squirrel-lang.org/squirreldoc/reference/index.html) scripts used to recreate server-side gamelogic and add [custom content](https://r2northstar.gitbook.io/r2northstar-wiki/using-northstar/gamemodes) to the game. +[Squirrel](http://www.squirrel-lang.org/squirreldoc/reference/index.html) scripts used to recreate server-side gamelogic and add [custom content](https://docs.northstar.tf/Wiki/using-northstar/gamemodes/) to the game. ## Contents: @@ -11,7 +11,7 @@ Issues in this repository should be created if they are related to these domains - `Northstar.Client` - Localisation files, UI and client-side scripts. - `Northstar.Coop` - Soon™. - `Northstar.Custom` - Northstar custom content. -- `Northstar.CustomServer` - Server config files and scripts necessary for multiplayer. +- `Northstar.CustomServers` - Server config files and scripts necessary for multiplayer. ### Translating