aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmma Miler <27428383+emma-miler@users.noreply.github.com>2022-04-19 23:27:54 +0200
committerGitHub <noreply@github.com>2022-04-19 22:27:54 +0100
commit31cba09fea290cfddde6cc77af836b1cd4756bdd (patch)
treecf58dd85bb8acbdcb002ca89bac5975b2b106b47
parent2da71a5730b65d13049fbbd2635d1fac189f6662 (diff)
downloadNorthstarMods-31cba09fea290cfddde6cc77af836b1cd4756bdd.tar.gz
NorthstarMods-31cba09fea290cfddde6cc77af836b1cd4756bdd.zip
Fix a freeze in stats menu (#307)
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_stats_maps.nut322
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_stats_utility.nut560
2 files changed, 882 insertions, 0 deletions
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<string> 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<string> 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<string> 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<string> gameModesArray = GetPersistenceEnumAsArray( "gameModes" )
+
+ array<PieChartEntry> 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<string> fdMaps = GetPlaylistMaps( "fd" )
+
+ if ( fdMaps.contains( mapName ) )
+ {
+ array<var> 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<var> 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<string, table> function GetOverviewWeaponData()
+{
+ table<string, 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<ItemDisplayData> 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