diff options
46 files changed, 596 insertions, 129 deletions
diff --git a/.gitattributes b/.gitattributes index c6a941dff..1bb89156e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,4 @@ -/Northstar.Client/mod/resource/northstar_client_localisation_*.txt text diff working-tree-encoding=UTF-16LE-BOM
\ No newline at end of file +/Northstar.Client/mod/resource/northstar_client_localisation_*.txt text diff working-tree-encoding=UTF-16LE-BOM + +# Highlight `.gnut` like `.nut` files +*.gnut linguist-language=Squirrel diff --git a/.github/nativefuncs.json b/.github/nativefuncs.json index 13a7edd3d..1148d3e58 100644 --- a/.github/nativefuncs.json +++ b/.github/nativefuncs.json @@ -421,13 +421,7 @@ "helpText":"Returns whether or not a given path leads to a folder.", "returnTypeString":"bool", "argTypes":"string path" - }, - { - "name":"NSPushGameStateData", - "helpText":"", - "returnTypeString":"void", - "argTypes":"struct gamestate" - } + } ], "UI":[ { @@ -509,6 +503,25 @@ "argTypes":"string modName" }, { + "name": "NSIsModDownloadable", + "helpText": "checks whether a mod is verified and can be auto-downloaded", + "returnTypeString": "bool", + "argTypes": "string name, string version" + + }, + { + "name": "NSDownloadMod", + "helpText": "downloads a given mod from distant API to local game profile", + "returnTypeString": "void", + "argTypes": "string name, string version" + }, + { + "name": "NSGetModInstallState", + "helpText": "get status of the mod currently being installed", + "returnTypeString": "ModInstallState", + "argTypes": "" + }, + { "name":"NSReloadMods", "helpText":"", "returnTypeString":"void", @@ -724,12 +737,6 @@ "returnTypeString":"bool", "argTypes":"string path" }, - { - "name":"NSPushUIPresence", - "helpText":"", - "returnTypeString":"void", - "argTypes":"struct presence" - }, { "name":"NSGetMasterServerAuthResult", "helpText":"", diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml index 3a1d1c9a5..773a52b8b 100644 --- a/.github/workflows/add-to-project.yml +++ b/.github/workflows/add-to-project.yml @@ -4,7 +4,7 @@ on: issues: types: - opened - pull_request: + pull_request_target: types: - opened diff --git a/.github/workflows/merge-conflict-auto-label.yml b/.github/workflows/merge-conflict-auto-label.yml new file mode 100644 index 000000000..e237726af --- /dev/null +++ b/.github/workflows/merge-conflict-auto-label.yml @@ -0,0 +1,16 @@ +name: Merge Conflict Auto Label +on: + push: + branches: + - main + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: mschilde/auto-label-merge-conflicts@master + with: + CONFLICT_LABEL_NAME: "merge conflicts" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MAX_RETRIES: 5 + WAIT_MS: 5000 diff --git a/Northstar.Client/mod.json b/Northstar.Client/mod.json index a22f49fdf..44937a2b0 100644 --- a/Northstar.Client/mod.json +++ b/Northstar.Client/mod.json @@ -6,6 +6,10 @@ "InitScript": "cl_northstar_client_init.nut", "ConVars": [ { + "Name": "allow_mod_auto_download", + "DefaultValue": "0" + }, + { "Name": "filter_hide_empty", "DefaultValue": "0" }, @@ -83,6 +87,10 @@ } }, { + "Path": "ui/menu_ns_moddownload.nut", + "RunOn": "UI" + }, + { "Path": "ui/menu_ns_serverbrowser.nut", "RunOn": "UI", "UICallback": { diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt index c7b25a706..e6518febb 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_english.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_english.txt @@ -304,6 +304,7 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a "SHOW_ONLY_DISABLED" "Only Disabled" "SHOW_ONLY_NOT_REQUIRED" "Only Optional Mods" "SHOW_ONLY_REQUIRED" "Only Required Mods" + "MOD_REQUIRED_WARNING" " : This mod may get (un)loaded when joining a server" // Maps menu "HIDE_LOCKED" "Hide locked" @@ -366,5 +367,26 @@ Press Yes if you agree to this. This choice can be changed in the mods menu at a "PROGRESSION_DISABLED_BODY" "^CCCC0000Progression has been disabled.^\n\nTitans, Weapons, Factions, Skins, etc. will all be unlocked and usable at any time.\n\nThis can be changed at any time in the multiplayer lobby." "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Progression can now be enabled!^\n\nNorthstar now supports vanilla progression, meaning you can choose to unlock Weapons, Skins, Titans, etc. through levelling up and completing challenges.\n\nYou can enable progression using the button at the bottom of the lobby screen.\n\nThis can be changed at any time." + + // Mod downloading + "MISSING_MOD" "Missing mod \"%s1\" v%s2" + "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)" + "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..." + "EXTRACTING_MOD_TITLE" "Extracting mod (%s1%)" + "EXTRACTING_MOD_TEXT" "Extracting %s1 v%s2...\n(%s3/%s4 MB)" + "FAILED_DOWNLOADING" "Failed downloading mod" + "FAILED_READING_ARCHIVE" "An error occurred while reading mod archive." + "FAILED_WRITING_TO_DISK" "An error occurred while extracting mod files to the filesystem." + "MOD_FETCHING_FAILED" "Mod archive could not be downloaded from Thunderstore." + "MOD_CORRUPTED" "Downloaded archive checksum does not match verified signature." + "NO_DISK_SPACE_AVAILABLE" "There is not enough space on your disk." + "MOD_FETCHING_FAILED_GENERAL" "Mod extraction failed. Check logs for more details." } } diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_french.txt b/Northstar.Client/mod/resource/northstar_client_localisation_french.txt index 377f69179..b0e579cff 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_french.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_french.txt @@ -353,5 +353,9 @@ Choisissez Oui si vous êtes d'accord. Ce choix peut être modifié à tout inst "TOGGLE_PROGRESSION" "Activer la progression" "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Activer la progression" "PROGRESSION_ANNOUNCEMENT_BODY" "^CCCC0000Le système de progression peut être activé !^\n\nNorthstar supporte désormais le système de progression du jeu original, vous permettant de choisir si vous souhaitez débloquer les armes, skins, titans etc. en gagnant des niveaux et en complétant des défis.\n\nVous pouvez activer la progression en utilisant le bouton en bas de l'écran d'accueil.\n\nCeci peut être changé à tout moment." + "AUTHENTICATION_FAILED_HEADER" "Échec de l'authentification" + "AUTHENTICATION_FAILED_HELP" "Aide" + "AUTHENTICATION_FAILED_ERROR_CODE" "Code d'erreur : ^DB6F2C00%s1^" + "AUTHENTICATION_FAILED_BODY" "L'authentification avec Atlas a échoué." } } diff --git a/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt b/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt index 186346680..50d1ef17f 100644 --- a/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt +++ b/Northstar.Client/mod/resource/northstar_client_localisation_mspanish.txt @@ -319,5 +319,35 @@ Si estas de acuerdo con esto, presiona SI. Esta decision puede ser cambiada en e "INVALID_MASTERSERVER_TOKEN" "Token de jugador expirado o invalido" "JSON_PARSE_ERROR" "Error procesando respuesta json" "UNSUPPORTED_VERSION" "La versión que estas usando ya no esta soportada" + "SHOW_ONLY_REQUIRED" "Solo Mods requeridos" + "player_force_respawn" "Reaparición Forzada" + "TOGGLE_PROGRESSION" "Alternar Progreso" + "PROGRESSION_TOGGLE_ENABLED_HEADER" "Desactivar Progreso?" + "NO_RESULTS" "No hay resultados." + "NO_MODS" "No hay configuraciones disponibles! Instala mas mods en: ^5588FF00northstar.thunderstore.io^0." + "AUTHENTICATION_FAILED_HEADER" "Verificacion fallida" + "AUTHENTICATION_FAILED_BODY" "Autenticación fallada con Atlas!" + "AUTHENTICATION_FAILED_ERROR_CODE" "Codigo de error: ^DB6F2C00%s1^" + "AUTHENTICATION_FAILED_HELP" "Ayuda" + "WILL_RESET_ALL_SETTINGS" "Esto reiniciará TODAS las configuraciones de categoría\nEsto no es reversible" + "WILL_RESET_SETTING" "Esto revertirá %s1 a la configuracion por defecto. \n \nEsto no es revertible." + "MOD_SETTINGS_SERVER" "Servidor" + "MOD_SETTINGS_RESET" "Reiniciar" + "MOD_SETTINGS_RESET_ALL" "Reiniciar todo" + "MOD_REQUIRED_WARNING" " Este mod puede ser deshabilitado al entrar a un servidor" + "MOD_SETTINGS" "Configuracion de Mods" + "NORTHSTAR_BASE_SETTINGS" "Configuracion base de Northstar" + "ONLY_HOST_MATCH_SETTINGS" "Solo el Host puede cambiar ajustes de partida" + "ONLY_HOST_CAN_START_MATCH" "Solo el host puede iniciar la partida" + "MATCH_COUNTDOWN_LENGTH" "Cuenta Atrás de partida privada" + "LOG_UNKNOWN_CLIENTCOMMANDS" "Registro desconocido de comandos de cliente" + "DISALLOWED_TACTICALS" "Tactica Prohibida" + "TACTICAL_REPLACEMENT" "Reemplazo de Tactica" + "DISALLOWED_WEAPONS" "Arma Prohibida" + "REPLACEMENT_WEAPON" "Arma de Reemplazo" + "SHOULD_RETURN_TO_LOBBY" "Volver al lobby al finalizar partida" + "ARE_YOU_SURE" "Seguro?" + "Y_BUTTON_TOGGLE_PROGRESSION" "%[Y_BUTTON|]% Alternar Progreso" + "SHOW_ONLY_NOT_REQUIRED" "Solo Mods Opcionales" } } diff --git a/Northstar.Client/mod/resource/ui/menus/modlist.menu b/Northstar.Client/mod/resource/ui/menus/modlist.menu index da59bcddc..bd350a338 100644 --- a/Northstar.Client/mod/resource/ui/menus/modlist.menu +++ b/Northstar.Client/mod/resource/ui/menus/modlist.menu @@ -488,8 +488,8 @@ resource/ui/menus/modlist.menu { ControlName Label - labelText " : This mod gets (un)loaded automatically" - wide 500 + labelText "#MOD_REQUIRED_WARNING" + auto_wide_tocontents 1 tall 50 visible 0 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 a844478a2..3560fd562 100644 --- a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut +++ b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut @@ -1,3 +1,11 @@ +global enum eDiscordGameState +{ + LOADING = 0 + MAINMENU + LOBBY + INGAME +} + global struct GameStateStruct { string map @@ -15,10 +23,7 @@ global struct GameStateStruct { } global struct UIPresenceStruct { - bool isLoading - bool isLobby - string loadingLevel - string loadedLevel + int gameState } global struct RequiredModInfo @@ -48,3 +53,11 @@ global struct MasterServerAuthResult string errorCode string errorMessage } + +global struct ModInstallState +{ + int status + int progress + int total + float ratio +} diff --git a/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut b/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut index c8a8274a5..f17216fbc 100644 --- a/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut +++ b/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut @@ -1,10 +1,8 @@ untyped globalize_all_functions -void function NorthstarCodeCallback_GenerateGameState() { - - GameStateStruct gs - +GameStateStruct function DiscordRPC_GenerateGameState( GameStateStruct gs ) +{ int highestScore = 0 int secondHighest = 0 @@ -40,6 +38,5 @@ void function NorthstarCodeCallback_GenerateGameState() { gs.timeEnd = expect float(level.nv.roundEndTime - Time()) else gs.timeEnd = expect float(level.nv.gameEndTime - Time()) - - NSPushGameStateData(gs) -}
\ No newline at end of file + return gs +} diff --git a/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut b/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut index cdf1c9815..ce5abe865 100644 --- a/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut +++ b/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut @@ -1,12 +1,16 @@ untyped globalize_all_functions -void function NorthstarCodeCallback_GenerateUIPresence() { - UIPresenceStruct uis +UIPresenceStruct function DiscordRPC_GenerateUIPresence( UIPresenceStruct uis ) +{ + if ( uiGlobal.isLoading ) + uis.gameState = eDiscordGameState.LOADING; + else if ( uiGlobal.loadedLevel == "" ) + uis.gameState = eDiscordGameState.MAINMENU; + else if ( IsLobby() || uiGlobal.loadedLevel == "mp_lobby" ) + uis.gameState = eDiscordGameState.LOBBY; + else + uis.gameState = eDiscordGameState.INGAME; - uis.isLoading = uiGlobal.isLoading - uis.isLobby = IsLobby() - uis.loadingLevel = uiGlobal.loadingLevel - uis.loadedLevel = uiGlobal.loadedLevel - NSPushUIPresence(uis) -}
\ No newline at end of file + return uis +} diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut new file mode 100644 index 000000000..4d299362d --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_moddownload.nut @@ -0,0 +1,117 @@ +global function DownloadMod +global function DisplayModDownloadErrorDialog + +global enum eModInstallStatus +{ + DOWNLOADING, + CHECKSUMING, + EXTRACTING, + DONE, + FAILED, + FAILED_READING_ARCHIVE, + FAILED_WRITING_TO_DISK, + MOD_FETCHING_FAILED, + MOD_CORRUPTED, + NO_DISK_SPACE_AVAILABLE, + NOT_FOUND +} + +const int MB = 1024*1000; + +bool function DownloadMod( RequiredModInfo mod ) +{ + // Downloading mod UI + DialogData dialogData + dialogData.header = Localize( "#DOWNLOADING_MOD_TITLE" ) + dialogData.message = Localize( "#DOWNLOADING_MOD_TEXT", mod.name, mod.version ) + dialogData.showSpinner = true; + + // Prevent user from closing dialog + dialogData.forceChoice = true; + OpenDialog( dialogData ) + + // Save reference to UI elements, to update their content + var menu = GetMenu( "Dialog" ) + var header = Hud_GetChild( menu, "DialogHeader" ) + var body = GetSingleElementByClassname( menu, "DialogMessageClass" ) + + // Start actual mod downloading + NSDownloadMod( mod.name, mod.version ) + + ModInstallState state = NSGetModInstallState() + while ( state.status < eModInstallStatus.DONE ) + { + state = NSGetModInstallState() + UpdateModDownloadDialog( mod, state, menu, header, body ) + WaitFrame() + } + + printt( "Mod status:", state.status ) + + // Close loading dialog + CloseActiveMenu() + + return state.status == eModInstallStatus.DONE +} + +void function UpdateModDownloadDialog( RequiredModInfo mod, ModInstallState state, var menu, var header, var body ) +{ + switch ( state.status ) + { + 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 ) ) ) + break + case eModInstallStatus.CHECKSUMING: + Hud_SetText( header, Localize( "#CHECKSUMING_TITLE" ) ) + Hud_SetText( body, Localize( "#CHECKSUMING_TEXT", mod.name, mod.version ) ) + break + case eModInstallStatus.EXTRACTING: + Hud_SetText( header, Localize( "#EXTRACTING_MOD_TITLE", string( state.ratio ) ) ) + Hud_SetText( body, Localize( "#EXTRACTING_MOD_TEXT", mod.name, mod.version, floor( state.progress / MB ), floor( state.total / MB ) ) ) + break + default: + break + } +} + +void function DisplayModDownloadErrorDialog( string modName ) +{ + ModInstallState state = NSGetModInstallState() + + DialogData dialogData + dialogData.header = Localize( "#FAILED_DOWNLOADING", modName ) + dialogData.image = $"ui/menu/common/dialog_error" + + switch ( state.status ) + { + case eModInstallStatus.FAILED_READING_ARCHIVE: + dialogData.message = Localize( "#FAILED_READING_ARCHIVE" ) + break + case eModInstallStatus.FAILED_WRITING_TO_DISK: + dialogData.message = Localize( "#FAILED_WRITING_TO_DISK" ) + break + case eModInstallStatus.MOD_FETCHING_FAILED: + dialogData.message = Localize( "#MOD_FETCHING_FAILED" ) + break + case eModInstallStatus.MOD_CORRUPTED: + dialogData.message = Localize( "#MOD_CORRUPTED" ) + break + case eModInstallStatus.NO_DISK_SPACE_AVAILABLE: + dialogData.message = Localize( "#NO_DISK_SPACE_AVAILABLE" ) + break + case eModInstallStatus.NOT_FOUND: + dialogData.message = Localize( "#NOT_FOUND" ) + break + case eModInstallStatus.FAILED: + default: + dialogData.message = Localize( "#MOD_FETCHING_FAILED_GENERAL" ) + break + } + + AddDialogButton( dialogData, "#DISMISS" ) + AddDialogFooter( dialogData, "#A_BUTTON_SELECT" ) + AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" ) + + OpenDialog( dialogData ) +} 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 efc8d66ce..1bc8e405b 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut @@ -952,32 +952,64 @@ string function FillInServerModsLabel( array<RequiredModInfo> mods ) void function OnServerSelected( var button ) { + thread OnServerSelected_Threaded( button ) +} + +void function OnServerSelected_Threaded( var button ) +{ if ( NSIsRequestingServerList() || NSGetServerCount() == 0 || file.serverListRequestFailed ) return ServerInfo server = file.focusedServer - file.lastSelectedServer = server + // Count mods that have been successfully downloaded + bool autoDownloadAllowed = GetConVarBool( "allow_mod_auto_download" ) + int downloadedMods = 0; + foreach ( RequiredModInfo mod in server.requiredMods ) { if ( !NSGetModNames().contains( mod.name ) ) { - DialogData dialogData - dialogData.header = "#ERROR" - dialogData.message = format( "Missing mod \"%s\" v%s", mod.name, mod.version ) - dialogData.image = $"ui/menu/common/dialog_error" + // Check if mod can be auto-downloaded + bool modIsVerified = NSIsModDownloadable( mod.name, mod.version ) + + // Display an error message if not + if ( !modIsVerified || !autoDownloadAllowed ) + { + 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" ) + } - #if PC_PROG AddDialogButton( dialogData, "#DISMISS" ) AddDialogFooter( dialogData, "#A_BUTTON_SELECT" ) - #endif // PC_PROG - AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" ) + AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" ) - OpenDialog( dialogData ) + OpenDialog( dialogData ) + + return + } - return + else // Launch download + { + if ( DownloadMod( mod ) ) + { + downloadedMods++ + } + else + { + DisplayModDownloadErrorDialog( mod.name ) + return + } + } } else { @@ -1001,7 +1033,7 @@ void function OnServerSelected( var button ) { DialogData dialogData dialogData.header = "#ERROR" - dialogData.message = format( "Server has mod \"%s\" v%s while we have v%s", mod.name, mod.version, NSGetModVersionByModName( mod.name ) ) + dialogData.message = Localize( "#WRONG_MOD_VERSION", mod.name, mod.version, NSGetModVersionByModName( mod.name ) ) dialogData.image = $"ui/menu/common/dialog_error" #if PC_PROG @@ -1018,6 +1050,13 @@ void function OnServerSelected( var button ) } } + // Make Northstar aware new mods have been added + if ( downloadedMods > 0 ) + { + print( "Some new mods have been downloaded or enabled, reloading mods." ) + NSReloadMods(); + } + if ( server.requiresPassword ) { OnCloseServerBrowserMenu() diff --git a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut index c97c8cdce..2f1bcf025 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut @@ -81,8 +81,9 @@ void function InitMainMenuPanel() headerIndex++ buttonIndex = 0 var multiplayerHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MULTIPLAYER_ALLCAPS" ) - file.mpButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MULTIPLAYER_LAUNCH" ) - Hud_AddEventHandler( file.mpButton, UIE_CLICK, OnPlayMPButton_Activate ) + // "Launch Multiplayer" button removed because we don't support vanilla yet :clueless: + //file.mpButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MULTIPLAYER_LAUNCH" ) + //Hud_AddEventHandler( file.mpButton, UIE_CLICK, OnPlayMPButton_Activate ) file.fdButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#MENU_LAUNCH_NORTHSTAR" ) Hud_AddEventHandler( file.fdButton, UIE_CLICK, OnPlayFDButton_Activate ) Hud_SetLocked( file.fdButton, true ) @@ -169,7 +170,8 @@ void function OnShowMainMenuPanel() #endif // PS4_PROG UpdateSPButtons() - thread UpdatePlayButton( file.mpButton ) + // dont try and update the launch multiplayer button, because it doesn't exist + //thread UpdatePlayButton( file.mpButton ) thread UpdatePlayButton( file.fdButton ) thread MonitorTrialVersionChange() @@ -459,7 +461,8 @@ void function UpdatePlayButton( var button ) message = "" } - ComboButton_SetText( file.mpButton, buttonText ) + // dont try and update the launch multiplayer button, because it doesn't exist + //ComboButton_SetText( file.mpButton, buttonText ) ComboButton_SetText( file.fdButton, "#MENU_LAUNCH_NORTHSTAR" ) //Hud_SetEnabled( file.fdButton, false ) diff --git a/Northstar.Custom/keyvalues/playlists_v2.txt b/Northstar.Custom/keyvalues/playlists_v2.txt index 21934b319..c22e2174d 100644 --- a/Northstar.Custom/keyvalues/playlists_v2.txt +++ b/Northstar.Custom/keyvalues/playlists_v2.txt @@ -47,8 +47,6 @@ playlists max_teams 12 classic_mp 1 - scorelimit 21 // temp until we have a way of dynamically setting non-default scorelimit in code - gamemode_score_hint #GAMEMODE_SCORE_HINT_FFA gamemode_bullet_001 #GAMEMODE_BULLET_FFA_001 gamemode_bullet_002 #GAMEMODE_BULLET_FFA_002 diff --git a/Northstar.Custom/mod.json b/Northstar.Custom/mod.json index 93f371bd0..399311e43 100644 --- a/Northstar.Custom/mod.json +++ b/Northstar.Custom/mod.json @@ -24,6 +24,11 @@ { "Name": "ns_force_melee", "DefaultValue": "" + }, + { + "Name": "ns_show_event_models", + "DefaultValue": "1", + "Flags": "ARCHIVE_PLAYERPROFILE" } ], "Scripts": [ @@ -434,6 +439,20 @@ { "Path": "sh_northstar_safe_io.gnut", "RunOn": "CLIENT || SERVER || UI" + }, + { + "Path": "_event_models.gnut", + "RunOn": "SERVER && LOBBY", + "ServerCallback": { + "Before": "EventModelsInit" + } + }, + { + "Path": "ui/ns_custom_mod_settings.gnut", + "RunOn": "UI", + "UICallback":{ + "Before": "NSCustomModSettings" + } } ], diff --git a/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vmt b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vmt new file mode 100644 index 000000000..22b81e9ac --- /dev/null +++ b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vmt @@ -0,0 +1,18 @@ +"UnlitTexture" +{ + $basetexture "models/northstartree/lightsflicker" + $color "[1.5 1.5 1.5]" + + Proxies + { + + TextureScroll + { + texturescrollvar $basetexturetransform + texturescrollrate 0.33 + texturescrollangle 45 + } + + } + +} diff --git a/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vtf b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vtf Binary files differnew file mode 100644 index 000000000..227756be1 --- /dev/null +++ b/Northstar.Custom/mod/materials/models/northstartree/lightsflicker.vtf diff --git a/Northstar.Custom/mod/models/northstartree/winter_holiday_floor.mdl b/Northstar.Custom/mod/models/northstartree/winter_holiday_floor.mdl Binary files differnew file mode 100644 index 000000000..aaf703634 --- /dev/null +++ b/Northstar.Custom/mod/models/northstartree/winter_holiday_floor.mdl diff --git a/Northstar.Custom/mod/models/northstartree/winter_holiday_tree.mdl b/Northstar.Custom/mod/models/northstartree/winter_holiday_tree.mdl Binary files differnew file mode 100644 index 000000000..4690475f4 --- /dev/null +++ b/Northstar.Custom/mod/models/northstartree/winter_holiday_tree.mdl diff --git a/Northstar.Custom/mod/scripts/vscripts/_event_models.gnut b/Northstar.Custom/mod/scripts/vscripts/_event_models.gnut new file mode 100644 index 000000000..0802d7698 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/_event_models.gnut @@ -0,0 +1,21 @@ +global function EventModelsInit + +void function EventModelsInit() +{ + if( !GetConVarBool( "ns_show_event_models" ) ) + return + + table timeParts = GetUnixTimeParts() + int month = expect int( timeParts[ "month" ] ) + int day = expect int( timeParts[ "day" ] ) + + // 18th December to 6th January + if( ( ( month == 12 ) && ( day >= 18 ) ) || ( ( month == 1 ) && ( day <= 6 ) ) ) + { + PrecacheModel( $"models/northstartee/winter_holiday_tree.mdl" ) + PrecacheModel( $"models/northstartree/winter_holiday_floor.mdl" ) + + CreatePropDynamic( $"models/northstartree/winter_holiday_tree.mdl", < -60, 740, 30 >, < 0, 0, 0 >, SOLID_VPHYSICS, 1000 ) + CreatePropDynamic( $"models/northstartree/winter_holiday_floor.mdl", < -60, 740, 30 >, < 0, 0, 0 >, SOLID_VPHYSICS, 1000 ) + } +} diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut index 8f34541bf..ad46b42e2 100644 --- a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut @@ -18,14 +18,6 @@ void function GamemodeGG_Init() AddCallback_GameStateEnter( eGameState.WinnerDetermined, OnWinnerDetermined ) AddCallback_GGEarnMeterFull( OnGGEarnMeterFilled ) - - // set scorelimit if it's wrong, sort of a jank way to do it but best i've got rn - try - { - if ( GetCurrentPlaylistVarInt( "scorelimit", GetGunGameWeapons().len() ) != GetGunGameWeapons().len() ) - SetPlaylistVarOverride( "scorelimit", GetGunGameWeapons().len().tostring() ) - } - catch ( ex ) {} } void function OnPlayerDisconnected(entity player) diff --git a/Northstar.Custom/mod/scripts/vscripts/ui/ns_custom_mod_settings.gnut b/Northstar.Custom/mod/scripts/vscripts/ui/ns_custom_mod_settings.gnut new file mode 100644 index 000000000..5a7d80b76 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/ui/ns_custom_mod_settings.gnut @@ -0,0 +1,8 @@ +global function NSCustomModSettings + +void function NSCustomModSettings() +{ + ModSettings_AddModTitle( "Northstar Custom" , 2 ) + ModSettings_AddModCategory( "Event Models" ) + ModSettings_AddEnumSetting( "ns_show_event_models", "Show Event Models", [ "#SETTING_OFF", "#SETTING_ON" ], 2 ) +} diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut index 011389670..defb1a56f 100644 --- a/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/_arc_cannon.nut @@ -536,12 +536,6 @@ function ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum = 1 ) thread CreateArcCannonBeam( zapInfo.weapon, target, beamStartPos, beamEndPos, zapInfo.player, ARC_CANNON_BEAM_LIFETIME, zapInfo.radius, boltWidth, 5, true, firstBeam ) #if SERVER - local isMissile = ( target.GetClassName() == "rpg_missile" ) - if ( !isMissile ) - wait ARC_CANNON_FORK_DELAY - else - wait 0.05 - local deathPackage = damageTypes.arcCannon float damageAmount @@ -569,40 +563,20 @@ function ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum = 1 ) bool hasFastPacitor = false bool noArcing = false - if ( IsValid( zapInfo.weapon ) ) - { - entity weap = expect entity( zapInfo.weapon ) - hasFastPacitor = weap.GetWeaponInfoFileKeyField( "push_apart" ) != null && weap.GetWeaponInfoFileKeyField( "push_apart" ) == 1 - noArcing = weap.GetWeaponInfoFileKeyField( "no_arcing" ) != null && weap.GetWeaponInfoFileKeyField( "no_arcing" ) == 1 - } + entity weapon = expect entity( zapInfo.weapon ) + hasFastPacitor = weapon.GetWeaponInfoFileKeyField( "push_apart" ) != null && weapon.GetWeaponInfoFileKeyField( "push_apart" ) == 1 + noArcing = weapon.GetWeaponInfoFileKeyField( "no_arcing" ) != null && weapon.GetWeaponInfoFileKeyField( "no_arcing" ) == 1 + float critScale = weapon.GetWeaponSettingFloat( eWeaponVar.critical_hit_damage_scale ) if ( target.GetArmorType() == ARMOR_TYPE_HEAVY ) { - if ( IsValid( zapInfo.weapon ) ) - { - entity weapon = expect entity( zapInfo.weapon ) - damageMin = weapon.GetWeaponSettingInt( damageFarValueTitanArmor ) - damageMax = weapon.GetWeaponSettingInt( damageNearValueTitanArmor ) - } - else - { - damageMin = 100 - damageMax = zapInfo.player.IsNPC() ? 1200 : 800 - } + damageMin = weapon.GetWeaponSettingInt( damageFarValueTitanArmor ) + damageMax = weapon.GetWeaponSettingInt( damageNearValueTitanArmor ) } else { - if ( IsValid( zapInfo.weapon ) ) - { - entity weapon = expect entity( zapInfo.weapon ) - damageMin = weapon.GetWeaponSettingInt( damageFarValue ) - damageMax = weapon.GetWeaponSettingInt( damageNearValue ) - } - else - { - damageMin = 120 - damageMax = zapInfo.player.IsNPC() ? 140 : 275 - } + damageMin = weapon.GetWeaponSettingInt( damageFarValue ) + damageMax = weapon.GetWeaponSettingInt( damageNearValue ) if ( target.IsNPC() ) { @@ -612,11 +586,10 @@ function ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum = 1 ) } - local chargeRatio = GetArcCannonChargeFraction( zapInfo.weapon ) - if ( IsValid( zapInfo.weapon ) && !zapInfo.weapon.GetWeaponSettingBool( eWeaponVar.charge_require_input ) ) + local chargeRatio = GetArcCannonChargeFraction( weapon ) + if ( !weapon.GetWeaponSettingBool( eWeaponVar.charge_require_input ) ) { // use distance for damage if the weapon auto-fires - entity weapon = expect entity( zapInfo.weapon ) float nearDist = weapon.GetWeaponSettingFloat( damageNearDistance ) float farDist = weapon.GetWeaponSettingFloat( damageFarDistance ) @@ -629,10 +602,19 @@ function ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum = 1 ) damageAmount = GraphCapped( zapInfo.chargeFrac, 0, chargeRatio, damageMin, damageMax ) } local damageFalloff = ARC_CANNON_DAMAGE_FALLOFF_SCALER - if ( IsValid( zapInfo.weapon ) && zapInfo.weapon.HasMod( "splitter" ) ) + if ( weapon.HasMod( "splitter" ) ) damageFalloff = SPLITTER_DAMAGE_FALLOFF_SCALER damageAmount *= pow( damageFalloff, chainNum - 1 ) + local isMissile = ( target.GetClassName() == "rpg_missile" ) + if ( !isMissile ) + wait ARC_CANNON_FORK_DELAY + else + wait 0.05 + + if ( !IsValid( target ) || !IsValid( zapInfo.player ) ) + return + local dmgSourceID = zapInfo.dmgSourceID // Update Later - This shouldn't be done here, this is not where we determine if damage actually happened to the target @@ -660,13 +642,13 @@ function ZapTarget( zapInfo, target, beamStartPos, beamEndPos, chainNum = 1 ) // Do 3rd person effect on the body asset effect string tag - target.TakeDamage( damageAmount, zapInfo.player, zapInfo.player, { origin = beamEndPos, force = Vector(0,0,0), scriptType = deathPackage, weapon = zapInfo.weapon, damageSourceId = dmgSourceID,criticalHitScale = zapInfo.weapon.GetWeaponSettingFloat( eWeaponVar.critical_hit_damage_scale ) } ) + target.TakeDamage( damageAmount, zapInfo.player, zapInfo.player, { origin = beamEndPos, force = Vector(0,0,0), scriptType = deathPackage, weapon = zapInfo.weapon, damageSourceId = dmgSourceID,criticalHitScale = critScale } ) //vector dir = Normalize( beamEndPos - beamStartPos ) //vector velocity = dir * 600 //PushPlayerAway( target, velocity ) //PushPlayerAway( expect entity( zapInfo.player ), -velocity ) - if ( IsValid( zapInfo.weapon ) && hasFastPacitor ) + if ( IsValid( weapon ) && hasFastPacitor ) { if ( IsAlive( target ) && IsAlive( expect entity( zapInfo.player ) ) && target.IsTitan() ) { diff --git a/Northstar.Custom/paks/bt.rpak b/Northstar.Custom/paks/bt.rpak Binary files differnew file mode 100644 index 000000000..7a4b9e31a --- /dev/null +++ b/Northstar.Custom/paks/bt.rpak diff --git a/Northstar.Custom/paks/bt.starpak b/Northstar.Custom/paks/bt.starpak Binary files differnew file mode 100644 index 000000000..70549d51b --- /dev/null +++ b/Northstar.Custom/paks/bt.starpak diff --git a/Northstar.Custom/paks/giftwrap.rpak b/Northstar.Custom/paks/giftwrap.rpak Binary files differnew file mode 100644 index 000000000..7b9200b30 --- /dev/null +++ b/Northstar.Custom/paks/giftwrap.rpak diff --git a/Northstar.Custom/paks/giftwrap.starpak b/Northstar.Custom/paks/giftwrap.starpak Binary files differnew file mode 100644 index 000000000..46ea6d8d2 --- /dev/null +++ b/Northstar.Custom/paks/giftwrap.starpak diff --git a/Northstar.Custom/paks/leaves.rpak b/Northstar.Custom/paks/leaves.rpak Binary files differnew file mode 100644 index 000000000..b17346dd7 --- /dev/null +++ b/Northstar.Custom/paks/leaves.rpak diff --git a/Northstar.Custom/paks/leaves.starpak b/Northstar.Custom/paks/leaves.starpak Binary files differnew file mode 100644 index 000000000..b37aa5230 --- /dev/null +++ b/Northstar.Custom/paks/leaves.starpak diff --git a/Northstar.Custom/paks/rpak.json b/Northstar.Custom/paks/rpak.json index 743468b4f..522c558ba 100644 --- a/Northstar.Custom/paks/rpak.json +++ b/Northstar.Custom/paks/rpak.json @@ -1,5 +1,10 @@ {
"Postload": {
- "mp_weapon_shotgun_doublebarrel.rpak": "common.rpak"
+ "mp_weapon_shotgun_doublebarrel.rpak": "common.rpak",
+ "leaves.rpak": "common.rpak",
+ "tree_stump.rpak": "common.rpak",
+ "bt.rpak": "common.rpak",
+ "giftwrap.rpak": "common.rpak",
+ "snow.rpak": "common.rpak"
}
-}
\ No newline at end of file +}
diff --git a/Northstar.Custom/paks/snow.rpak b/Northstar.Custom/paks/snow.rpak Binary files differnew file mode 100644 index 000000000..4756b6c7f --- /dev/null +++ b/Northstar.Custom/paks/snow.rpak diff --git a/Northstar.Custom/paks/snow.starpak b/Northstar.Custom/paks/snow.starpak Binary files differnew file mode 100644 index 000000000..7f3dbf19b --- /dev/null +++ b/Northstar.Custom/paks/snow.starpak diff --git a/Northstar.Custom/paks/tree_stump.rpak b/Northstar.Custom/paks/tree_stump.rpak Binary files differnew file mode 100644 index 000000000..3cdf18662 --- /dev/null +++ b/Northstar.Custom/paks/tree_stump.rpak diff --git a/Northstar.Custom/paks/tree_stump.starpak b/Northstar.Custom/paks/tree_stump.starpak Binary files differnew file mode 100644 index 000000000..b233176eb --- /dev/null +++ b/Northstar.Custom/paks/tree_stump.starpak diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut index a5c3e2709..a23a68b0c 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_items.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_items.nut @@ -10231,6 +10231,10 @@ void function StatUnlock_Unlocked( entity player, string itemRef, string parentR if ( IsItemNew( player, itemRef, parentRef ) ) return + // early out if the player has progression disabled + if ( !ProgressionEnabledForPlayer( player ) ) + return + int refGuid = file.itemRefToGuid[itemRef] int parentRefGuid = parentRef == "" ? 0 : file.itemRefToGuid[parentRef] diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut index 24dbb62d8..103efe6f6 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/_loadouts_mp.gnut @@ -201,6 +201,10 @@ bool function ClientCommandCallback_SwapSecondaryAndWeapon3PersistentLoadoutData // get loadout int index = args[0].tointeger() + + if ( !IsValidPilotLoadoutIndex(index) ) + return false + PilotLoadoutDef loadout = GetPilotLoadoutFromPersistentData( player, index ) // swap loadouts diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut index bab7eaede..9288f75e3 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut @@ -295,6 +295,10 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga }) entity attacker = DamageInfo_GetAttacker( damageInfo ) + entity inflictor = DamageInfo_GetInflictor( damageInfo ) + int eHandle = attacker.GetEncodedEHandle() + if ( inflictor && ShouldTryUseProjectileReplay( player, attacker, damageInfo, false ) ) + eHandle = inflictor.GetEncodedEHandle() int methodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo ) table<int, bool> alreadyAssisted @@ -374,7 +378,7 @@ void function PostDeathThread_MP( entity player, var damageInfo ) // based on ga if ( "respawnTime" in attacker.s ) respawnTime = Time() - expect float ( attacker.s.respawnTime ) - thread PlayerWatchesKillReplayWrapper( player, attacker, respawnTime, timeOfDeath, beforeTime, replayTracker ) + thread PlayerWatchesKillReplayWrapper( player, attacker, eHandle, respawnTime, timeOfDeath, beforeTime, replayTracker ) } player.SetPlayerSettings( "spectator" ) // prevent a crash with going from titan => pilot on respawn @@ -432,7 +436,7 @@ void function ForceRespawnMeSignalAfterDelay( entity player, int delay = 5 ) player.Signal( "RespawnMe" ) } -void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, float timeSinceAttackerSpawned, float timeOfDeath, float beforeTime, table replayTracker ) +void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, int eHandle, float timeSinceAttackerSpawned, float timeOfDeath, float beforeTime, table replayTracker ) { player.EndSignal( "RespawnMe" ) player.EndSignal( "OnRespawned" ) @@ -455,7 +459,7 @@ void function PlayerWatchesKillReplayWrapper( entity player, entity attacker, fl }) player.SetPredictionEnabled( false ) - PlayerWatchesKillReplay( player, attacker.GetEncodedEHandle(), attacker.GetIndexForEntity(), timeSinceAttackerSpawned, timeOfDeath, beforeTime, replayTracker ) + PlayerWatchesKillReplay( player, eHandle, attacker.GetIndexForEntity(), timeSinceAttackerSpawned, timeOfDeath, beforeTime, replayTracker ) } void function DecideRespawnPlayer( entity player ) @@ -628,6 +632,10 @@ void function SetRecalculateRespawnAsTitanStartPointCallback( entity functionref bool function ShouldEntTakeDamage_SPMP( entity ent, var damageInfo ) { + // dropships are immune to being crushed + if ( ( IsDropship( ent ) || IsEvacDropship( ent ) ) && IsTitanCrushDamage( damageInfo ) ) + return false + return true } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index 3426cec52..4c52a9bfd 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -13,6 +13,8 @@ global function SetTimerBased global function SetShouldUseRoundWinningKillReplay global function SetRoundWinningKillReplayKillClasses global function SetRoundWinningKillReplayAttacker +global function SetCallback_TryUseProjectileReplay +global function ShouldTryUseProjectileReplay global function SetWinner global function SetTimeoutWinnerDecisionFunc global function AddTeamScore @@ -48,13 +50,28 @@ struct { float roundWinningKillReplayTime entity roundWinningKillReplayVictim entity roundWinningKillReplayAttacker + int roundWinningKillReplayInflictorEHandle // this is either the inflictor or the attacker int roundWinningKillReplayMethodOfDeath float roundWinningKillReplayTimeOfDeath float roundWinningKillReplayHealthFrac array<void functionref()> roundEndCleanupCallbacks + 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() { // todo: using the pin telemetry function here, weird and was done veeery early on before i knew how this all worked, should use a different one @@ -319,6 +336,7 @@ void function GameStateEnter_WinnerDetermined_Threaded() WaitFrame() // prevent a race condition with PlayerWatchesRoundWinningKillReplay file.roundWinningKillReplayAttacker = null // clear this + file.roundWinningKillReplayInflictorEHandle = -1 if ( killcamsWereEnabled ) SetKillcamsEnabled( true ) @@ -394,7 +412,7 @@ void function PlayerWatchesRoundWinningKillReplay( entity player, float replayLe if ( IsValid( attacker ) ) { player.SetKillReplayDelay( Time() - replayLength, THIRD_PERSON_KILL_REPLAY_ALWAYS ) - player.SetKillReplayInflictorEHandle( attacker.GetEncodedEHandle() ) + player.SetKillReplayInflictorEHandle( file.roundWinningKillReplayInflictorEHandle ) player.SetKillReplayVictim( file.roundWinningKillReplayVictim ) player.SetViewIndex( attacker.GetIndexForEntity() ) player.SetIsReplayRoundWinning( true ) @@ -460,6 +478,7 @@ void function GameStateEnter_SwitchingSides_Threaded() svGlobal.levelEnt.Signal( "RoundEnd" ) // might be good to get a new signal for this? not 100% necessary tho i think SetServerVar( "switchedSides", 1 ) file.roundWinningKillReplayAttacker = null // reset this after replay + file.roundWinningKillReplayInflictorEHandle = -1 if ( file.usePickLoadoutScreen ) SetGameState( eGameState.PickLoadout ) @@ -483,7 +502,7 @@ void function PlayerWatchesSwitchingSidesKillReplay( entity player, bool doRepla entity attacker = file.roundWinningKillReplayAttacker player.SetKillReplayDelay( Time() - replayLength, THIRD_PERSON_KILL_REPLAY_ALWAYS ) - player.SetKillReplayInflictorEHandle( attacker.GetEncodedEHandle() ) + player.SetKillReplayInflictorEHandle( file.roundWinningKillReplayInflictorEHandle ) player.SetKillReplayVictim( file.roundWinningKillReplayVictim ) player.SetViewIndex( attacker.GetIndexForEntity() ) player.SetIsReplayRoundWinning( true ) @@ -578,6 +597,9 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) return } + entity inflictor = DamageInfo_GetInflictor( damageInfo ) + bool shouldUseInflictor = IsValid( inflictor ) && ShouldTryUseProjectileReplay( victim, attacker, damageInfo, true ) + // set round winning killreplay info here if we're tracking pilot kills // todo: make this not count environmental deaths like falls, unsure how to prevent this if ( file.roundWinningKillReplayTrackPilotKills && victim != attacker && attacker != svGlobal.worldspawn && IsValid( attacker ) ) @@ -587,6 +609,7 @@ void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) file.roundWinningKillReplayTime = Time() file.roundWinningKillReplayVictim = victim file.roundWinningKillReplayAttacker = attacker + file.roundWinningKillReplayInflictorEHandle = ( shouldUseInflictor ? inflictor : attacker ).GetEncodedEHandle() file.roundWinningKillReplayMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo ) file.roundWinningKillReplayTimeOfDeath = Time() file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker ) @@ -637,6 +660,9 @@ void function OnTitanKilled( entity victim, var damageInfo ) return } + entity inflictor = DamageInfo_GetInflictor( damageInfo ) + bool shouldUseInflictor = IsValid( inflictor ) && ShouldTryUseProjectileReplay( victim, DamageInfo_GetAttacker( damageInfo ), damageInfo, true ) + // set round winning killreplay info here if we're tracking titan kills // todo: make this not count environmental deaths like falls, unsure how to prevent this entity attacker = DamageInfo_GetAttacker( damageInfo ) @@ -647,6 +673,7 @@ void function OnTitanKilled( entity victim, var damageInfo ) file.roundWinningKillReplayTime = Time() file.roundWinningKillReplayVictim = victim file.roundWinningKillReplayAttacker = attacker + file.roundWinningKillReplayInflictorEHandle = ( shouldUseInflictor ? inflictor : attacker ).GetEncodedEHandle() file.roundWinningKillReplayMethodOfDeath = DamageInfo_GetDamageSourceIdentifier( damageInfo ) file.roundWinningKillReplayTimeOfDeath = Time() file.roundWinningKillReplayHealthFrac = GetHealthFrac( attacker ) @@ -761,11 +788,12 @@ void function SetRoundWinningKillReplayKillClasses( bool pilot, bool titan ) file.roundWinningKillReplayTrackTitanKills = titan // player kills in titans should get tracked anyway, might be worth renaming this } -void function SetRoundWinningKillReplayAttacker( entity attacker ) +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() } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut index 0b55e9ffd..be20982df 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_score.nut @@ -28,6 +28,10 @@ void function InitPlayerForScoreEvents( entity player ) player.s.currentKillstreak <- 0 player.s.lastKillTime <- 0.0 player.s.currentTimedKillstreak <- 0 + player.s.lastKillTime_Mayhem <- 0.0 + player.s.currentTimedKillstreak_Mayhem <- 0 + player.s.lastKillTime_Onslaught <- 0.0 + player.s.currentTimedKillstreak_Onslaught <- 0 } void function AddPlayerScore( entity targetPlayer, string scoreEventName, entity associatedEnt = null, string noideawhatthisis = "", int pointValueOverride = -1 ) @@ -93,6 +97,7 @@ void function ScoreEvent_PlayerKilled( entity victim, entity attacker, var damag victim.s.currentTimedKillstreak = 0 victim.p.numberOfDeathsSinceLastKill++ // this is reset on kill + victim.p.lastKiller = attacker // have to do this early before we reset victim's player killstreaks // nemesis when you kill a player that is dominating you @@ -131,12 +136,20 @@ void function ScoreEvent_PlayerKilled( entity victim, entity attacker, var damag attacker.p.numberOfDeathsSinceLastKill = 0 } + // revenge + quick revenge + if ( attacker.p.lastKiller == victim ) + { + if ( Time() - GetPlayerLastRespawnTime( attacker ) < QUICK_REVENGE_TIME_LIMIT ) + AddPlayerScore( attacker, "QuickRevenge" ) + else + AddPlayerScore( attacker, "Revenge" ) + } // untimed killstreaks attacker.s.currentKillstreak++ - if ( attacker.s.currentKillstreak == 3 ) + if ( attacker.s.currentKillstreak == KILLINGSPREE_KILL_REQUIREMENT ) AddPlayerScore( attacker, "KillingSpree" ) - else if ( attacker.s.currentKillstreak == 5 ) + else if ( attacker.s.currentKillstreak == RAMPAGE_KILL_REQUIREMENT ) AddPlayerScore( attacker, "Rampage" ) // increment untimed killstreaks against specific players @@ -202,18 +215,23 @@ void function ScoreEvent_TitanKilled( entity victim, entity attacker, var damage AddPlayerScore( attacker, "KillTitan" ) } - table<int, bool> alreadyAssisted - foreach( DamageHistoryStruct attackerInfo in victim.e.recentDamageHistory ) + entity soul = victim.GetTitanSoul() + if ( IsValid( soul ) ) { - if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == victim ) - continue - - bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false - if( attackerInfo.attacker != attacker && !exists ) + table<int, bool> alreadyAssisted + + foreach( DamageHistoryStruct attackerInfo in soul.e.recentDamageHistory ) { - alreadyAssisted[attackerInfo.attacker.GetEncodedEHandle()] <- true - AddPlayerScore(attackerInfo.attacker, "TitanAssist" ) - Remote_CallFunction_NonReplay( attackerInfo.attacker, "ServerCallback_SetAssistInformation", attackerInfo.damageSourceId, attacker.GetEncodedEHandle(), victim.GetEncodedEHandle(), attackerInfo.time ) + if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == soul ) + continue + + bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false + if( attackerInfo.attacker != attacker && !exists ) + { + alreadyAssisted[attackerInfo.attacker.GetEncodedEHandle()] <- true + AddPlayerScore(attackerInfo.attacker, "TitanAssist" ) + Remote_CallFunction_NonReplay( attackerInfo.attacker, "ServerCallback_SetAssistInformation", attackerInfo.damageSourceId, attacker.GetEncodedEHandle(), soul.GetEncodedEHandle(), attackerInfo.time ) + } } } @@ -229,6 +247,39 @@ void function ScoreEvent_NPCKilled( entity victim, entity attacker, var damageIn AddPlayerScore( attacker, ScoreEventForNPCKilled( victim, damageInfo ), victim ) } catch ( ex ) {} + + if ( !attacker.IsPlayer() ) + return + + // mayhem/onslaught (timed killstreaks vs AI) + + // reset before checking + if ( Time() - attacker.s.lastKillTime_Mayhem > MAYHEM_REQUIREMENT_TIME ) + { + attacker.s.currentTimedKillstreak_Mayhem = 0 + attacker.s.lastKillTime_Mayhem = Time() + } + if ( Time() - attacker.s.lastKillTime_Mayhem <= MAYHEM_REQUIREMENT_TIME ) + { + attacker.s.currentTimedKillstreak_Mayhem++ + + if ( attacker.s.currentTimedKillstreak_Mayhem == MAYHEM_REQUIREMENT_KILLS ) + AddPlayerScore( attacker, "Mayhem" ) + } + + // reset before checking + if ( Time() - attacker.s.lastKillTime_Onslaught > ONSLAUGHT_REQUIREMENT_TIME ) + { + attacker.s.currentTimedKillstreak_Onslaught = 0 + attacker.s.lastKillTime_Onslaught = Time() + } + if ( Time() - attacker.s.lastKillTime_Onslaught <= ONSLAUGHT_REQUIREMENT_TIME ) + { + attacker.s.currentTimedKillstreak_Onslaught++ + + if ( attacker.s.currentTimedKillstreak_Onslaught == ONSLAUGHT_REQUIREMENT_KILLS ) + AddPlayerScore( attacker, "Onslaught" ) + } } void function ScoreEvent_MatchComplete( int winningTeam ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut index aa2fc1089..510a9b7e0 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_spectator.gnut @@ -170,6 +170,9 @@ void function SpectatorFunc_Default( entity player ) { player.SetObserverTarget( target ) player.StartObserverMode( OBS_MODE_CHASE ) + // the delay of 0.1 seems to fix the spec_mode command not working + // when using the keybind + player.SetSpecReplayDelay( 0.1 ) } catch ( ex ) { } } @@ -215,9 +218,12 @@ bool function ClientCommandCallback_spec_mode( entity player, array<string> args else if ( player.GetObserverMode() == OBS_MODE_IN_EYE ) { // set to third person spectate - player.SetSpecReplayDelay( 0.0 ) + + // the delay of 0.1 seems to fix the spec_mode command not working + // when using the keybind + player.SetSpecReplayDelay( 0.1 ) player.StartObserverMode( OBS_MODE_CHASE ) } return true -}
\ No newline at end of file +} diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut index 208e6da12..bd64e4caa 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_stats.nut @@ -264,7 +264,7 @@ void function Stats_IncrementStat( entity player, string statCategory, string st // persistence string, we can't save the persistence so we have to just return if ( str != saveVar ) { - printt( ex ) + //printt( ex, str, GetMapName(), mode ) // Commented out due to spamming logs on invalid modes (e.g. Gun Game, Infection, ...) return } } @@ -571,9 +571,11 @@ void function HandleKillStats( entity victim, entity attacker, var damageInfo ) // assistsTotal ( weapon_kill_stats ) // note: eww table<int, bool> alreadyAssisted - foreach( DamageHistoryStruct attackerInfo in victim.e.recentDamageHistory ) + // titans store their recentDamageHistory in the soul + entity assistVictim = ( victim.IsTitan() && IsValid( victim.GetTitanSoul() ) ) ? victim.GetTitanSoul() : victim + foreach( DamageHistoryStruct attackerInfo in assistVictim.e.recentDamageHistory ) { - if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == victim ) + if ( !IsValid( attackerInfo.attacker ) || !attackerInfo.attacker.IsPlayer() || attackerInfo.attacker == assistVictim ) continue bool exists = attackerInfo.attacker.GetEncodedEHandle() in alreadyAssisted ? true : false @@ -585,7 +587,7 @@ void function HandleKillStats( entity victim, entity attacker, var damageInfo ) string source = DamageSourceIDToString( attackerInfo.damageSourceId ) if ( IsValidStatItemString( source ) ) - Stats_IncrementStat( attacker, "weapon_kill_stats", "assistsTotal", source, 1.0 ) + Stats_IncrementStat( attackerInfo.attacker, "weapon_kill_stats", "assistsTotal", source, 1.0 ) } } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut index ceb5e837c..2dc88d0d4 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_progression.nut @@ -38,6 +38,7 @@ void function Progression_Init() #if SERVER AddCallback_OnClientDisconnected( OnClientDisconnected ) AddClientCommandCallback( "ns_progression", ClientCommand_SetProgression ) + AddClientCommandCallback( "ns_resettitanaegis", ClientCommand_ResetTitanAegis ) AddCallback_GameStateEnter( eGameState.Playing, OnPlaying ) #elseif CLIENT AddCallback_OnClientScriptInit( OnClientScriptInit ) @@ -84,6 +85,28 @@ bool function ClientCommand_SetProgression( entity player, array<string> args ) return true } + +/// Resets a specific Titan's Aegis rank back to `0` +/// * `player` - The player entity to perform the action on +/// * `args` - The arguments passed from the client command. `args[0]` should be an integer corresponding to the index of the Titan to reset. +/// +/// Returns `true` on success and `false` on missing args. +bool function ClientCommand_ResetTitanAegis( entity player, array<string> args ) +{ + if ( !args.len() ) + return false + + int suitIndex = args[0].tointeger() + player.SetPersistentVar( "titanFDUnlockPoints[" + suitIndex + "]", 0 ) + player.SetPersistentVar( "previousFDUnlockPoints[" + suitIndex + "]", 0 ) + player.SetPersistentVar( "fdTitanXP[" + suitIndex + "]", 0 ) + player.SetPersistentVar( "fdPreviousTitanXP[" + suitIndex + "]", 0 ) + + // Refresh Highest Aegis Titan since we might get all of them back to 1 if players wants + RecalculateHighestTitanFDLevel( player ) + + return true +} #endif #if CLIENT @@ -268,7 +291,7 @@ void function ValidateEquippedItems( entity player ) // camoIndex if ( loadout.skinIndex == TITAN_SKIN_INDEX_CAMO ) { - array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN ) + array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN_TITAN ) if ( loadout.camoIndex >= camoSkins.len() || loadout.camoIndex < 0 ) { printt( " - INVALID TITAN CAMO/SKIN, RESETTING" ) @@ -363,7 +386,7 @@ void function ValidateEquippedItems( entity player ) // primeCamoIndex if ( loadout.primeSkinIndex == TITAN_SKIN_INDEX_CAMO ) { - array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN ) + array<ItemData> camoSkins = GetAllItemsOfType( eItemTypes.CAMO_SKIN_TITAN ) if ( loadout.primeCamoIndex >= camoSkins.len() || loadout.primeCamoIndex < 0 ) { printt( " - INVALID TITAN CAMO/SKIN, RESETTING" ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut index 9e7629858..2ca051cf8 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/sh_utility_all.gnut @@ -348,6 +348,14 @@ string function GetMapDisplayDesc( string mapname ) return "#" + mapname + "_CLASSIC_DESC" } +/// Sends a string message to player +/// * `baseString` - The input string to search through +/// * `searchString` - Find this substring... +/// * `replaceString` - ...and replace with this substring +/// * `replaceAll` - Whether to replace all occurences or just the first +/// * `caseInsensitive` - Whether to consider casing (upper/lower) +/// +/// Returns the updated string string function StringReplace( string baseString, string searchString, string replaceString, bool replaceAll = false, bool caseInsensitive = false ) { bool loopedOnce = false @@ -1532,7 +1540,23 @@ array<string> function GetAvailableTitanRefs( entity player ) return availableTitanRefs } +/// Gets the highest Titan FD level and stores it in the corresponding persistent var. +/// * `player` - The player entity to perform the action on #if MP +void function RecalculateHighestTitanFDLevel( entity player ) +{ + int enumCount = PersistenceGetEnumCount( "titanClasses" ) + int highestAegis = 0 + for ( int i = 0; i < enumCount; i++ ) + { + string enumName = PersistenceGetEnumItemNameForIndex( "titanClasses", i ) + int aegisLevel = FD_TitanGetLevelForXP( enumName, FD_TitanGetXP( player, enumName ) ) + if ( highestAegis < aegisLevel ) + highestAegis = aegisLevel + } + player.SetPersistentVar( "fdStats.highestTitanFDLevel", highestAegis ) +} + string function GetTitanRefForLoadoutIndex( entity player, int loadoutIndex ) { TitanLoadoutDef loadout = GetTitanLoadoutFromPersistentData( player, loadoutIndex ) @@ -1,4 +1,7 @@ # NorthstarMods +<a href="https://translate.harmony.tf/engage/northstar/"> +<img src="https://translate.harmony.tf/widgets/northstar/-/client/svg-badge.svg" alt="Translation status" /> +</a> [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. @@ -9,3 +12,11 @@ Issues in this repository should be created if they are related to these domains - `Northstar.Coop` - Soon™. - `Northstar.Custom` - Northstar custom content. - `Northstar.CustomServer` - Server config files and scripts necessary for multiplayer. + +### Translating + +Translations can be submitted via [weblate](https://translate.harmony.tf/projects/northstar/client/). + +<a href="https://translate.harmony.tf/engage/northstar/"> +<img src="https://translate.harmony.tf/widgets/northstar/-/client/multi-auto.svg" alt="Translation status" /> +</a> |