From bbf6b54a87467b4c028bb296cb72ffc7880e4513 Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Thu, 12 Oct 2023 01:29:31 +0100 Subject: [PATCH] Fix INVALID_REF errors caused by mods (#657) `INVALID_REF` errors were being caused by trying to get the item image for weapon skins. If a mod (e.g. moreskins) added new skins, the client equipped one, and then the mod is disabled, the client now has bad persistence. This is mostly handled fine by the game, except for weapon skins. This PR prevents the crash, and resets the bad persistence to default. --- .../vscripts/ui/menu_edit_pilot_loadouts.nut | 170 ++++++++++ .../ui/menu_pilot_loadouts_shared.nut | 300 ++++++++++++++++++ 2 files changed, 470 insertions(+) create mode 100644 Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut create mode 100644 Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut new file mode 100644 index 000000000..89479a76c --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_edit_pilot_loadouts.nut @@ -0,0 +1,170 @@ +untyped + +global function InitEditPilotLoadoutsMenu + +struct +{ + var menu + var loadoutPanel + var[NUM_PERSISTENT_PILOT_LOADOUTS] loadoutHeaders + var[NUM_PERSISTENT_PILOT_LOADOUTS] activateButtons + bool enteringEdit = false + var unlockReq +} file + +void function InitEditPilotLoadoutsMenu() +{ + file.menu = GetMenu( "EditPilotLoadoutsMenu" ) + var menu = file.menu + + AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnPilotLoadoutsMenu_Open ) + AddMenuEventHandler( menu, eUIEvent.MENU_CLOSE, OnPilotLoadoutsMenu_Close ) + AddMenuEventHandler( menu, eUIEvent.MENU_INPUT_MODE_CHANGED, OnPilotLoadoutsMenu_InputModeChanged ) + + for ( int i = 0; i < NUM_PERSISTENT_PILOT_LOADOUTS; i++ ) + { + var activateButton = Hud_GetChild( menu, "Button" + i ) + activateButton.s.rowIndex <- i + Hud_SetVisible( activateButton, true ) + Hud_AddEventHandler( activateButton, UIE_CLICK, OnLoadoutButton_Activate ) + Hud_AddEventHandler( activateButton, UIE_GET_FOCUS, OnLoadoutButton_Focused ) + Hud_AddEventHandler( activateButton, UIE_LOSE_FOCUS, OnLoadoutButton_LostFocus ) + file.activateButtons[i] = activateButton + } + + Hud_SetFocused( file.activateButtons[0] ) + + file.loadoutPanel = Hud_GetChild( menu, "PilotLoadoutDisplay" ) + file.unlockReq = Hud_GetChild( menu, "UnlockReq" ) + + AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" ) + AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" ) +} + +void function OnPilotLoadoutsMenu_Open() +{ + entity player = GetUIPlayer() + if ( player == null ) + return + + RunMenuClientFunction( "ClearEditingPilotLoadoutIndex" ) + + int loadoutIndex = uiGlobal.pilotSpawnLoadoutIndex + UpdatePilotLoadoutButtons( loadoutIndex, file.activateButtons ) + UpdatePilotLoadoutPanel( file.loadoutPanel, GetCachedPilotLoadout( loadoutIndex ) ) + UI_SetPresentationType( ePresentationType.PILOT ) + + RefreshCreditsAvailable() +} + +void function OnPilotLoadoutsMenu_Close() +{ + entity player = GetUIPlayer() + if ( player == null ) + return + + foreach ( i, button in file.activateButtons ) + { + string pilotLoadoutRef = "pilot_loadout_" + ( i + 1 ) + if ( !IsItemNew( player, pilotLoadoutRef ) ) + continue + + ClearNewStatus( button, pilotLoadoutRef ) + } +} + +void function OnPilotLoadoutsMenu_InputModeChanged() +{ + UpdatePilotLoadoutPanelBinds( file.loadoutPanel ) +} + +void function OnLoadoutButton_Focused( var button ) +{ + int index = expect int( button.s.rowIndex ) + + // update the editingLoadoutIndex on focus so that it always matches + // with the pilot loadout panel + uiGlobal.editingLoadoutIndex = index + uiGlobal.editingLoadoutType = "pilot" + + UpdatePilotLoadout( index ) + + string pilotLoadoutRef = "pilot_loadout_" + ( index + 1 ) + string unlockReq = GetItemUnlockReqText( pilotLoadoutRef ) + RHud_SetText( file.unlockReq, unlockReq ) +} + +void function UpdatePilotLoadout( int loadoutIndex ) +{ + PilotLoadoutDef loadout = GetCachedPilotLoadout( loadoutIndex ) + + UpdatePilotLoadoutPanel( file.loadoutPanel, loadout ) + RunMenuClientFunction( "UpdatePilotModel", loadoutIndex ) +} + +void function OnLoadoutButton_Activate( var button ) +{ + if ( !IsFullyConnected() ) + return + + if ( Hud_IsLocked( button ) ) + { + int index = expect int ( button.s.rowIndex ) + string pilotLoadoutRef = "pilot_loadout_" + ( index + 1 ) + + array buttons + foreach ( button in file.activateButtons ) + { + buttons.append( button ) + } + + OpenBuyItemDialog( buttons, button, GetItemName( pilotLoadoutRef ), pilotLoadoutRef ) + return + } + + int loadoutIndex = expect int ( button.s.rowIndex ) + SetEditLoadout( "pilot", loadoutIndex ) + + if ( EDIT_LOADOUT_SELECTS ) + { + bool indexChanged = loadoutIndex != uiGlobal.pilotSpawnLoadoutIndex + + if ( indexChanged ) + { + EmitUISound( "Menu_LoadOut_Pilot_Select" ) + + if ( !IsLobby() ) + uiGlobal.updatePilotSpawnLoadout = true + } + + uiGlobal.pilotSpawnLoadoutIndex = loadoutIndex + ClientCommand( "RequestPilotLoadout " + loadoutIndex ) + } + + if ( PRE_RELEASE_DEMO && loadoutIndex < 3 ) + { + UpdatePilotLoadoutButtons( loadoutIndex, file.activateButtons ) + return + } + + RunMenuClientFunction( "SetEditingPilotLoadoutIndex", loadoutIndex ) + AdvanceMenu( GetMenu( "EditPilotLoadoutMenu" ) ) +} + +void function OnLoadoutButton_LostFocus( var button ) +{ + entity player = GetUIPlayer() + if ( !IsValid( player ) ) + return + + int loadoutIndex = expect int ( button.s.rowIndex ) + string pilotLoadoutRef = "pilot_loadout_" + ( loadoutIndex + 1 ) + ClearNewStatus( button, pilotLoadoutRef ) + + if ( IsItemLocked( player, pilotLoadoutRef ) ) + return + + PilotLoadoutDef loadout = GetCachedPilotLoadout( loadoutIndex ) + if ( (RefHasAnyNewSubitem( player, loadout.primary ) || RefHasAnyNewSubitem( player, loadout.secondary ) || RefHasAnyNewSubitem( player, loadout.weapon3 )) ) + Hud_SetNew( button, true ) +} diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut new file mode 100644 index 000000000..f0139e04e --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_pilot_loadouts_shared.nut @@ -0,0 +1,300 @@ + +global function UpdatePilotLoadoutPanel +global function UpdatePilotLoadoutPanelBinds +global function UpdatePilotLoadoutButtons +global function UpdatePilotItemButton + +void function UpdatePilotLoadoutButtons( int selectedIndex, var[NUM_PERSISTENT_PILOT_LOADOUTS] buttons, bool focusSelected = true ) +{ + entity player = GetUIPlayer() + if ( player == null ) + return + + int numLoadouts = GetAllCachedPilotLoadouts().len() + + // HACK: num_pilot_loadouts is just used to disable certain loadouts for FNF + int numLoadoutsForPlaylist = GetCurrentPlaylistVarInt( "num_pilot_loadouts", 0 ) + if ( numLoadoutsForPlaylist > 0 ) + numLoadouts = numLoadoutsForPlaylist + + foreach ( index, button in buttons ) + { + PilotLoadoutDef loadout = GetCachedPilotLoadout( index ) + RHud_SetText( button, GetPilotLoadoutName( loadout ) ) + Hud_SetPanelAlpha( button, 0 ) + + bool isSelected = ( index == selectedIndex ) ? true : false + Hud_SetSelected( button, isSelected ) + + string pilotLoadoutRef = "pilot_loadout_" + ( index + 1 ) + Hud_SetLocked( button, IsItemLocked( player, pilotLoadoutRef ) ) + + bool shouldShowNew = ButtonShouldShowNew( eItemTypes.FEATURE, pilotLoadoutRef ) + if ( !shouldShowNew && (RefHasAnyNewSubitem( player, loadout.primary ) || RefHasAnyNewSubitem( player, loadout.secondary ) || RefHasAnyNewSubitem( player, loadout.weapon3 )) ) + shouldShowNew = true + + if ( IsItemLocked( player, pilotLoadoutRef ) ) + shouldShowNew = false + + Hud_SetNew( button, shouldShowNew ) + + RefreshButtonCost( button, pilotLoadoutRef ) + } + + if ( focusSelected ) + Hud_SetFocused( buttons[ selectedIndex ] ) +} + +void function UpdatePilotLoadoutPanel( var loadoutPanel, PilotLoadoutDef loadout ) +{ + SetLabelRuiText( Hud_GetChild( loadoutPanel, "TacticalName" ), Localize( GetItemName( loadout.special ) ) ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "PrimaryName" ), Localize( GetItemName( loadout.primary ) ) ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "SecondaryName" ), Localize( GetItemName( loadout.secondary ) ) ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "Weapon3Name" ), Localize( GetItemName( loadout.weapon3 ) ) ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "OrdnanceName" ), Localize( GetItemName( loadout.ordnance ) ) ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "Kit1Name" ), Localize( GetItemName( loadout.passive1 ) ) ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "Kit2Name" ), Localize( GetItemName( loadout.passive2 ) ) ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "ExecutionName" ), Localize( GetItemName( loadout.execution ) ) ) + + UpdatePilotLoadoutPanelBinds( loadoutPanel ) + + var menu = Hud_GetParent( loadoutPanel ) + array buttons = GetElementsByClassname( menu, "PilotLoadoutPanelButtonClass" ) + + /*if ( button ) + { + // TEMP disabled since Hud_GetChild( menu, "ButtonTooltip" ) will fail + //if ( HandleLockedMenuItem( menu, button ) ) + // return + }*/ + bool isEdit + if ( Hud_GetHudName( loadoutPanel ) == "PilotLoadoutButtons" ) // Edit menu + isEdit = true + else // Select menu + isEdit = false + + foreach ( button in buttons ) + UpdatePilotItemButton( button, loadout, isEdit ) + + var renameEditBox = Hud_GetChild( loadoutPanel, "RenameEditBox" ) + + asset pilotAppearanceImage = loadout.camoIndex > 0 ? CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.camoIndex ) ) : $"rui/menu/common/appearance_button_swatch" + + asset primaryAppearanceImage + if ( loadout.primarySkinIndex == 0 ) // default skin + primaryAppearanceImage = $"rui/menu/common/appearance_button_swatch" + else if ( loadout.primarySkinIndex == 1 ) // camo + primaryAppearanceImage = CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.primaryCamoIndex ) ) + else // warpaint skin + primaryAppearanceImage = GetItemImageFromWeaponRefAndPersistenceValue( loadout.primary, loadout.primarySkinIndex, "primarySkinIndex" ) + + asset secondaryAppearanceImage + if ( loadout.secondarySkinIndex == 0 ) // default skin + secondaryAppearanceImage = $"rui/menu/common/appearance_button_swatch" + else if ( loadout.secondarySkinIndex == 1 ) // camo + secondaryAppearanceImage = CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.secondaryCamoIndex ) ) + else // warpaint skin + secondaryAppearanceImage = GetItemImageFromWeaponRefAndPersistenceValue( loadout.secondary, loadout.secondarySkinIndex, "secondarySkinIndex" ) + + asset weapon3AppearanceImage + if ( loadout.weapon3SkinIndex == 0 ) // default skin + weapon3AppearanceImage = $"rui/menu/common/appearance_button_swatch" + else if ( loadout.weapon3SkinIndex == 1 ) // camo + weapon3AppearanceImage = CamoSkin_GetImage( CamoSkins_GetByIndex( loadout.weapon3CamoIndex ) ) + else // warpaint skin + weapon3AppearanceImage = GetItemImageFromWeaponRefAndPersistenceValue( loadout.weapon3, loadout.weapon3SkinIndex, "weapon3SkinIndex" ) + + RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonPilotCamo" ) ), "camoImage", pilotAppearanceImage ) + RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonPrimarySkin" ) ), "camoImage", primaryAppearanceImage ) + RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonSecondarySkin" ) ), "camoImage", secondaryAppearanceImage ) + RuiSetImage( Hud_GetRui( Hud_GetChild( loadoutPanel, "ButtonWeapon3Skin" ) ), "camoImage", weapon3AppearanceImage ) + + array nonItemElements + nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonPilotCamo" ) ) + nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonGender" ) ) + nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonPrimarySkin" ) ) + nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonSecondarySkin" ) ) + nonItemElements.append( Hud_GetChild( loadoutPanel, "ButtonWeapon3Skin" ) ) + nonItemElements.append( renameEditBox ) + + foreach ( elem in nonItemElements ) + { + if ( isEdit ) + Hud_Show( elem ) + else + Hud_Hide( elem ) + } + Hud_SetEnabled( renameEditBox, isEdit ) +} + +void function UpdatePilotItemButton( var button, PilotLoadoutDef loadout, bool isEdit ) +{ + string propertyName = Hud_GetScriptID( button ) + string itemRef = GetPilotLoadoutValue( loadout, propertyName ) + int itemType = GetItemTypeFromPilotLoadoutProperty( propertyName ) + asset image + + ItemDisplayData parentItem + + if ( itemType == eItemTypes.PILOT_PRIMARY_ATTACHMENT || itemType == eItemTypes.PILOT_PRIMARY_MOD || itemType == eItemTypes.PILOT_SECONDARY_MOD || itemType == eItemTypes.PILOT_WEAPON_MOD3 ) + { + string parentProperty = GetParentLoadoutProperty( "pilot", propertyName ) + Assert( parentProperty == "primary" || parentProperty == "secondary" || parentProperty == "weapon3" ) + + if ( parentProperty == "primary" ) + parentItem = GetItemDisplayData( loadout.primary ) + else if ( parentProperty == "secondary" ) + parentItem = GetItemDisplayData( loadout.secondary ) + else + parentItem = GetItemDisplayData( loadout.weapon3 ) + + bool isHiddenAttachment = false + + if ( itemType == eItemTypes.PILOT_PRIMARY_ATTACHMENT ) + { + // Disable attachment option for "special" primary weapons + if ( "menuCategory" in parentItem.i && ( expect int( parentItem.i.menuCategory ) == ePrimaryWeaponCategory.SPECIAL || expect int( parentItem.i.menuCategory ) == ePrimaryWeaponCategory.HANDGUN ) ) + { + isHiddenAttachment = true + + Hud_SetWidth( button, 0 ) + Hud_SetPos( button, 0, 0 ) // Clear sibling offset + } + else + { + int defaultButtonWidth = int( ContentScaledX( 72 ) ) + int defaultOffsetX = int( ContentScaledX( 6 ) ) + + Hud_SetWidth( button, defaultButtonWidth ) + Hud_SetPos( button, defaultOffsetX, 0 ) + } + } + + if ( !isHiddenAttachment ) + image = GetImage( itemType, parentItem.ref, itemRef ) + } + else + { + image = GetImage( itemType, itemRef ) + } + + if ( itemType == eItemTypes.PILOT_PRIMARY || itemType == eItemTypes.PILOT_SECONDARY ) + { + //if ( isEdit ) + //{ + // RuiSetString( Hud_GetRui( button ), "subText", "" ) + // RuiSetFloat( Hud_GetRui( button ), "numSegments", 0 ) + // RuiSetFloat( Hud_GetRui( button ), "filledSegments", 0 ) + //} + //else + { + int currentXP = WeaponGetXP( GetLocalClientPlayer(), itemRef ) + int numPips = WeaponGetNumPipsForXP( itemRef, currentXP ) + int filledPips = WeaponGetFilledPipsForXP( itemRef, currentXP ) + RuiSetString( Hud_GetRui( button ), "subText", WeaponGetDisplayGenAndLevelForXP( itemRef, currentXP ) ) + RuiSetFloat( Hud_GetRui( button ), "numSegments", float( numPips ) ) + RuiSetFloat( Hud_GetRui( button ), "filledSegments", float( filledPips ) ) + } + } + + var rui = Hud_GetRui( button ) + + if ( image == $"" ) + { + RuiSetBool( rui, "isVisible", false ) + Hud_SetEnabled( button, false ) + } + else + { + RuiSetBool( rui, "isVisible", true ) + RuiSetImage( rui, "buttonImage", image ) + + Hud_SetEnabled( button, true ) + } + + bool isLocked = false + bool shouldShowNew = false + + // For unlock and subitem checks below, treat weapon3 as secondary + if ( propertyName == "weapon3Mod1" ) + propertyName = "secondaryMod1" + else if ( propertyName == "weapon3Mod2" ) + propertyName = "secondaryMod2" + else if ( propertyName == "weapon3Mod3" ) + propertyName = "secondaryMod3" + + string propertyRef = propertyName.tolower() + + if ( !IsSubItemType( itemType ) ) + { + if ( IsUnlockValid( propertyRef ) && IsItemLocked( GetUIPlayer(), propertyRef ) ) + { + RefreshButtonCost( button, propertyRef ) + isLocked = true + } + shouldShowNew = ButtonShouldShowNew( itemType, itemRef ) + } + else + { + if ( IsUnlockValid( propertyRef, parentItem.ref ) && IsSubItemLocked( GetUIPlayer(), propertyRef, parentItem.ref ) ) + { + RefreshButtonCost( button, propertyRef ) + isLocked = true + } + shouldShowNew = ButtonShouldShowNew( itemType, itemRef, parentItem.ref ) + } + + Hud_SetLocked( button, isLocked ) + + if ( !shouldShowNew && IsUnlockValid( propertyRef, parentItem.ref ) ) + shouldShowNew = ButtonShouldShowNew( GetSubitemType( parentItem.ref, propertyRef ), propertyRef, parentItem.ref ) + Hud_SetNew( button, shouldShowNew ) + +#if HAS_THREAT_SCOPE_SLOT_LOCK + if ( propertyName == "primaryMod2" ) + { + string attatchmentRef = GetPilotLoadoutValue( loadout, "primaryAttachment" ) + if ( attatchmentRef == "threat_scope" ) + { + Hud_SetLocked( button, true ) + RefreshButtonCost( button, propertyRef, "", 0, 0 ) + Hud_SetNew( button, false ) + } + } +#endif +} + +void function UpdatePilotLoadoutPanelBinds( var loadoutPanel ) +{ + if ( IsControllerModeActive() ) + { + //SetLabelRuiText( Hud_GetChild( loadoutPanel, "PrimaryBind" ), "%weaponCycle%" ) + //SetLabelRuiText( Hud_GetChild( loadoutPanel, "SecondaryBind" ), "%weaponCycle%" ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "Weapon3Bind" ), Localize( "#WEAPON3_HOLD_HINT" ) ) + } + else + { + //SetLabelRuiText( Hud_GetChild( loadoutPanel, "PrimaryBind" ), "%weaponSelectPrimary0%" ) + //SetLabelRuiText( Hud_GetChild( loadoutPanel, "SecondaryBind" ), "%weaponSelectPrimary1%" ) + SetLabelRuiText( Hud_GetChild( loadoutPanel, "Weapon3Bind" ), Localize( "#WEAPON3_PRESS_HINT" ) ) + } + + //SetLabelRuiText( Hud_GetChild( loadoutPanel, "TacticalBind" ), Localize( "%offhand1%" ) ) + //SetLabelRuiText( Hud_GetChild( loadoutPanel, "OrdnanceBind" ), Localize( "%offhand0%" ) ) +} + +asset function GetItemImageFromWeaponRefAndPersistenceValue(string weaponRef, int persistenceValue, string loadoutProperty) +{ + string skinRef = GetSkinRefFromWeaponRefAndPersistenceValue( weaponRef, persistenceValue ) + if (!IsRefValid(skinRef)) + { + if (uiGlobal.editingLoadoutIndex != -1) + { + printt( "Resetting invalid " + loadoutProperty + " for weapon " + weaponRef ) + SetCachedLoadoutValue(GetUIPlayer(), "pilot", uiGlobal.editingLoadoutIndex, loadoutProperty, "0") + } + return $"rui/menu/common/appearance_button_swatch" + } + + return GetItemImage( skinRef ) +}