From 554761ab629418048e367c7ba1504acd87b5b6e1 Mon Sep 17 00:00:00 2001 From: NoCatt <86153630+NoCatt@users.noreply.github.com> Date: Sat, 25 Feb 2023 22:52:08 +0100 Subject: Fix chat messages when dead (#574) * Fix chat messages when dead * Spoons fix was better Co-authored-by: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> --- Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Northstar.Client/mod/scripts/vscripts') diff --git a/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut b/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut index 277ed030..db4865ee 100644 --- a/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut +++ b/Northstar.Client/mod/scripts/vscripts/_custom_codecallbacks_client.gnut @@ -23,7 +23,7 @@ struct { void function OnReceivedMessage(ClClient_MessageStruct localMessage) { - if ( IsWatchingReplay() ) + if ( IsWatchingReplay() && localMessage.player == null ) return if (localMessage.player != null) -- cgit v1.2.3 From 2376c5339776d8ef6cb6e0f60d8fcf5a61ef2885 Mon Sep 17 00:00:00 2001 From: Respawn Date: Wed, 8 Mar 2023 02:08:35 +0100 Subject: Add _menus.nut from englishclient_frontend --- .../mod/scripts/vscripts/ui/_menus.nut | 2002 ++++++++++++++++++++ 1 file changed, 2002 insertions(+) create mode 100644 Northstar.Client/mod/scripts/vscripts/ui/_menus.nut (limited to 'Northstar.Client/mod/scripts/vscripts') diff --git a/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut b/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut new file mode 100644 index 00000000..90a535ee --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut @@ -0,0 +1,2002 @@ +untyped + +global const bool EDIT_LOADOUT_SELECTS = true +global const string PURCHASE_SUCCESS_SOUND = "UI_Menu_Store_Purchase_Success" + +global function UICodeCallback_CloseAllMenus +global function UICodeCallback_ActivateMenus +global function UICodeCallback_LevelInit +global function UICodeCallback_LevelLoadingStarted +global function UICodeCallback_LevelLoadingFinished +global function UICodeCallback_LevelShutdown +global function UICodeCallback_OnConnected +global function UICodeCallback_OnFocusChanged +global function UICodeCallback_NavigateBack +global function UICodeCallback_ToggleInGameMenu +global function UICodeCallback_TryCloseDialog +global function UICodeCallback_UpdateLoadingLevelName +global function UICodeCallback_ConsoleKeyboardClosed +global function UICodeCallback_ErrorDialog +global function UICodeCallback_AcceptInvite +global function UICodeCallback_OnDetenteDisplayed +global function UICodeCallback_OnSpLogDisplayed +global function UICodeCallback_EntitlementsChanged +global function UICodeCallback_StoreTransactionCompleted +global function UICodeCallback_GamePurchased +global function UICodeCallback_PartyUpdated +global function UICodeCallback_KeyBindOverwritten + +global function AdvanceMenu +global function OpenSubmenu // REMOVE +global function CloseSubmenu // REMOVE +global function CloseActiveMenu +global function CloseActiveMenuNoParms +global function CloseAllMenus +global function CloseAllInGameMenus +global function CloseAllDialogs +global function CloseAllToTargetMenu +global function PrintMenuStack +global function CleanupInGameMenus +global function GetActiveMenu +global function GetMenu +global function GetPanel +global function GetAllMenuPanels +global function InitGamepadConfigs +global function InitMenus +global function AdvanceMenuEventHandler +global function PCSwitchTeamsButton_Activate +global function PCToggleSpectateButton_Activate +global function AddMenuElementsByClassname +global function FocusDefault +global function SetPanelDefaultFocus +global function PanelFocusDefault +global function OpenMenuWrapper +global function CloseMenuWrapper +global function IsLevelMultiplayer +global function AddMenuEventHandler +global function AddPanelEventHandler +global function AddButtonEventHandler +global function AddEventHandlerToButton +global function AddEventHandlerToButtonClass +global function DisableMusic +global function EnableMusic +global function PlayMusic +global function StopMusic +global function IsMenuInMenuStack +global function GetTopNonDialogMenu +global function IsDialog +global function IsDialogActive +global function IsDialogOnlyActiveMenu +global function SetNavUpDown +global function SetNavLeftRight +global function IsTrialPeriodActive +global function LaunchGamePurchaseOrDLCStore +global function SetMenuThinkFunc + +global function PCBackButton_Activate + +global function RegisterMenuVarInt +global function GetMenuVarInt +global function SetMenuVarInt +global function RegisterMenuVarBool +global function GetMenuVarBool +global function SetMenuVarBool +global function RegisterMenuVarVar +global function GetMenuVarVar +global function SetMenuVarVar +global function AddMenuVarChangeHandler + +global function InviteFriends + +global function HACK_DelayedSetFocus_BecauseWhy + +#if DURANGO_PROG + global function OpenXboxPartyApp + global function OpenXboxHelp +#endif // DURANGO_PROG + +global function OpenReviewTermsDialog +global function ClassicMusic_OnChange +global function IsClassicMusicAvailable + + +void function UICodeCallback_CloseAllMenus() +{ + printt( "UICodeCallback_CloseAllMenus" ) + CloseAllMenus() + // This is usually followed by a call to UICodeCallback_ActivateMenus(). +} + +// Bringing up the console will cause this, and it probably shouldn't +void function UICodeCallback_ActivateMenus() +{ + if ( IsConnected() ) + return + + printt( "UICodeCallback_ActivateMenus:", uiGlobal.activeMenu && Hud_GetHudName( uiGlobal.activeMenu ) ) + + if ( uiGlobal.menuStack.len() == 0 ) + { + AdvanceMenu( GetMenu( "MainMenu" ) ) + } + + if ( uiGlobal.activeMenu == GetMenu( "MainMenu" ) ) + Signal( uiGlobal.signalDummy, "OpenErrorDialog" ) + + PlayMusic() + + #if DURANGO_PROG + Durango_LeaveParty() + #endif // DURANGO_PROG +} + +void function UICodeCallback_ToggleInGameMenu() +{ + if ( !IsFullyConnected() ) + return + + var activeMenu = uiGlobal.activeMenu + bool isMP = IsLevelMultiplayer( GetActiveLevel() ) + bool isLobby = IsLobby() + + var ingameMenu + if ( isMP ) + { + ingameMenu = GetMenu( "InGameMPMenu" ) + } + else + { + // Disable this callback for this special case menu so players can't skip it. + var spTitanTutorialMenu = GetMenu( "SPTitanLoadoutTutorialMenu" ) + if ( activeMenu == spTitanTutorialMenu ) + return + + ingameMenu = GetMenu( "InGameSPMenu" ) + } + + if ( IsDialog( uiGlobal.activeMenu ) ) + { + // Do nothing if a dialog is showing + } + else if ( TeamTitanSelectMenuIsOpen() ) + { + if ( uiGlobal.activeMenu == GetMenu( "TeamTitanSelectMenu" ) ) + { + // Do nothing here either + } + else + { + CloseActiveMenu() + } + } + else if ( ( isMP && !isLobby ) || !isMP ) + { + if ( !activeMenu ) + AdvanceMenu( ingameMenu ) + else + CloseAllInGameMenus() + } +} + +// Return true to show load screen, false to not show load screen. +// levelname can be "" because the level to load isn't always known when the load screen starts +bool function UICodeCallback_LevelLoadingStarted( string levelname ) +{ + printt( "UICodeCallback_LevelLoadingStarted: " + levelname ) + + CloseAllDialogs() + + uiGlobal.loadingLevel = levelname + + StopMusic() + + if ( uiGlobal.playingVideo ) + Signal( uiGlobal.signalDummy, "PlayVideoEnded" ) + + if ( uiGlobal.playingCredits ) + Signal( uiGlobal.signalDummy, "PlayingCreditsDone" ) + + // kill lingering postgame summary since persistent data may not be available at this point + Signal( uiGlobal.signalDummy, "PGDisplay" ) + +#if CONSOLE_PROG + if ( !Console_IsSignedIn() ) + return false +#endif + + return true +} + +// Return true to show load screen, false to not show load screen. +bool function UICodeCallback_UpdateLoadingLevelName( string levelname ) +{ + printt( "UICodeCallback_UpdateLoadingLevelName: " + levelname ) + +#if CONSOLE_PROG + if ( !Console_IsSignedIn() ) + return false +#endif + + return true +} + +void function UICodeCallback_LevelLoadingFinished( bool error ) +{ + printt( "UICodeCallback_LevelLoadingFinished: " + uiGlobal.loadingLevel + " (" + error + ")" ) + + if ( !IsLobby() ) + { + HudChat_ClearTextFromAllChatPanels() + ResetActiveChatroomLastModified() + } + else + { + uiGlobal.lobbyFromLoadingScreen = true + } + + uiGlobal.loadingLevel = "" + Signal( uiGlobal.signalDummy, "LevelFinishedLoading" ) +} + +void function UICodeCallback_LevelInit( string levelname ) +{ + Assert( IsConnected() ) + + StopVideo() + + uiGlobal.loadedLevel = levelname + + printt( "UICodeCallback_LevelInit: " + uiGlobal.loadedLevel ) + + if ( !uiGlobal.loadoutsInitialized ) + { + string gameModeString = GetConVarString( "mp_gamemode" ) + if ( gameModeString != "solo" ) + { + InitStatsTables() + } + } + + InitItems() + + if ( IsMultiplayer() ) + { + ShWeaponXP_Init() + ShTitanXP_Init() + ShFactionXP_Init() + } + else + { + SPObjectiveStringsInit() + } + + #if DEV + UpdatePrecachedSPWeapons() + #endif + + + if ( !uiGlobal.loadoutsInitialized ) + { + string gameModeString = GetConVarString( "mp_gamemode" ) + if ( gameModeString != "solo" ) + { + DeathHints_Init() + InitDefaultLoadouts() + CreateChallenges() + uiGlobal.loadoutsInitialized = true + } + } + + if ( IsLevelMultiplayer( levelname ) || IsLobbyMapName( levelname ) ) + { + thread UpdateCachedLoadouts() + thread UpdateCachedNewItems() + thread InitUISpawnLoadoutIndexes() + + if ( !uiGlobal.eventHandlersAdded ) + { + uiGlobal.eventHandlersAdded = true + } + + UI_GetAllChallengesProgress() + + bool isLobby = IsLobbyMapName( levelname ) + + string gameModeString = GetConVarString( "mp_gamemode" ) + if ( gameModeString == "" ) + gameModeString = "" + + Assert( gameModeString == GetConVarString( "mp_gamemode" ) ) + Assert( gameModeString != "" ) + + int gameModeId = GameMode_GetGameModeId( gameModeString ) + + int mapId = eMaps.invalid + if ( levelname in getconsttable().eMaps ) + { + mapId = expect int( getconsttable().eMaps[ levelname ] ) + } + else + { + // Don't worry about this until we have to consider R2 Durango TCRs (10/2015) + //if ( !IsTestMap() ) + // CodeWarning( "No map named '" + levelname + "' exists in eMaps, all shipping maps should be in this enum" ) + } + + int difficultyLevelId = 0 + int roundId = 0 + + if ( isLobby ) + Durango_OnLobbySessionStart( gameModeId, difficultyLevelId ) + else + Durango_OnMultiplayerRoundStart( gameModeId, mapId, difficultyLevelId, roundId, 0 ) + } + else + { + // SP loadout stuff + UI_GetAllChallengesProgress() // TODO: Can this be moved so we don't call it twice? It's called above. + + SP_ResetObjectiveStringIndex() // Since this persists thru level load, we need to explicitely clear it. + } + + if ( IsMultiplayer() ) + { + foreach ( callbackFunc in uiGlobal.onLevelInitCallbacks ) + { + thread callbackFunc() + } + + } + thread UpdateMenusOnConnect( levelname ) + + uiGlobal.previousLevel = uiGlobal.loadedLevel + uiGlobal.previousPlaylist = GetCurrentPlaylistName() +} + +void function UICodeCallback_LevelShutdown() +{ + Signal( uiGlobal.signalDummy, "LevelShutdown" ) + + printt( "UICodeCallback_LevelShutdown: " + uiGlobal.loadedLevel ) + + StopVideo() + + if ( uiGlobal.loadedLevel != "" ) + CleanupInGameMenus() + + uiGlobal.loadedLevel = "" + uiGlobal.mapSupportsMenuModelsUpdated = false + uiGlobal.sp_showAlternateMissionLog = false +} + +void function UICodeCallback_NavigateBack() +{ + if ( uiGlobal.activeMenu == null ) + return + + if ( IsDialog( uiGlobal.activeMenu ) ) + { + if ( uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData.noChoice || + uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData.forceChoice || + Time() < uiGlobal.dialogInputEnableTime ) + return + } + + Assert( uiGlobal.activeMenu in uiGlobal.menuData ) + if ( uiGlobal.menuData[ uiGlobal.activeMenu ].navBackFunc != null ) + { + thread uiGlobal.menuData[ uiGlobal.activeMenu ].navBackFunc() + return + } + + if ( uiGlobal.activeMenu.GetType() == "submenu" ) // REMOVE + { + CloseSubmenu() + return + } + + CloseActiveMenu( true ) +} + +// Called when IsConnected() will start returning true. +void function UICodeCallback_OnConnected() +{ + +} + +void function UICodeCallback_OnFocusChanged( var oldFocusedPanel, var newFocusedPanel ) +{ + +} + +// Accepting an origin invite closes dialogs, or aborts if they can't be closed +bool function UICodeCallback_TryCloseDialog() +{ + if ( !IsDialog( uiGlobal.activeMenu ) ) + return true + + if ( uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData.forceChoice ) + return false + + CloseAllDialogs() + Assert( !IsDialog( uiGlobal.activeMenu ) ) + return true +} + +void function UICodeCallback_ConsoleKeyboardClosed() +{ + switch ( uiGlobal.activeMenu ) + { + case GetMenu( "EditPilotLoadoutMenu" ): + string oldName = GetPilotLoadoutName( GetCachedPilotLoadout( uiGlobal.editingLoadoutIndex ) ) + string newName = GetPilotLoadoutRenameText() + + // strip doesn't work on UTF-8 strings + // newName = strip( newName ) // Remove leading/trailing whitespace + if ( newName == "" ) // If all whitespace entered reset to previous name + newName = oldName + + SetPilotLoadoutName( newName ) + SelectPilotLoadoutRenameText() + if ( newName != oldName ) + EmitUISound( "Menu.Accept" ) // No callback when cancelled so for now assume name was changed + break + + default: + break + } +} + +void function UICodeCallback_OnDetenteDisplayed() +{ +// thread PlayDetentSound() +//} +// +//void function PlayDetentSound() +//{ +// WaitFrame() // otherwise gets killed off by code pause +// WaitFrame() // otherwise gets killed off by code pause +// EmitUISound( "Pilot_Killed_Indicator" ) +} + +void function UICodeCallback_OnSpLogDisplayed() +{ +} + +void function UICodeCallback_ErrorDialog( string errorDetails ) +{ + printt( "UICodeCallback_ErrorDialog: " + errorDetails ) + thread OpenErrorDialog( errorDetails ) +} + +void function UICodeCallback_AcceptInviteThread( string accesstoken ) +{ + printt( "UICodeCallback_AcceptInviteThread '" + accesstoken + "'") + + #if PS4_PROG + if ( !Ps4_PSN_Is_Loggedin() ) + { + Ps4_LoginDialog_Schedule(); + while( Ps4_LoginDialog_Running() ) + WaitFrame() + if ( !Ps4_PSN_Is_Loggedin() ) + return; + } + + if( Ps4_CheckPlus_Schedule() ) + { + while( Ps4_CheckPlus_Running() ) + WaitFrame() + if( !Ps4_CheckPlus_Allowed() ) + { + if( Ps4_CheckPlus_GetLastRequestResults() != 0 ) + { + return + } + + if( Ps4_ScreenPlusDialog_Schedule() ) + { + while( Ps4_ScreenPlusDialog_Running() ) + WaitFrame() + if( !Ps4_ScreenPlusDialog_Allowed() ) + return; + } + else + { + return; + } + } + } + + #endif // #if PS4_PROG + + SubscribeToChatroomPartyChannel( accesstoken ); + +} + + +void function UICodeCallback_AcceptInvite( string accesstoken ) +{ + printt( "UICodeCallback_AcceptInvite '" + accesstoken + "'") + thread UICodeCallback_AcceptInviteThread( accesstoken ) +} + +// TODO: replaceCurrent should not be an option. It should be a different function. +void function AdvanceMenu( var menu, bool replaceCurrent = false ) +{ + //foreach ( index, menu in uiGlobal.menuStack ) + //{ + // if ( menu != null ) + // printt( "menu index " + index + " is named " + menu.GetDisplayName() ) + //} + + if ( uiGlobal.activeMenu ) + { + // Don't open the same menu again if it's already open + if ( uiGlobal.activeMenu == menu ) + return + + // Opening a normal menu while a dialog is open + Assert( !IsDialog( uiGlobal.activeMenu ), "Tried opening menu: " + Hud_GetHudName( menu ) + " when uiGlobal.activeMenu was: " + Hud_GetHudName( uiGlobal.activeMenu ) ) + } + + if ( uiGlobal.activeMenu && !IsDialog( menu ) ) // Dialogs show on top so don't close existing menu when opening them + { + SetBlurEnabled( false ) + + if ( replaceCurrent ) + { + CloseMenuWrapper( uiGlobal.activeMenu ) + uiGlobal.menuStack.pop() + } + else + { + CloseMenu( uiGlobal.activeMenu ) + printt( Hud_GetHudName( uiGlobal.activeMenu ), "menu closed" ) + } + } + + if ( IsDialog( menu ) && uiGlobal.activeMenu ) + SetFooterPanelVisibility( uiGlobal.activeMenu, false ) + + uiGlobal.menuStack.push( menu ) + uiGlobal.activeMenu = menu + + uiGlobal.lastMenuNavDirection = MENU_NAV_FORWARD + + if ( uiGlobal.activeMenu ) + { + if ( !IsLobby() && !uiGlobal.mapSupportsMenuModels ) + SetBlurEnabled( true ) + + OpenMenuWrapper( uiGlobal.activeMenu, true ) + } + + Signal( uiGlobal.signalDummy, "ActiveMenuChanged" ) +} + +void function SetFooterPanelVisibility( var menu, bool visible ) +{ + if ( !Hud_HasChild( menu, "FooterButtons" ) ) + return + + var panel = Hud_GetChild( menu, "FooterButtons" ) + Hud_SetVisible( panel, visible ) +} + +void function OpenSubmenu( var menu, bool updateMenuPos = true ) +{ + Assert( menu ) + Assert( menu.GetType() == "submenu" ) + + if ( uiGlobal.activeMenu ) + { + // Don't open the same menu again if it's already open + if ( uiGlobal.activeMenu == menu ) + return + } + + local submenuPos = Hud_GetAbsPos( GetFocus() ) + + uiGlobal.menuStack.push( menu ) + uiGlobal.activeMenu = menu + + OpenMenuWrapper( uiGlobal.activeMenu, true ) + + if ( updateMenuPos ) + { + var vguiButtonFrame = Hud_GetChild( uiGlobal.activeMenu, "ButtonFrame" ) + Hud_SetPos( vguiButtonFrame, submenuPos[0], submenuPos[1] ) + } + + uiGlobal.lastMenuNavDirection = MENU_NAV_FORWARD + + Signal( uiGlobal.signalDummy, "ActiveMenuChanged" ) +} + +void function CloseSubmenu( bool openStackMenu = true ) +{ + if ( !uiGlobal.activeMenu ) + return + + if ( uiGlobal.activeMenu.GetType() != "submenu" ) + return + + CloseMenuWrapper( uiGlobal.activeMenu ) + uiGlobal.menuStack.pop() + + uiGlobal.lastMenuNavDirection = MENU_NAV_FORWARD + + if ( uiGlobal.menuStack.len() ) + { + uiGlobal.activeMenu = uiGlobal.menuStack.top() + + // This runs any OnOpen function for the menu and sets focus, but doesn't actually open the menu because it is already open + if ( openStackMenu ) + OpenMenuWrapper( uiGlobal.activeMenu, false ) + } + else + { + uiGlobal.activeMenu = null + } + + Signal( uiGlobal.signalDummy, "ActiveMenuChanged" ) +} + +void function CloseActiveMenuNoParms() +{ + CloseActiveMenu() +} + +void function CloseActiveMenu( bool cancelled = false, bool openStackMenu = true ) +{ + bool updateBlur = true + bool wasDialog = false + + if ( uiGlobal.activeMenu ) + { + if ( IsDialog( uiGlobal.activeMenu ) ) + { + updateBlur = false + wasDialog = true + uiGlobal.dialogInputEnableTime = 0.0 + + if ( uiGlobal.dialogCloseCallback ) + { + uiGlobal.dialogCloseCallback( cancelled ) + uiGlobal.dialogCloseCallback = null + } + } + + if ( updateBlur ) + SetBlurEnabled( false ) + + CloseMenuWrapper( uiGlobal.activeMenu ) + } + + uiGlobal.menuStack.pop() + if ( uiGlobal.menuStack.len() ) + uiGlobal.activeMenu = uiGlobal.menuStack.top() + else + uiGlobal.activeMenu = null + + uiGlobal.lastMenuNavDirection = MENU_NAV_BACK + + if ( wasDialog ) + { + if ( uiGlobal.activeMenu ) + SetFooterPanelVisibility( uiGlobal.activeMenu, true ) + + if ( IsDialog( uiGlobal.activeMenu ) ) + openStackMenu = true + else + openStackMenu = false + } + + if ( uiGlobal.activeMenu ) + { + if ( uiGlobal.activeMenu.GetType() == "submenu" ) + { + Hud_SetFocused( uiGlobal.menuData[ uiGlobal.activeMenu ].lastFocus ) + } + else if ( openStackMenu ) + { + OpenMenuWrapper( uiGlobal.activeMenu, false ) + + if ( updateBlur && !IsLobby() && !uiGlobal.mapSupportsMenuModels ) + SetBlurEnabled( true ) + } + } + + Signal( uiGlobal.signalDummy, "ActiveMenuChanged" ) +} + +void function CloseAllMenus() +{ + if ( IsDialog( uiGlobal.activeMenu ) ) + CloseActiveMenu( true ) + + if ( uiGlobal.activeMenu && uiGlobal.activeMenu.GetType() == "submenu" ) + CloseSubmenu( false ) + + if ( uiGlobal.activeMenu ) + { + SetBlurEnabled( false ) + CloseMenuWrapper( uiGlobal.activeMenu ) + } + + uiGlobal.menuStack = [] + uiGlobal.activeMenu = null + + uiGlobal.lastMenuNavDirection = MENU_NAV_BACK + + Signal( uiGlobal.signalDummy, "ActiveMenuChanged" ) +} + +void function CloseAllInGameMenus() +{ + while ( uiGlobal.activeMenu ) + { + if ( uiGlobal.activeMenu.GetType() == "submenu" ) + CloseSubmenu( false ) + + CloseActiveMenu( true, false ) + } +} + +void function CloseAllDialogs() +{ + while ( IsDialog( uiGlobal.activeMenu ) ) + CloseActiveMenu( true ) +} + +void function CloseAllToTargetMenu( var targetMenu ) +{ + while ( uiGlobal.activeMenu != targetMenu ) + CloseActiveMenu( true, false ) +} + +void function PrintMenuStack() +{ + array stack = clone uiGlobal.menuStack + stack.reverse() + + printt( "MENU STACK:" ) + + foreach ( menu in stack ) + { + if ( menu ) + printt( " ", Hud_GetHudName( menu ) ) + else + printt( " null" ) + } +} + +// Happens on any level load +void function UpdateMenusOnConnect( string levelname ) +{ + EndSignal( uiGlobal.signalDummy, "LevelShutdown" ) // HACK fix because UICodeCallback_LevelInit() incorrectly runs when disconnected by client error. Test with "script_error_client" while a level is loaded. + + CloseAllDialogs() + + var mainMenu = GetMenu( "MainMenu" ) + if ( IsMenuInMenuStack( mainMenu ) && !IsMenuInMenuStack( null ) ) + CloseAllToTargetMenu( mainMenu ) + + Assert( uiGlobal.activeMenu != null || uiGlobal.menuStack.len() == 0 ) + + AdvanceMenu( null ) + + // TODO: The order things are called in should be predictable so this isn't needed + while ( !uiGlobal.mapSupportsMenuModelsUpdated ) + { + //printt( Time(), "beginning waitframe, uiGlobal.mapSupportsMenuModelsUpdated is:", uiGlobal.mapSupportsMenuModelsUpdated ) + WaitFrame() + //printt( Time(), "ended waitframe, uiGlobal.mapSupportsMenuModelsUpdated is:", uiGlobal.mapSupportsMenuModelsUpdated ) + } + + if ( IsLevelMultiplayer( levelname ) ) + { + bool isLobby = IsLobbyMapName( levelname ) + + if ( isLobby ) + { + if ( IsPrivateMatch() ) + { + AdvanceMenu( GetMenu( "PrivateLobbyMenu" ) ) + } + else + { + AdvanceMenu( GetMenu( "LobbyMenu" ) ) + } + + thread UpdateAnnouncementDialog() + } + else + { + UI_SetPresentationType( ePresentationType.INACTIVE ) + } + } +} + +bool function IsMenuInMenuStack( var searchMenu ) +{ + foreach ( menu in uiGlobal.menuStack ) + { + // loading a map pushes a null sentinel onto the menu stack + if ( !menu ) + continue + + if ( menu == searchMenu ) + return true + } + + return false +} + +var function GetTopNonDialogMenu() +{ + array menuArray = clone uiGlobal.menuStack + menuArray.reverse() + + foreach ( menu in menuArray ) + { + if ( menu == null || IsDialog( menu ) ) + continue + + return menu + } + + return null +} + +void function CleanupInGameMenus() +{ + Signal( uiGlobal.signalDummy, "CleanupInGameMenus" ) + + CloseAllInGameMenus() + Assert( uiGlobal.activeMenu == null ) + if ( uiGlobal.menuStack.len() ) + { + if ( uiGlobal.loadingLevel == "" ) + CloseActiveMenu() // Disconnected. Remove stack null and open main menu. + else + CloseActiveMenu( true, false ) // Level to level transition. Remove stack null and DON'T open main menu. + } +} + +var function GetActiveMenu() +{ + return uiGlobal.activeMenu +} + +var function GetMenu( string menuName ) +{ + return uiGlobal.menus[ menuName ] +} + +var function GetPanel( string panelName ) +{ + return uiGlobal.panels[ panelName ] +} + +array function GetAllMenuPanels( var menu ) +{ + array menuPanels + + foreach ( panel in uiGlobal.allPanels ) + { + if ( Hud_GetParent( panel ) == menu ) + menuPanels.append( panel ) + } + + return menuPanels +} + +void function InitGamepadConfigs() +{ + uiGlobal.buttonConfigs = [ { orthodox = "gamepad_button_layout_default.cfg", southpaw = "gamepad_button_layout_default_southpaw.cfg" } ] + uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_bumper_jumper.cfg", southpaw = "gamepad_button_layout_bumper_jumper_southpaw.cfg" } ) + uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_bumper_jumper_alt.cfg", southpaw = "gamepad_button_layout_bumper_jumper_alt_southpaw.cfg" } ) + uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_pogo_stick.cfg", southpaw = "gamepad_button_layout_pogo_stick_southpaw.cfg" } ) + uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_button_kicker.cfg", southpaw = "gamepad_button_layout_button_kicker_southpaw.cfg" } ) + uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_circle.cfg", southpaw = "gamepad_button_layout_circle_southpaw.cfg" } ) + uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_ninja.cfg", southpaw = "gamepad_button_layout_ninja_southpaw.cfg" } ) + uiGlobal.buttonConfigs.append( { orthodox = "gamepad_button_layout_custom.cfg", southpaw = "gamepad_button_layout_custom.cfg" } ) + + uiGlobal.stickConfigs = [] + uiGlobal.stickConfigs.append( "gamepad_stick_layout_default.cfg" ) + uiGlobal.stickConfigs.append( "gamepad_stick_layout_southpaw.cfg" ) + uiGlobal.stickConfigs.append( "gamepad_stick_layout_legacy.cfg" ) + uiGlobal.stickConfigs.append( "gamepad_stick_layout_legacy_southpaw.cfg" ) + + foreach ( key, val in uiGlobal.buttonConfigs ) + { + VPKNotifyFile( "cfg/" + val.orthodox ) + VPKNotifyFile( "cfg/" + val.southpaw ) + } + + foreach ( key, val in uiGlobal.stickConfigs ) + VPKNotifyFile( "cfg/" + val ) + + ExecCurrentGamepadButtonConfig() + ExecCurrentGamepadStickConfig() + + SetStandardAbilityBindingsForPilot( GetLocalClientPlayer() ) +} + +void function InitMenus() +{ + InitGlobalMenuVars() + SpShWeaponsInit() + + AddMenu( "MainMenu", $"resource/ui/menus/main.menu", InitMainMenu, "#MAIN" ) + AddPanel( GetMenu( "MainMenu" ), "EstablishUserPanel", InitEstablishUserPanel ) + AddPanel( GetMenu( "MainMenu" ), "MainMenuPanel", InitMainMenuPanel ) + + AddMenu( "PlayVideoMenu", $"resource/ui/menus/play_video.menu", InitPlayVideoMenu ) + AddMenu( "LobbyMenu", $"resource/ui/menus/lobby.menu", InitLobbyMenu, "#LOBBY" ) + + AddMenu( "FDMenu", $"resource/ui/menus/playlist_fd.menu", InitFDPlaylistMenu ) + AddMenu( "TeamTitanSelectMenu", $"resource/ui/menus/team_titan_select.menu", InitTeamTitanSelectMenu ) + AddMenu( "PlaylistMenu", $"resource/ui/menus/playlist.menu", InitPlaylistMenu ) + AddMenu( "PlaylistMixtapeMenu", $"resource/ui/menus/playlist_mixtape.menu", InitPlaylistMixtapeMenu ) + AddMenu( "PlaylistMixtapeChecklistMenu", $"resource/ui/menus/playlist_mixtape_checklist.menu", InitPlaylistMixtapeChecklistMenu ) + + AddMenu( "SinglePlayerDevMenu", $"resource/ui/menus/singleplayer_dev.menu", InitSinglePlayerDevMenu, "SINGLE PLAYER DEV" ) + AddMenu( "SinglePlayerMenu", $"resource/ui/menus/singleplayer.menu", InitSinglePlayerMenu, "SINGLE PLAYER" ) + + AddMenu( "SearchMenu", $"resource/ui/menus/search.menu", InitSearchMenu ) + + AddMenu( "GammaMenu", $"resource/ui/menus/gamma.menu", InitGammaMenu, "#BRIGHTNESS" ) + + AddMenu( "CommunitiesMenu", $"resource/ui/menus/community.menu", InitCommunitiesMenu ) + AddMenu( "Notifications", $"resource/ui/menus/notifications.menu", InitNotificationsMenu ) + AddMenu( "MyNetworks", $"resource/ui/menus/communities_mine.menu", InitMyNetworksMenu ) + AddMenu( "InboxFrontMenu", $"resource/ui/menus/inbox_front.menu", InitInboxFrontMenu ) + AddMenu( "Inbox", $"resource/ui/menus/inbox.menu", InitInboxMenu ) + AddMenu( "BrowseCommunities", $"resource/ui/menus/communities_browse.menu" ) + AddMenu( "CommunityEditMenu", $"resource/ui/menus/community_edit.menu" ) + AddMenu( "CommunityAdminSendMessage", $"resource/ui/menus/community_sendMessage.menu" ) + AddMenu( "CommunityAdminInviteRequestMenu", $"resource/ui/menus/community_inviteRequest.menu" ) +#if NETWORK_INVITE + AddMenu( "InviteFriendsToNetworkMenu", $"resource/ui/menus/invite_friends.menu", InitInviteFriendsToNetworkMenu ) +#endif + + AddMenu( "InGameMPMenu", $"resource/ui/menus/ingame_mp.menu", InitInGameMPMenu ) + AddMenu( "InGameSPMenu", $"resource/ui/menus/ingame_sp.menu", InitInGameSPMenu ) + + AddMenu( "Dialog", $"resource/ui/menus/dialog.menu", InitDialogMenu ) + AddMenu( "AnnouncementDialog", $"resource/ui/menus/dialog_announcement.menu", InitAnnouncementDialog ) + AddMenu( "ConnectingDialog", $"resource/ui/menus/dialog_connecting.menu", InitConnectingDialog ) + AddMenu( "DataCenterDialog", $"resource/ui/menus/dialog_datacenter.menu", InitDataCenterDialogMenu ) + AddMenu( "EULADialog", $"resource/ui/menus/dialog_eula.menu", InitEULADialog ) + AddMenu( "ReviewTermsDialog", $"resource/ui/menus/dialog_review_terms.menu", InitReviewTermsDialog ) + AddMenu( "RegistrationDialog", $"resource/ui/menus/dialog_registration.menu", InitRegistrationDialog ) + AddMenu( "AdvocateGiftDialog", $"resource/ui/menus/dialog_advocate_gift.menu", InitAdvocateGiftDialog ) + + AddMenu( "ControlsMenu", $"resource/ui/menus/controls.menu", InitControlsMenu, "#CONTROLS" ) + AddMenu( "ControlsAdvancedLookMenu", $"resource/ui/menus/controls_advanced_look.menu", InitControlsAdvancedLookMenu, "#CONTROLS_ADVANCED_LOOK" ) + AddMenu( "GamepadLayoutMenu", $"resource/ui/menus/gamepadlayout.menu", InitGamepadLayoutMenu ) +#if PC_PROG + AddMenu_WithCreateFunc( "MouseKeyboardBindingsMenu", $"resource/ui/menus/mousekeyboardbindings.menu", InitMouseKeyboardMenu, CreateKeyBindingMenu ) + AddMenu( "AudioMenu", $"resource/ui/menus/audio.menu", InitAudioMenu, "#AUDIO" ) + AddMenu_WithCreateFunc( "VideoMenu", $"resource/ui/menus/video.menu", InitVideoMenu, CreateVideoOptionsMenu ) +#elseif CONSOLE_PROG + AddMenu( "AudioVideoMenu", $"resource/ui/menus/audio_video.menu", InitAudioVideoMenu, "#AUDIO_VIDEO" ) +#endif + + AddMenu( "AdvancedHudMenu", $"resource/ui/menus/advanced_hud.menu", InitAdvancedHudMenu, "#ADVANCED_HUD" ) + + AddMenu( "PilotLoadoutsMenu", $"resource/ui/menus/pilotloadouts.menu", InitPilotLoadoutsMenu ) + AddMenu( "TitanLoadoutsMenu", $"resource/ui/menus/titanloadouts.menu", InitTitanLoadoutsMenu ) + AddMenu( "EditPilotLoadoutsMenu", $"resource/ui/menus/pilotloadouts.menu", InitEditPilotLoadoutsMenu ) + AddMenu( "EditTitanLoadoutsMenu", $"resource/ui/menus/titanloadouts.menu", InitEditTitanLoadoutsMenu ) + AddMenu( "EditPilotLoadoutMenu", $"resource/ui/menus/editpilotloadout.menu", InitEditPilotLoadoutMenu ) + AddMenu( "EditTitanLoadoutMenu", $"resource/ui/menus/edittitanloadout.menu", InitEditTitanLoadoutMenu ) + + AddMenu( "SPTitanLoadoutMenu", $"resource/ui/menus/sptitanloadout.menu", InitSPTitanLoadoutMenu ) + AddMenu( "SPTitanLoadoutTutorialMenu", $"resource/ui/menus/sptitanloadout_tutorial.menu", InitSPTitanLoadoutTutorialMenu ) + + AddMenu( "SuitSelectMenu", $"resource/ui/menus/suitselect.menu", InitSuitSelectMenu ) + AddMenu( "WeaponSelectMenu", $"resource/ui/menus/weaponselect.menu", InitWeaponSelectMenu ) + AddMenu( "CategorySelectMenu", $"resource/ui/menus/categoryselect.menu", InitCategorySelectMenu ) + AddMenu( "AbilitySelectMenu", $"resource/ui/menus/abilityselect.menu", InitAbilitySelectMenu ) + AddMenu( "PassiveSelectMenu", $"resource/ui/menus/passiveselect.menu", InitPassiveSelectMenu ) + AddSubmenu( "ModSelectMenu", $"resource/ui/menus/modselect.menu", InitModSelectMenu ) + AddMenu( "CamoSelectMenu", $"resource/ui/menus/camoselect.menu", InitCamoSelectMenu ) + AddMenu( "NoseArtSelectMenu", $"resource/ui/menus/noseartselect.menu", InitNoseArtSelectMenu ) + AddMenu( "CallsignCardSelectMenu", $"resource/ui/menus/callsigncardselect.menu", InitCallsignCardSelectMenu ) + AddMenu( "CallsignIconSelectMenu", $"resource/ui/menus/callsigniconselect.menu", InitCallsignIconSelectMenu ) + AddMenu( "BoostStoreMenu", $"resource/ui/menus/booststore.menu", InitBoostStoreMenu ) + + AddMenu( "PrivateLobbyMenu", $"resource/ui/menus/private_lobby.menu", InitPrivateMatchMenu, "#PRIVATE_MATCH" ) + AddMenu( "MapsMenu", $"resource/ui/menus/map_select.menu", InitMapsMenu ) + AddMenu( "ModesMenu", $"resource/ui/menus/mode_select.menu", InitModesMenu ) + AddMenu( "MatchSettingsMenu", $"resource/ui/menus/match_settings.menu", InitMatchSettingsMenu ) + + AddMenu( "Advocate_Letter", $"resource/ui/menus/advocate_letter.menu", InitAdvocateLetterMenu ) + AddMenu( "Generation_Respawn", $"resource/ui/menus/generation_respawn.menu", InitGenerationRespawnMenu ) + AddMenu( "ChallengesMenu", $"resource/ui/menus/challenges.menu", InitChallengesMenu ) + + AddMenu( "ViewStatsMenu", $"resource/ui/menus/viewstats.menu", InitViewStatsMenu, "#PERSONAL_STATS" ) + AddMenu( "ViewStats_Overview_Menu", $"resource/ui/menus/viewstats_overview.menu", InitViewStatsOverviewMenu ) + //AddMenu( "ViewStats_Kills_Menu", $"resource/ui/menus/viewstats_kills.menu", InitViewStatsKillsMenu ) + AddMenu( "ViewStats_Time_Menu", $"resource/ui/menus/viewstats_time.menu", InitViewStatsTimeMenu ) + //AddMenu( "ViewStats_Distance_Menu", $"resource/ui/menus/viewstats_distance.menu", InitViewStatsDistanceMenu ) + AddMenu( "ViewStats_Weapons_Menu", $"resource/ui/menus/viewstats_weapons.menu", InitViewStatsWeaponsMenu ) + AddMenu( "ViewStats_Titans_Menu", $"resource/ui/menus/viewstats_titans.menu", InitViewStatsTitansMenu ) + AddMenu( "ViewStats_Misc_Menu", $"resource/ui/menus/viewstats_misc.menu", InitViewStatsMiscMenu ) + AddMenu( "ViewStats_Maps_Menu", $"resource/ui/menus/viewstats_maps.menu", InitViewStatsMapsMenu ) + + AddMenu( "PostGameMenu", $"resource/ui/menus/postgame.menu", InitPostGameMenu ) + AddMenu( "EOG_XP", $"resource/ui/menus/eog_xp.menu", InitEOG_XPMenu ) + AddMenu( "EOG_Coins", $"resource/ui/menus/eog_coins.menu", InitEOG_CoinsMenu ) + AddMenu( "EOG_Challenges", $"resource/ui/menus/eog_challenges.menu", InitEOG_ChallengesMenu ) + AddMenu( "EOG_Unlocks", $"resource/ui/menus/eog_unlocks.menu", InitEOG_UnlocksMenu ) + AddMenu( "EOG_Scoreboard", $"resource/ui/menus/eog_scoreboard.menu", InitEOG_ScoreboardMenu ) + + AddMenu( "CreditsMenu", $"resource/ui/menus/credits.menu", InitCreditsMenu, "#CREDITS" ) + + AddMenu( "BurnCardMenu", $"resource/ui/menus/burn_cards.menu", InitBurnCardMenu, "#MENU_BURNCARD_MENU" ) + AddMenu( "FactionChoiceMenu", $"resource/ui/menus/faction_choice.menu", InitFactionChoiceMenu, "#FACTION_CHOICE_MENU" ) + AddMenu( "ArmoryMenu", $"resource/ui/menus/armory.menu", InitArmoryMenu, "#ARMORY_MENU" ) + + AddMenu( "StoreMenu", $"resource/ui/menus/store.menu", InitStoreMenu, "#STORE_MENU" ) + AddMenu( "StoreMenu_NewReleases", $"resource/ui/menus/store_new_releases.menu", InitStoreMenuNewReleases, "#STORE_NEW_RELEASES" ) + AddMenu( "StoreMenu_Limited", $"resource/ui/menus/store_limited.menu", InitStoreMenuLimited, "#STORE_LIMITED" ) + AddMenu( "StoreMenu_Sales", $"resource/ui/menus/store_bundles.menu", InitStoreMenuSales, "#STORE_BUNDLES" ) + AddMenu( "StoreMenu_Titans", $"resource/ui/menus/store_prime_titans.menu", InitStoreMenuTitans, "#STORE_TITANS" ) // reusing store_prime_titans.menu + AddMenu( "StoreMenu_PrimeTitans", $"resource/ui/menus/store_prime_titans.menu", InitStoreMenuPrimeTitans, "#STORE_PRIME_TITANS" ) + //AddMenu( "StoreMenu_WeaponSelect", $"resource/ui/menus/store_weapon_select.menu", InitStoreMenuWeaponSelect ) + //AddMenu( "StoreMenu_WeaponSkinPreview", $"resource/ui/menus/store_weapon_skin_preview.menu", InitStoreMenuWeaponSkinPreview ) + AddMenu( "StoreMenu_WeaponSkinBundles", $"resource/ui/menus/store_weapon_skin_bundles.menu", InitStoreMenuWeaponSkinBundles ) + AddMenu( "StoreMenu_WeaponSkins", $"resource/ui/menus/store_weapons.menu", InitStoreMenuWeaponSkins ) + AddMenu( "StoreMenu_Customization", $"resource/ui/menus/store_customization.menu", InitStoreMenuCustomization, "#STORE_CUSTOMIZATION_PACKS" ) + AddMenu( "StoreMenu_CustomizationPreview", $"resource/ui/menus/store_customization_preview.menu", InitStoreMenuCustomizationPreview, "#STORE_CUSTOMIZATION_PACKS" ) + AddMenu( "StoreMenu_Camo", $"resource/ui/menus/store_camo.menu", InitStoreMenuCamo, "#STORE_CAMO_PACKS" ) + AddMenu( "StoreMenu_CamoPreview", $"resource/ui/menus/store_camo_preview.menu", InitStoreMenuCamoPreview, "#STORE_CAMO_PACKS" ) + AddMenu( "StoreMenu_Callsign", $"resource/ui/menus/store_callsign.menu", InitStoreMenuCallsign, "#STORE_CALLSIGN_PACKS" ) + AddMenu( "StoreMenu_CallsignPreview", $"resource/ui/menus/store_callsign_preview.menu", InitStoreMenuCallsignPreview, "#STORE_CALLSIGN_PACKS" ) + + AddMenu( "KnowledgeBaseMenu", $"resource/ui/menus/knowledgebase.menu", InitKnowledgeBaseMenu ) + AddMenu( "KnowledgeBaseMenuSubMenu", $"resource/ui/menus/knowledgebase_submenu.menu", InitKnowledgeBaseMenuSubMenu ) + + AddMenu( "DevMenu", $"resource/ui/menus/dev.menu", InitDevMenu, "Dev" ) + InitSharedStartPoints() + + foreach ( menu in uiGlobal.allMenus ) + { + if ( uiGlobal.menuData[ menu ].initFunc != null ) + uiGlobal.menuData[ menu ].initFunc() + + array elems = GetElementsByClassname( menu, "TabsCommonClass" ) + if ( elems.len() ) + uiGlobal.menuData[ menu ].hasTabs = true + + elems = GetElementsByClassname( menu, "EnableKeyBindingIcons" ) + foreach ( elem in elems ) + Hud_EnableKeyBindingIcons( elem ) + } + + InitTabs() + + var tabbedMenu = GetMenu( "PostGameMenu" ) + AddPanel( tabbedMenu, "PVEPanel", InitPVEPanel ) + AddPanel( tabbedMenu, "SummaryPanel", InitSummaryPanel ) + AddPanel( tabbedMenu, "FDAwardsPanel", InitFDAwardsPanel ) + + AddPanel( tabbedMenu, "ScoreboardPanel", InitScoreboardPanel ) + + foreach ( panel in uiGlobal.allPanels ) + { + if ( uiGlobal.panelData[ panel ].initFunc != null ) + uiGlobal.panelData[ panel ].initFunc() + } + + // A little weird, but GetElementsByClassname() uses menu scope rather than parent scope. + foreach ( menu in uiGlobal.allMenus ) + { + array buttons = GetElementsByClassname( menu, "DefaultFocus" ) + foreach ( button in buttons ) + { + var panel = Hud_GetParent( button ) + + //Assert( elems.len() == 1, "More than 1 panel element set as DefaultFocus!" ) + Assert( panel != null, "no parent panel found for button " + Hud_GetHudName( button ) ) + Assert( panel in uiGlobal.panelData, "panel " + Hud_GetHudName( panel ) + " isn't in uiGlobal.panelData, but button " + Hud_GetHudName( button ) + " has defaultFocus set!" ) + uiGlobal.panelData[ panel ].defaultFocus = button + //printt( "Found DefaultFocus, button was:", Hud_GetHudName( button ), "panel was:", Hud_GetHudName( panel ) ) + } + } + + InitFooterOptions() + + #if DEV + if ( Dev_CommandLineHasParm( "-autoprecache_all" ) ) + { + // repreache all levels + ExecuteLoadingClientCommands_SetStartPoint( "sp_training" ) + ClientCommand( "map sp_training" ) + CloseAllMenus() + } + #endif +} + +void functionref( var ) function AdvanceMenuEventHandler( var menu ) +{ + return void function( var item ) : ( menu ) + { + if ( Hud_IsLocked( item ) ) + return + + AdvanceMenu( menu ) + } +} + +void function PCBackButton_Activate( var button ) +{ + UICodeCallback_NavigateBack() +} + +void function PCSwitchTeamsButton_Activate( var button ) +{ + ClientCommand( "PrivateMatchSwitchTeams" ) +} + +void function PCToggleSpectateButton_Activate( var button ) +{ + ClientCommand( "PrivateMatchToggleSpectate" ) +} + +void function ToggleButtonStates( var button ) +{ + for ( ;; ) + { + Hud_SetEnabled( button, true ) + wait 1 + Hud_SetSelected( button, true ) + wait 1 + Hud_SetLocked( button, true ) + wait 1 + Hud_SetNew( button, true ) + wait 1 + Hud_SetNew( button, false ) + wait 1 + Hud_SetLocked( button, false ) + wait 1 + Hud_SetSelected( button, false ) + wait 1 + Hud_SetEnabled( button, false ) + wait 1 + } +} + +void function AddMenuElementsByClassname( var menu, string classname ) +{ + array elements = GetElementsByClassname( menu, classname ) + + if ( !(classname in menu.classElements) ) + menu.classElements[classname] <- [] + + menu.classElements[classname].extend( elements ) +} + +void function FocusDefault( var menu ) +{ + if ( + menu == GetMenu( "MainMenu" ) || + menu == GetMenu( "CategorySelectMenu" ) || + menu == GetMenu( "AbilitySelectMenu" ) || + menu == GetMenu( "PassiveSelectMenu" ) || + menu == GetMenu( "WeaponSelectMenu" ) || + menu == GetMenu( "SuitSelectMenu" ) || + menu == GetMenu( "CamoSelectMenu" ) || + menu == GetMenu( "NoseArtSelectMenu" ) || + menu == GetMenu( "FactionChoiceMenu" ) || + menu == GetMenu( "BurnCardMenu" ) || + menu == GetMenu( "CallsignCardSelectMenu" ) || + menu == GetMenu( "CallsignIconSelectMenu" ) ) + { + } + else + { + //printt( "FocusDefaultMenuItem() called" ) + FocusDefaultMenuItem( menu ) + } +} + +void function SetPanelDefaultFocus( var panel, var button ) +{ + uiGlobal.panelData[ panel ].defaultFocus = button +} + +void function PanelFocusDefault( var panel ) +{ + //printt( "PanelFocusDefault called" ) + if ( uiGlobal.panelData[ panel ].defaultFocus ) + { + Hud_SetFocused( uiGlobal.panelData[ panel ].defaultFocus ) + //printt( "PanelFocusDefault if passed,", Hud_GetHudName( uiGlobal.panelData[ panel ].defaultFocus ), "focused" ) + } +} + +void function SetMenuThinkFunc( var menu, void functionref() func ) +{ + Assert( uiGlobal.menuData[ menu ].thinkFunc == null ) + uiGlobal.menuData[ menu ].thinkFunc = func +} + +void function AddMenuEventHandler( var menu, int event, void functionref() func ) +{ + if ( event == eUIEvent.MENU_OPEN ) + { + Assert( uiGlobal.menuData[ menu ].openFunc == null ) + uiGlobal.menuData[ menu ].openFunc = func + } + else if ( event == eUIEvent.MENU_CLOSE ) + { + Assert( uiGlobal.menuData[ menu ].closeFunc == null ) + uiGlobal.menuData[ menu ].closeFunc = func + } + else if ( event == eUIEvent.MENU_SHOW ) + { + Assert( uiGlobal.menuData[ menu ].showFunc == null ) + uiGlobal.menuData[ menu ].showFunc = func + } + else if ( event == eUIEvent.MENU_HIDE ) + { + Assert( uiGlobal.menuData[ menu ].hideFunc == null ) + uiGlobal.menuData[ menu ].hideFunc = func + } + else if ( event == eUIEvent.MENU_NAVIGATE_BACK ) + { + Assert( uiGlobal.menuData[ menu ].navBackFunc == null ) + uiGlobal.menuData[ menu ].navBackFunc = func + } + else if ( event == eUIEvent.MENU_TAB_CHANGED ) + { + Assert( uiGlobal.menuData[ menu ].tabChangedFunc == null ) + uiGlobal.menuData[ menu ].tabChangedFunc = func + } + else if ( event == eUIEvent.MENU_ENTITLEMENTS_CHANGED ) + { + Assert( uiGlobal.menuData[ menu ].entitlementsChangedFunc == null ) + uiGlobal.menuData[ menu ].entitlementsChangedFunc = func + } + else if ( event == eUIEvent.MENU_INPUT_MODE_CHANGED ) + { + Assert( uiGlobal.menuData[ menu ].inputModeChangedFunc == null ) + uiGlobal.menuData[ menu ].inputModeChangedFunc = func + } +} + +void function AddPanelEventHandler( var panel, int event, void functionref() func ) +{ + if ( event == eUIEvent.PANEL_SHOW ) + uiGlobal.panelData[ panel ].showFunc = func + else if ( event == eUIEvent.PANEL_HIDE ) + uiGlobal.panelData[ panel ].hideFunc = func +} + +// TODO: Get a real on open event from code? +void function OpenMenuWrapper( var menu, bool focusDefault ) +{ + OpenMenu( menu ) + printt( Hud_GetHudName( menu ), "menu opened" ) + + Assert( menu in uiGlobal.menuData ) + if ( uiGlobal.menuData[ menu ].openFunc != null ) + { + thread uiGlobal.menuData[ menu ].openFunc() + //printt( "Called openFunc for:", menu.GetHudName() ) + } + + if ( focusDefault ) + FocusDefault( menu ) + + //UpdateMenuTabs() + UpdateFooterOptions() +} + +void function CloseMenuWrapper( var menu ) +{ + CloseMenu( menu ) + printt( Hud_GetHudName( menu ), "menu closed" ) + + Assert( menu in uiGlobal.menuData ) + if ( uiGlobal.menuData[ menu ].closeFunc != null ) + { + thread uiGlobal.menuData[ menu ].closeFunc() + //printt( "Called closeFunc for:", Hud_GetHudName( menu ) ) + } +} + +bool function IsLevelMultiplayer( string levelname ) +{ + return levelname.find( "mp_" ) == 0 +} + +void function AddButtonEventHandler( var button, int event, void functionref( var ) func ) +{ + Hud_AddEventHandler( button, event, func ) +} + +void function AddEventHandlerToButton( var menu, string buttonName, int event, void functionref( var ) func ) +{ + var button = Hud_GetChild( menu, buttonName ) + Hud_AddEventHandler( button, event, func ) +} + +void function AddEventHandlerToButtonClass( var menu, string classname, int event, void functionref( var ) func ) +{ + array buttons = GetElementsByClassname( menu, classname ) + + foreach ( button in buttons ) + { + //printt( "button name:", Hud_GetHudName( button ) ) + Hud_AddEventHandler( button, event, func ) + } +} + +// Added slight delay to main menu music to work around a hitch caused when the game first starts up +void function PlayMusicAfterDelay() +{ + wait MAINMENU_MUSIC_DELAY + if ( uiGlobal.playingMusic ) + EmitUISound( "MainMenu_Music" ) +} + +void function DisableMusic() +{ + EmitUISound( "Movie_MuteAllGameSound" ) +} + +void function EnableMusic() +{ + StopUISoundByName( "Movie_MuteAllGameSound" ) +} + +void function PlayMusic() +{ + if ( !uiGlobal.playingMusic && !uiGlobal.playingVideo && !uiGlobal.playingCredits ) + { + //printt( "PlayMusic() called. Playing: MainMenu_Music. uiGlobal.playingMusic:", uiGlobal.playingMusic, "uiGlobal.playingVideo:", uiGlobal.playingVideo, "uiGlobal.playingCredits:", uiGlobal.playingCredits ) + uiGlobal.playingMusic = true + thread PlayMusicAfterDelay() + } + else + { + //printt( "PlayMusic() called, but doing nothing. uiGlobal.playingMusic:", uiGlobal.playingMusic, "uiGlobal.playingVideo:", uiGlobal.playingVideo, "uiGlobal.playingCredits:", uiGlobal.playingCredits ) + } +} + +void function StopMusic() +{ + //printt( "StopMusic() called. Stopping: MainMenu_Music" ) + StopUISound( "MainMenu_Music" ) + uiGlobal.playingMusic = false +} + +void function RegisterMenuVarInt( string varName, int value ) +{ + table intVars = uiGlobal.intVars + + Assert( !( varName in intVars ) ) + + intVars[varName] <- value +} + +void function RegisterMenuVarBool( string varName, bool value ) +{ + table boolVars = uiGlobal.boolVars + + Assert( !( varName in boolVars ) ) + + boolVars[varName] <- value +} + +void function RegisterMenuVarVar( string varName, var value ) +{ + table varVars = uiGlobal.varVars + + Assert( !( varName in varVars ) ) + + varVars[varName] <- value +} + +int function GetMenuVarInt( string varName ) +{ + table intVars = uiGlobal.intVars + + Assert( varName in intVars ) + + return intVars[varName] +} + +bool function GetMenuVarBool( string varName ) +{ + table boolVars = uiGlobal.boolVars + + Assert( varName in boolVars ) + + return boolVars[varName] +} + +var function GetMenuVarVar( string varName ) +{ + table varVars = uiGlobal.varVars + + Assert( varName in varVars ) + + return varVars[varName] +} + +void function SetMenuVarInt( string varName, int value ) +{ + table intVars = uiGlobal.intVars + + Assert( varName in intVars ) + + if ( intVars[varName] == value ) + return + + intVars[varName] = value + + table > varChangeFuncs = uiGlobal.varChangeFuncs + + if ( varName in varChangeFuncs ) + { + foreach ( func in varChangeFuncs[varName] ) + { + //printt( varName, "changed, calling changeFunc:", string( func ) ) + func() + } + } +} + +void function SetMenuVarBool( string varName, bool value ) +{ + table boolVars = uiGlobal.boolVars + + Assert( varName in boolVars ) + + if ( boolVars[varName] == value ) + return + + boolVars[varName] = value + + table > varChangeFuncs = uiGlobal.varChangeFuncs + + if ( varName in varChangeFuncs ) + { + foreach ( func in varChangeFuncs[varName] ) + { + //printt( varName, "changed, calling changeFunc:", string( func ) ) + func() + } + } +} + +void function SetMenuVarVar( string varName, var value ) +{ + table varVars = uiGlobal.varVars + + Assert( varName in varVars ) + + if ( varVars[varName] == value ) + return + + varVars[varName] = value + + table > varChangeFuncs = uiGlobal.varChangeFuncs + + if ( varName in varChangeFuncs ) + { + foreach ( func in varChangeFuncs[varName] ) + { + //printt( varName, "changed, calling changeFunc:", string( func ) ) + func() + } + } +} + +void function AddMenuVarChangeHandler( string varName, void functionref() func ) +{ + table > varChangeFuncs = uiGlobal.varChangeFuncs + + if ( !( varName in varChangeFuncs ) ) + varChangeFuncs[varName] <- [] + + // TODO: Verify we're not duplicating an existing func + varChangeFuncs[varName].append( func ) +} + +// These are common menu statuses that trigger menu logic any time they change +// They should become code callbacks, so script doesn't poll +void function InitGlobalMenuVars() +{ + RegisterMenuVarVar( "focus", null ) + RegisterMenuVarBool( "isConnected", false ) + RegisterMenuVarBool( "isFullyConnected", false ) + RegisterMenuVarBool( "isPartyLeader", false ) + RegisterMenuVarBool( "isPrivateMatch", false ) + RegisterMenuVarBool( "isGamepadActive", IsControllerModeActive() ) + + #if CONSOLE_PROG + RegisterMenuVarBool( "CONSOLE_isOnline", false ) + RegisterMenuVarBool( "CONSOLE_isSignedIn", false ) + #endif // CONSOLE_PROG + + #if DURANGO_PROG + RegisterMenuVarBool( "DURANGO_isGameFullyInstalled", false ) + RegisterMenuVarBool( "DURANGO_canInviteFriends", false ) + RegisterMenuVarBool( "DURANGO_isJoinable", false ) + #elseif PS4_PROG + RegisterMenuVarBool( "PS4_canInviteFriends", false) + #elseif PC_PROG + RegisterMenuVarBool( "ORIGIN_isEnabled", false ) + RegisterMenuVarBool( "ORIGIN_isJoinable", false ) + #endif + + thread UpdateFocus() + thread UpdateIsConnected() + thread UpdateIsFullyConnected() + thread UpdateAmIPartyLeader() + thread UpdateIsPrivateMatch() + thread UpdateActiveMenuThink() + + #if CONSOLE_PROG + thread UpdateConsole_IsOnline() + thread UpdateConsole_IsSignedIn() + #endif // CONSOLE_PROG + + #if DURANGO_PROG + thread UpdateDurango_IsGameFullyInstalled() + thread UpdateDurango_CanInviteFriends() + thread UpdateDurango_IsJoinable() + #elseif PS4_PROG + thread UpdatePS4_CanInviteFriends() + #elseif PC_PROG + thread UpdateOrigin_IsEnabled() + thread UpdateOrigin_IsJoinable() + thread UpdateIsGamepadActive() + #endif +} + +void function UpdateFocus() +{ + while ( true ) + { + SetMenuVarVar( "focus", GetFocus() ) + WaitFrame() + } +} + +void function UpdateActiveMenuThink() +{ + while ( true ) + { + var menu = GetActiveMenu() + if ( menu ) + { + Assert( menu in uiGlobal.menuData ) + if ( uiGlobal.menuData[ menu ].thinkFunc != null ) + uiGlobal.menuData[ menu ].thinkFunc() + } + + WaitFrame() + } +} + +void function UpdateIsConnected() +{ + while ( true ) + { + SetMenuVarBool( "isConnected", IsConnected() ) + WaitFrame() + } +} + +void function UpdateIsFullyConnected() +{ + while ( true ) + { + SetMenuVarBool( "isFullyConnected", IsFullyConnected() ) + WaitFrame() + } +} + +void function UpdateAmIPartyLeader() +{ + while ( true ) + { + SetMenuVarBool( "isPartyLeader", AmIPartyLeader() ) + WaitFrame() + } +} + +void function UpdateIsPrivateMatch() +{ + while ( true ) + { + SetMenuVarBool( "isPrivateMatch", IsPrivateMatch() ) + WaitFrame() + } +} + +#if CONSOLE_PROG + void function UpdateConsole_IsOnline() + { + while ( true ) + { + SetMenuVarBool( "CONSOLE_isOnline", Console_IsOnline() ) + WaitFrame() + } + } + + void function UpdateConsole_IsSignedIn() + { + while ( true ) + { + SetMenuVarBool( "CONSOLE_isSignedIn", Console_IsSignedIn() ) + WaitFrame() + } + } +#endif // CONSOLE_PROG + + +#if PS4_PROG + void function UpdatePS4_CanInviteFriends() + { + while ( true ) + { + SetMenuVarBool( "PS4_canInviteFriends", PS4_canInviteFriends() ) + WaitFrame() + } + } +#endif // PS4_PROG + + + +#if DURANGO_PROG + void function UpdateDurango_IsGameFullyInstalled() + { + while ( true ) + { + SetMenuVarBool( "DURANGO_isGameFullyInstalled", IsGameFullyInstalled() ) + wait 1 // Poll less frequent + } + } + + void function UpdateDurango_CanInviteFriends() + { + while ( true ) + { + SetMenuVarBool( "DURANGO_canInviteFriends", Durango_CanInviteFriends() ) + WaitFrame() + } + } + + void function UpdateDurango_IsJoinable() + { + while ( true ) + { + SetMenuVarBool( "DURANGO_isJoinable", Durango_IsJoinable() ) + WaitFrame() + } + } +#endif // DURANGO_PROG + +#if PC_PROG + void function UpdateOrigin_IsEnabled() + { + while ( true ) + { + SetMenuVarBool( "ORIGIN_isEnabled", Origin_IsEnabled() ) + WaitFrame() + } + } + + void function UpdateOrigin_IsJoinable() + { + while ( true ) + { + SetMenuVarBool( "ORIGIN_isJoinable", Origin_IsJoinable() ) + WaitFrame() + } + } + + void function UpdateIsGamepadActive() + { + while ( true ) + { + SetMenuVarBool( "isGamepadActive", IsControllerModeActive() ) + WaitFrame() + } + } +#endif // PC_PROG + +void function InviteFriends( var button ) +{ + //AdvanceMenu( GetMenu( "InviteFriendsToPartyMenu" ) ) + + #if DURANGO_PROG + Durango_InviteFriends() + #elseif PS4_PROG + ClientCommand("session_debug_invite"); + #elseif PC_PROG + Assert( Origin_IsEnabled() ) + Assert( Origin_IsJoinable() ) + + Origin_ShowInviteFriendsDialog() + #endif +} + +#if DURANGO_PROG +void function OpenXboxPartyApp( var button ) +{ + Durango_OpenPartyApp() +} + +void function OpenXboxHelp( var button ) +{ + Durango_ShowHelpWindow() +} +#endif // DURANGO_PROG + +void function OpenReviewTermsDialog( var button ) +{ + AdvanceMenu( GetMenu( "ReviewTermsDialog" ) ) +} + +void function OpenErrorDialog( string errorDetails ) +{ + DialogData dialogData + dialogData.header = "#ERROR" + dialogData.message = errorDetails + dialogData.image = $"ui/menu/common/dialog_error" + +#if PC_PROG + AddDialogButton( dialogData, "#DISMISS" ) + + AddDialogFooter( dialogData, "#A_BUTTON_SELECT" ) +#endif // PC_PROG + AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" ) + + while ( uiGlobal.activeMenu != GetMenu( "MainMenu" ) ) + { + WaitSignal( uiGlobal.signalDummy, "OpenErrorDialog", "ActiveMenuChanged" ) + } + + OpenDialog( dialogData ) +} + +bool function IsDialog( var menu ) +{ + if ( menu == null ) + return false + + return uiGlobal.menuData[ menu ].isDialog +} + +bool function IsDialogActive( DialogData dialogData ) +{ + if ( !IsDialog( uiGlobal.activeMenu ) ) + return false + + return uiGlobal.menuData[ uiGlobal.activeMenu ].dialogData == dialogData +} + +bool function IsDialogOnlyActiveMenu() +{ + if ( !IsDialog( uiGlobal.activeMenu ) ) + return false + + int stackLen = uiGlobal.menuStack.len() + if ( stackLen < 1 ) + return false + + if ( uiGlobal.menuStack[stackLen - 1] != uiGlobal.activeMenu ) + return false + + if ( stackLen == 1 ) + return true + + if ( uiGlobal.menuStack[stackLen - 2] == null ) + return true + + return false +} + +void function SetNavUpDown( array buttons, var wrap = true ) +{ + Assert( buttons.len() > 0 ) + + var first = buttons[0] + var last = buttons[buttons.len() - 1] + var prev + var next + var button + + for ( int i = 0; i < buttons.len(); i++ ) + { + button = buttons[i] + + if ( button == first ) + prev = last + else + prev = buttons[i - 1] + + if ( button == last ) + next = first + else + next = buttons[i + 1] + + button.SetNavUp( prev ) + button.SetNavDown( next ) + + //printt( "SetNavUP for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( prev ) ) + //printt( "SetNavDown for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( next ) ) + } +} + +void function SetNavLeftRight( array buttons, var wrap = true ) +{ + Assert( buttons.len() > 0 ) + + var first = buttons[0] + var last = buttons[buttons.len() - 1] + var prev + var next + var button + + for ( int i = 0; i < buttons.len(); i++ ) + { + button = buttons[i] + + if ( button == first ) + prev = last + else + prev = buttons[i - 1] + + if ( button == last ) + next = first + else + next = buttons[i + 1] + + button.SetNavLeft( prev ) + button.SetNavRight( next ) + + //printt( "SetNavUP for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( prev ) ) + //printt( "SetNavDown for:", Hud_GetHudName( button ), "to:", Hud_GetHudName( next ) ) + } +} + +void function UICodeCallback_EntitlementsChanged() +{ + if ( uiGlobal.activeMenu == null ) + return + + if ( uiGlobal.menuData[ uiGlobal.activeMenu ].entitlementsChangedFunc != null ) + thread uiGlobal.menuData[ uiGlobal.activeMenu ].entitlementsChangedFunc() +} + +#if PC_PROG +void function QuitGame() +{ + ClientCommand( "quit" ) +} +#endif + +void function UICodeCallback_StoreTransactionCompleted() +{ + // this callback is only supported and needed on PS4 currently +#if PS4_PROG + if ( InStoreMenu() ) + OnOpenDLCStore() +#endif +} + +void function UICodeCallback_GamePurchased() +{ + // this callback is only supported and needed on PC currently +#if PC_PROG + DialogData dialogData + dialogData.header = "#PURCHASE_GAME_COMPLETE" + dialogData.message = "#PURCHASE_GAME_RESTART" + AddDialogButton( dialogData, "#QUIT", QuitGame ) + + OpenDialog( dialogData ) +#endif +} + +bool function IsTrialPeriodActive() +{ + return GetConVarBool( "trialPeriodIsActive" ) +} + +void function LaunchGamePurchaseOrDLCStore( array menuNames = [ "StoreMenu" ] ) +{ + if ( Script_IsRunningTrialVersion() ) + { + LaunchGamePurchase() + } + else + { + void functionref() preOpenFunc = null + + foreach ( menuName in menuNames ) + { + // Special case because this menu needs a few properties set before opening + if ( menuName == "StoreMenu_WeaponSkins" ) + { + preOpenFunc = DefaultToDLC11WeaponWarpaintBundle + break + } + } + + OpenStoreMenu( menuNames, preOpenFunc ) + } +} + +void function UICodeCallback_PartyUpdated() +{ + if ( AmIPartyLeader() ) + { + string activeSearchingPlaylist = GetActiveSearchingPlaylist() + if ( activeSearchingPlaylist != "" && !CanPlaylistFitMyParty( activeSearchingPlaylist ) ) + { + CancelMatchSearch() + + DialogData dialogData + dialogData.header = "#MATCHMAKING_CANCELED" + dialogData.message = "#MATCHMAKING_CANCELED_REASON_PARTY_SIZE" + AddDialogButton( dialogData, "#OK" ) + + OpenDialog( dialogData ) + } + } +} + + +void function HACK_DelayedSetFocus_BecauseWhy( var item ) +{ + wait 0.1 + if ( IsValid( item ) ) + Hud_SetFocused( item ) +} + +void function ClassicMusic_OnChange( var button ) +{ + bool isEnabled = GetConVarBool( "sound_classic_music" ) + + if ( IsFullyConnected() && IsMultiplayer() && GetUIPlayer() ) + { + if ( IsItemLocked( GetUIPlayer(), "classic_music" ) ) + SetConVarBool( "sound_classic_music", false ) + + if ( IsLobby() ) + thread RunClientScript( "OnSoundClassicMusicChanged" ) + } +} + +bool function IsClassicMusicAvailable() +{ + bool classicMusicAvailable = false + if ( IsFullyConnected() && IsMultiplayer() && GetUIPlayer() ) + classicMusicAvailable = !IsItemLocked( GetUIPlayer(), "classic_music" ) + + return classicMusicAvailable +} + +void function UICodeCallback_KeyBindOverwritten( string key, string oldbinding, string newbinding ) +{ + DialogData dialogData + dialogData.header = Localize( "#MENU_KEYBIND_WAS_BEING_USED", key ) + dialogData.message = Localize( "#MENU_KEYBIND_WAS_BEING_USED_SUB", key, Localize( oldbinding ) ) + + AddDialogButton( dialogData, "#OK" ) + + OpenDialog( dialogData ) +} -- cgit v1.2.3 From 80ceb2b317336c454d83392028501a31da89e77e Mon Sep 17 00:00:00 2001 From: Respawn Date: Wed, 5 Apr 2023 01:25:35 +0200 Subject: Add menu_ingame.nut from englishclient_frontend --- .../mod/scripts/vscripts/ui/menu_ingame.nut | 693 +++++++++++++++++++++ 1 file changed, 693 insertions(+) create mode 100644 Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut (limited to 'Northstar.Client/mod/scripts/vscripts') diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut new file mode 100644 index 00000000..03bd8959 --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ingame.nut @@ -0,0 +1,693 @@ +//global function InitLobbyStartMenu +global function InitInGameMPMenu +global function InitInGameSPMenu +global function ServerCallback_UI_ObjectiveUpdated +global function ServerCallback_UI_UpdateMissionLog +global function SP_ResetObjectiveStringIndex +global function SCB_SetDoubleXPStatus + +global function SCB_SetCompleteMeritState +global function SCB_SetEvacMeritState +global function SCB_SetMeritCount +global function SCB_SetScoreMeritState +global function SCB_SetWinMeritState +global function SCB_SetWeaponMeritCount +global function SCB_SetTitanMeritCount + +const DATA_TABLE = $"datatable/sp_difficulty.rpak" + +struct +{ + var menuMP + var menuSP + var BtnTrackedChallengeBackground + var BtnTrackedChallengeTitle + array trackedChallengeButtons + var BtnLastCheckpoint + int objectiveStringIndex + bool SP_displayObjectiveOnClose + var settingsHeader + var faqButton + int titanHeaderIndex + var titanHeader + var titanSelectButton + var titanEditButton + + ComboStruct &comboStruct + + array loadoutButtons + array loadoutHeaders +} file + +void function InitInGameMPMenu() +{ + var menu = GetMenu( "InGameMPMenu" ) + file.menuMP = menu + + SP_ResetObjectiveStringIndex() + file.SP_displayObjectiveOnClose = true + + AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnInGameMPMenu_Open ) + AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnInGameMPMenu_Close ) + + AddUICallback_OnLevelInit( OnInGameLevelInit ) + + ComboStruct comboStruct = ComboButtons_Create( menu ) + + int headerIndex = 0 + int buttonIndex = 0 + var pilotHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_PILOT" ) + file.loadoutHeaders.append( pilotHeader ) + var pilotSelectButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#SELECT" ) + Hud_AddEventHandler( pilotSelectButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "PilotLoadoutsMenu" ) ) ) + file.loadoutButtons.append( pilotSelectButton ) + var pilotEditButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#EDIT" ) + Hud_AddEventHandler( pilotEditButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "EditPilotLoadoutsMenu" ) ) ) + file.loadoutButtons.append( pilotEditButton ) + + headerIndex++ + buttonIndex = 0 + var titanHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_TITAN" ) + file.titanHeader = titanHeader + file.titanHeaderIndex = headerIndex + file.loadoutHeaders.append( titanHeader ) + var titanSelectButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#SELECT" ) + file.titanSelectButton = titanSelectButton + file.loadoutButtons.append( titanSelectButton ) + Hud_AddEventHandler( titanSelectButton, UIE_CLICK, TitanSelectButtonHandler ) + var titanEditButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#EDIT" ) + Hud_AddEventHandler( titanEditButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "EditTitanLoadoutsMenu" ) ) ) + file.titanEditButton = titanEditButton + file.loadoutButtons.append( titanEditButton ) + + headerIndex++ + buttonIndex = 0 + var gameHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_GAME" ) + var leaveButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#LEAVE_MATCH" ) + Hud_AddEventHandler( leaveButton, UIE_CLICK, OnLeaveButton_Activate ) + #if DEV + var devButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "Dev" ) + Hud_AddEventHandler( devButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "DevMenu" ) ) ) + #endif + + headerIndex++ + buttonIndex = 0 + var dummyHeader = AddComboButtonHeader( comboStruct, headerIndex, "" ) + var dummyButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "" ) + Hud_SetVisible( dummyHeader, false ) + Hud_SetVisible( dummyButton, false ) + + headerIndex++ + buttonIndex = 0 + file.settingsHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_SETTINGS" ) + var controlsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#CONTROLS" ) + Hud_AddEventHandler( controlsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ControlsMenu" ) ) ) + #if CONSOLE_PROG + var avButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO_VIDEO" ) + Hud_AddEventHandler( avButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "AudioVideoMenu" ) ) ) + #elseif PC_PROG + var videoButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO" ) + Hud_AddEventHandler( videoButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "AudioMenu" ) ) ) + var soundButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#VIDEO" ) + Hud_AddEventHandler( soundButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "VideoMenu" ) ) ) + #endif + + file.faqButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#KNB_MENU_HEADER" ) + Hud_AddEventHandler( file.faqButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "KnowledgeBaseMenu" ) ) ) + + //var dataCenterButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#DATA_CENTER" ) + //Hud_AddEventHandler( dataCenterButton, UIE_CLICK, OpenDataCenterDialog ) + + ComboButtons_Finalize( comboStruct ) + + file.comboStruct = comboStruct + + AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" ) + AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_CLOSE", "#CLOSE" ) +} + +void function OnInGameMPMenu_Open() +{ + Lobby_SetFDMode( GetCurrentPlaylistVarInt( "ingame_menu_fd_mode", 0 ) == 1 ) + UI_SetPresentationType( ePresentationType.DEFAULT ) + + bool faqIsNew = !GetConVarBool( "menu_faq_viewed" ) || HaveNewPatchNotes() || HaveNewCommunityNotes() + RuiSetBool( Hud_GetRui( file.settingsHeader ), "isNew", faqIsNew ) + ComboButton_SetNew( file.faqButton, faqIsNew ) + + UpdateLoadoutButtons() + RefreshCreditsAvailable() + thread UpdateCachedNewItems() +} + +void function OnInGameMPMenu_Close() +{ + UI_SetPresentationType( ePresentationType.INACTIVE ) + + if ( IsConnected() && !IsLobby() && IsLevelMultiplayer( GetActiveLevel() ) ) + { + //printt( "OnInGameMPMenu_Close() uiGlobal.updatePilotSpawnLoadout is:", uiGlobal.updatePilotSpawnLoadout ) + //printt( "OnInGameMPMenu_Close() uiGlobal.updateTitanSpawnLoadout is:", uiGlobal.updateTitanSpawnLoadout ) + + string updatePilotSpawnLoadout = uiGlobal.updatePilotSpawnLoadout ? "1" : "0" + string updateTitanSpawnLoadout = uiGlobal.updateTitanSpawnLoadout ? "1" : "0" + + ClientCommand( "InGameMPMenuClosed " + updatePilotSpawnLoadout + " " + updateTitanSpawnLoadout ) + + uiGlobal.updatePilotSpawnLoadout = false + uiGlobal.updateTitanSpawnLoadout = false + + RunClientScript( "RefreshIntroLoadoutDisplay", GetLocalClientPlayer(), uiGlobal.pilotSpawnLoadoutIndex, uiGlobal.titanSpawnLoadoutIndex ) + } +} + +void function UpdateLoadoutButtons() +{ + bool loadoutSelectionEnabled = (GetCurrentPlaylistVarInt( "loadout_selection_enabled", 1 ) == 1) + + SetTitanSelectButtonVisibleState( true ) + + foreach ( button in file.loadoutButtons ) + { + Hud_SetEnabled( button, loadoutSelectionEnabled ) + } + + foreach ( header in file.loadoutHeaders ) + { + if ( loadoutSelectionEnabled ) + Hud_Show( header ) + else + Hud_Hide( header ) + } + + entity player = GetUIPlayer() + + if ( GetAvailableTitanRefs( player ).len() > 1 ) + { + SetComboButtonHeaderTitle( file.menuMP, file.titanHeaderIndex, "#MENU_HEADER_TITAN" ) + ComboButton_SetText( file.titanSelectButton, "#SELECT" ) + Hud_Show( file.titanEditButton ) + } + else if ( GetAvailableTitanRefs( player ).len() == 1 ) + { + TitanLoadoutDef loadout = GetCachedTitanLoadout( uiGlobal.titanSpawnLoadoutIndex ) + + SetComboButtonHeaderTitle( file.menuMP, file.titanHeaderIndex, GetTitanLoadoutName( loadout ) ) + ComboButton_SetText( file.titanSelectButton, "#EDIT" ) + + Hud_Hide( file.titanEditButton ) + ComboButtons_ResetColumnFocus( file.comboStruct ) + } + else + { + SetTitanSelectButtonVisibleState( true ) + } +} + +////////// + +////////// + +void function InitInGameSPMenu() +{ + var menu = GetMenu( "InGameSPMenu" ) + file.menuSP = menu + + AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnOpenInGameSPMenu ) + AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnCloseInGameSPMenu ) + + ComboStruct comboStruct = ComboButtons_Create( menu ) + + int headerIndex = 0 + int buttonIndex = 0 + + // MISSION Menu + var missionHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_MISSION" ) + var resumeButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#RESUME_GAME_SHORT" ) + Hud_AddEventHandler( resumeButton, UIE_CLICK, OnResumeGame_Activate ) + var lastCheckpointButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#LAST_CHECKPOINT" ) + Hud_AddEventHandler( lastCheckpointButton, UIE_CLICK, OnReloadCheckpoint_Activate ) + file.BtnLastCheckpoint = lastCheckpointButton + var restartButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#RESTART_LEVEL_SHORT" ) + Hud_AddEventHandler( restartButton, UIE_CLICK, OnRestartLevel_Activate ) + + // GAME Menu + // headerIndex++ + // buttonIndex = 0 + // var gameHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_GAME" ) + // var difficultyButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#CHANGE_DIFFICULTY" ) + // Hud_AddEventHandler( difficultyButton, UIE_CLICK, OnChangeDifficulty_Activate ) + // var leaveButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#QUIT" ) + // Hud_AddEventHandler( leaveButton, UIE_CLICK, OnLeaveButton_Activate ) + + // SETTINGS Menu + headerIndex++ + buttonIndex = 0 + var settingsHeader = AddComboButtonHeader( comboStruct, headerIndex, "#MENU_HEADER_SETTINGS" ) + var controlsButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#CONTROLS" ) + Hud_AddEventHandler( controlsButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ControlsMenu" ) ) ) + #if CONSOLE_PROG + var avButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO_VIDEO" ) + Hud_AddEventHandler( avButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "AudioVideoMenu" ) ) ) + #elseif PC_PROG + var audioButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#AUDIO" ) + Hud_AddEventHandler( audioButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "AudioMenu" ) ) ) + var videoButton = AddComboButton( comboStruct, headerIndex, buttonIndex++, "#VIDEO" ) + Hud_AddEventHandler( videoButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "VideoMenu" ) ) ) + #endif + + array orderedButtons + + var changeDifficultyBtn = Hud_GetChild( menu, "BtnChangeDifficulty" ) + + AddButtonEventHandler( changeDifficultyBtn, UIE_CLICK, OnChangeDifficulty_Activate ) + Hud_Show( changeDifficultyBtn ) + orderedButtons.append( changeDifficultyBtn ) + + var quitBtn = Hud_GetChild( menu, "BtnQuit" ) + SetButtonRuiText( quitBtn, "#QUIT" ) + AddButtonEventHandler( quitBtn, UIE_CLICK, OnLeaveButton_Activate ) + Hud_Show( quitBtn ) + orderedButtons.append( quitBtn ) + + // DEV button + var devButton = Hud_GetChild( menu, "BtnDev" ) + #if DEV + SetButtonRuiText( devButton, "--- Dev" ) + AddButtonEventHandler( devButton, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "DevMenu" ) ) ) + Hud_Show( devButton ) + orderedButtons.append( devButton ) + comboStruct.navUpButton = devButton + #else + Hud_Hide( devButton ) + comboStruct.navUpButton = quitBtn + #endif // DEV + + SetNavUpDown( orderedButtons ) + comboStruct.navDownButton = changeDifficultyBtn + + ComboButtons_Finalize( comboStruct ) + + AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" ) + AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_CLOSE", "#CLOSE" ) +} + + +void function OnOpenInGameSPMenu() +{ + var collectiblesFoundDesc = Hud_GetChild( file.menuSP, "CollectiblesFoundDesc" ) + var missionLogDesc = Hud_GetChild( file.menuSP, "MissionLogDesc" ) + var changeDifficultyBtn = Hud_GetChild( file.menuSP, "BtnChangeDifficulty" ) + + Hud_SetEnabled( file.BtnLastCheckpoint, HasValidSaveGame() ) + + int currentDifficulty = GetConVarInt( "sp_difficulty" ) + string newDifficultyString + + switch ( currentDifficulty ) + { + case 0: + newDifficultyString = "#CHANGE_DIFFICULTY_EASY" + break + + case 1: + newDifficultyString = "#CHANGE_DIFFICULTY_REGULAR" + break + + case 2: + newDifficultyString = "#CHANGE_DIFFICULTY_HARD" + break + + case 3: + newDifficultyString = "#CHANGE_DIFFICULTY_MASTER" + break + + default: + Assert( 0, "Unknown difficulty " + currentDifficulty ) + break + } + SetButtonRuiText( changeDifficultyBtn, newDifficultyString ) + + string activeLevelName = GetActiveLevel() + if ( activeLevelName != "" ) + { + var dataTable = GetDataTable( $"datatable/sp_levels_data.rpak" ) + + // Make sure this level actually has data to display. + bool levelHasData = false + int numRows = GetDatatableRowCount( dataTable ) + for ( int i = 0; i < numRows; i++ ) + { + string levelName = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "level" ) ) + if ( activeLevelName == levelName ) + { + levelHasData = true + break + } + } + + if ( levelHasData ) + { + // Mission Log + int row = GetDataTableRowMatchingStringValue( dataTable, GetDataTableColumnByName( dataTable, "level" ), activeLevelName ) + string missionLog = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "missionLog" ) ) + + if ( uiGlobal.sp_showAlternateMissionLog ) + { + string alternateMissionLog = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "alternateMissionLog" ) ) + missionLog = alternateMissionLog + } + + Hud_SetText( missionLogDesc, missionLog ) + + // Collectibles + int foundLions = GetCollectiblesFoundForLevel( activeLevelName ) + int maxLions = GetMaxLionsInLevel( activeLevelName ) + Hud_SetText( collectiblesFoundDesc, "#MENU_SP_COLLECTIBLE_DESC", foundLions, maxLions ) + } + else + { + Hud_SetText( missionLogDesc, "#MENU_SP_OBJECTIVES_NO_ENTRY" ) + Hud_SetText( collectiblesFoundDesc, "#MENU_SP_COLLECTIBLE_DESC", 0, 0 ) + } + + // Make sure trial mode doesn't reveal any spoilers! + if ( Script_IsRunningTrialVersion() ) + Hud_SetText( missionLogDesc, "#MENU_SP_OBJECTIVES_NO_ENTRY" ) + } + + SPMenu_UpdateReloadCheckpointButton() +} + + +void function OnCloseInGameSPMenu() +{ + if ( file.SP_displayObjectiveOnClose ) + ClientCommand( "ShowObjective closedSPMenu" ) +} + +void function SPMenu_UpdateReloadCheckpointButton() +{ + if ( level.ui.playerRunningGauntlet ) + ComboButton_SetText( file.BtnLastCheckpoint, "#GAUNTLET_RESTART" ) + else + ComboButton_SetText( file.BtnLastCheckpoint, "#LAST_CHECKPOINT" ) +} + +void function MobilityDifficultyButton_Activate( var button ) +{ + OpenMobilityDifficultyMenu() +} + +void function OnLeaveButton_Activate( var button ) +{ + file.SP_displayObjectiveOnClose = false + LeaveDialog() +} + +void function OnRestartLevel_Activate( var button ) +{ + ShowAreYouSureDialog( "#MENU_RESTART_MISSION_CONFIRM", RestartMission, "#WARNING_LOSE_PROGRESS" ) +} + +void function OnChangeDifficulty_Activate( var button ) +{ + SPDifficultyButton_Click( button ) +} + +void function OnResumeGame_Activate( var button ) +{ + CloseActiveMenu() +} + +void function OnReloadCheckpoint_Activate( var button ) +{ + if ( level.ui.playerRunningGauntlet ) + { + CloseActiveMenu() + ClientCommand( "Gauntlet_PlayerRestartedFromMenu" ) + } + else + { + ShowAreYouSureDialog( "#MENU_RESTART_CHECKPOINT_CONFIRM", ReloadLastCheckpoint, "#EMPTY_STRING" ) + } +} + +void function ShowAreYouSureDialog( string header, void functionref() func, string details ) +{ + DialogData dialogData + dialogData.header = header + dialogData.message = details + + AddDialogButton( dialogData, "#NO" ) + AddDialogButton( dialogData, "#YES", func ) + + AddDialogFooter( dialogData, "#A_BUTTON_SELECT" ) + AddDialogFooter( dialogData, "#B_BUTTON_BACK" ) + + OpenDialog( dialogData ) +} + +void function RestartMission() +{ + file.SP_displayObjectiveOnClose = false + ClientCommand( "RestartMission" ) +} + +void function ReloadLastCheckpoint() +{ + file.SP_displayObjectiveOnClose = false + + printt( "SAVEGAME: Trying to load saveName" ) + if ( HasValidSaveGame() ) + { + printt( "SAVEGAME: Trying to load checkpoint from menu_ingame" ) + SaveGame_LoadWithStartPointFallback() + return + } + + ClientCommand( "RestartFromLevelTransition" ) +} + +void function SP_ResetObjectiveStringIndex() +{ + file.objectiveStringIndex = -1 +} + +void function ServerCallback_UI_ObjectiveUpdated( int stringIndex ) +{ + file.objectiveStringIndex = stringIndex +} + +void function ServerCallback_UI_UpdateMissionLog( bool showAltLog ) +{ + uiGlobal.sp_showAlternateMissionLog = showAltLog +} + +void function SPDifficultyButton_Click( var button ) +{ + DialogData dialogData + dialogData.header = "#SP_DIFFICULTY_MISSION_SELECT_TITLE" + + int currentDifficulty = GetConVarInt( "sp_difficulty" ) + dialogData.coloredButton[ currentDifficulty ] <- true + + if ( currentDifficulty == DIFFICULTY_EASY ) + AddDialogButton( dialogData, "#SP_DIFFICULTY_EASY_TITLE", SPPickEasy, "#SP_DIFFICULTY_EASY_DESCRIPTION", true ) + else + AddDialogButton( dialogData, "#SP_DIFFICULTY_EASY_TITLE", SPPickEasy, "#SP_DIFFICULTY_EASY_DESCRIPTION", false ) + + + if ( currentDifficulty == DIFFICULTY_NORMAL ) + AddDialogButton( dialogData, "#SP_DIFFICULTY_NORMAL_TITLE", SPPickNormal, "#SP_DIFFICULTY_NORMAL_DESCRIPTION", true ) + else + AddDialogButton( dialogData, "#SP_DIFFICULTY_NORMAL_TITLE", SPPickNormal, "#SP_DIFFICULTY_NORMAL_DESCRIPTION", false ) + + + if ( currentDifficulty == DIFFICULTY_HARD ) + AddDialogButton( dialogData, "#SP_DIFFICULTY_HARD_TITLE", SPPickHard, "#SP_DIFFICULTY_HARD_DESCRIPTION", true ) + else + AddDialogButton( dialogData, "#SP_DIFFICULTY_HARD_TITLE", SPPickHard, "#SP_DIFFICULTY_HARD_DESCRIPTION", false ) + + + if ( currentDifficulty == DIFFICULTY_MASTER ) + AddDialogButton( dialogData, "#SP_DIFFICULTY_MASTER_TITLE", SPPickMaster, "#SP_DIFFICULTY_MASTER_DESCRIPTION", true ) + else + AddDialogButton( dialogData, "#SP_DIFFICULTY_MASTER_TITLE", SPPickMaster, "#SP_DIFFICULTY_MASTER_DESCRIPTION", false ) + + + AddDialogFooter( dialogData, "#A_BUTTON_SELECT" ) + AddDialogFooter( dialogData, "#B_BUTTON_BACK" ) + AddDialogPCBackButton( dialogData ) + + OpenDialog( dialogData ) +} + +void function SPPickEasy() +{ + RequestSPDifficultyChange( DIFFICULTY_EASY ) + CloseAllMenus() +} + +void function SPPickNormal() +{ + RequestSPDifficultyChange( DIFFICULTY_NORMAL ) + CloseAllMenus() +} + +void function SPPickHard() +{ + RequestSPDifficultyChange( DIFFICULTY_HARD ) + CloseAllMenus() +} + +void function SPPickMaster() +{ + RequestSPDifficultyChange( DIFFICULTY_MASTER ) + CloseAllMenus() +} + +void function RequestSPDifficultyChange( int selectedDifficulty ) +{ + var dataTable = GetDataTable( DATA_TABLE ) + int difficulty = GetDataTableInt( dataTable, selectedDifficulty, GetDataTableColumnByName( dataTable, "index" ) ) + + ClientCommand( "ClientCommand_RequestSPDifficultyChange " + difficulty ) +} + +void function SCB_SetDoubleXPStatus( int status ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + RuiSetInt( Hud_GetRui( doubleXPWidget ), "doubleXPStatus", status ) + + // update this menu too + TTSUpdateDoubleXPStatus( status ) +} + +void function OnInGameLevelInit() +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + RuiSetInt( rui, "doubleXPStatus", 0 ) + RuiSetBool( rui, "isVisible", false ) + + string gameModeScoreHint = expect string( GetCurrentPlaylistVar( "gamemode_score_hint" ) ) + if ( gameModeScoreHint != "" ) + { + RuiSetString( rui, "scoreMeritText", Localize( gameModeScoreHint ) ) + RuiSetInt( rui, "matchScoreMerit", MERIT_STATE_AVAILABLE ) + } + else + { + RuiSetString( rui, "scoreMeritText", "" ) + RuiSetInt( rui, "matchScoreMerit", MERIT_STATE_HIDDEN ) + } + + Hud_SetVisible( doubleXPWidget, !IsPrivateMatch() ) +} +/* +int matchScoreMerit = MERIT_STATE_AVAILABLE +int matchCompleteMerit = MERIT_STATE_AVAILABLE +int matchWinMerit = MERIT_STATE_AVAILABLE +int matchEvacMerit = MERIT_STATE_HIDDEN +int happyHourMerits = MERIT_STATE_HIDDEN + +int meritCount = 0 +*/ + +void function SCB_SetScoreMeritState( int meritState ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + + RuiSetInt( rui, "matchScoreMerit", meritState ) +} + +void function SCB_SetCompleteMeritState( int meritState ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + + RuiSetInt( rui, "matchCompleteMerit", meritState ) +} + +void function SCB_SetWinMeritState( int meritState ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + + RuiSetInt( rui, "matchWinMerit", meritState ) +} + +void function SCB_SetEvacMeritState( int meritState ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + + RuiSetInt( rui, "matchEvacMerit", meritState ) +} + +void function SCB_SetMeritCount( int meritCount ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + + RuiSetInt( rui, "meritCount", meritCount ) +} + +void function SCB_SetWeaponMeritCount( int meritCount ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + + RuiSetInt( rui, "weaponMeritCount", meritCount ) +} + +void function SCB_SetTitanMeritCount( int meritCount ) +{ + var doubleXPWidget = Hud_GetChild( file.menuMP, "DoubleXP" ) + var rui = Hud_GetRui( doubleXPWidget ) + + RuiSetInt( rui, "titanMeritCount", meritCount ) +} + +void function TitanSelectButtonHandler( var button ) +{ + if ( !IsFullyConnected() ) + return + + entity player = GetUIPlayer() + if ( GetAvailableTitanRefs( player ).len() > 1 ) + { + AdvanceMenu( GetMenu( "TitanLoadoutsMenu" ) ) + } + else if ( GetAvailableTitanRefs( player ).len() == 1 ) + { + uiGlobal.updateTitanSpawnLoadout = false + SetEditLoadout( "titan", uiGlobal.titanSpawnLoadoutIndex ) + + RunMenuClientFunction( "SetEditingTitanLoadoutIndex", uiGlobal.titanSpawnLoadoutIndex ) + AdvanceMenu( GetMenu( "EditTitanLoadoutMenu" ) ) + } + else + { + // HIDE + } +} + +void function SetTitanSelectButtonVisibleState( bool state ) +{ + if ( state ) + { + Hud_Show( file.titanHeader ) + Hud_Show( file.titanEditButton ) + Hud_Show( file.titanSelectButton ) + } + else + { + ComboButtons_ResetColumnFocus( file.comboStruct ) + Hud_Hide( file.titanHeader ) + Hud_Hide( file.titanEditButton ) + Hud_Hide( file.titanSelectButton ) + } +} -- cgit v1.2.3 From db2434b524c284913c91556c75ff1ebd4226c96e Mon Sep 17 00:00:00 2001 From: Emma Miler Date: Tue, 11 Apr 2023 20:49:35 +0200 Subject: New presence code for plugins v2 (#532) * test * Update cl_presence.nut * Update Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut * Apply suggestions from code review * New scripts * fix playlist and add native funcs to build * remove snake cases from GameStateStruct * I forgor --------- Co-authored-by: cat_or_not <41955154+catornot@users.noreply.github.com> --- .github/nativefuncs.json | 12 +++ Northstar.Client/mod.json | 1 + .../scripts/vscripts/cl_northstar_client_init.nut | 22 +++++ .../mod/scripts/vscripts/presence/cl_presence.nut | 95 +++++++++------------- .../mod/scripts/vscripts/presence/ui_presence.nut | 42 ++-------- .../mod/scripts/vscripts/ui/_menus.nut | 2 + .../mod/scripts/vscripts/ui/menu_lobby.nut | 2 - .../scripts/vscripts/ui/menu_ns_serverbrowser.nut | 12 --- 8 files changed, 85 insertions(+), 103 deletions(-) create mode 100644 Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut (limited to 'Northstar.Client/mod/scripts/vscripts') diff --git a/.github/nativefuncs.json b/.github/nativefuncs.json index 50bba0e5..f52a969c 100644 --- a/.github/nativefuncs.json +++ b/.github/nativefuncs.json @@ -325,6 +325,12 @@ "helpText":"Whether or not HTTP requests can be made to a private network address. You can enable this by starting the game with -allowlocalhttp.", "returnTypeString":"bool", "argTypes":"" + }, + { + "name":"NSPushGameStateData", + "helpText":"", + "returnTypeString":"void", + "argTypes":"struct gamestate" } ], "UI":[ @@ -639,6 +645,12 @@ "helpText":"Whether or not HTTP requests can be made to a private network address. You can enable this by starting the game with -allowlocalhttp.", "returnTypeString":"bool", "argTypes":"" + }, + { + "name":"NSPushUIPresence", + "helpText":"", + "returnTypeString":"void", + "argTypes":"struct presence" } ] } \ No newline at end of file diff --git a/Northstar.Client/mod.json b/Northstar.Client/mod.json index d52f95fc..f0f45e67 100644 --- a/Northstar.Client/mod.json +++ b/Northstar.Client/mod.json @@ -3,6 +3,7 @@ "Description": "Various ui and client changes to fix bugs and add better support for mods", "Version": "1.12.0", "LoadPriority": 0, + "InitScript": "cl_northstar_client_init.nut", "ConVars": [ { "Name": "filter_hide_empty", diff --git a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut new file mode 100644 index 00000000..212568d0 --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut @@ -0,0 +1,22 @@ +global struct GameStateStruct { + + string map + string mapDisplayname + + string playlist + string playlistDisplayname + + int currentPlayers + int maxPlayers + int ownScore + int otherHighestScore + int maxScore + float timeEnd +} + +global struct UIPresenceStruct { + bool isLoading + bool isLobby + string loadingLevel + string loadedLevel +} \ 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 755396e3..c8a8274a 100644 --- a/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut +++ b/Northstar.Client/mod/scripts/vscripts/presence/cl_presence.nut @@ -1,60 +1,45 @@ untyped globalize_all_functions -struct { - int highestScore = 0 - int secondHighestScore = 0 -} file - -void function OnPrematchStart() -{ - if ( GetServerVar( "roundBased" ) ) - NSUpdateTimeInfo( level.nv.roundEndTime - Time() ) - else - NSUpdateTimeInfo( level.nv.gameEndTime - Time() ) -} - -void function NSUpdateGameStateClientStart() -{ - #if MP - AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart ) - #endif - - thread NSUpdateGameStateLoopClient() - OnPrematchStart() -} - -void function NSUpdateGameStateLoopClient() -{ - while ( true ) +void function NorthstarCodeCallback_GenerateGameState() { + + GameStateStruct gs + + int highestScore = 0 + int secondHighest = 0 + + foreach ( player in GetPlayerArray() ) { - if ( IsSingleplayer() ) - { - NSUpdateGameStateClient( GetPlayerArray().len(), GetCurrentPlaylistVarInt( "max_players", 65535 ), 1, 1, 1, GetServerVar( "roundBased" ), 1 ) - wait 1.0 - } - else - { - foreach ( player in GetPlayerArray() ) - { - if ( GameRules_GetTeamScore( player.GetTeam() ) >= file.highestScore ) - { - file.highestScore = GameRules_GetTeamScore( player.GetTeam() ) - } - else if ( GameRules_GetTeamScore( player.GetTeam() ) > file.secondHighestScore ) - { - file.secondHighestScore = GameRules_GetTeamScore( player.GetTeam() ) - } - } - - int ourScore = 0 - if ( IsValid( GetLocalClientPlayer() ) ) - ourScore = GameRules_GetTeamScore( GetLocalClientPlayer().GetTeam() ) - - int limit = IsRoundBased() ? GetCurrentPlaylistVarInt( "roundscorelimit", 0 ) : GetCurrentPlaylistVarInt( "scorelimit", 0 ) - NSUpdateGameStateClient( GetPlayerArray().len(), GetCurrentPlaylistVarInt( "max_players", 65535 ), ourScore, file.secondHighestScore, file.highestScore, GetServerVar( "roundBased" ), limit ) - OnPrematchStart() - wait 1.0 - } + if ( GameRules_GetTeamScore( player.GetTeam() ) >= highestScore ) + { + highestScore = GameRules_GetTeamScore( player.GetTeam() ) + } + else if ( GameRules_GetTeamScore( player.GetTeam() ) > secondHighest ) + { + secondHighest = GameRules_GetTeamScore( player.GetTeam() ) + } } -} + + gs.map = GetMapName() + gs.mapDisplayname = Localize(GetMapDisplayName(GetMapName())) + + gs.playlist = GetCurrentPlaylistName() + gs.playlistDisplayname = Localize(GetCurrentPlaylistVarString("name", GetCurrentPlaylistName())) + + gs.currentPlayers = GetPlayerArray().len() + gs.maxPlayers = GetCurrentPlaylistVarInt( "maxPlayers", -1 ) + + if ( IsValid( GetLocalClientPlayer() ) ) + gs.ownScore = GameRules_GetTeamScore( GetLocalClientPlayer().GetTeam() ) + + gs.otherHighestScore = gs.ownScore == highestScore ? secondHighest : highestScore + + gs.maxScore = IsRoundBased() ? GetCurrentPlaylistVarInt( "roundscorelimit", 0 ) : GetCurrentPlaylistVarInt( "scorelimit", 0 ) + + if ( GetServerVar( "roundBased" ) ) + 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 diff --git a/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut b/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut index 1e381989..cdf1c981 100644 --- a/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut +++ b/Northstar.Client/mod/scripts/vscripts/presence/ui_presence.nut @@ -1,38 +1,12 @@ untyped globalize_all_functions -void function NSUpdateGameStateUIStart() -{ - thread NSUpdateGameStateLoopUI() -} +void function NorthstarCodeCallback_GenerateUIPresence() { + UIPresenceStruct uis -void function NSUpdateGameStateLoopUI() -{ - while ( true ) - { - wait 1.0 - - if ( uiGlobal.loadedLevel == "" ) - { - if ( uiGlobal.isLoading ) - NSSetLoading( true ) - else - { - NSSetLoading( false ) - NSUpdateGameStateUI( "", "", "", "", true, false ) - } - - continue - } - - NSSetLoading( false ) - if( GetConVarString( "mp_gamemode" ) == "solo" ) - { - NSUpdateGameStateUI( GetActiveLevel(), Localize( GetMapDisplayName( GetActiveLevel() + "_CAMPAIGN_NAME" ) ), "Campaign", "Campaign", IsFullyConnected(), false ) - } - else - { - NSUpdateGameStateUI( GetActiveLevel(), Localize( GetMapDisplayName( GetActiveLevel() ) ), GetConVarString( "mp_gamemode" ), Localize( GetPlaylistDisplayName( GetConVarString( "mp_gamemode" ) ) ), IsFullyConnected(), false ) - } - } -} + uis.isLoading = uiGlobal.isLoading + uis.isLobby = IsLobby() + uis.loadingLevel = uiGlobal.loadingLevel + uis.loadedLevel = uiGlobal.loadedLevel + NSPushUIPresence(uis) +} \ No newline at end of file diff --git a/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut b/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut index 90a535ee..c83381fd 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/_menus.nut @@ -187,6 +187,7 @@ bool function UICodeCallback_LevelLoadingStarted( string levelname ) CloseAllDialogs() uiGlobal.loadingLevel = levelname + uiGlobal.isLoading = true StopMusic() @@ -235,6 +236,7 @@ void function UICodeCallback_LevelLoadingFinished( bool error ) } uiGlobal.loadingLevel = "" + uiGlobal.isLoading = false Signal( uiGlobal.signalDummy, "LevelFinishedLoading" ) } diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut index 938e0d3f..3c868aab 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_lobby.nut @@ -372,8 +372,6 @@ void function StartPrivateMatch( var button ) return ClientCommand( "StartPrivateMatchSearch" ) - NSSetLoading(true) - NSUpdateListenServer() } void function DoRoomInviteIfAllowed( var button ) 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 03028255..b2e2a8b6 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut @@ -1058,18 +1058,6 @@ void function ThreadedAuthAndConnectToServer( string password = "" ) } file.cancelConnection = false - NSSetLoading( true ) - NSUpdateServerInfo( - NSGetServerID( file.lastSelectedServer ), - NSGetServerName( file.lastSelectedServer ), - password, - NSGetServerPlayerCount( file.lastSelectedServer ), - NSGetServerMaxPlayerCount( file.lastSelectedServer ), - NSGetServerMap( file.lastSelectedServer ), - Localize( GetMapDisplayName( NSGetServerMap( file.lastSelectedServer ) ) ), - NSGetServerPlaylist( file.lastSelectedServer ), - Localize( GetPlaylistDisplayName( NSGetServerPlaylist( file.lastSelectedServer ) ) ) - ) if ( NSWasAuthSuccessful() ) { -- cgit v1.2.3 From ecfdf4a47beb94f90a91e0ccac25d95b5c5f3cbb Mon Sep 17 00:00:00 2001 From: pg9182 <96569817+pg9182@users.noreply.github.com> Date: Thu, 20 Apr 2023 18:22:54 -0400 Subject: Improve server connection error messages (#598) * Also show masterserver error for server connections It's already used for self auth and is available for both, but we currently only show it for the former. * Don't localize server connection errors It never fully worked, and it's more useful to have non-localized detailed error messages. * Don't prepend NS_SERVERBROWSER_CONNECTIONFAILED to server conn err msg The message always contains something like it, and it's always right after a connectiom anyways. * NSGetAuthFailReason always returns a string, so don't make it untyped --- Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut | 4 +++- Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'Northstar.Client/mod/scripts/vscripts') 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 b2e2a8b6..b663a25a 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut @@ -1085,9 +1085,11 @@ void function ThreadedAuthAndConnectToServer( string password = "" ) } else { + string reason = NSGetAuthFailReason() + DialogData dialogData dialogData.header = "#ERROR" - dialogData.message = "Authentication Failed" + dialogData.message = reason dialogData.image = $"ui/menu/common/dialog_error" #if PC_PROG diff --git a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut index 95b7bdae..53d85387 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut @@ -571,12 +571,12 @@ void function TryAuthWithLocalServer() { CloseAllDialogs() - var reason = NSGetAuthFailReason() + string reason = NSGetAuthFailReason() DialogData dialogData dialogData.image = $"ui/menu/common/dialog_error" dialogData.header = "#ERROR" - dialogData.message = Localize("#NS_SERVERBROWSER_CONNECTIONFAILED") + "\nERROR: " + reason + "\n" + Localize("#" + reason) + dialogData.message = reason AddDialogButton( dialogData, "#OK", null ) OpenDialog( dialogData ) -- cgit v1.2.3 From 2d6ca83c3ab5f045dfbc5d0a167b925112b2bd84 Mon Sep 17 00:00:00 2001 From: uniboi <64006268+uniboi@users.noreply.github.com> Date: Thu, 27 Apr 2023 12:55:32 +0000 Subject: Use structs in the server browser (#623) * move to native structs * add connect callbacks * update native functions register * trigger callbacks on double click * use suggestions --- .github/nativefuncs.json | 74 +----- .../scripts/vscripts/cl_northstar_client_init.nut | 23 +- .../vscripts/ui/menu_ns_connect_password.nut | 3 + .../scripts/vscripts/ui/menu_ns_serverbrowser.nut | 254 +++++++++++---------- 4 files changed, 165 insertions(+), 189 deletions(-) (limited to 'Northstar.Client/mod/scripts/vscripts') diff --git a/.github/nativefuncs.json b/.github/nativefuncs.json index f52a969c..751b4893 100644 --- a/.github/nativefuncs.json +++ b/.github/nativefuncs.json @@ -449,76 +449,10 @@ "argTypes":"" }, { - "name":"NSGetServerName", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerDescription", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerMap", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerPlaylist", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerPlayerCount", - "helpText":"", - "returnTypeString":"int", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerMaxPlayerCount", - "helpText":"", - "returnTypeString":"int", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerID", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex" - }, - { - "name":"NSServerRequiresPassword", - "helpText":"", - "returnTypeString":"bool", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerRequiredModsCount", - "helpText":"", - "returnTypeString":"int", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerRegion", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex" - }, - { - "name":"NSGetServerRequiredModName", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex, int modIndex" - }, - { - "name":"NSGetServerRequiredModVersion", - "helpText":"", - "returnTypeString":"string", - "argTypes":"int serverIndex, int modIndex" + "name": "NSGetGameServers", + "helpText": "Gets all fetched servers", + "returnTypeString": "array", + "argTypes": "" }, { "name":"NSClearRecievedServerList", 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 212568d0..2a2ed3db 100644 --- a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut +++ b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut @@ -19,4 +19,25 @@ global struct UIPresenceStruct { bool isLobby string loadingLevel string loadedLevel -} \ No newline at end of file +} + +global struct RequiredModInfo +{ + string name + string version +} + +global struct ServerInfo +{ + int index + string id + string name + string description + string map + string playlist + int playerCount + int maxPlayerCount + bool requiresPassword + string region + array< RequiredModInfo > requiredMods +} diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut index b5a2e9b6..1e10aa45 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_connect_password.nut @@ -54,5 +54,8 @@ void function OnConnectWithPasswordMenuOpened() void function ConnectWithPassword( var button ) { if ( GetTopNonDialogMenu() == file.menu ) + { + TriggerConnectToServerCallbacks() thread ThreadedAuthAndConnectToServer( Hud_GetUTF8Text( Hud_GetChild( file.menu, "EnterPasswordBox" ) ) ) + } } \ No newline at end of file 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 b663a25a..c31185ee 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut @@ -4,6 +4,9 @@ untyped global function AddNorthstarServerBrowserMenu global function ThreadedAuthAndConnectToServer +global function AddConnectToServerCallback +global function RemoveConnectToServerCallback +global function TriggerConnectToServerCallbacks // Stop peeking @@ -67,7 +70,6 @@ struct serverStruct { struct { // UI state vars var menu - int lastSelectedServer = 999 int focusedServerIndex = 0 int scrollOffset = 0 bool serverListRequestFailed = false @@ -79,6 +81,10 @@ struct { // filtered array of servers array serversArrayFiltered + + array filteredServers + ServerInfo& focusedServer + ServerInfo& lastSelectedServer // UI references array serverButtons @@ -88,6 +94,8 @@ struct { array serversMap array serversGamemode array serversRegion + + array< void functionref( ServerInfo ) > connectCallbacks } file @@ -253,7 +261,7 @@ void function FlushMouseDeltaBuffer() void function SliderBarUpdate() { - if ( file.serversArrayFiltered.len() <= BUTTONS_PER_PAGE ) + if ( file.filteredServers.len() <= BUTTONS_PER_PAGE ) { FlushMouseDeltaBuffer() return @@ -270,7 +278,7 @@ void function SliderBarUpdate() float maxYPos = minYPos - ( maxHeight - Hud_GetHeight( sliderPanel ) ) float useableSpace = ( maxHeight - Hud_GetHeight( sliderPanel ) ) - float jump = minYPos - ( useableSpace / ( float( file.serversArrayFiltered.len() ) ) ) + float jump = minYPos - ( useableSpace / ( float( file.filteredServers.len() ) ) ) // got local from official respaw scripts, without untyped throws an error local pos = Hud_GetPos( sliderButton )[1] @@ -284,7 +292,7 @@ void function SliderBarUpdate() Hud_SetPos( sliderPanel , 2, newPos ) Hud_SetPos( movementCapture , 2, newPos ) - file.scrollOffset = -int( ( ( newPos - minYPos ) / useableSpace ) * ( file.serversArrayFiltered.len() - BUTTONS_PER_PAGE ) ) + file.scrollOffset = -int( ( ( newPos - minYPos ) / useableSpace ) * ( file.filteredServers.len() - BUTTONS_PER_PAGE ) ) UpdateShownPage() } @@ -328,13 +336,13 @@ void function UpdateListSliderPosition( int servers ) void function OnScrollDown( var button ) { - if (file.serversArrayFiltered.len() <= BUTTONS_PER_PAGE) return + if (file.filteredServers.len() <= BUTTONS_PER_PAGE) return file.scrollOffset += 5 - if (file.scrollOffset + BUTTONS_PER_PAGE > file.serversArrayFiltered.len()) { - file.scrollOffset = file.serversArrayFiltered.len() - BUTTONS_PER_PAGE + if (file.scrollOffset + BUTTONS_PER_PAGE > file.filteredServers.len()) { + file.scrollOffset = file.filteredServers.len() - BUTTONS_PER_PAGE } UpdateShownPage() - UpdateListSliderPosition( file.serversArrayFiltered.len() ) + UpdateListSliderPosition( file.filteredServers.len() ) } void function OnScrollUp( var button ) @@ -344,7 +352,7 @@ void function OnScrollUp( var button ) file.scrollOffset = 0 } UpdateShownPage() - UpdateListSliderPosition( file.serversArrayFiltered.len() ) + UpdateListSliderPosition( file.filteredServers.len() ) } //////////////////////////// @@ -484,7 +492,7 @@ void function OnHitDummyTop( var button ) { // only update if list position changed UpdateShownPage() - UpdateListSliderPosition( file.serversArrayFiltered.len() ) + UpdateListSliderPosition( file.filteredServers.len() ) DisplayFocusedServerInfo( file.serverButtonFocusedID ) Hud_SetFocused( Hud_GetChild( file.menu, "BtnServer1" ) ) } @@ -493,10 +501,10 @@ void function OnHitDummyTop( var button ) void function OnHitDummyBottom( var button ) { file.scrollOffset += 1 - if ( file.scrollOffset + BUTTONS_PER_PAGE > file.serversArrayFiltered.len() ) + if ( file.scrollOffset + BUTTONS_PER_PAGE > file.filteredServers.len() ) { // was at bottom already - file.scrollOffset = file.serversArrayFiltered.len() - BUTTONS_PER_PAGE + file.scrollOffset = file.filteredServers.len() - BUTTONS_PER_PAGE Hud_SetFocused( Hud_GetChild( file.menu, "BtnServerSearch" ) ) HideServerInfo() } @@ -504,7 +512,7 @@ void function OnHitDummyBottom( var button ) { // only update if list position changed UpdateShownPage() - UpdateListSliderPosition( file.serversArrayFiltered.len() ) + UpdateListSliderPosition( file.filteredServers.len() ) DisplayFocusedServerInfo( file.serverButtonFocusedID ) Hud_SetFocused( Hud_GetChild( file.menu, "BtnServer15" ) ) } @@ -518,15 +526,15 @@ void function OnHitDummyAfterFilterClear( var button ) void function OnDownArrowSelected( var button ) { - if ( file.serversArrayFiltered.len() <= BUTTONS_PER_PAGE ) return + if ( file.filteredServers.len() <= BUTTONS_PER_PAGE ) return file.scrollOffset += 1 - if ( file.scrollOffset + BUTTONS_PER_PAGE > file.serversArrayFiltered.len() ) + if ( file.scrollOffset + BUTTONS_PER_PAGE > file.filteredServers.len() ) { - file.scrollOffset = file.serversArrayFiltered.len() - BUTTONS_PER_PAGE + file.scrollOffset = file.filteredServers.len() - BUTTONS_PER_PAGE } UpdateShownPage() - UpdateListSliderPosition( file.serversArrayFiltered.len() ) + UpdateListSliderPosition( file.filteredServers.len() ) } @@ -539,7 +547,7 @@ void function OnUpArrowSelected( var button ) } UpdateShownPage() - UpdateListSliderPosition( file.serversArrayFiltered.len() ) + UpdateListSliderPosition( file.filteredServers.len() ) } //////////////////////// @@ -642,7 +650,7 @@ void function FilterAndUpdateList( var n ) filterArguments.hideProtected = GetConVarBool( "filter_hide_protected" ) file.scrollOffset = 0 - UpdateListSliderPosition( file.serversArrayFiltered.len() ) + UpdateListSliderPosition( file.filteredServers.len() ) HideServerInfo() FilterServerList() @@ -741,51 +749,42 @@ void function WaitForServerListRequest() void function FilterServerList() { - file.serversArrayFiltered.clear() + file.filteredServers.clear() int totalPlayers = 0 - for ( int i = 0; i < NSGetServerCount(); i++ ) - { - serverStruct tempServer - tempServer.serverIndex = i - tempServer.serverProtected = NSServerRequiresPassword( i ) - tempServer.serverName = NSGetServerName( i ) - tempServer.serverPlayers = NSGetServerPlayerCount( i ) - tempServer.serverPlayersMax = NSGetServerMaxPlayerCount( i ) - tempServer.serverMap = NSGetServerMap( i ) - tempServer.serverGamemode = GetGameModeDisplayName( NSGetServerPlaylist ( i ) ) - tempServer.serverRegion = NSGetServerRegion( i ) - - totalPlayers += tempServer.serverPlayers + array servers = NSGetGameServers() + foreach ( ServerInfo server in servers ) + { + totalPlayers += server.playerCount // Filters - if ( filterArguments.hideEmpty && tempServer.serverPlayers == 0 ) + if ( filterArguments.hideEmpty && server.playerCount == 0 ) continue; - if ( filterArguments.hideFull && tempServer.serverPlayers == tempServer.serverPlayersMax ) + if ( filterArguments.hideFull && server.playerCount == server.maxPlayerCount ) continue; - if ( filterArguments.hideProtected && tempServer.serverProtected ) + if ( filterArguments.hideProtected && server.requiresPassword ) continue; - if ( filterArguments.filterMap != "SWITCH_ANY" && filterArguments.filterMap != tempServer.serverMap ) + if ( filterArguments.filterMap != "SWITCH_ANY" && filterArguments.filterMap != server.map ) continue; - if ( filterArguments.filterGamemode != "SWITCH_ANY" && filterArguments.filterGamemode != tempServer.serverGamemode ) + if ( filterArguments.filterGamemode != "SWITCH_ANY" && filterArguments.filterGamemode != server.playlist ) continue; - + // Search if ( filterArguments.useSearch ) { array sName - sName.append( tempServer.serverName.tolower() ) - sName.append( Localize( GetMapDisplayName( tempServer.serverMap ) ).tolower() ) - sName.append( tempServer.serverMap.tolower() ) - sName.append( tempServer.serverGamemode.tolower() ) - sName.append( Localize( tempServer.serverGamemode ).tolower() ) - sName.append( NSGetServerDescription( i ).tolower() ) - sName.append( NSGetServerRegion( i ).tolower() ) + sName.append( server.name.tolower() ) + sName.append( Localize( GetMapDisplayName( server.map ) ).tolower() ) + sName.append( server.map.tolower() ) + sName.append( server.playlist.tolower() ) + sName.append( Localize( server.playlist ).tolower() ) + sName.append( server.description.tolower() ) + sName.append( server.region.tolower() ) string sTerm = filterArguments.searchTerm.tolower() @@ -799,9 +798,8 @@ void function FilterServerList() if ( !found ) continue; } - - // Server fits our requirements, add it to the list - file.serversArrayFiltered.append( tempServer ) + + file.filteredServers.append( server ) } // Update player and server count @@ -824,23 +822,22 @@ void function UpdateShownPage() Hud_SetText( file.serversRegion[ i ], "" ) } - int j = file.serversArrayFiltered.len() > BUTTONS_PER_PAGE ? BUTTONS_PER_PAGE : file.serversArrayFiltered.len() + int j = file.filteredServers.len() > BUTTONS_PER_PAGE ? BUTTONS_PER_PAGE : file.filteredServers.len() for ( int i = 0; i < j; i++ ) { - int buttonIndex = file.scrollOffset + i - int serverIndex = file.serversArrayFiltered[ buttonIndex ].serverIndex + ServerInfo server = file.filteredServers[ buttonIndex ] Hud_SetEnabled( file.serverButtons[ i ], true ) Hud_SetVisible( file.serverButtons[ i ], true ) - Hud_SetVisible( file.serversProtected[ i ], file.serversArrayFiltered[ buttonIndex ].serverProtected ) - Hud_SetText( file.serversName[ i ], file.serversArrayFiltered[ buttonIndex ].serverName ) - Hud_SetText( file.playerCountLabels[ i ], format( "%i/%i", file.serversArrayFiltered[ buttonIndex ].serverPlayers, file.serversArrayFiltered[ buttonIndex ].serverPlayersMax ) ) - Hud_SetText( file.serversMap[ i ], GetMapDisplayName( file.serversArrayFiltered[ buttonIndex ].serverMap ) ) - Hud_SetText( file.serversGamemode[ i ], file.serversArrayFiltered[ buttonIndex ].serverGamemode ) - Hud_SetText( file.serversRegion[ i ], file.serversArrayFiltered[ buttonIndex ].serverRegion ) + Hud_SetVisible( file.serversProtected[ i ], server.requiresPassword ) + Hud_SetText( file.serversName[ i ], server.name ) + Hud_SetText( file.playerCountLabels[ i ], format( "%i/%i", server.playerCount, server.maxPlayerCount ) ) + Hud_SetText( file.serversMap[ i ], GetMapDisplayName( server.map ) ) + Hud_SetText( file.serversGamemode[ i ], GetGameModeDisplayName( server.playlist ) ) + Hud_SetText( file.serversRegion[ i ], server.region ) } @@ -850,7 +847,7 @@ void function UpdateShownPage() Hud_SetVisible( file.serverButtons[ 0 ], true ) Hud_SetText( file.serversName[ 0 ], "#NS_SERVERBROWSER_NOSERVERS" ) } - UpdateListSliderHeight( float( file.serversArrayFiltered.len() ) ) + UpdateListSliderHeight( float( file.filteredServers.len() ) ) } void function OnServerButtonFocused( var button ) @@ -860,8 +857,9 @@ void function OnServerButtonFocused( var button ) int scriptID = int ( Hud_GetScriptID( button ) ) file.serverButtonFocusedID = scriptID - if ( file.serversArrayFiltered.len() > 0 ) - file.focusedServerIndex = file.serversArrayFiltered[ file.scrollOffset + scriptID ].serverIndex + if ( file.filteredServers.len() > 0 ) + // file.focusedServerIndex = file.filteredServers[ file.scrollOffset + scriptID ].serverIndex + file.focusedServer = file.filteredServers[ file.scrollOffset + scriptID ] DisplayFocusedServerInfo( scriptID ) } @@ -882,13 +880,12 @@ void function CheckDoubleClick( int scriptID, bool wasClickNav ) int serverIndex = file.scrollOffset + scriptID bool sameServer = false - if ( file.lastSelectedServer == serverIndex ) sameServer = true - + if ( file.lastSelectedServer == file.filteredServers[ serverIndex ] ) sameServer = true file.serverSelectedTimeLast = file.serverSelectedTime file.serverSelectedTime = Time() - file.lastSelectedServer = serverIndex + file.lastSelectedServer = file.filteredServers[ serverIndex ] if ( wasClickNav && ( file.serverSelectedTime - file.serverSelectedTimeLast < DOUBLE_CLICK_TIME_MS ) && sameServer ) { @@ -900,7 +897,7 @@ void function DisplayFocusedServerInfo( int scriptID ) { if ( scriptID == 999 || scriptID == -1 || scriptID == 16 ) return - if ( NSIsRequestingServerList() || NSGetServerCount() == 0 || file.serverListRequestFailed || file.serversArrayFiltered.len() == 0 ) + if ( NSIsRequestingServerList() || NSGetServerCount() == 0 || file.serverListRequestFailed || file.filteredServers.len() == 0 ) return var menu = GetMenu( "ServerBrowserMenu" ) @@ -908,6 +905,7 @@ void function DisplayFocusedServerInfo( int scriptID ) int serverIndex = file.scrollOffset + scriptID if ( serverIndex < 0 ) serverIndex = 0 + ServerInfo server = file.filteredServers[ serverIndex ] Hud_SetVisible( Hud_GetChild( menu, "BtnServerDescription" ), true ) Hud_SetVisible( Hud_GetChild( menu, "BtnServerMods" ), true ) @@ -915,39 +913,39 @@ void function DisplayFocusedServerInfo( int scriptID ) // text panels Hud_SetVisible( Hud_GetChild( menu, "LabelDescription" ), true ) Hud_SetVisible( Hud_GetChild( menu, "LabelMods" ), false ) - Hud_SetText( Hud_GetChild( menu, "LabelDescription" ), NSGetServerDescription( file.serversArrayFiltered[ serverIndex ].serverIndex ) + "\n\nRequired Mods:\n" + FillInServerModsLabel( file.serversArrayFiltered[ serverIndex ].serverIndex ) ) + Hud_SetText( Hud_GetChild( menu, "LabelDescription" ), server.description + "\n\nRequired Mods:\n" + FillInServerModsLabel( server.requiredMods ) ) // map name/image/server name - string map = file.serversArrayFiltered[ serverIndex ].serverMap + string map = server.map Hud_SetVisible( Hud_GetChild( menu, "NextMapImage" ), true ) Hud_SetVisible( Hud_GetChild( menu, "NextMapBack" ), true ) RuiSetImage( Hud_GetRui( Hud_GetChild( menu, "NextMapImage" ) ), "basicImage", GetMapImageForMapName( map ) ) Hud_SetVisible( Hud_GetChild( menu, "NextMapName" ), true ) Hud_SetText( Hud_GetChild( menu, "NextMapName" ), GetMapDisplayName( map ) ) Hud_SetVisible( Hud_GetChild( menu, "ServerName" ), true ) - Hud_SetText( Hud_GetChild( menu, "ServerName" ), NSGetServerName( file.serversArrayFiltered[ serverIndex ].serverIndex ) ) + Hud_SetText( Hud_GetChild( menu, "ServerName" ), server.name ) // mode name/image - string mode = file.serversArrayFiltered[ serverIndex ].serverGamemode + string mode = server.playlist Hud_SetVisible( Hud_GetChild( menu, "NextModeIcon" ), true ) RuiSetImage( Hud_GetRui( Hud_GetChild( menu, "NextModeIcon" ) ), "basicImage", GetPlaylistThumbnailImage( mode ) ) Hud_SetVisible( Hud_GetChild( menu, "NextGameModeName" ), true ) if ( mode.len() != 0 ) - Hud_SetText( Hud_GetChild( menu, "NextGameModeName" ), mode ) + Hud_SetText( Hud_GetChild( menu, "NextGameModeName" ), GetGameModeDisplayName( mode ) ) else Hud_SetText( Hud_GetChild( menu, "NextGameModeName" ), "#NS_SERVERBROWSER_UNKNOWNMODE" ) } -string function FillInServerModsLabel( int server ) +string function FillInServerModsLabel( array mods ) { string ret - for ( int i = 0; i < NSGetServerRequiredModsCount( server ); i++ ) + foreach ( RequiredModInfo mod in mods ) { - ret += " " - ret += NSGetServerRequiredModName( server, i ) + " v" + NSGetServerRequiredModVersion( server, i ) + "\n" + ret += format( " %s v%s\n", mod.name, mod.version ) } + return ret } @@ -957,18 +955,17 @@ void function OnServerSelected( var button ) if ( NSIsRequestingServerList() || NSGetServerCount() == 0 || file.serverListRequestFailed ) return - int serverIndex = file.focusedServerIndex + ServerInfo server = file.focusedServer - file.lastSelectedServer = serverIndex + file.lastSelectedServer = server - // check mods - for ( int i = 0; i < NSGetServerRequiredModsCount( serverIndex ); i++ ) + foreach ( RequiredModInfo mod in server.requiredMods ) { - if ( !NSGetModNames().contains( NSGetServerRequiredModName( serverIndex, i ) ) ) + if ( !NSGetModNames().contains( mod.name ) ) { DialogData dialogData dialogData.header = "#ERROR" - dialogData.message = "Missing mod \"" + NSGetServerRequiredModName( serverIndex, i ) + "\" v" + NSGetServerRequiredModVersion( serverIndex, i ) + dialogData.message = format( "Missing mod \"%s\" v%s", mod.name, mod.version ) dialogData.image = $"ui/menu/common/dialog_error" #if PC_PROG @@ -985,8 +982,8 @@ void function OnServerSelected( var button ) else { // this uses semver https://semver.org - array serverModVersion = split( NSGetServerRequiredModVersion( serverIndex, i ), "." ) - array clientModVersion = split( NSGetModVersionByModName( NSGetServerRequiredModName( serverIndex, i ) ), "." ) + array serverModVersion = split( mod.name, "." ) + array clientModVersion = split( NSGetModVersionByModName( mod.name ), "." ) bool semverFail = false // if server has invalid semver don't bother checking @@ -1004,7 +1001,7 @@ void function OnServerSelected( var button ) { DialogData dialogData dialogData.header = "#ERROR" - dialogData.message = "Server has mod \"" + NSGetServerRequiredModName( serverIndex, i ) + "\" v" + NSGetServerRequiredModVersion( serverIndex, i ) + " while we have v" + NSGetModVersionByModName( NSGetServerRequiredModName( serverIndex, i ) ) + dialogData.message = format( "Server has mod \"%s\" v%s while we have v%s", mod.name, mod.version, NSGetModVersionByModName( mod.name ) ) dialogData.image = $"ui/menu/common/dialog_error" #if PC_PROG @@ -1021,13 +1018,16 @@ void function OnServerSelected( var button ) } } - if ( NSServerRequiresPassword( serverIndex ) ) + if ( server.requiresPassword ) { OnCloseServerBrowserMenu() AdvanceMenu( GetMenu( "ConnectWithPasswordMenu" ) ) } else + { + TriggerConnectToServerCallbacks() thread ThreadedAuthAndConnectToServer() + } } @@ -1036,9 +1036,7 @@ void function ThreadedAuthAndConnectToServer( string password = "" ) if ( NSIsAuthenticatingWithServer() ) return - print( "trying to authenticate with server " + NSGetServerName( file.lastSelectedServer ) + " with password " + password ) - - NSTryAuthWithServer( file.lastSelectedServer, password ) + NSTryAuthWithServer( file.lastSelectedServer.index, password ) ToggleConnectingHUD( true ) @@ -1063,17 +1061,13 @@ void function ThreadedAuthAndConnectToServer( string password = "" ) { bool modsChanged - array requiredMods - for ( int i = 0; i < NSGetServerRequiredModsCount( file.lastSelectedServer ); i++ ) - requiredMods.append( NSGetServerRequiredModName( file.lastSelectedServer, i ) ) - // unload mods we don't need, load necessary ones and reload mods before connecting - foreach ( string mod in NSGetModNames() ) + foreach ( RequiredModInfo mod in file.lastSelectedServer.requiredMods ) { - if ( NSIsModRequiredOnClient( mod ) ) + if ( NSIsModRequiredOnClient( mod.name ) ) { - modsChanged = modsChanged || NSIsModEnabled( mod ) != requiredMods.contains( mod ) - NSSetModEnabled( mod, requiredMods.contains( mod ) ) + modsChanged = modsChanged || NSIsModEnabled( mod.name ) != file.lastSelectedServer.requiredMods.contains( mod ) + NSSetModEnabled( mod.name, file.lastSelectedServer.requiredMods.contains( mod ) ) } } @@ -1106,7 +1100,7 @@ void function ThreadedAuthAndConnectToServer( string password = "" ) ////////////////////////////////////// // Shadow realm ////////////////////////////////////// -int function ServerSortLogic ( serverStruct a, serverStruct b ) +int function ServerSortLogic ( ServerInfo a, ServerInfo b ) { var aTemp var bTemp @@ -1117,44 +1111,44 @@ int function ServerSortLogic ( serverStruct a, serverStruct b ) switch ( filterDirection.sortingBy ) { case sortingBy.DEFAULT: - aTemp = a.serverPlayers - bTemp = b.serverPlayers + aTemp = a.playerCount + bTemp = b.playerCount // `1000` is assumed to always be higher than `serverPlayersMax` - if (aTemp + 1 < a.serverPlayersMax) + if (aTemp + 1 < a.maxPlayerCount) aTemp = aTemp+2000 - if (bTemp + 1 < b.serverPlayersMax) + if (bTemp + 1 < b.maxPlayerCount) bTemp = bTemp+2000 - if (aTemp + 1 == a.serverPlayersMax) + if (aTemp + 1 == a.maxPlayerCount) aTemp = aTemp+1000 - if (bTemp + 1 == b.serverPlayersMax) + if (bTemp + 1 == b.maxPlayerCount) bTemp = bTemp+1000 direction = filterDirection.serverName break; case sortingBy.NAME: - aTemp = a.serverName.tolower() - bTemp = b.serverName.tolower() + aTemp = a.name.tolower() + bTemp = b.name.tolower() direction = filterDirection.serverName break; case sortingBy.PLAYERS: - aTemp = a.serverPlayers - bTemp = b.serverPlayers + aTemp = a.playerCount + bTemp = b.playerCount direction = filterDirection.serverPlayers break; case sortingBy.MAP: - aTemp = Localize( a.serverMap ).tolower() - bTemp = Localize( b.serverMap ).tolower() + aTemp = Localize( a.map ).tolower() + bTemp = Localize( b.map ).tolower() direction = filterDirection.serverMap break; case sortingBy.GAMEMODE: - aTemp = Localize( a.serverGamemode ).tolower() - bTemp = Localize( b.serverGamemode ).tolower() + aTemp = Localize( a.playlist ).tolower() + bTemp = Localize( b.playlist ).tolower() direction = filterDirection.serverGamemode break; case sortingBy.REGION: - aTemp = a.serverRegion - bTemp = b.serverRegion + aTemp = a.region + bTemp = b.region direction = filterDirection.serverRegion break; default: @@ -1176,7 +1170,7 @@ void function SortServerListByDefault_Activate ( var button ) { filterDirection.sortingBy = sortingBy.DEFAULT - file.serversArrayFiltered.sort( ServerSortLogic ) + file.filteredServers.sort( ServerSortLogic ) filterDirection.serverName = !filterDirection.serverName @@ -1188,7 +1182,7 @@ void function SortServerListByName_Activate ( var button ) { filterDirection.sortingBy = sortingBy.NAME - file.serversArrayFiltered.sort( ServerSortLogic ) + file.filteredServers.sort( ServerSortLogic ) filterDirection.serverName = !filterDirection.serverName @@ -1200,7 +1194,7 @@ void function SortServerListByPlayers_Activate( var button ) { filterDirection.sortingBy = sortingBy.PLAYERS - file.serversArrayFiltered.sort( ServerSortLogic ) + file.filteredServers.sort( ServerSortLogic ) filterDirection.serverPlayers = !filterDirection.serverPlayers @@ -1211,7 +1205,7 @@ void function SortServerListByMap_Activate( var button ) { filterDirection.sortingBy = sortingBy.MAP - file.serversArrayFiltered.sort( ServerSortLogic ) + file.filteredServers.sort( ServerSortLogic ) filterDirection.serverMap = !filterDirection.serverMap @@ -1222,7 +1216,7 @@ void function SortServerListByGamemode_Activate( var button ) { filterDirection.sortingBy = sortingBy.GAMEMODE - file.serversArrayFiltered.sort( ServerSortLogic ) + file.filteredServers.sort( ServerSortLogic ) filterDirection.serverGamemode = !filterDirection.serverGamemode @@ -1233,9 +1227,33 @@ void function SortServerListByRegion_Activate( var button ) { filterDirection.sortingBy = sortingBy.REGION - file.serversArrayFiltered.sort( ServerSortLogic ) + file.filteredServers.sort( ServerSortLogic ) filterDirection.serverRegion = !filterDirection.serverRegion UpdateShownPage() } + +////////////////////////////////////// +// Callbacks +////////////////////////////////////// + +void function AddConnectToServerCallback( void functionref( ServerInfo ) callback ) +{ + if ( file.connectCallbacks.find( callback ) >= 0 ) + throw "ConnectToServerCallback has been registered twice. Duplicate callbacks are not allowed." + file.connectCallbacks.append( callback ) +} + +void function RemoveConnectToServerCallback( void functionref( ServerInfo ) callback ) +{ + file.connectCallbacks.fastremovebyvalue( callback ) +} + +void function TriggerConnectToServerCallbacks() +{ + foreach( callback in file.connectCallbacks ) + { + callback( file.lastSelectedServer ) + } +} -- cgit v1.2.3 From 0c14242f758d294187cd9cfbe7057bbe239d1ee1 Mon Sep 17 00:00:00 2001 From: F1F7Y <64418963+F1F7Y@users.noreply.github.com> Date: Mon, 1 May 2023 02:56:11 +0200 Subject: Add Bounty Hunt (#634) * Unlock bountyhunt in mode select menu * Initial push * Go through bank functions * Bug spotting * dissolve droppods faster * better camp hack solution * Revert wave start to 1st wave * Small formatting cleanup * Limit score to max playlist allows * Try to make ai camps work * Potentially fix winner not being set * metal pipe falling sound effect * Acteally document change * Better fix for match not properly ending * Add MVP dialogue (#631) * MVP dialogue and small audio fix * maybe better AI movement * revert accidental change * Remove comment about a already fixed issue * Revert froce assault point waitsignal * Don't reward player for ejecting * Don't reward for killing player npc titan * Don't allow titans to run banking thread * Bored npcs cleanup logic * Remove lefover whitespace changes * Metal pipe falling sound 2x * One day my brain will start properly functioning * Include reapers in bored check * Add bored check to other droppod func * Revert testing change * Fix typo * Remove debug prints * Debug log stuck AI for future navmesh debugging Co-authored-by: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> * Fix formatting * [BH] Lock scores after game end (#635) * stop awarding score after game is ended --------- Co-authored-by: DBmaoha <56738369+DBmaoha@users.noreply.github.com> Co-authored-by: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> --- .../mod/scripts/vscripts/ui/menu_mode_select.nut | 3 +- .../scripts/vscripts/gamemodes/_ai_gamemodes.gnut | 4 +- .../scripts/vscripts/gamemodes/_gamemode_at.nut | 1941 +++++++++++++++++--- 3 files changed, 1694 insertions(+), 254 deletions(-) (limited to 'Northstar.Client/mod/scripts/vscripts') 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 32a3c8f5..52a99b6f 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_mode_select.nut @@ -52,7 +52,8 @@ void function UpdateVisibleModes() Hud_SetEnabled( buttons[ i ], true ) Hud_SetVisible( buttons[ i ], true ) - if ( !ModeSettings_RequiresAI( modesArray[ modeIndex ] ) || modesArray[ modeIndex ] == "aitdm" ) + // 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 ) else Hud_SetLocked( buttons[ i ], true ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut index 78a9a208..4ed7ee4a 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_ai_gamemodes.gnut @@ -62,11 +62,11 @@ void function AiGameModes_SpawnDropShip( vector pos, vector rot, int team, int c } -void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array guys ) squadHandler = null ) +void function AiGameModes_SpawnDropPod( vector pos, vector rot, int team, string content /*( ͡° ͜ʖ ͡°)*/, void functionref( array guys ) squadHandler = null, int flags = 0 ) { entity pod = CreateDropPod( pos, <0,0,0> ) - InitFireteamDropPod( pod ) + InitFireteamDropPod( pod, flags ) waitthread LaunchAnimDropPod( pod, "pod_testpath", pos, rot ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut index 915e03e0..c61cb585 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_at.nut @@ -1,12 +1,54 @@ +untyped // AddCallback_OnUseEntity() needs this global function GamemodeAt_Init global function RateSpawnpoints_AT -const int BH_AI_TEAM = TEAM_BOTH -const int BOUNTY_TITAN_DAMAGE_POOL = 400 // Rewarded for damage -const int BOUNTY_TITAN_KILL_REWARD = 100 // Rewarded for kill -const float WAVE_STATE_TRANSITION_TIME = 5.0 +// Old bobr note which still applies after a year :) +// IMPLEMENTATION NOTES: +// bounty hunt is a mode that was clearly pretty heavily developed, and had alot of scrapped concepts (i.e. most wanted player bounties, turret bounties, collectable blackbox objectives) +// in the interest of time, this script isn't gonna support any of that atm +// alot of the remote functions also take parameters that aren't used, i'm not gonna populate these and just use default values for now instead +// however, if you do want to mess with this stuff, almost all the remote functions for this stuff are still present in cl_gamemode_at, and should work fine with minimal fuckery in my experience + + +// Bank settings +const float AT_BANKS_OPEN_DURATION = 45.0 // Bank open time +const int AT_BANK_DEPOSIT_RATE = 10 // Amount deposited per second +const int AT_BANK_DEPOSIT_RADIUS = 256 // bank radius for depositing +const float AT_BANK_FORCE_CLOSE_DELAY = 4.0 // If all bonus money has been deposited close the banks after this constant early + +// TODO: The reference function no longer exists, check if this still holds true +// VoyageDB: HACK score events... respawn made things in AT_SetScoreEventOverride() really messed up, have to do some hack here +const array AT_ENABLE_SCOREEVENTS = +[ + // these are disabled in AT_SetScoreEventOverride(), but related scoreEvents are not implemented into gamemode + // needs to re-enable them + "DoomTitan", + "DoomAutoTitan" +] +const array AT_DISABLE_SCOREEVENTS = +[ + // these are missed in AT_SetScoreEventOverride(), but game actually used them + // needs to disable them + "KillStalker" +] + +// Wave settings +// General +const int AT_AI_TEAM = TEAM_BOTH // Allow AI to attack and be attacked by both player teams +const float AT_FIRST_WAVE_START_DELAY = 10.0 // First wave has an extra delay before begining +const float AT_WAVE_TRANSITION_DELAY = 5.0 // Time between each wave and banks opening/closing +const float AT_WAVE_END_ANNOUNCEMENT_DELAY = 1.0 // Extra wait before announcing wave cleaned + +// Squad settings +const int AT_DROPPOD_SQUADS_ALLOWED_ON_FIELD = 4 // default is 4 droppod squads on field, won't use if AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK turns on // TODO: verify this + +// Titan bounty settings +const float AT_BOUNTY_TITAN_CHECK_DELAY = 10.0 // wait for bounty titans landing before we start checking their life state +const float AT_BOUNTY_TITAN_HEALTH_MULTIPLIER = 3 // TODO: Verify this -const array VALID_BOUNTY_TITAN_SETTINGS = [ +// Titan boss settings, check sh_gamemode_at.nut for more info +const array AT_BOUNTY_TITANS_AI_SETTINGS = +[ "npc_titan_atlas_stickybomb_bounty", "npc_titan_atlas_tracker_bounty", "npc_titan_ogre_minigun_bounty", @@ -16,302 +58,1565 @@ const array VALID_BOUNTY_TITAN_SETTINGS = [ "npc_titan_atlas_vanguard_bounty" ] +// Extra +// Respawn didn't use the "totalAllowedOnField" for npc spawning, they only allow 1 squad to be on field for each type of npc. enabling this might cause too much npcs spawning and crash the game +const bool AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK = false + +// Objectives +const int AT_OBJECTIVE_EMPTY = -1 // Remove objective +const int AT_OBJECTIVE_KILL_DZ = 104 // #AT_OBJECTIVE_KILL_DZ +const int AT_OBJECTIVE_KILL_DZ_MULTI = 105 // #AT_OBJECTIVE_KILL_DZ_MULTI +const int AT_OBJECTIVE_KILL_BOSS = 106 // #AT_OBJECTIVE_KILL_BOSS +const int AT_OBJECTIVE_KILL_BOSS_MULTI = 107 // #AT_OBJECTIVE_KILL_BOSS_MULTI +const int AT_OBJECTIVE_BANK_OPEN = 109 // #AT_BANK_OPEN_OBJECTIVE + +// When a player tries to deposit when they have 0 bonus money +// we show a help mesage, this is the ratelimit for that message +// so that we dont spam it too much +const float AT_PLAYER_HUD_MESSAGE_COOLDOWN = 2.5 + +// Due to bad navmeshes NPCs may wonder off to bumfuck nowhere or the game +// might teleport them into the map while trying to correct their position +// This obviously breaks bounty hunt where the objective is to kill ALL ai +// so we try to cleanup the camps after a set amount of time of inactivity +const int AT_CAMP_BORED_NPCS_LEFT_TO_START_CLEANUP = 3 +const float AT_CAMP_BORED_CLEANUP_WAIT = 60.0 +struct +{ + array banks + array camps + + // Used to track ScriptmanagedEntArrays of ai squads + table< int, array > campScriptEntArrays + + table< entity, bool > titanIsBountyBoss + table< entity, int > bountyTitanRewards + table< entity, int > npcStolenBonus + table< entity, bool > playerBankUploading + table< entity, table > playerSavedBountyDamage + table< entity, float > playerHudMessageAllowedTime +} file + +void function GamemodeAt_Init() +{ + // wave + RegisterSignal( "ATWaveEnd" ) + // camp + RegisterSignal( "ATCampClean" ) + RegisterSignal( "ATAllCampsClean" ) + + // Set-up score callbacks + ScoreEvent_SetupEarnMeterValuesForMixedModes() + AddDamageFinalCallback( "npc_titan", OnNPCTitanFinalDamaged ) + AddCallback_OnPlayerKilled( AT_PlayerOrNPCKilledScoreEvent ) + AddCallback_OnNPCKilled( AT_PlayerOrNPCKilledScoreEvent ) + + // Set npc weapons + AiGameModes_SetNPCWeapons( "npc_soldier", [ "mp_weapon_rspn101", "mp_weapon_dmr", "mp_weapon_r97", "mp_weapon_lmg" ] ) + AiGameModes_SetNPCWeapons( "npc_spectre", [ "mp_weapon_hemlok_smg", "mp_weapon_doubletake", "mp_weapon_mastiff" ] ) + AiGameModes_SetNPCWeapons( "npc_stalker", [ "mp_weapon_hemlok_smg", "mp_weapon_lstar", "mp_weapon_mastiff" ] ) + + // Gamestate callbacks + AddCallback_GameStateEnter( eGameState.Prematch, OnATGamePrematch ) + AddCallback_GameStateEnter( eGameState.Playing, OnATGamePlaying ) + + // Initilaze player + AddCallback_OnClientConnected( InitialiseATPlayer ) + + // Initilaze gamemode entities + AddCallback_EntitiesDidLoad( OnEntitiesDidLoad ) +} + +void function RateSpawnpoints_AT( int checkclass, array spawnpoints, int team, entity player ) +{ + RateSpawnpoints_Generic( checkclass, spawnpoints, team, player ) +} + + + +//////////////////////////////////////// +///// GAMESTATE CALLBACK FUNCTIONS ///// +//////////////////////////////////////// + +void function OnATGamePrematch() +{ + AT_ScoreEventsValueSetUp() +} + +void function OnATGamePlaying() +{ + thread AT_GameLoop_Threaded() +} + +//////////////////////////////////////////// +///// GAMESTATE CALLBACK FUNCTIONS END ///// +//////////////////////////////////////////// + + + +//////////////////////////// +///// PLAYER FUNCTIONS ///// +//////////////////////////// + +void function InitialiseATPlayer( entity player ) +{ + Remote_CallFunction_NonReplay( player, "ServerCallback_AT_OnPlayerConnected" ) + player.SetPlayerNetInt( "AT_bonusPointMult", 1 ) + file.playerBankUploading[ player ] <- false + file.playerSavedBountyDamage[ player ] <- {} + file.playerHudMessageAllowedTime[ player ] <- 0.0 + thread AT_PlayerTitleThink( player ) + thread AT_PlayerObjectiveThink( player ) +} + +void function AT_PlayerTitleThink( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + while ( true ) + { + if ( GetGameState() == eGameState.Playing ) + { + // Set player money count + player.SetTitle( "$" + string( AT_GetPlayerBonusPoints( player ) ) ) + } + else if ( GetGameState() >= eGameState.WinnerDetermined ) + { + if ( player.IsTitan() ) + player.SetTitle( GetTitanPlayerTitle( player ) ) + else + player.SetTitle( "" ) + + return + } + + WaitFrame() + } +} + +string function GetTitanPlayerTitle( entity player ) +{ + entity soul = player.GetTitanSoul() + + if ( !IsValid( soul ) ) + return "" + + string settings = GetSoulPlayerSettings( soul ) + var title = GetPlayerSettingsFieldForClassName( settings, "printname" ) + + if ( title == null ) + return "" + + return expect string( title ) +} + +void function AT_PlayerObjectiveThink( entity player ) +{ + player.EndSignal( "OnDestroy" ) + + int curObjective = AT_OBJECTIVE_EMPTY + while ( true ) + { + // game entered other state + if ( GetGameState() >= eGameState.WinnerDetermined ) + { + player.SetPlayerNetInt( "gameInfoStatusText", AT_OBJECTIVE_EMPTY ) + return + } + + int nextObjective = AT_OBJECTIVE_EMPTY + + // Determine objective text for player + if ( !IsAlive( player ) ) // Don't show objective to dead players + { + nextObjective = AT_OBJECTIVE_EMPTY + } + else // We're still alive + { + if ( GetGlobalNetBool( "banksOpen" ) ) + { + nextObjective = AT_OBJECTIVE_BANK_OPEN + } + else if ( GetGlobalNetBool( "preBankPhase" ) ) + { + nextObjective = AT_OBJECTIVE_EMPTY + } + else + { + // No checks have passed, try to do a "Kill all x near the marked dropzone" objective + int dropZoneActiveCount = 0 + int bossAliveCount = 0 + array campEnts + campEnts.append( GetGlobalNetEnt( "camp1Ent" ) ) + campEnts.append( GetGlobalNetEnt( "camp2Ent" ) ) + + foreach ( entity ent in campEnts ) + { + if ( IsValid( ent ) ) + { + if ( ent.IsTitan() ) + bossAliveCount += 1 + else + dropZoneActiveCount += 1 + } + } + + switch( dropZoneActiveCount ) + { + case 1: + nextObjective = AT_OBJECTIVE_KILL_DZ + break + case 2: + nextObjective = AT_OBJECTIVE_KILL_DZ_MULTI + break + } + + switch( bossAliveCount ) + { + case 1: + nextObjective = AT_OBJECTIVE_KILL_BOSS + break + case 2: + nextObjective = AT_OBJECTIVE_KILL_BOSS_MULTI + break + } + + // We couldn't get an objective, set it to empty + if ( dropZoneActiveCount == 0 && bossAliveCount == 0 ) + nextObjective = AT_OBJECTIVE_EMPTY + } + } + + // Set the objective when changed + if ( curObjective != nextObjective ) + { + player.SetPlayerNetInt( "gameInfoStatusText", nextObjective ) + curObjective = nextObjective + } + + WaitFrame() + } +} + +//////////////////////////////// +///// PLAYER FUNCTIONS END ///// +//////////////////////////////// + + + +//////////////////////////////////////// +///// GAMEMODE INITILAZE FUNCTIONS ///// +//////////////////////////////////////// + +void function OnEntitiesDidLoad() +{ + foreach ( entity info_target in GetEntArrayByClass_Expensive( "info_target" ) ) + { + if( info_target.HasKey( "editorclass" ) ) + { + switch( info_target.kv.editorclass ) + { + case "info_attrition_bank": + entity bank = CreateEntity( "prop_script" ) + bank.SetScriptName( "AT_Bank" ) // VoyageDB: don't know how to make client able to track it + bank.SetOrigin( info_target.GetOrigin() ) + bank.SetAngles( info_target.GetAngles() ) + DispatchSpawn( bank ) + bank.kv.solid = SOLID_VPHYSICS + bank.SetModel( info_target.GetModelName() ) + + // Minimap icon init + bank.Minimap_SetCustomState( eMinimapObject_prop_script.AT_BANK ) + bank.Minimap_SetAlignUpright( true ) + bank.Minimap_SetZOrder( MINIMAP_Z_OBJECT ) + bank.Minimap_Hide( TEAM_IMC, null ) + bank.Minimap_Hide( TEAM_MILITIA, null ) + + // Create tracker ent + // we don't need to store these at all, client just needs to get them + DispatchSpawn( GetAvailableBankTracker( bank ) ) + + // Make sure the bank is in it's disabled pose + thread PlayAnim( bank, "mh_inactive_idle" ) + // Set the bank usable + AddCallback_OnUseEntity( bank, OnPlayerUseBank ) + bank.SetUsable() + bank.SetUsePrompts( "#AT_USE_BANK_CLOSED", "#AT_USE_BANK_CLOSED" ) + + file.banks.append( bank ) + break; + case "info_attrition_camp": + AT_WaveOrigin campStruct + campStruct.ent = info_target + campStruct.origin = info_target.GetOrigin() + campStruct.radius = expect string( info_target.kv.radius ).tofloat() + campStruct.height = expect string( info_target.kv.height ).tofloat() + + // Assumes every info_attrition_camp will have all 9 phases, possibly not a good idea? + // TODO: verify this on all vanilla maps before release + for ( int i = 0; i < 9; i++ ) + campStruct.phaseAllowed.append( expect string( info_target.kv[ "phase_" + ( i + 1 ) ] ) == "1" ) + + // Get droppod spawns within the camp + foreach ( entity spawnpoint in SpawnPoints_GetDropPod() ) + { + vector campPos = info_target.GetOrigin() + vector spawnPos = spawnpoint.GetOrigin() + if ( Distance( campPos, spawnPos ) < campStruct.radius ) + campStruct.dropPodSpawnPoints.append( spawnpoint ) + } + + // Get titan spawns within the camp + foreach ( entity spawnpoint in SpawnPoints_GetTitan() ) + { + vector campPos = info_target.GetOrigin() + vector spawnPos = spawnpoint.GetOrigin() + if ( Distance( campPos, spawnPos ) < campStruct.radius ) + campStruct.titanSpawnPoints.append( spawnpoint ) + } + + file.camps.append( campStruct ) + break; + } + } + } +} + +//////////////////////////////////////////// +///// GAMEMODE INITILAZE FUNCTIONS END ///// +//////////////////////////////////////////// + + + +///////////////////////////// +///// SCORING FUNCTIONS ///// +///////////////////////////// + +// TODO: Don't reward in postmatch +// TODO: Dropping a titan on a bounty with it's dome-shield still up rewards you the bonus, but +// it doesn't actually damage the bounty titan + +void function AT_ScoreEventsValueSetUp() +{ + ScoreEvent_SetEarnMeterValues( "KillTitan", 0.10, 0.15 ) + ScoreEvent_SetEarnMeterValues( "KillAutoTitan", 0.10, 0.15 ) + ScoreEvent_SetEarnMeterValues( "AttritionTitanKilled", 0.10, 0.15 ) + ScoreEvent_SetEarnMeterValues( "KillPilot", 0.10, 0.10 ) + ScoreEvent_SetEarnMeterValues( "AttritionPilotKilled", 0.10, 0.10 ) + ScoreEvent_SetEarnMeterValues( "AttritionBossKilled", 0.10, 0.20 ) + ScoreEvent_SetEarnMeterValues( "AttritionGruntKilled", 0.02, 0.02, 0.5 ) + ScoreEvent_SetEarnMeterValues( "AttritionSpectreKilled", 0.02, 0.02, 0.5 ) + ScoreEvent_SetEarnMeterValues( "AttritionStalkerKilled", 0.02, 0.02, 0.5 ) + ScoreEvent_SetEarnMeterValues( "AttritionSuperSpectreKilled", 0.10, 0.10, 0.5 ) + + // HACK + foreach ( string eventName in AT_ENABLE_SCOREEVENTS ) + ScoreEvent_Enable( GetScoreEvent( eventName ) ) + + foreach ( string eventName in AT_DISABLE_SCOREEVENTS ) + ScoreEvent_Disable( GetScoreEvent( eventName ) ) +} + +void function AT_PlayerOrNPCKilledScoreEvent( entity victim, entity attacker, var damageInfo ) +{ + if ( !IsValid( attacker ) ) + return + + // Suicide + if ( attacker == victim ) + { + if ( victim.IsPlayer() ) + AT_PlayerBonusLoss( victim, AT_GetPlayerBonusPoints( victim ) / 2 ) + + return + } + + // NPC is the attacker + if ( !attacker.IsPlayer() ) + { + if ( attacker.IsTitan() && IsValid( GetPetTitanOwner( attacker ) ) ) // Re-asign attacker + attacker = GetPetTitanOwner( attacker ) + else // NPC steals money from player, killing it will award the stolen bonus + normal reward + AT_NPCTryStealBonusPoints( attacker, victim ) + + return + } + + // Get event name + string eventName = GetAttritionScoreEventName( victim.GetClassName() ) + + if ( victim.IsTitan() ) // titan specific + eventName = GetAttritionScoreEventNameFromAI( victim ) + + if ( eventName == "" ) // no valid scoreEvent + return + + int scoreVal = ScoreEvent_GetPointValue( GetScoreEvent( eventName ) ) + + // pet titan check + if ( victim.IsTitan() && IsValid( GetPetTitanOwner( victim ) ) ) + { + if( GetPetTitanOwner( victim ) == attacker ) // Player ejected + return + + if( GetPetTitanOwner( victim ).IsPlayer() ) // Killed player npc titan + return + + scoreVal = ATTRITION_SCORE_TITAN_MIN + } + + // killed npc + if ( victim.IsNPC() ) + { + int bonusFromNPC = 0 + // If NPC was carrying a bonus award it to the attacker + if ( victim in file.npcStolenBonus ) + { + bonusFromNPC = file.npcStolenBonus[ victim ] + delete file.npcStolenBonus[ victim ] + } + AT_AddPlayerBonusPointsForEntityKilled( attacker, scoreVal, damageInfo, bonusFromNPC ) + AddPlayerScore( attacker, eventName ) // we add scoreEvent here, since basic score events has been overwrited by sh_gamemode_at.nut + // update score difference and scoreboard + AT_AddToPlayerTeamScore( attacker, scoreVal ) + } + + // bonus stealing check + if ( victim.IsPlayer() ) + AT_PlayerTryStealBonusPoints( attacker, victim, damageInfo ) +} + +bool function AT_NPCTryStealBonusPoints( entity attacker, entity victim ) +{ + // basic checks + if ( !attacker.IsNPC() ) + return false + + if ( !victim.IsPlayer() ) + return false + + int victimBonus = AT_GetPlayerBonusPoints( victim ) + int bonusToSteal = victimBonus / 2 // npc always steal half the bonus from player, no extra bonus for killing the player + if ( bonusToSteal == 0 ) // player has no bonus! + return false + + if ( !( attacker in file.npcStolenBonus ) ) // init + file.npcStolenBonus[ attacker ] <- 0 + + file.npcStolenBonus[ attacker ] += bonusToSteal + + AT_PlayerBonusLoss( victim, bonusToSteal ) // tell victim of bonus stolen + + if ( !( attacker in file.titanIsBountyBoss ) ) // if attacker npc is not a bounty titan, we make them highlighted + NPCBountyStolenHighlight( attacker ) + + return true +} + +void function NPCBountyStolenHighlight( entity npc ) +{ + Highlight_SetEnemyHighlight( npc, "enemy_boss_bounty" ) +} + +bool function AT_PlayerTryStealBonusPoints( entity attacker, entity victim, var damageInfo ) +{ + // basic checks + if ( !attacker.IsPlayer() ) + return false + + if ( !victim.IsPlayer() ) + return false + + int victimBonus = AT_GetPlayerBonusPoints( victim ) + + int minScoreCanSteal = ATTRITION_SCORE_PILOT_MIN + if ( victim.IsTitan() ) + minScoreCanSteal = ATTRITION_SCORE_TITAN_MIN + + int bonusToSteal = victimBonus / 2 + int attackerScore = bonusToSteal + bool realStealBonus = true + if ( bonusToSteal <= minScoreCanSteal ) // no enough bonus to steal + { + attackerScore = minScoreCanSteal // give attacker min bonus + realStealBonus = false // we don't do attacker steal events below, just half victim's bonus + } + + // servercallback + int victimEHandle = victim.GetEncodedEHandle() + vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo ) + + // only do attacker events if victim has enough bonus to steal + if ( realStealBonus ) + { + Remote_CallFunction_NonReplay( + attacker, + "ServerCallback_AT_PlayerKillScorePopup", + bonusToSteal, // stolenScore + victimEHandle, // victimEHandle + damageOrigin.x, // x + damageOrigin.y, // y + damageOrigin.z // z + ) + } + else // otherwise we do a normal entity killed scoreEvent + { + AT_AddPlayerBonusPointsForEntityKilled( attacker, attackerScore, damageInfo ) + } + + // update score difference and scoreboard + AT_AddToPlayerTeamScore( attacker, minScoreCanSteal ) + + // steal bonus + // only do attacker events if victim has enough bonus to steal + if ( realStealBonus ) + { + AT_AddPlayerBonusPoints( attacker, bonusToSteal ) + AddPlayerScore( attacker, "AttritionBonusStolen" ) + } + + // tell victim of bonus stolen + AT_PlayerBonusLoss( victim, bonusToSteal ) + + return realStealBonus +} + +void function AT_PlayerBonusLoss( entity player, int bonusLoss ) +{ + AT_AddPlayerBonusPoints( player, -bonusLoss ) + Remote_CallFunction_NonReplay( + player, + "ServerCallback_AT_ShowStolenBonus", + bonusLoss // stolenScore + ) +} + +// team score meter +void function AT_AddToPlayerTeamScore( entity player, int amount ) +{ + // do not award any score after the match is ended + if ( GetGameState() > eGameState.Playing ) + return + + // add to scoreboard + player.AddToPlayerGameStat( PGS_ASSAULT_SCORE, amount ) + + // Check score so we dont go over max + if ( GameRules_GetTeamScore(player.GetTeam()) + amount > GetScoreLimit_FromPlaylist() ) + { + amount = GetScoreLimit_FromPlaylist() - GameRules_GetTeamScore(player.GetTeam()) + } + + // update score difference + AddTeamScore( player.GetTeam(), amount ) +} + +// bonus points, players earn from killing +void function AT_AddPlayerBonusPoints( entity player, int amount ) +{ + // do not award any score after the match is ended + if ( GetGameState() > eGameState.Playing ) + return + + // add to scoreboard + player.AddToPlayerGameStat( PGS_SCORE, amount ) + AT_SetPlayerBonusPoints( player, player.GetPlayerNetInt( "AT_bonusPoints" ) + ( player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256 ) + amount ) +} + +int function AT_GetPlayerBonusPoints( entity player ) +{ + return player.GetPlayerNetInt( "AT_bonusPoints" ) + player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256 +} + +void function AT_SetPlayerBonusPoints( entity player, int amount ) +{ + // split into stacks of 256 where necessary + int stacks = amount / 256 // automatically rounds down because int division + + player.SetPlayerNetInt( "AT_bonusPoints256", stacks ) + player.SetPlayerNetInt( "AT_bonusPoints", amount - stacks * 256 ) +} + +// total points, the value player actually uploaded to team score +void function AT_AddPlayerTotalPoints( entity player, int amount ) +{ + // update score difference and scoreboard, calling this function meaning player has deposited their bonus to team score + AT_AddToPlayerTeamScore( player, amount ) + AT_SetPlayerTotalPoints( player, player.GetPlayerNetInt( "AT_totalPoints" ) + ( player.GetPlayerNetInt( "AT_totalPoints256" ) * 256 ) + amount ) +} + +void function AT_SetPlayerTotalPoints( entity player, int amount ) +{ + // split into stacks of 256 where necessary + int stacks = amount / 256 // automatically rounds down because int division + + player.SetPlayerNetInt( "AT_totalPoints256", stacks ) + player.SetPlayerNetInt( "AT_totalPoints", amount - stacks * 256 ) +} + +// earn points, seems not used +void function AT_AddPlayerEarnedPoints( entity player, int amount ) +{ + AT_SetPlayerBonusPoints( player, player.GetPlayerNetInt( "AT_earnedPoints" ) + ( player.GetPlayerNetInt( "AT_earnedPoints256" ) * 256 ) + amount ) +} + +void function AT_SetPlayerEarnedPoints( entity player, int amount ) +{ + // split into stacks of 256 where necessary + int stacks = amount / 256 // automatically rounds down because int division + + player.SetPlayerNetInt( "AT_earnedPoints256", stacks ) + player.SetPlayerNetInt( "AT_earnedPoints", amount - stacks * 256 ) +} + +// damaging bounty +void function AT_AddPlayerBonusPointsForBossDamaged( entity player, entity victim, int amount, var damageInfo ) +{ + AT_AddPlayerBonusPoints( player, amount ) + // update score difference and scoreboard + AT_AddToPlayerTeamScore( player, amount ) + + // send servercallback for damaging + int bossEHandle = victim.GetEncodedEHandle() + vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo ) + + Remote_CallFunction_NonReplay( + player, + "ServerCallback_AT_BossDamageScorePopup", + amount, // damageScore + amount, // damageBonus + bossEHandle, // bossEHandle + damageOrigin.x, // x + damageOrigin.y, // y + damageOrigin.z // z + ) +} + +void function AT_AddPlayerBonusPointsForEntityKilled( entity player, int amount, var damageInfo, int extraBonus = 0 ) +{ + AT_AddPlayerBonusPoints( player, amount + extraBonus ) + + // send servercallback for damaging + int attackerEHandle = player.GetEncodedEHandle() + vector damageOrigin = DamageInfo_GetDamagePosition( damageInfo ) + + Remote_CallFunction_NonReplay( + player, + "ServerCallback_AT_ShowATScorePopup", + attackerEHandle, // attackerEHandle + amount, // damageScore + amount + extraBonus, // damageBonus + damageOrigin.x, // damagePosX + damageOrigin.y, // damagePosX + damageOrigin.z, // damagePosX + 0 // damageType ( not used ) + ) +} + +///////////////////////////////// +///// SCORING FUNCTIONS END ///// +///////////////////////////////// + + + +////////////////////////////// +///// GAMELOOP FUNCTIONS ///// +////////////////////////////// + +void function AT_GameLoop_Threaded() +{ + svGlobal.levelEnt.EndSignal( "GameStateChanged" ) + + // game end func + // TODO: Cant seem to be able to get this crash ??? + OnThreadEnd + ( + function() + { + // prevent crash before entity creation on map change + if ( GetGameState() >= eGameState.Prematch ) + { + SetGlobalNetBool( "preBankPhase", false ) + SetGlobalNetBool( "banksOpen", false ) + } + } + ) + + // Initial wait before first wave + wait AT_FIRST_WAVE_START_DELAY - AT_WAVE_TRANSITION_DELAY + + int lastWaveId = -1 + for ( int waveCount = 1; ; waveCount++ ) + { + wait AT_WAVE_TRANSITION_DELAY + + // cap to number of real waves + int waveId = ( waveCount - 1 ) / 2 + int waveCapAmount = 2 + waveId = int( min( waveId, GetWaveDataSize() - waveCapAmount ) ) + + // New wave dialogue + bool waveChanged = lastWaveId != waveId + if ( waveChanged ) + { + PlayFactionDialogueToTeam( "bh_newWave", TEAM_IMC ) + PlayFactionDialogueToTeam( "bh_newWave", TEAM_MILITIA ) + } + else // same wave, second half + { + PlayFactionDialogueToTeam( "bh_incoming", TEAM_IMC ) + PlayFactionDialogueToTeam( "bh_incoming", TEAM_MILITIA ) + } + + lastWaveId = waveId + + SetGlobalNetInt( "AT_currentWave", waveId ) + bool isBossWave = waveCount % 2 == 0 // even number waveCount means boss wave + + // announce the wave + foreach ( entity player in GetPlayerArray() ) + { + if ( isBossWave ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_AT_AnnounceBoss" ) + } + else + { + Remote_CallFunction_NonReplay( + player, + "ServerCallback_AT_AnnouncePreParty", + 0.0, // endTime ( not used ) + waveId // waveNum + ) + } + } + + wait AT_WAVE_TRANSITION_DELAY + + // Run the wave + thread AT_CampSpawnThink( waveId, isBossWave ) + + if ( !isBossWave ) + { + svGlobal.levelEnt.WaitSignal( "ATAllCampsClean" ) // signaled when all camps cleaned in spawn functions + } + else + { + wait AT_BOUNTY_TITAN_CHECK_DELAY + // wait until all bounty titans killed + while ( IsAlive( GetGlobalNetEnt( "camp1Ent" ) ) || IsAlive( GetGlobalNetEnt( "camp2Ent" ) ) ) + WaitFrame() + } + + // wave end, prebank phase + svGlobal.levelEnt.Signal( "ATWaveEnd" ) // defensive fix, destroy existing campEnts + SetGlobalNetBool( "preBankPhase", true ) + + wait AT_WAVE_END_ANNOUNCEMENT_DELAY + + // announce wave end + foreach ( entity player in GetPlayerArray() ) + { + Remote_CallFunction_NonReplay( + player, + "ServerCallback_AT_AnnounceWaveOver", + waveId, // waveNum ( not used ) + 0, // militiaDamageTotal ( not used ) + 0, // imcDamageTotal ( not used ) + 0, // milMVP ( not used ) + 0, // imcMVP ( not used ) + 0, // milMVPDamage ( not used ) + 0 // imcMVPDamage ( not used ) + ) + } + + wait AT_WAVE_TRANSITION_DELAY + + // banking phase + SetGlobalNetBool( "preBankPhase", false ) + SetGlobalNetTime( "AT_bankStartTime", Time() ) + SetGlobalNetTime( "AT_bankEndTime", Time() + AT_BANKS_OPEN_DURATION ) + SetGlobalNetBool( "banksOpen", true ) + + foreach ( entity player in GetPlayerArray() ) + Remote_CallFunction_NonReplay( player, "ServerCallback_AT_BankOpen" ) + + foreach ( entity bank in file.banks ) + thread AT_BankActiveThink( bank ) + + + float endTime = Time() + AT_BANKS_OPEN_DURATION + bool forceCloseTriggered = false + // wait until no player is holding bonus, or max wait time + while ( Time() <= endTime ) + { + // If everyone has deposited their bonuses close the banks early + if ( !ATAnyPlayerHasBonus() && !forceCloseTriggered ) + { + forceCloseTriggered = true + endTime = Time() + AT_BANK_FORCE_CLOSE_DELAY + } + + WaitFrame() + } + + SetGlobalNetBool( "banksOpen", false ) + foreach ( entity player in GetPlayerArray() ) + Remote_CallFunction_NonReplay( player, "ServerCallback_AT_BankClose" ) + } +} + +bool function ATAnyPlayerHasBonus() +{ + foreach ( entity player in GetPlayerArray() ) + { + if ( AT_GetPlayerBonusPoints( player ) ) + return true + } + return false +} + +////////////////////////////////// +///// GAMELOOP FUNCTIONS END ///// +////////////////////////////////// + + + +////////////////////////// +///// CAMP FUNCTIONS ///// +////////////////////////// + +void function AT_CampSpawnThink( int waveId, bool isBossWave ) +{ + AT_WaveData wave = GetWaveData( waveId ) + array< array > campSpawnData + + if ( isBossWave ) + campSpawnData = wave.bossSpawnData + else + campSpawnData = wave.spawnDataArrays + + array allCampsToUse + foreach ( AT_WaveOrigin campStruct in file.camps ) + { + if ( campStruct.phaseAllowed[ waveId ] ) + allCampsToUse.append( campStruct ) + } + + // HACK + // There's too many phase3 camps on exoplanet and rise, make sure we always have the correct count + int maxCampsForWave = waveId == 0 ? 1 : 2 + while( allCampsToUse.len() > maxCampsForWave ) + { + // Get the required number of camps + array tempCamps + for( int i = 0; i < maxCampsForWave; i++ ) + tempCamps.append( allCampsToUse[RandomInt( allCampsToUse.len() )] ) + + + // Check if they're intersecting, if they are, try again + bool intersecting = false + for( int i = 0; i < tempCamps.len(); i++ ) + { + AT_WaveOrigin campA = tempCamps[i] + for( int j = 0; j < tempCamps.len(); j++ ) + { + // Don't compare the same two camps + if( j == i ) + continue + + AT_WaveOrigin campB = tempCamps[j] + + if( Distance( campA.origin, campB.origin ) < campA.radius + campB.radius ) + intersecting = true + } + } + + if( !intersecting ) + allCampsToUse = tempCamps + + // If we ever get really unlucky just wait a frame + WaitFrame() + } + + foreach ( int spawnId, AT_WaveOrigin curCampData in allCampsToUse ) + { + array curSpawnData = campSpawnData[ spawnId ] + + int totalNPCsToSpawn = 0 + // initialise pending spawns and get total npcs + foreach ( AT_SpawnData spawnData in curSpawnData ) + { + spawnData.pendingSpawns = spawnData.totalToSpawn + // add to network variables + string npcNetVar = GetNPCNetVarName( spawnData.aitype, spawnId ) + SetGlobalNetInt( npcNetVar, spawnData.totalToSpawn ) + + totalNPCsToSpawn += spawnData.totalToSpawn + } + + if ( !isBossWave ) + { + // camp Ent, boss wave will use boss themselves as campEnt + string campEntVarName = "camp" + string( spawnId + 1 ) + "Ent" + bool waveNotActive = GetGlobalNetBool( "preBankPhase" ) || GetGlobalNetBool( "banksOpen" ) + if ( !IsValid( GetGlobalNetEnt( campEntVarName ) ) && !waveNotActive ) + SetGlobalNetEnt( campEntVarName, CreateCampTracker( curCampData, spawnId ) ) + + array minionSquadDatas + foreach ( AT_SpawnData data in curSpawnData ) + { + switch ( data.aitype ) + { + case "npc_soldier": + case "npc_spectre": + case "npc_stalker": + if ( !AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK ) + minionSquadDatas.append( data ) + else + thread AT_DroppodSquadEvent_Single( curCampData, spawnId, data ) + break + + case "npc_super_spectre": + thread AT_ReaperEvent( curCampData, spawnId, data ) + break + } + } + + // minions squad spawn + if ( !AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK ) + { + if ( minionSquadDatas.len() > 0 ) + thread AT_DroppodSquadEvent( curCampData, spawnId, minionSquadDatas ) + } + + // use campProgressThink for handling wave state + thread CampProgressThink( spawnId, totalNPCsToSpawn ) + } + else // bosswave spawn + { + foreach ( AT_SpawnData data in curSpawnData ) + { + if( data.aitype != "npc_titan" ) + continue + + thread AT_BountyTitanEvent( curCampData, spawnId, data ) + break + } + } + } +} + +void function CampProgressThink( int spawnId, int totalNPCsToSpawn ) +{ + string campLetter = GetCampLetter( spawnId ) + string campProgressName = campLetter + "campProgress" + string campEntVarName = "camp" + string( spawnId + 1 ) + "Ent" + + // initial wait + SetGlobalNetFloat( campProgressName, 1.0 ) + + // TODO: random wait, make this a constant ?? + wait 3.0 + + float cleanUpTime = -1.0 + + while ( true ) + { + int npcsLeft + // get all npcs might be in this camp + for ( int i = 0; i < 5; i++ ) + { + string netVarName = string( i + 1 ) + campLetter + "campCount" + int netVarValue = GetGlobalNetInt( netVarName ) + if ( netVarValue >= 0 ) // uninitialized network var starts from -1, avoid checking them + npcsLeft += netVarValue + } + + float campLeft = float( npcsLeft ) / float( totalNPCsToSpawn ) + SetGlobalNetFloat( campProgressName, campLeft ) + + if( npcsLeft <= AT_CAMP_BORED_NPCS_LEFT_TO_START_CLEANUP && cleanUpTime < 0.0 ) + { + cleanUpTime = Time() + AT_CAMP_BORED_CLEANUP_WAIT + print("Cleanup timer started!") + } + + if( Time() > cleanUpTime && cleanUpTime > 0.0 && spawnId in file.campScriptEntArrays ) + { + foreach( int handle in file.campScriptEntArrays[spawnId] ) + { + array entities = GetScriptManagedEntArray( handle ) + entities.removebyvalue( null ) + foreach ( entity ent in entities ) + { + if ( IsAlive( ent ) && ent.IsNPC() ) + { + printt( "Killing bored AI " + ent.GetClassName() + " at " + ent.GetOrigin() ) + ent.Die() + } + } + } + } -// IMPLEMENTATION NOTES: -// bounty hunt is a mode that was clearly pretty heavily developed, and had alot of scrapped concepts (i.e. most wanted player bounties, turret bounties, collectable blackbox objectives) -// in the interest of time, this script isn't gonna support any of that atm -// alot of the remote functions also take parameters that aren't used, i'm not gonna populate these and just use default values for now instead -// however, if you do want to mess with this stuff, almost all the remote functions for this stuff are still present in cl_gamemode_at, and should work fine with minimal fuckery in my experience + if ( campLeft <= 0.0 ) // camp wiped! + { + PlayFactionDialogueToTeam( "bh_cleared" + campLetter, TEAM_IMC ) + PlayFactionDialogueToTeam( "bh_cleared" + campLetter, TEAM_MILITIA ) -struct { - array campsToRegisterOnEntitiesDidLoad + entity campEnt = GetGlobalNetEnt( campEntVarName ) + if ( IsValid( campEnt ) ) + campEnt.Signal( "ATCampClean" ) // destroy the camp ent - array banks - array camps - - table< int, table< string, int > > trackedCampNPCSpawns -} file + // check if both camps being destroyed + if ( !IsValid( GetGlobalNetEnt( "camp1Ent" ) ) && !IsValid( GetGlobalNetEnt( "camp2Ent" ) ) ) + svGlobal.levelEnt.Signal( "ATAllCampsClean" ) // end the wave + + return + } -void function GamemodeAt_Init() + WaitFrame() + } +} + +// entity funcs +// camp +entity function CreateCampTracker( AT_WaveOrigin campData, int spawnId ) { - AddCallback_GameStateEnter( eGameState.Playing, RunATGame ) - - AddCallback_OnClientConnected( InitialiseATPlayer ) + // store data + vector campOrigin = campData.origin + float campRadius = campData.radius + float campHeight = campData.height + // add a minimap icon + entity mapIconEnt = CreateEntity( "prop_script" ) + DispatchSpawn( mapIconEnt ) + + mapIconEnt.SetOrigin( campOrigin ) + mapIconEnt.DisableHibernation() + SetTeam( mapIconEnt, AT_AI_TEAM ) + mapIconEnt.Minimap_AlwaysShow( TEAM_IMC, null ) + mapIconEnt.Minimap_AlwaysShow( TEAM_MILITIA, null ) + + mapIconEnt.Minimap_SetCustomState( GetCampMinimapState( spawnId ) ) + mapIconEnt.Minimap_SetAlignUpright( true ) + mapIconEnt.Minimap_SetZOrder( MINIMAP_Z_OBJECT ) + mapIconEnt.Minimap_SetObjectScale( campRadius / 16000.0 ) // proper icon on the map - AddSpawnCallbackEditorClass( "info_target", "info_attrition_bank", CreateATBank ) - AddSpawnCallbackEditorClass( "info_target", "info_attrition_camp", CreateATCamp ) - AddCallback_EntitiesDidLoad( CreateATCamps_Delayed ) + // attach a location tracker + entity tracker = GetAvailableLocationTracker() + tracker.SetOwner( mapIconEnt ) // needs a owner to show up + tracker.SetOrigin( campOrigin ) + SetLocationTrackerRadius( tracker, campRadius ) + SetLocationTrackerID( tracker, spawnId ) + DispatchSpawn( tracker ) + + thread TrackWaveEndForCampInfo( tracker, mapIconEnt ) + return tracker } -void function RateSpawnpoints_AT( int checkclass, array spawnpoints, int team, entity player ) +void function TrackWaveEndForCampInfo( entity tracker, entity mapIconEnt ) { - RateSpawnpoints_Generic( checkclass, spawnpoints, team, player ) // temp + tracker.EndSignal( "OnDestroy" ) + tracker.EndSignal( "ATCampClean" ) + + OnThreadEnd + ( + function(): ( tracker, mapIconEnt ) + { + // camp cleaned, wave or game ended, destroy the camp info + if ( IsValid( tracker ) ) + tracker.Destroy() + + if ( IsValid( mapIconEnt ) ) + mapIconEnt.Destroy() + } + ) + + WaitSignal( svGlobal.levelEnt, "GameStateChanged", "ATWaveEnd" ) } -// world and player inits +string function GetCampLetter( int spawnId ) +{ + return spawnId == 0 ? "A" : "B" +} -void function InitialiseATPlayer( entity player ) +int function GetCampMinimapState( int id ) { - Remote_CallFunction_NonReplay( player, "ServerCallback_AT_OnPlayerConnected" ) + switch ( id ) + { + case 0: + return eMinimapObject_prop_script.AT_DROPZONE_A + case 1: + return eMinimapObject_prop_script.AT_DROPZONE_B + case 2: + return eMinimapObject_prop_script.AT_DROPZONE_C + } + + unreachable } -void function CreateATBank( entity spawnpoint ) +////////////////////////////// +///// CAMP FUNCTIONS END ///// +////////////////////////////// + + + +////////////////////////// +///// BANK FUNCTIONS ///// +////////////////////////// + +void function AT_BankActiveThink( entity bank ) { - entity bank = CreatePropDynamic( spawnpoint.GetModelName(), spawnpoint.GetOrigin(), spawnpoint.GetAngles(), SOLID_VPHYSICS ) - bank.SetScriptName( "AT_Bank" ) - - // create tracker ent - // we don't need to store these at all, client just needs to get them - DispatchSpawn( GetAvailableBankTracker( bank ) ) + svGlobal.levelEnt.EndSignal( "GameStateChanged" ) + bank.EndSignal( "OnDestroy" ) - thread PlayAnim( bank, "mh_inactive_idle" ) + // Banks closed + OnThreadEnd + ( + function(): ( bank ) + { + if ( IsValid( bank ) ) + { + // Update use prompt + if ( GetGameState() != eGameState.Playing ) + bank.UnsetUsable() + else + bank.SetUsePrompts( "#AT_USE_BANK_CLOSED", "#AT_USE_BANK_CLOSED" ) + + thread PlayAnim( bank, "mh_active_2_inactive" ) + FadeOutSoundOnEntity( bank, "Mobile_Hardpoint_Idle", 0.5 ) + bank.Minimap_Hide( TEAM_IMC, null ) + bank.Minimap_Hide( TEAM_MILITIA, null ) + } + } + ) + + // Update use prompt to usable + bank.SetUsable() + bank.SetUsePrompts( "#AT_USE_BANK", "#AT_USE_BANK_PC" ) + + thread PlayAnim( bank, "mh_inactive_2_active" ) + EmitSoundOnEntity( bank, "Mobile_Hardpoint_Idle" ) + + // Show minimap icon for bank + bank.Minimap_AlwaysShow( TEAM_IMC, null ) + bank.Minimap_AlwaysShow( TEAM_MILITIA, null ) + bank.Minimap_SetCustomState( eMinimapObject_prop_script.AT_BANK ) - file.banks.append( bank ) + // Wait for bank close or game end + while ( GetGlobalNetBool( "banksOpen" ) ) + WaitFrame() } -void function CreateATCamp( entity spawnpoint ) +function OnPlayerUseBank( bank, player ) { - // delay this so we don't do stuff before all spawns are initialised and that - file.campsToRegisterOnEntitiesDidLoad.append( spawnpoint ) -} + // Banks are always usable so that we can show the use prompt + // Only allow deposit when banks are open + if ( !GetGlobalNetBool( "banksOpen" ) ) + return -void function CreateATCamps_Delayed() -{ - // we delay registering camps until EntitiesDidLoad since they rely on spawnpoints and stuff, which might not all be ready in the creation callback - // unsure if this would be an issue in practice, but protecting against it in case it would be - foreach ( entity camp in file.campsToRegisterOnEntitiesDidLoad ) + expect entity( bank ) + expect entity( player ) + + // bank.SetUsableByGroup( "pilot" ) didn't seem to work so we just + // exit here if player is in a titan + if( player.IsTitan() ) + return + + // Player has no bonus, try to send a tip using SendHUDMessage + if ( AT_GetPlayerBonusPoints( player ) == 0 ) { - AT_WaveOrigin campStruct - campStruct.ent = camp - campStruct.origin = camp.GetOrigin() - campStruct.radius = expect string( camp.kv.radius ).tofloat() - campStruct.height = expect string( camp.kv.height ).tofloat() - - // assumes every info_attrition_camp will have all 9 phases, possibly not a good idea? - for ( int i = 0; i < 9; i++ ) - campStruct.phaseAllowed.append( expect string( camp.kv[ "phase_" + ( i + 1 ) ] ) == "1" ) - - // get droppod spawns - foreach ( entity spawnpoint in SpawnPoints_GetDropPod() ) - if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 ) - campStruct.dropPodSpawnPoints.append( spawnpoint ) - - foreach ( entity spawnpoint in SpawnPoints_GetTitan() ) - if ( Distance( camp.GetOrigin(), spawnpoint.GetOrigin() ) < 1500.0 ) - campStruct.titanSpawnPoints.append( spawnpoint ) - - // todo: turret spawns someday maybe - - file.camps.append( campStruct ) + ATSendDepositTipToPlayer( player, "#AT_USE_BANK_NO_BONUS_HINT" ) + return } - - file.campsToRegisterOnEntitiesDidLoad.clear() -} -// scoring funcs + // Prevent more than one instance of this thread running + if ( !file.playerBankUploading[ player ] ) + thread PlayerUploadingBonus_Threaded( bank, player ) +} -// don't use this where possible as it doesn't set score and stuff -void function AT_SetPlayerCash( entity player, int amount ) +bool function ATSendDepositTipToPlayer( entity player, string message ) { - // split into stacks of 256 where necessary - int stacks = amount / 256 // automatically rounds down because int division + if ( Time() < file.playerHudMessageAllowedTime[ player ] ) + return false + + SendHudMessage( player, message, -1, 0.4, 255, 255, 255, 255, 0.5, 1.0, 0.5 ) + file.playerHudMessageAllowedTime[ player ] = Time() + AT_PLAYER_HUD_MESSAGE_COOLDOWN - player.SetPlayerNetInt( "AT_bonusPoints256", stacks ) - player.SetPlayerNetInt( "AT_bonusPoints", amount - stacks * 256 ) + return true } -void function AT_AddPlayerCash( entity player, int amount ) +struct AT_playerUploadStruct { - // update score difference - AddTeamScore( player.GetTeam(), amount / 2 ) - AT_SetPlayerCash( player, player.GetPlayerNetInt( "AT_bonusPoints" ) + ( player.GetPlayerNetInt( "AT_bonusPoints256" ) * 256 ) + amount ) + bool uploadSuccess = false + int depositedPoints = 0 +} + +void function PlayerUploadingBonus_Threaded( entity bank, entity player ) +{ + bank.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) + + file.playerBankUploading[ player ] = true + + // this literally only exists because structs are passed by ref, + // and primitives like ints and bools are passed by val + // which meant that the OnThreadEnd was just getting 0 and false + AT_playerUploadStruct uploadInfo + + // Cleanup and call finish deposit func + OnThreadEnd + ( + function(): ( player, uploadInfo ) + { + if ( IsValid( player ) ) + { + file.playerBankUploading[ player ] = false + + // Clean up looping sound + StopSoundOnEntity( player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_1P" ) + StopSoundOnEntity( player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_3P" ) + + // Do medal event + // TODO: Check if vanilla actually do.s this every time you finish depositing??? + AddPlayerScore( player, "AttritionCashedBonus" ) + + // Do server callback + Remote_CallFunction_NonReplay( + player, + "ServerCallback_AT_FinishDeposit", + uploadInfo.depositedPoints // deposit + ) + + player.SetPlayerNetBool( "AT_playerUploading", false ) + + if ( uploadInfo.uploadSuccess ) // Player deposited all remaining bonus + { + // Emit uploading successful sound + EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Successful_1P" ) + EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Successful_3P" ) + + // player is MVP + int ourScore = player.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + bool isMVP = true + foreach(teamPlayer in GetPlayerArrayOfTeam(player.GetTeam())) + { + if (ourScore < teamPlayer.GetPlayerGameStat( PGS_ASSAULT_SCORE )) + { + isMVP = false + break + } + } + if (isMVP) + PlayFactionDialogueToPlayer( "bh_mvp", player ) + } + else // Player was killed or left the bank radius + { + // Emit uploading failed sound + EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Unsuccessful_1P" ) + EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_End_Unsuccessful_3P" ) + } + } + } + ) + + // Uploading start sound + EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_Start_1P" ) + EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Deposit_Start_3P" ) + EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_1P" ) + EmitSoundOnEntityExceptToPlayer( player, player, "HUD_MP_BountyHunt_BankBonusPts_Ticker_Loop_3P" ) + + player.SetPlayerNetBool( "AT_playerUploading", true ) + + // Upload bonus while the player is within range of the bank + while ( Distance( player.GetOrigin(), bank.GetOrigin() ) <= AT_BANK_DEPOSIT_RADIUS && GetGlobalNetBool( "banksOpen" ) ) + { + // Calling this moves the "Uploading..." graphic to the same place it is + // in vanilla + Remote_CallFunction_NonReplay( player, "ServerCallback_AT_ShowRespawnBonusLoss" ) + + int bonusToUpload = int( min( AT_BANK_DEPOSIT_RATE, AT_GetPlayerBonusPoints( player ) ) ) + // No more bonus to upload, return + if ( bonusToUpload == 0 ) + { + uploadInfo.uploadSuccess = true + return + } + + // Remove bonus points and add them to total poins + AT_AddPlayerBonusPoints( player, -bonusToUpload ) + AT_AddPlayerTotalPoints( player, bonusToUpload ) + + uploadInfo.depositedPoints += bonusToUpload + WaitFrame() + } } -// run gamestate +////////////////////////////// +///// BANK FUNCTIONS END ///// +////////////////////////////// + -void function RunATGame() + +///////////////////////// +///// NPC FUNCTIONS ///// +///////////////////////// + +int function GetScriptManagedNPCArrayLength_Alive( int scriptManagerId ) { - thread RunATGame_Threaded() + array entities = GetScriptManagedEntArray( scriptManagerId ) + entities.removebyvalue( null ) + int npcsAlive = 0 + foreach ( entity ent in entities ) + { + if ( IsAlive( ent ) && ent.IsNPC() ) + npcsAlive += 1 + } + return npcsAlive } -void function RunATGame_Threaded() +void function AT_DroppodSquadEvent( AT_WaveOrigin campData, int spawnId, array minionDatas ) { svGlobal.levelEnt.EndSignal( "GameStateChanged" ) + // create a script managed array for all handled minions + int eventManager = CreateScriptManagedEntArray() + + if( !(spawnId in file.campScriptEntArrays) ) + file.campScriptEntArrays[spawnId] <- [] - OnThreadEnd( function() - { - SetGlobalNetBool( "banksOpen", false ) - }) - - wait WAVE_STATE_TRANSITION_TIME // initial wait before first wave - - for ( int waveCount = 1; ; waveCount++ ) + file.campScriptEntArrays[spawnId].append(eventManager) + + int totalAllowedOnField = SQUAD_SIZE * AT_DROPPOD_SQUADS_ALLOWED_ON_FIELD + while ( true ) { - wait WAVE_STATE_TRANSITION_TIME - - // cap to number of real waves - int waveId = ( waveCount / 2 ) - // last wave is clearly unfinished so don't use, just cap to last actually used one - if ( waveId >= GetWaveDataSize() - 1 ) + foreach ( AT_SpawnData data in minionDatas ) { - waveId = GetWaveDataSize() - 2 - waveCount = waveId * 2 + string ent = data.aitype + waitthread AT_SpawnDroppodSquad( campData, spawnId, ent, eventManager ) + data.pendingSpawns -= SQUAD_SIZE + if ( data.pendingSpawns <= 0 ) // current spawn data has reached max spawn amount + minionDatas.removebyvalue( data ) // remove this data + if ( GetScriptManagedNPCArrayLength_Alive( eventManager ) >= totalAllowedOnField ) // we have enough npcs on field? + break // stop following spawning functions } - - SetGlobalNetInt( "AT_currentWave", waveId ) - bool isBossWave = waveCount / float( 2 ) > waveId // odd number waveCount means boss wave - - // announce the wave - foreach ( entity player in GetPlayerArray() ) + if ( minionDatas.len() == 0 ) // all spawn data has finished spawn + return + + int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) + while ( npcOnFieldCount >= totalAllowedOnField - SQUAD_SIZE ) // wait until we have lost more than 1 squad { - if ( isBossWave ) - Remote_CallFunction_NonReplay( player, "ServerCallback_AT_AnnounceBoss" ) - else - Remote_CallFunction_NonReplay( player, "ServerCallback_AT_AnnouncePreParty", 0.0, waveId ) + WaitFrame() + npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) } - - wait WAVE_STATE_TRANSITION_TIME - - // run the wave - - AT_WaveData wave = GetWaveData( waveId ) - array< array > campSpawnData - - if ( isBossWave ) - campSpawnData = wave.bossSpawnData - else - campSpawnData = wave.spawnDataArrays - - // initialise pending spawns - foreach ( array< AT_SpawnData > campData in campSpawnData ) + } +} + +// for AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK, handles a single spawndata +void function AT_DroppodSquadEvent_Single( AT_WaveOrigin campData, int spawnId, AT_SpawnData data ) +{ + svGlobal.levelEnt.EndSignal( "GameStateChanged" ) + + // get ent and create a script managed array for current event + string ent = data.aitype + int eventManager = CreateScriptManagedEntArray() + + if( !(spawnId in file.campScriptEntArrays) ) + file.campScriptEntArrays[spawnId] <- [] + + file.campScriptEntArrays[spawnId].append(eventManager) + + int totalAllowedOnField = data.totalAllowedOnField // mostly 12 for grunts and spectres, too much! + // start spawner + while ( true ) + { + waitthread AT_SpawnDroppodSquad( campData, spawnId, ent, eventManager ) + data.pendingSpawns -= SQUAD_SIZE + if ( data.pendingSpawns <= 0 ) // we have reached max npcs + return // stop any spawning functions + + int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) + while ( npcOnFieldCount >= totalAllowedOnField - SQUAD_SIZE ) // wait until we have less npcs than allowed count { - foreach ( AT_SpawnData spawnData in campData ) - spawnData.pendingSpawns = spawnData.totalToSpawn - } - - // clear tracked spawns - file.trackedCampNPCSpawns = {} - while ( true ) - { - // if this is ever 0 by the end of this loop, wave is complete - int numActiveCampSpawners = 0 - - // iterate over camp data for wave - for ( int campIdx = 0; campIdx < campSpawnData.len() && campIdx < file.camps.len(); campIdx++ ) - { - if ( !( campIdx in file.trackedCampNPCSpawns ) ) - file.trackedCampNPCSpawns[ campIdx ] <- {} - - // iterate over ai spawn data for camp - foreach ( AT_SpawnData spawnData in campSpawnData[ campIdx ] ) - { - if ( !( spawnData.aitype in file.trackedCampNPCSpawns[ campIdx ] ) ) - file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] <- 0 - - if ( spawnData.pendingSpawns > 0 || file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] > 0 ) - numActiveCampSpawners++ - - // try to spawn as many ai as we can, as long as the camp doesn't already have too many spawned - int spawnCount - for ( spawnCount = 0; spawnCount < spawnData.pendingSpawns && spawnCount < spawnData.totalAllowedOnField - file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ]; ) - { - // not doing this in a generic way atm, but could be good for the future if we want to support more ai - switch ( spawnData.aitype ) - { - case "npc_soldier": - case "npc_spectre": - case "npc_stalker": - thread AT_SpawnDroppodSquad( campIdx, spawnData.aitype ) - spawnCount += 4 - break - - case "npc_super_spectre": - thread AT_SpawnReaper( campIdx ) - spawnCount += 1 - break - - case "npc_titan": - thread AT_SpawnBountyTitan( campIdx ) - spawnCount += 1 - break - - default: - print( "BOUNTY HUNT: Tried to spawn unsupported ai of type \"" + "\" at camp " + campIdx ) - } - } - - // track spawns - file.trackedCampNPCSpawns[ campIdx ][ spawnData.aitype ] += spawnCount - spawnData.pendingSpawns -= spawnCount - } - } - - if ( numActiveCampSpawners == 0 ) - break - - wait 0.5 + WaitFrame() + npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) } - - wait WAVE_STATE_TRANSITION_TIME - - // banking phase } } -// entity funcs - -void function AT_SpawnDroppodSquad( int camp, string aiType ) +void function AT_SpawnDroppodSquad( AT_WaveOrigin campData, int spawnId, string aiType, int scriptManagerId ) { entity spawnpoint - if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 ) - spawnpoint = file.camps[ camp ].ent + if ( campData.dropPodSpawnPoints.len() == 0 ) + spawnpoint = campData.ent else - spawnpoint = file.camps[ camp ].dropPodSpawnPoints.getrandom() + spawnpoint = campData.dropPodSpawnPoints.getrandom() + // anti-crash + if ( !IsValid( spawnpoint ) ) + spawnpoint = campData.ent // add variation to spawns wait RandomFloat( 1.0 ) - AiGameModes_SpawnDropPod( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, aiType, void function( array guys ) : ( camp, aiType ) - { - AT_HandleSquadSpawn( guys, camp, aiType ) - }) + AiGameModes_SpawnDropPod( + spawnpoint.GetOrigin(), + spawnpoint.GetAngles(), + AT_AI_TEAM, + aiType, + // squad handler + void function( array guys ) : ( campData, spawnId, aiType, scriptManagerId ) + { + AT_HandleSquadSpawn( guys, campData, spawnId, aiType, scriptManagerId ) + }, + eDropPodFlag.DISSOLVE_AFTER_DISEMBARKS + ) } -void function AT_HandleSquadSpawn( array guys, int camp, string aiType ) +void function AT_HandleSquadSpawn( array guys, AT_WaveOrigin campData, int spawnId, string aiType, int scriptManagerId ) { foreach ( entity guy in guys ) { - guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_INVESTIGATE | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) - - // untrack them on death - thread AT_WaitToUntrackNPC( guy, camp, aiType ) + // TODO: NPCs still seem to go outside their camp ??? + //guy.EnableNPCFlag( NPC_ALLOW_PATROL | NPC_ALLOW_HAND_SIGNALS | NPC_ALLOW_FLEE ) + + // tracking lifetime + AddToScriptManagedEntArray( scriptManagerId, guy ) + thread AT_TrackNPCLifeTime( guy, spawnId, aiType ) + + thread AT_ForceAssaultAroundCamp( guy, campData ) + } +} + +void function AT_ForceAssaultAroundCamp( entity guy, AT_WaveOrigin campData ) +{ + guy.EndSignal( "OnDestroy" ) + guy.EndSignal( "OnDeath" ) + + // goal check + vector ornull goalPos = NavMesh_ClampPointForAI(campData.origin, guy) + goalPos = goalPos == null ? campData.origin : goalPos + expect vector(goalPos) + + float goalRadius = campData.radius / 4 + float guyGoalRadius = guy.GetMinGoalRadius() + if ( guyGoalRadius > goalRadius ) // this npc cannot use forced goal radius? + goalRadius = guyGoalRadius + + while( true ) + { + guy.AssaultPoint( goalPos ) + guy.AssaultSetGoalRadius( goalRadius ) + guy.AssaultSetFightRadius( 0 ) + guy.AssaultSetArrivalTolerance( int(goalRadius) ) + + wait RandomFloatRange( 1, 5 ) + } +} + +void function AT_ReaperEvent( AT_WaveOrigin campData, int spawnId, AT_SpawnData data ) +{ + svGlobal.levelEnt.EndSignal( "GameStateChanged" ) + + // create a script managed array for current event + int eventManager = CreateScriptManagedEntArray() + + if( !(spawnId in file.campScriptEntArrays) ) + file.campScriptEntArrays[spawnId] <- [] + + file.campScriptEntArrays[spawnId].append(eventManager) + + int totalAllowedOnField = 1 // 1 allowed at the same time for heavy armor units + if ( AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK ) + totalAllowedOnField = data.totalAllowedOnField + + while ( true ) + { + waitthread AT_SpawnReaper( campData, spawnId, eventManager ) + data.pendingSpawns -= 1 + if ( data.pendingSpawns <= 0 ) // we have reached max npcs + return // stop any spawning functions + + int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) + while ( npcOnFieldCount >= totalAllowedOnField ) // wait until we have less npcs than allowed count + { + WaitFrame() + npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) + } } } -void function AT_SpawnReaper( int camp ) +void function AT_SpawnReaper( AT_WaveOrigin campData, int spawnId, int scriptManagerId ) { entity spawnpoint - if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 ) - spawnpoint = file.camps[ camp ].ent + if ( campData.dropPodSpawnPoints.len() == 0 ) + spawnpoint = campData.ent else - spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom() + spawnpoint = campData.dropPodSpawnPoints.getrandom() + // anti-crash + if ( !IsValid( spawnpoint ) ) + spawnpoint = campData.ent // add variation to spawns wait RandomFloat( 1.0 ) - AiGameModes_SpawnReaper( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, "npc_super_spectre",void function( entity reaper ) : ( camp ) + AiGameModes_SpawnReaper( + spawnpoint.GetOrigin(), + spawnpoint.GetAngles(), + AT_AI_TEAM, + "npc_super_spectre_aitdm", + // reaper handler + void function( entity reaper ) : ( campData, spawnId, scriptManagerId ) + { + AT_HandleReaperSpawn( reaper, campData, spawnId, scriptManagerId ) + } + ) +} + +void function AT_HandleReaperSpawn( entity reaper, AT_WaveOrigin campData, int spawnId, int scriptManagerId ) +{ + // tracking lifetime + AddToScriptManagedEntArray( scriptManagerId, reaper ) + thread AT_TrackNPCLifeTime( reaper, spawnId, "npc_super_spectre" ) + + thread AT_ForceAssaultAroundCamp( reaper, campData ) +} + +void function AT_BountyTitanEvent( AT_WaveOrigin campData, int spawnId, AT_SpawnData data ) +{ + svGlobal.levelEnt.EndSignal( "GameStateChanged" ) + + // create a script managed array for current event + int eventManager = CreateScriptManagedEntArray() + + int totalAllowedOnField = 1 // 1 allowed at the same time for heavy armor units + if ( AT_USE_TOTAL_ALLOWED_ON_FIELD_CHECK ) + totalAllowedOnField = data.totalAllowedOnField + while ( true ) { - thread AT_WaitToUntrackNPC( reaper, camp, "npc_super_spectre" ) - }) + waitthread AT_SpawnBountyTitan( campData, spawnId, eventManager ) + data.pendingSpawns -= 1 + if ( data.pendingSpawns <= 0 ) // we have reached max npcs + return // stop any spawning functions + + int npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) + while ( npcOnFieldCount >= totalAllowedOnField ) // wait until we have less npcs than allowed count + { + WaitFrame() + npcOnFieldCount = GetScriptManagedNPCArrayLength_Alive( eventManager ) + } + } } -void function AT_SpawnBountyTitan( int camp ) +void function AT_SpawnBountyTitan( AT_WaveOrigin campData, int spawnId, int scriptManagerId ) { entity spawnpoint - if ( file.camps[ camp ].dropPodSpawnPoints.len() == 0 ) - spawnpoint = file.camps[ camp ].ent + if ( campData.titanSpawnPoints.len() == 0 ) + spawnpoint = campData.ent else - spawnpoint = file.camps[ camp ].titanSpawnPoints.getrandom() + spawnpoint = campData.titanSpawnPoints.getrandom() + // anti-crash + if ( !IsValid( spawnpoint ) ) + spawnpoint = campData.ent // add variation to spawns wait RandomFloat( 1.0 ) @@ -320,57 +1625,191 @@ void function AT_SpawnBountyTitan( int camp ) int bountyID = 0 try { - bountyID = ReserveBossID( VALID_BOUNTY_TITAN_SETTINGS.getrandom() ) + bountyID = ReserveBossID( AT_BOUNTY_TITANS_AI_SETTINGS.getrandom() ) } catch ( ex ) {} // if we go above the expected wave count that vanilla supports, there's basically no way to ensure that this func won't error, so default 0 after that point string aisettings = GetTypeFromBossID( bountyID ) string titanClass = expect string( Dev_GetAISettingByKeyField_Global( aisettings, "npc_titan_player_settings" ) ) + AiGameModes_SpawnTitan( + spawnpoint.GetOrigin(), + spawnpoint.GetAngles(), + AT_AI_TEAM, + titanClass, + aisettings, + // titan handler + void function( entity titan ) : ( campData, spawnId, bountyID, scriptManagerId ) + { + AT_HandleBossTitanSpawn( titan, campData, spawnId, bountyID, scriptManagerId ) + } + ) +} + +void function AT_HandleBossTitanSpawn( entity titan, AT_WaveOrigin campData, int spawnId, int bountyID, int scriptManagerId ) +{ + // set the bounty to be campEnt, for client tracking + SetGlobalNetEnt( "camp" + string( spawnId + 1 ) + "Ent", titan ) + // set up health + titan.SetMaxHealth( titan.GetMaxHealth() * AT_BOUNTY_TITAN_HEALTH_MULTIPLIER ) + titan.SetHealth( titan.GetMaxHealth() ) + // make minimap always show them and highlight them + titan.Minimap_AlwaysShow( TEAM_IMC, null ) + titan.Minimap_AlwaysShow( TEAM_MILITIA, null ) + thread BountyBossHighlightThink( titan ) + + // set up titan-specific death callbacks, mark it as bounty boss for finalDamageCallbacks to work + file.titanIsBountyBoss[ titan ] <- true + file.bountyTitanRewards[ titan ] <- ATTRITION_SCORE_BOSS_DAMAGE + AddEntityCallback_OnKilled( titan, OnBountyTitanKilled ) - AiGameModes_SpawnTitan( spawnpoint.GetOrigin(), spawnpoint.GetAngles(), BH_AI_TEAM, titanClass, aisettings, void function( entity titan ) : ( camp, bountyID ) + titan.GetTitanSoul().soul.skipDoomState = true + // i feel like this should be localised, but there's nothing for it in r1_english? + titan.SetTitle( GetNameFromBossID( bountyID ) ) + + // tracking lifetime + AddToScriptManagedEntArray( scriptManagerId, titan ) + thread AT_TrackNPCLifeTime( titan, spawnId, "npc_titan" ) +} + +void function BountyBossHighlightThink( entity titan ) +{ + titan.EndSignal( "OnDestroy" ) + titan.EndSignal( "OnDeath" ) + + while ( true ) { - // set up titan-specific death/damage callbacks - AddEntityCallback_OnDamaged( titan, OnBountyDamaged) - AddEntityCallback_OnKilled( titan, OnBountyKilled ) - - titan.GetTitanSoul().soul.skipDoomState = true - // i feel like this should be localised, but there's nothing for it in r1_english? - titan.SetTitle( GetNameFromBossID( bountyID ) ) - thread AT_WaitToUntrackNPC( titan, camp, "npc_titan" ) - } ) + Highlight_SetEnemyHighlight( titan, "enemy_boss_bounty" ) + titan.WaitSignal( "StopPhaseShift" ) // prevent phase shift mess up highlights + } +} + +void function OnNPCTitanFinalDamaged( entity titan, var damageInfo ) +{ + if ( titan in file.titanIsBountyBoss ) + OnBountyTitanDamaged( titan, damageInfo ) } -// Tracked entities will require their own "wallet" -// for titans it should be used for rounding error compenstation -// for infantry it sould be used to store money if the npc kills a player -void function OnBountyDamaged( entity titan, var damageInfo ) +void function OnBountyTitanDamaged( entity titan, var damageInfo ) { entity attacker = DamageInfo_GetAttacker( damageInfo ) + if ( !IsValid( attacker ) ) // delayed by projectile shots + return + // damaged by npc or something? if ( !attacker.IsPlayer() ) - attacker = GetLatestAssistingPlayerInfo( titan ).player - - if ( IsValid( attacker ) && attacker.IsPlayer() ) { - int reward = int ( BOUNTY_TITAN_DAMAGE_POOL * DamageInfo_GetDamage( damageInfo ) / titan.GetMaxHealth() ) - printt ( titan.GetMaxHealth(), DamageInfo_GetDamage( damageInfo ) ) - - AT_AddPlayerCash( attacker, reward ) + attacker = GetBountyBossDamageOwner( attacker, titan ) + if ( !IsValid( attacker ) || !attacker.IsPlayer() ) + return } + + // respawn FUCKED UP pilot weapon against titan's damage calculation, have to copy-paste this check from Titan_NPCTookDamage() + if ( HeavyArmorCriticalHitRequired( damageInfo ) && + CritWeaponInDamageInfo( damageInfo ) && + !IsCriticalHit( attacker, titan, DamageInfo_GetHitBox( damageInfo ), DamageInfo_GetDamage( damageInfo ), DamageInfo_GetDamageType( damageInfo ) ) && + IsValid( attacker ) && + !attacker.IsTitan() ) + return + + int rewardSegment = ATTRITION_SCORE_BOSS_DAMAGE + int healthSegment = titan.GetMaxHealth() / rewardSegment + + // sometimes damage is not enough to add 1 point, we save the damage for player's next attack + if ( !( titan in file.playerSavedBountyDamage[ attacker ] ) ) + file.playerSavedBountyDamage[ attacker ][ titan ] <- 0 + + file.playerSavedBountyDamage[ attacker ][ titan ] += int( DamageInfo_GetDamage( damageInfo ) ) + if ( file.playerSavedBountyDamage[ attacker ][ titan ] < healthSegment ) + return // they can't earn reward from this shot + + int damageSegment = file.playerSavedBountyDamage[ attacker ][ titan ] / healthSegment + int savedDamageLeft = file.playerSavedBountyDamage[ attacker ][ titan ] % healthSegment + file.playerSavedBountyDamage[ attacker ][ titan ] = savedDamageLeft + + float damageFrac = float( damageSegment ) / rewardSegment + int rewardLeft = file.bountyTitanRewards[ titan ] + int reward = int( ATTRITION_SCORE_BOSS_DAMAGE * damageFrac ) + if ( reward >= rewardLeft ) // overloaded shot? + reward = rewardLeft + file.bountyTitanRewards[ titan ] -= reward + + if ( reward > 0 ) + AT_AddPlayerBonusPointsForBossDamaged( attacker, titan, reward, damageInfo ) } -void function OnBountyKilled( entity titan, var damageInfo ) +void function OnBountyTitanKilled( entity titan, var damageInfo ) { entity attacker = DamageInfo_GetAttacker( damageInfo ) + if ( !IsValid( attacker ) ) // delayed by projectile shots + return + // damaged by npc or something? if ( !attacker.IsPlayer() ) - attacker = GetLatestAssistingPlayerInfo( titan ).player + { + attacker = GetBountyBossDamageOwner( attacker, titan ) + if ( !IsValid( attacker ) || !attacker.IsPlayer() ) + return + } + + // add all remaining reward to attacker + // bounty killed bonus handled by AT_PlayerOrNPCKilledScoreEvent() + int rewardLeft = file.bountyTitanRewards[ titan ] + delete file.bountyTitanRewards[ titan ] + if ( rewardLeft > 0 ) + AT_AddPlayerBonusPointsForBossDamaged( attacker, titan, rewardLeft, damageInfo ) + + // remove this bounty's damage saver + foreach ( entity player in GetPlayerArray() ) + { + if ( titan in file.playerSavedBountyDamage[ player ] ) + delete file.playerSavedBountyDamage[ player ][ titan ] + } + + // faction dialogue + int team = attacker.GetTeam() + PlayFactionDialogueToPlayer( "bh_playerKilledBounty", attacker ) + PlayFactionDialogueToTeamExceptPlayer( "bh_bountyClaimedByFriendly", team, attacker ) + PlayFactionDialogueToTeam( "bh_bountyClaimedByEnemy", GetOtherTeam( team ) ) +} + +entity function GetBountyBossDamageOwner( entity attacker, entity titan ) +{ + if ( attacker.IsPlayer() ) // already a player + return attacker - if ( IsValid( attacker ) && attacker.IsPlayer() ) - AT_AddPlayerCash( attacker, BOUNTY_TITAN_KILL_REWARD ) + if ( attacker.IsTitan() ) // attacker is a npc titan + { + // try to find it's pet titan owner + if ( IsValid( GetPetTitanOwner( attacker ) ) ) + return GetPetTitanOwner( attacker ) + } + + // other damages or non-owner npcs, not sure how it happens, just use this titan's last attacker + return GetLatestAssistingPlayerInfo( titan ).player } -void function AT_WaitToUntrackNPC( entity guy, int camp, string aiType ) +void function AT_TrackNPCLifeTime( entity guy, int spawnId, string aiType ) { guy.WaitSignal( "OnDeath", "OnDestroy" ) - file.trackedCampNPCSpawns[ camp ][ aiType ]-- + + string npcNetVar = GetNPCNetVarName( aiType, spawnId ) + SetGlobalNetInt( npcNetVar, GetGlobalNetInt( npcNetVar ) - 1 ) +} + + +// network var +string function GetNPCNetVarName( string className, int spawnId ) +{ + string npcId = string( GetAiTypeInt( className ) + 1 ) + string campLetter = GetCampLetter( spawnId ) + if ( npcId == "0" ) // cannot find this ai support! + { + if ( className == "npc_super_spectre" ) // stupid, reapers are not handled by GetAiTypeInt(), but it must be 4 + return "4" + campLetter + "campCount" + return "" + } + return npcId + campLetter + "campCount" } + +///////////////////////////// +///// NPC FUNCTIONS END ///// +///////////////////////////// -- cgit v1.2.3 From 9181e52e0af606986af3c733ad78360f16a40be8 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Thu, 4 May 2023 22:32:48 +0100 Subject: Fix serverbrowser filtering by gamemode (#639) --- Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Northstar.Client/mod/scripts/vscripts') 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 c31185ee..7ea8134a 100644 --- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut @@ -771,7 +771,7 @@ void function FilterServerList() if ( filterArguments.filterMap != "SWITCH_ANY" && filterArguments.filterMap != server.map ) continue; - if ( filterArguments.filterGamemode != "SWITCH_ANY" && filterArguments.filterGamemode != server.playlist ) + if ( filterArguments.filterGamemode != "SWITCH_ANY" && filterArguments.filterGamemode != GetGameModeDisplayName(server.playlist) ) continue; // Search -- cgit v1.2.3