From 31cba09fea290cfddde6cc77af836b1cd4756bdd Mon Sep 17 00:00:00 2001 From: Emma Miler <27428383+emma-miler@users.noreply.github.com> Date: Tue, 19 Apr 2022 23:27:54 +0200 Subject: Fix a freeze in stats menu (#307) --- .../mod/scripts/vscripts/ui/menu_stats_maps.nut | 322 ++++++++++++ .../mod/scripts/vscripts/ui/menu_stats_utility.nut | 560 +++++++++++++++++++++ 2 files changed, 882 insertions(+) create mode 100644 Northstar.Client/mod/scripts/vscripts/ui/menu_stats_maps.nut create mode 100644 Northstar.Client/mod/scripts/vscripts/ui/menu_stats_utility.nut (limited to 'Northstar.Client/mod/scripts') diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_stats_maps.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_stats_maps.nut new file mode 100644 index 00000000..eb844af4 --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_stats_maps.nut @@ -0,0 +1,322 @@ +global function InitViewStatsMapsMenu +global function setit +struct +{ + var menu + GridMenuData gridData + bool isGridInitialized = false + array allMaps +} file + +// Why this file is included in Northstar: +// As it turns out, the Respawn developers in all their infinite wisdom decided to add a check to floats for NaN and Inf +// which does not exist in base Squirrel +// The code looks a bit like this: +// if ( strcpy_s(fos + 4, 0x16ui64, "1#QNAN") ) +// invoke_watson(0i64, 0i64, 0i64, 0, 0i64); +// goto LABEL_27; +// As if turns out, there is NO way to check if a float is one of these values in script +// This alone, however, wouldn't be that bad, as it would simply result in a 1#QNAN being output to the screen. +// Unfortunately, Respawns universe sized brains did not stop there. +// One day, a dev at Respawn had to write a function to convert an amount of hours into a timestring. +// Now, you and i dear reader would, being mortals, opt for the O(1) time solution of using the floor() and modulo functions +// You may think this would work perfectly, but you would be wrong. This is not the respawn Way! +// Instead, they opted to write the following piece of O(n) time algorithm: +// while ( minutes >= 60 ) +// { +// minutes -= 60 +// hours++ +// } +// Now you may ask: "Is this horribly inefficient and bug-prone?", but you must understand: This is the Respawn Way +// Passing in a NaN does not simply output a NaN to the screen, for that would be too simple. +// Nay, instead, it hangs the UI thread for all eternity, as it tries to subtract 60 from a NaN +// In fact, i think we should thank that developer for all the fun times we have had tracking and fixing this bug +// However, we mortals cannot possibly wield the greatness of Respawn's code, and we must settle for a lowly O(1) algorithm instead +// P.S: The other part of this fix is menu_stats_utility.nut + +void function InitViewStatsMapsMenu() +{ + var menu = GetMenu( "ViewStats_Maps_Menu" ) + file.menu = menu + + Hud_SetText( Hud_GetChild( file.menu, "MenuTitle" ), "#STATS_MAPS" ) + + file.gridData.rows = 5 + file.gridData.columns = 1 + //file.gridData.numElements // Set in OnViewStatsWeapons_Open after itemdata exists + file.gridData.pageType = eGridPageType.VERTICAL + file.gridData.tileWidth = 224 + file.gridData.tileHeight = 112 + file.gridData.paddingVert = 6 + file.gridData.paddingHorz = 6 + + Grid_AutoAspectSettings( menu, file.gridData ) + + file.gridData.initCallback = MapButton_Init + file.gridData.getFocusCallback = MapButton_GetFocus + file.gridData.clickCallback = MapButton_Activate + + AddMenuEventHandler( file.menu, eUIEvent.MENU_OPEN, OnViewStatsWeapons_Open ) + + AddMenuFooterOption( file.menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" ) +} + +void function OnViewStatsWeapons_Open() +{ + UI_SetPresentationType( ePresentationType.NO_MODELS ) + + file.allMaps = GetPrivateMatchMaps() + + file.gridData.numElements = file.allMaps.len() + + if ( !file.isGridInitialized ) + { + GridMenuInit( file.menu, file.gridData ) + file.isGridInitialized = true + } + + file.gridData.currentPage = 0 + + Grid_InitPage( file.menu, file.gridData ) + Hud_SetFocused( Grid_GetButtonForElementNumber( file.menu, 0 ) ) + UpdateStatsForMap( file.allMaps[ 0 ] ) +} + +bool function MapButton_Init( var button, int elemNum ) +{ + string mapName = file.allMaps[ elemNum ] + + asset mapImage = GetMapImageForMapName( mapName ) + + var rui = Hud_GetRui( button ) + RuiSetImage( rui, "buttonImage", mapImage ) + + Hud_SetEnabled( button, true ) + Hud_SetVisible( button, true ) + + return true +} + +void function MapButton_GetFocus( var button, int elemNum ) +{ + if ( IsControllerModeActive() ) + UpdateStatsForMap( file.allMaps[ elemNum ] ) +} + +void function MapButton_Activate( var button, int elemNum ) +{ + if ( !IsControllerModeActive() ) + UpdateStatsForMap( file.allMaps[ elemNum ] ) +} + +int function GetGameStatForMapInt( string gameStat, string mapName ) +{ + array privateMatchModes = GetPrivateMatchModes() + + int totalStat = 0 + int enumCount = PersistenceGetEnumCount( "gameModes" ) + for ( int modeId = 0; modeId < enumCount; modeId++ ) + { + string modeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeId ) + + totalStat += GetGameStatForMapAndModeInt( gameStat, mapName, modeName ) + } + + return totalStat +} + +int function GetGameStatForMapAndModeInt( string gameStat, string mapName, string modeName, string difficulty = "1" ) +{ + string statString = GetStatVar( "game_stats", gameStat, "" ) + string persistentVar = Stats_GetFixedSaveVar( statString, mapName, modeName, difficulty ) + + return GetUIPlayer().GetPersistentVarAsInt( persistentVar ) +} + +float function GetGameStatForMapFloat( string gameStat, string mapName ) +{ + array privateMatchModes = GetPrivateMatchModes() + + float totalStat = 0 + int enumCount = PersistenceGetEnumCount( "gameModes" ) + for ( int modeId = 0; modeId < enumCount; modeId++ ) + { + string modeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeId ) + + if ( (GetGameStatForMapAndModeFloat( gameStat, mapName, modeName ).tostring() ) != "1.#QNAN" ) + totalStat += GetGameStatForMapAndModeFloat( gameStat, mapName, modeName ) + else + print("Hey buddy, I just saved you from a game freeze. You're welcome :)") + } + + return totalStat +} + +float function GetGameStatForMapAndModeFloat( string gameStat, string mapName, string modeName ) +{ + string statString = GetStatVar( "game_stats", gameStat, "" ) + string persistentVar = Stats_GetFixedSaveVar( statString, mapName, modeName, "1" ) + + return expect float( GetUIPlayer().GetPersistentVar( persistentVar ) ) +} + + +void function UpdateStatsForMap( string mapName ) +{ + entity player = GetUIPlayer() + if ( player == null ) + return + + Hud_SetText( Hud_GetChild( file.menu, "WeaponName" ), GetMapDisplayName( mapName ) ) + + // Image + var imageElem = Hud_GetRui( Hud_GetChild( file.menu, "WeaponImageLarge" ) ) + RuiSetImage( imageElem, "basicImage", GetMapImageForMapName( mapName ) ) + var hoursplayed = GetGameStatForMapFloat( "hoursPlayed", mapName ) + string timePlayed = HoursToTimeString( GetGameStatForMapFloat( "hoursPlayed", mapName ) ) + string gamesPlayed = string( GetGameStatForMapInt( "game_completed", mapName ) ) + + SetStatBoxDisplay( Hud_GetChild( file.menu, "Stat0" ), Localize( "#STATS_HEADER_TIME_PLAYED" ), timePlayed ) + SetStatBoxDisplay( Hud_GetChild( file.menu, "Stat1" ), Localize( "#STATS_GAMES_PLAYED" ), gamesPlayed ) + //SetStatBoxDisplay( Hud_GetChild( file.menu, "Stat2" ), Localize( "#STATS_GAMES_PLAYED" ), gamesPlayed ) + //SetStatBoxDisplay( Hud_GetChild( file.menu, "Stat3" ), Localize( "#STATS_GAMES_PLAYED" ), gamesPlayed ) + + string winPercent = GetPercent( float( GetGameStatForMapInt( "game_won", mapName ) ), float( GetGameStatForMapInt( "game_completed", mapName ) ), 0 ) + + SetStatsLabelValue( file.menu, "KillsLabel0", "#STATS_GAMES_WIN_PERCENT" ) + SetStatsLabelValue( file.menu, "KillsValue0", ("" + winPercent + "%") ) + + SetStatsLabelValue( file.menu, "KillsLabel1", "#STATS_GAMES_WON" ) + SetStatsLabelValue( file.menu, "KillsValue1", GetGameStatForMapInt( "game_won", mapName ) ) + + SetStatsLabelValue( file.menu, "KillsLabel2", "#STATS_GAMES_MVP" ) + SetStatsLabelValue( file.menu, "KillsValue2", GetGameStatForMapInt( "mvp", mapName ) ) + + SetStatsLabelValue( file.menu, "KillsLabel3", "#STATS_GAMES_TOP3" ) + SetStatsLabelValue( file.menu, "KillsValue3", GetGameStatForMapInt( "top3OnTeam", mapName ) ) + + SetStatsLabelValue( file.menu, "KillsLabel4", "--" ) + SetStatsLabelValue( file.menu, "KillsValue4", "--" ) + + //var anchorElem = Hud_GetChild( file.menu, "WeaponStatsBackground" ) + //printt( Hud_GetX( anchorElem ) ) + //printt( Hud_GetX( anchorElem ) ) + //printt( Hud_GetX( anchorElem ) ) + //printt( Hud_GetX( anchorElem ) ) + //Hud_SetX( anchorElem, 0 ) + // + array gameModesArray = GetPersistenceEnumAsArray( "gameModes" ) + + array modes + foreach ( modeName in gameModesArray ) + { + float modePlayedTime = GetGameStatForMapAndModeFloat( "hoursPlayed", mapName, modeName ) + if ( modePlayedTime > 0 ) + AddPieChartEntry( modes, GameMode_GetName( modeName ), modePlayedTime, GetGameModeDisplayColor( modeName ) ) + } + + const MAX_MODE_ROWS = 8 + + if ( modes.len() > 0 ) + { + modes.sort( ComparePieChartEntryValues ) + + if ( modes.len() > MAX_MODE_ROWS ) + { + float otherValue + for ( int i = MAX_MODE_ROWS-1; i < modes.len() ; i++ ) + otherValue += modes[i].numValue + + modes.resize( MAX_MODE_ROWS-1 ) + AddPieChartEntry( modes, "#GAMEMODE_OTHER", otherValue, [127, 127, 127, 255] ) + } + } + + PieChartData modesPlayedData + modesPlayedData.entries = modes + modesPlayedData.labelColor = [ 255, 255, 255, 255 ] + SetPieChartData( file.menu, "ModesPieChart", "#GAME_MODES_PLAYED", modesPlayedData ) + + array fdMaps = GetPlaylistMaps( "fd" ) + + if ( fdMaps.contains( mapName ) ) + { + array pveElems = GetElementsByClassname( file.menu, "PvEGroup" ) + foreach ( elem in pveElems ) + { + Hud_Show( elem ) + } + + vector perfectColor = TEAM_COLOR_FRIENDLY / 219.0 + + var iconLegendRui = Hud_GetRui( Hud_GetChild( file.menu, "PvEIconLegend" ) ) + RuiSetImage( iconLegendRui, "basicImage", $"rui/menu/gametype_select/playlist_fd_normal" ) + RuiSetFloat3( iconLegendRui, "basicImageColor", perfectColor ) + + var icon0Rui = Hud_GetRui( Hud_GetChild( file.menu, "PvEIcon0" ) ) + RuiSetImage( icon0Rui, "basicImage", $"rui/menu/gametype_select/playlist_fd_easy" ) + int easyWins = GetGameStatForMapAndModeInt( "games_completed_fd", mapName, "fd", "0" ) + SetStatsLabelValue( file.menu, "PvELabel0", "#FD_DIFFICULTY_EASY" ) + SetStatsLabelValue( file.menu, "PvEValueA0", easyWins ) + if ( GetGameStatForMapAndModeInt( "perfectMatches", mapName, "fd", "0" ) ) + RuiSetFloat3( icon0Rui, "basicImageColor", perfectColor ) + else + RuiSetFloat3( icon0Rui, "basicImageColor", easyWins > 0 ? <1, 1, 1> : <0.15, 0.15, 0.15> ) + + var icon1Rui = Hud_GetRui( Hud_GetChild( file.menu, "PvEIcon1" ) ) + RuiSetImage( icon1Rui, "basicImage", $"rui/menu/gametype_select/playlist_fd_normal" ) + int normalWins = GetGameStatForMapAndModeInt( "games_completed_fd", mapName, "fd", "1" ) + SetStatsLabelValue( file.menu, "PvELabel1", "#FD_DIFFICULTY_NORMAL" ) + SetStatsLabelValue( file.menu, "PvEValueA1", normalWins ) + if ( GetGameStatForMapAndModeInt( "perfectMatches", mapName, "fd", "1" ) ) + RuiSetFloat3( icon1Rui, "basicImageColor", perfectColor ) + else + RuiSetFloat3( icon1Rui, "basicImageColor", normalWins > 0 ? <1, 1, 1> : <0.15, 0.15, 0.15> ) + + var icon2Rui = Hud_GetRui( Hud_GetChild( file.menu, "PvEIcon2" ) ) + RuiSetImage( icon2Rui, "basicImage", $"rui/menu/gametype_select/playlist_fd_hard" ) + int hardWins = GetGameStatForMapAndModeInt( "games_completed_fd", mapName, "fd", "2" ) + SetStatsLabelValue( file.menu, "PvELabel2", "#FD_DIFFICULTY_HARD" ) + SetStatsLabelValue( file.menu, "PvEValueA2", hardWins ) + if ( GetGameStatForMapAndModeInt( "perfectMatches", mapName, "fd", "2" ) ) + RuiSetFloat3( icon2Rui, "basicImageColor", perfectColor ) + else + RuiSetFloat3( icon2Rui, "basicImageColor", hardWins > 0 ? <1, 1, 1> : <0.15, 0.15, 0.15> ) + + var icon3Rui = Hud_GetRui( Hud_GetChild( file.menu, "PvEIcon3" ) ) + RuiSetImage( icon3Rui, "basicImage", $"rui/menu/gametype_select/playlist_fd_master" ) + int masterWins = GetGameStatForMapAndModeInt( "games_completed_fd", mapName, "fd", "3" ) + SetStatsLabelValue( file.menu, "PvELabel3", "#FD_DIFFICULTY_MASTER" ) + SetStatsLabelValue( file.menu, "PvEValueA3", masterWins ) + if ( GetGameStatForMapAndModeInt( "perfectMatches", mapName, "fd", "3" ) ) + RuiSetFloat3( icon3Rui, "basicImageColor", perfectColor ) + else + RuiSetFloat3( icon3Rui, "basicImageColor", masterWins > 0 ? <1, 1, 1> : <0.15, 0.15, 0.15> ) + + var icon4Rui = Hud_GetRui( Hud_GetChild( file.menu, "PvEIcon4" ) ) + RuiSetImage( icon4Rui, "basicImage", $"rui/menu/gametype_select/playlist_fd_insane" ) + int insaneWins = GetGameStatForMapAndModeInt( "games_completed_fd", mapName, "fd", "4" ) + SetStatsLabelValue( file.menu, "PvELabel4", "#FD_DIFFICULTY_INSANE" ) + SetStatsLabelValue( file.menu, "PvEValueA4", insaneWins ) + if ( GetGameStatForMapAndModeInt( "perfectMatches", mapName, "fd", "4" ) ) + RuiSetFloat3( icon4Rui, "basicImageColor", perfectColor ) + else + RuiSetFloat3( icon4Rui, "basicImageColor", insaneWins > 0 ? <1, 1, 1> : <0.15, 0.15, 0.15> ) + } + else + { + array pveElems = GetElementsByClassname( file.menu, "PvEGroup" ) + foreach ( elem in pveElems ) + { + Hud_Hide( elem ) + } + } +} + + +var function setit( vector color ) +{ + var iconLegendRui = Hud_GetRui( Hud_GetChild( file.menu, "PvEIconLegend" ) ) + RuiSetImage( iconLegendRui, "basicImage", $"rui/menu/gametype_select/playlist_fd_normal" ) + RuiSetFloat3( iconLegendRui, "basicImageColor", color ) +} \ No newline at end of file diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_stats_utility.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_stats_utility.nut new file mode 100644 index 00000000..e1d0a8f9 --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_stats_utility.nut @@ -0,0 +1,560 @@ +untyped + + +global function SetPieChartData +global function SetStatsBarValues +global function SetStatsValueInfo +global function SetStatsLabelValue +global function GetPercent +//global function GetChallengeCompleteData +global function GetItemUnlockCountData +global function GetOverviewWeaponData +global function StatToTimeString +global function HoursToTimeString +global function StatToDistanceString +global function ComparePieChartEntryValues +global function SetStatBoxDisplay +global function SetMedalStatBoxDisplay + +void function SetPieChartData( var menu, string panelName, string titleString, PieChartData data ) +{ + Assert( data.entries.len() <= 8 ) + + // Get nested panel + var piePanel = GetElem( menu, panelName ) + + // Create background + var background = Hud_GetChild( piePanel, "BarBG" ) + Hud_SetBarProgress( background, 1.0 ) + Hud_SetColor( background, [190, 190, 190, 255] ) + + // Calculate total of all values combined + foreach ( entry in data.entries ) + data.sum += entry.numValue + + // Calculate bar fraction for each value + foreach ( entry in data.entries ) + { + if ( data.sum > 0 ) + entry.fracValue = entry.numValue / data.sum + else + entry.fracValue = 0.0 + } + + // Set slice sizes and text data + var titleLabel = Hud_GetChild( piePanel, "Title" ) + Hud_SetText( titleLabel, titleString ) + Hud_SetColor( titleLabel, data.labelColor ) + + var noDataLabel = Hud_GetChild( piePanel, "NoData" ) + + for ( int index = 0; index < 8; index++ ) + { + var barColorGuide = Hud_GetChild( piePanel, "BarColorGuide" + index ) + // Hud_SetColor( barColorGuide, entry.color ) + Hud_Hide( barColorGuide ) + + var barColorGuideFrame = Hud_GetChild( piePanel, "BarColorGuideFrame" + index ) + Hud_Hide( barColorGuideFrame ) + + var barName = Hud_GetChild( piePanel, "BarName" + index ) + Hud_SetColor( barName, data.labelColor ) + Hud_SetText( barName, "" ) + + var bar = Hud_GetChild( piePanel, "Bar" + index ) + //Hud_SetBarProgress( bar, combinedFrac ) + //Hud_SetColor( bar, entry.color ) + Hud_Hide( bar ) + } + + if ( data.entries.len() > 0 ) + { + Hud_Hide( noDataLabel ) + + float combinedFrac = 0.0 + int largestTextWidth = 0 + + foreach ( index, entry in data.entries ) + { + var barColorGuide = Hud_GetChild( piePanel, "BarColorGuide" + index ) + Hud_SetColor( barColorGuide, entry.color ) + Hud_Show( barColorGuide ) + + var barColorGuideFrame = Hud_GetChild( piePanel, "BarColorGuideFrame" + index ) + Hud_Show( barColorGuideFrame ) + + string percent = GetPercent( entry.fracValue, 1.0, 0, true ) + var barName = Hud_GetChild( piePanel, "BarName" + index ) + Hud_SetColor( barName, data.labelColor ) + + if ( data.timeBased ) + Hud_SetText( barName, PieChartHoursToTimeString( entry.numValue, entry.displayName, percent ) ) + else + Hud_SetText( barName, "#STATS_TEXT_AND_PERCENTAGE", entry.displayName, percent ) + + int currentTextWidth = Hud_GetTextWidth( barName ) + if ( currentTextWidth > largestTextWidth ) + largestTextWidth = currentTextWidth + + Hud_Show( barName ) + + combinedFrac += entry.fracValue + var bar = Hud_GetChild( piePanel, "Bar" + index ) + Hud_SetBarProgress( bar, combinedFrac ) + Hud_SetColor( bar, entry.color ) + Hud_Show( bar ) + } + + // Position the list + int xOffset = int( ( largestTextWidth / -2 ) + ContentScaledX( 18 ) ) + Hud_SetX( Hud_GetChild( piePanel, "BarName0" ), xOffset ) + } + else + { + Hud_Show( noDataLabel ) + for ( int index = 0; index < 8; index++ ) + { + var barColorGuide = Hud_GetChild( piePanel, "BarColorGuide" + index ) +// Hud_SetColor( barColorGuide, entry.color ) + Hud_Hide( barColorGuide ) + + var barColorGuideFrame = Hud_GetChild( piePanel, "BarColorGuideFrame" + index ) + Hud_Hide( barColorGuideFrame ) + + var barName = Hud_GetChild( piePanel, "BarName" + index ) + Hud_SetColor( barName, data.labelColor ) + Hud_SetText( barName, "" ) + + var bar = Hud_GetChild( piePanel, "Bar" + index ) + //Hud_SetBarProgress( bar, combinedFrac ) + //Hud_SetColor( bar, entry.color ) + Hud_Hide( bar ) + } + } +} + +function SetStatsBarValues( menu, panelName, titleString, startValue, endValue, currentValue ) +{ + Assert( endValue > startValue ) + Assert( currentValue >= startValue && currentValue <= endValue ) + + // Get nested panel + var panel = GetElem( menu, panelName ) + + // Update titel + var title = Hud_GetChild( panel, "Title" ) + Hud_SetText( title, titleString ) + + // Update progress text + var progressText = Hud_GetChild( panel, "ProgressText" ) + Hud_SetText( progressText, "#STATS_PROGRESS_BAR_VALUE", currentValue, endValue ) + + // Update bar progress + float frac = GraphCapped( currentValue, startValue, endValue, 0.0, 1.0 ) + + var barFill = Hud_GetChild( panel, "BarFill" ) + Hud_SetScaleX( barFill, frac ) + + var barFillShadow = Hud_GetChild( panel, "BarFillShadow" ) + Hud_SetScaleX( barFillShadow, frac ) +} + +void function SetStatsValueInfo( var menu, valueID, labelText, textString ) +{ + var elem = GetElem( menu, "Column0Label" + valueID ) + Assert( elem != null ) + Hud_SetText( elem, labelText ) + + elem = GetElem( menu, "Column0Value" + valueID ) + Assert( elem != null ) + SetStatsLabelValueOnLabel( elem, textString ) +} + +void function SetStatsLabelValue( var menu, labelName, textString ) +{ + var elem = GetElem( menu, labelName ) + Assert( elem != null) + SetStatsLabelValueOnLabel( elem, textString ) +} + +void function SetStatsLabelValueOnLabel( elem, textString ) +{ + if ( type( textString ) == "array" ) + { + if ( textString.len() == 6 ) + Hud_SetText( elem, string( textString[0] ), textString[1], textString[2], textString[3], textString[4], textString[5] ) + if ( textString.len() == 5 ) + Hud_SetText( elem, string( textString[0] ), textString[1], textString[2], textString[3], textString[4] ) + if ( textString.len() == 4 ) + Hud_SetText( elem, string( textString[0] ), textString[1], textString[2], textString[3] ) + if ( textString.len() == 3 ) + Hud_SetText( elem, string( textString[0] ), textString[1], textString[2] ) + if ( textString.len() == 2 ) + Hud_SetText( elem, string( textString[0] ), textString[1] ) + if ( textString.len() == 1 ) + Hud_SetText( elem, string( textString[0] ) ) + } + else + { + Hud_SetText( elem, string( textString ) ) + } +} + +string function GetPercent( float val, float total, float defaultPercent, bool doClamp = true ) +{ + float percent = defaultPercent + if ( total > 0 ) + { + percent = val / total + percent *= 100 + } + + if ( doClamp ) + percent = clamp( percent, 0, 100 ) + + string formattedPercent + if ( int( percent * 10 ) % 10 == 0 ) + formattedPercent = format( "%.0f", percent ) + else + formattedPercent = format( "%.1f", percent ) + + return formattedPercent +} + +//function GetChallengeCompleteData() +//{ +// local Table = {} +// Table.total <- 0 +// Table.complete <- 0 +// +// UI_GetAllChallengesProgress() +// var allChallenges = GetLocalChallengeTable() +// +// foreach( challengeRef, val in allChallenges ) +// { +// if ( IsDailyChallenge( challengeRef ) ) +// continue +// local tierCount = GetChallengeTierCount( challengeRef ) +// Table.total += tierCount +// for ( int i = 0; i < tierCount; i++ ) +// { +// if ( IsChallengeTierComplete( challengeRef, i ) ) +// Table.complete++ +// } +// } +// +// return Table +//} + +function GetItemUnlockCountData() +{ + entity player = GetUIPlayer() + if ( player == null ) + return {} + + local Table = {} + Table[ "weapons" ] <- {} + Table[ "weapons" ].total <- 0 + Table[ "weapons" ].unlocked <- 0 + Table[ "attachments" ] <- {} + Table[ "attachments" ].total <- 0 + Table[ "attachments" ].unlocked <- 0 + Table[ "mods" ] <- {} + Table[ "mods" ].total <- 0 + Table[ "mods" ].unlocked <- 0 + Table[ "abilities" ] <- {} + Table[ "abilities" ].total <- 0 + Table[ "abilities" ].unlocked <- 0 + Table[ "gear" ] <- {} + Table[ "gear" ].total <- 0 + Table[ "gear" ].unlocked <- 0 +/* + local tableMapping = {} + + tableMapping[ eItemTypes.PILOT_PRIMARY ] <- "weapons" + tableMapping[ eItemTypes.PILOT_SECONDARY ] <- "weapons" + tableMapping[ eItemTypes.PILOT_ORDNANCE ] <- "weapons" + tableMapping[ eItemTypes.TITAN_PRIMARY ] <- "weapons" + tableMapping[ eItemTypes.TITAN_ORDNANCE ] <- "weapons" + tableMapping[ eItemTypes.PILOT_PRIMARY_ATTACHMENT ] <- "attachments" + tableMapping[ eItemTypes.PILOT_PRIMARY_MOD ] <- "mods" + tableMapping[ eItemTypes.PILOT_SECONDARY_MOD ] <- "mods" + tableMapping[ eItemTypes.TITAN_PRIMARY_MOD ] <- "mods" + tableMapping[ eItemTypes.PILOT_SPECIAL_MOD ] <- "mods" + tableMapping[ eItemTypes.TITAN_SPECIAL_MOD ] <- "mods" + tableMapping[ eItemTypes.PILOT_SPECIAL ] <- "abilities" + tableMapping[ eItemTypes.TITAN_SPECIAL ] <- "abilities" + + local itemRefs = GetAllItemRefs() + foreach ( data in itemRefs ) + { + if ( !( data.Type in tableMapping ) ) + continue + Table[ tableMapping[ data.Type ] ].total++ + + if ( !IsItemLocked( player, expect string( data.childRef ), expect string( data.ref ) ) ) + Table[ tableMapping[ data.Type ] ].unlocked++ + } +*/ + return Table +} + +table function GetOverviewWeaponData() +{ + table Table = {} + Table[ "most_kills" ] <- {} + Table[ "most_kills" ].ref <- "" + Table[ "most_kills" ].printName <- "" + Table[ "most_kills" ].val <- 0 + Table[ "most_used" ] <- {} + Table[ "most_used" ].ref <- "" + Table[ "most_used" ].printName <- "" + Table[ "most_used" ].val <- 0 + Table[ "highest_kpm" ] <- {} + Table[ "highest_kpm" ].ref <- "" + Table[ "highest_kpm" ].printName <- "" + Table[ "highest_kpm" ].val <- 0 + + entity player = GetUIPlayer() + if ( player == null ) + return Table + + array allWeapons = [] + + allWeapons.extend( GetVisibleItemsOfType( eItemTypes.PILOT_PRIMARY ) ) + allWeapons.extend( GetVisibleItemsOfType( eItemTypes.PILOT_SECONDARY ) ) + //allWeapons.extend( GetVisibleItemsOfType( eItemTypes.PILOT_ORDNANCE ) ) // art looks bad + allWeapons.extend( GetVisibleItemsOfType( eItemTypes.TITAN_PRIMARY ) ) + allWeapons.extend( GetVisibleItemsOfType( eItemTypes.TITAN_ORDNANCE ) ) + allWeapons.extend( GetVisibleItemsOfType( eItemTypes.TITAN_ANTIRODEO ) ) + allWeapons.extend( GetVisibleItemsOfType( eItemTypes.TITAN_SPECIAL ) ) + //allWeapons.extend( GetVisibleItemsOfType( eItemTypes.BURN_METER_REWARD ) ) // script errors + + foreach ( weapon in allWeapons ) + { + string weaponName = weapon.ref + string weaponDisplayName = expect string( GetWeaponInfoFileKeyField_Global( weaponName, "printname" ) ) + + if ( !PersistenceEnumValueIsValid( "loadoutWeaponsAndAbilities", weaponName ) ) + continue + + int val = GetPlayerStatInt( player, "weapon_kill_stats", "total", weaponName ) + if ( val > Table[ "most_kills" ].val ) + { + Table[ "most_kills" ].ref = weaponName + Table[ "most_kills" ].printName = weaponDisplayName + Table[ "most_kills" ].val = val + } + + float fVal = GetPlayerStatFloat( player, "weapon_stats", "hoursUsed", weaponName ) + if ( fVal > Table[ "most_used" ].val ) + { + Table[ "most_used" ].ref = weaponName + Table[ "most_used" ].printName = weaponDisplayName + Table[ "most_used" ].val = fVal + } + + local killsPerMinute = 0 + local hoursEquipped = GetPlayerStatFloat( player, "weapon_stats", "hoursEquipped", weaponName ) + local killCount = GetPlayerStatInt( player, "weapon_kill_stats", "total", weaponName ) + if ( hoursEquipped > 0 ) + killsPerMinute = format( "%.2f", ( killCount / ( hoursEquipped * 60.0 ) ).tofloat() ) + if ( killsPerMinute.tofloat() > Table[ "highest_kpm" ].val.tofloat() ) + { + Table[ "highest_kpm" ].ref = weaponName + Table[ "highest_kpm" ].printName = weaponDisplayName + Table[ "highest_kpm" ].val = killsPerMinute + } + } + + return Table +} + +string function StatToTimeString( string category, string alias, string weapon = "" ) +{ + entity player = GetUIPlayer() + if ( player == null ) + return "0" + + string statString = GetStatVar( category, alias, weapon ) + float savedHours = expect float( player.GetPersistentVar( statString ) ) + + return HoursToTimeString( savedHours ) +} + +string function HoursToTimeString( float savedHours ) +{ + string timeString + local minutes = floor( savedHours * 60.0 ) + + if ( minutes < 0 ) + minutes = 0 + + int days = 0 + int hours = 0 + + // For archiving code, i would like to keep this code here + // It is a testament to Respawn's hubris and determination to writing the absolutely worst fucking code ever + // These motherfuckers managed to run an O(1) operation in O(n) time. Genuinely impressive. + // while ( minutes >= 1440 ) + // { + // minutes -= 1440 + // days++ + // } + // + // while ( minutes >= 60 ) + // { + // minutes -= 60 + // hours++ + // } + + days = int(floor(minutes / 1440)) + minutes = minutes % 1440 + + hours = int(floor(minutes / 60)) + minutes = minutes % 60 + + if ( days > 0 && hours > 0 && minutes > 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D_H_M", days, hours, minutes ) + } + else if ( days > 0 && hours == 0 && minutes == 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D", days ) + } + else if ( days == 0 && hours > 0 && minutes == 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_H", hours ) + } + else if ( days == 0 && hours == 0 && minutes >= 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_M", minutes ) + } + else if ( days > 0 && hours > 0 && minutes == 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D_H", days, hours ) + } + else if ( days == 0 && hours > 0 && minutes > 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_H_M", hours, minutes ) + } + else if ( days > 0 && hours == 0 && minutes > 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D_M", days, minutes ) + } + else + { + Assert( 0, "Unhandled time string creation case" ) + } + + return timeString +} + +string function PieChartHoursToTimeString( float savedHours, string pieChartHeader, string pieChartPercent ) +{ + string timeString + local minutes = floor( savedHours * 60.0 ) + + if ( minutes < 0 ) + minutes = 0 + + int days = 0 + int hours = 0 + + while ( minutes >= 1440 ) + { + minutes -= 1440 + days++ + } + + while ( minutes >= 60 ) + { + minutes -= 60 + hours++ + } + + if ( days > 0 && hours > 0 && minutes > 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D_H_M_PIECHART", days, hours, minutes, Localize( pieChartHeader ), pieChartPercent ) + } + else if ( days > 0 && hours == 0 && minutes == 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D_PIECHART", days, Localize( pieChartHeader ), pieChartPercent ) + } + else if ( days == 0 && hours > 0 && minutes == 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_H_PIECHART", hours, Localize( pieChartHeader ), pieChartPercent ) + } + else if ( days == 0 && hours == 0 && minutes >= 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_M_PIECHART", minutes, Localize( pieChartHeader ), pieChartPercent ) + } + else if ( days > 0 && hours > 0 && minutes == 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D_H_PIECHART", days, hours, Localize( pieChartHeader ), pieChartPercent ) + } + else if ( days == 0 && hours > 0 && minutes > 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_H_M_PIECHART", hours, minutes, Localize( pieChartHeader ), pieChartPercent ) + } + else if ( days > 0 && hours == 0 && minutes > 0 ) + { + timeString = Localize( "#STATS_TIME_STRING_D_M_PIECHART", days, minutes, Localize( pieChartHeader ), pieChartPercent ) + } + else + { + Assert( 0, "Unhandled time string creation case" ) + } + + return timeString +} + +string function StatToDistanceString( string category, string alias, string weapon = "" ) +{ + entity player = GetUIPlayer() + if ( player == null ) + return "" + + string statString = GetStatVar( category, alias, weapon ) + float kilometers = expect float( player.GetPersistentVar( statString ) ) + + string formattedNum + if ( kilometers % 1 == 0 ) + formattedNum = format( "%.0f", kilometers ) + else + formattedNum = format( "%.2f", kilometers ) + + string distString = Localize( "#STATS_KILOMETERS_ABBREVIATION", formattedNum ) + + return distString +} + +int function ComparePieChartEntryValues( PieChartEntry a, PieChartEntry b ) +{ + float aVal = a.numValue + float bVal = b.numValue + + if ( aVal < bVal ) + return 1 + else if ( aVal > bVal ) + return -1 + + return 0 +} + +void function SetStatBoxDisplay( var vguiElem, string text, string value ) +{ + var rui = Hud_GetRui( vguiElem ) + + RuiSetString( rui, "statText", text ) + RuiSetString( rui, "statValue", value ) +} + +void function SetMedalStatBoxDisplay( var vguiElem, string text, asset image, int value ) +{ + var rui = Hud_GetRui( vguiElem ) + + RuiSetString( rui, "statText", text ) + RuiSetString( rui, "statValue", string( value ) ) + RuiSetImage( rui, "statImage", image ) +} \ No newline at end of file -- cgit v1.2.3