1 Commits
Author SHA1 Message Date
okvdai 586fa32b69 v1.2.1 (#19)
* Update README.md

* Add global K/D calculation

* Display current and latest version in terminal #18
2023-05-03 18:25:19 +02:00
24 changed files with 423 additions and 1869 deletions
+4 -23
View File
@@ -1,6 +1,6 @@
# pulse # Pulse
**pulse** is a clientside mod for [TF|2 + Northstar](https://github.com/R2Northstar/Northstar) letting you view killstats collected by [the Tone API](https://toneapi.github.io/ToneAPI_webclient/) using the otherwise non-functional `Stats` tab. Pulse is a clientside mod for [TF|2 + Northstar](https://github.com/R2Northstar/Northstar) letting you view killstats collected by [the Tone API](https://toneapi.github.io/ToneAPI_webclient/) using the otherwise non-functional `Stats` tab.
## Currently, features include: ## Currently, features include:
- mostly functional `Overview` tab, - mostly functional `Overview` tab,
@@ -21,7 +21,7 @@ Found a bug? Make an issue on GitHub [here.](https://github.com/ToneAPI/pulse/is
- Most Kills - shows the weapon with most accumulated kills. - Most Kills - shows the weapon with most accumulated kills.
- Nemesis Weapon - shows the weapon that has killed you the most. - Nemesis Weapon - shows the weapon that has killed you the most.
- Most Effective - shows weapon with the highest K/D ratio. - Most Effective - shows weapon with the highest K/D ratio.
- Kill/Death Ratio - shows the overall K/D ratio of player compared to global K/D. - Kill/Death Ratio - shows the overall K/D ratio of player. (the two line graphs "Lifetime Avg." and "Lifetime Avg. vs. Players" don't work.)
- Kills as Pilot - shows general kill data for player as Pilot, visualised in the table below: - Kills as Pilot - shows general kill data for player as Pilot, visualised in the table below:
| Name | Description | | Name | Description |
@@ -76,24 +76,5 @@ Found a bug? Make an issue on GitHub [here.](https://github.com/ToneAPI/pulse/is
## DISCLAIMER ## DISCLAIMER
The Tone API is NOT used by every server, view a list of supported servers [here.](https://tone.sleepycat.date/v2/client/servers) The Tone API is NOT used by every server, view a list of supported servers [here.](https://tone.sleepycat.date/v2/client/servers)
**pulse** is very much a W.I.P as of right now, features may be missing/nonfunctional.
## CHANGELOG Pulse is very much a W.I.P as of right now, features may be missing/nonfunctional.
<details>
<summary> pulse v2.0.0 "Demeter" </summary>
- Switched to Thunderstore template on the GitHub site for easier development.
- General code rewrite - lots of improvements for easier development. Rewrite includes:
- common code file, for better code readability and ease of development,
- new parser, allowing for better expandability and faster data processing (and much simpler implementation, unlike the hellspawn that was the previous `getFromToneAPI` function),
- new request function, more compact and simple than previous iterations,
- other functions that simplify the code and make it easier to read / develop
- Localisation, currently available in:
- English,
- French,
- German,
- Italian,
- Polish. (Northstar isn't translated into Polish, but the game supports it.)
- Locking the Stats tab, avoiding situations where you could click too fast and see no stats. Also prevents the game from showing stats when the API is unreachable.
</details>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

-7
View File
@@ -1,7 +0,0 @@
{
"name": "Pulse",
"description": "Adds stats (collected by the Tone API) back to the Stats tab.",
"version_number": "2.0.0",
"website_url": "https://github.com/ToneAPI/pulse",
"dependencies": []
}
+18 -13
View File
@@ -1,7 +1,7 @@
{ {
"Name": "ToneAPI.pulse", "Name": "ToneAPI.pulse",
"Description": "Displays Tone API kill data in the Stats tab.", "Description": "Displays Tone API kill data in the Stats tab.",
"Version": "2.0.0", "Version": "1.2.1",
"LoadPriority": 1, "LoadPriority": 1,
"ConVars": [ "ConVars": [
{ {
@@ -11,22 +11,27 @@
{ {
"Name": "VersionURL", "Name": "VersionURL",
"DefaultValue": "https://raw.githubusercontent.com/ToneAPI/pulse/main/version.json" "DefaultValue": "https://raw.githubusercontent.com/ToneAPI/pulse/main/version.json"
},
{
"Name": "MajorVersion",
"DefaultValue": "1"
},
{
"Name": "MinorVersion",
"DefaultValue": "2"
},
{
"Name": "PatchVersion",
"DefaultValue": "1"
} }
], ],
"Scripts": [ "Scripts": [
{ {
"Path":"pulse-common-func.gnut", "Path": "pulse.gnut",
"RunOn":"UI" "RunOn": "UI",
}, "UICallback": {
{ "After": "pulseInit"
"Path": "pulse.gnut", }
"RunOn": "UI",
"UICallback": {
"After": "pulseInit"
}
} }
],
"Localisation": [
"resource/pulse_%language%.txt"
] ]
} }
@@ -323,7 +323,7 @@ resource/ui/menus/viewstats_overview.menu
tall 45 tall 45
visible 1 visible 1
font Default_31 font Default_31
labelText "#NEMESIS_WEAPON" labelText "NEMESIS WEAPON"
allcaps 1 allcaps 1
wrap 1 wrap 1
textAlignment west textAlignment west
@@ -345,7 +345,7 @@ resource/ui/menus/viewstats_weapons.menu
tall 36 tall 36
visible 1 visible 1
font Default_23 font Default_23
labelText "#DEATHS_WHILE_EQUIPPED" labelText "DEATHS WITH WEAPON"
allcaps 1 allcaps 1
textAlignment west textAlignment west
fgcolor_override "255 255 255 255" fgcolor_override "255 255 255 255"
+267
View File
@@ -0,0 +1,267 @@
global function pulseInit
global function GetWeaponStatsFromToneAPI
global function GetGlobalDataFromToneAPI
global function GetMapStatsFromToneAPI
global function GetGamemodeStatsFromToneAPI
global function getWeaponKillsFromToneAPI
global function getNemesisWeaponFromToneAPI
global function getDWEFromToneAPI
global function getMapKillFromToneAPI
global function getMapDeathFromToneAPI
global function getMapDistFromToneAPI
global function getMapMDistFromToneAPI
global function fetchGamemodeStatsFromToneAPI
global table globalToneAPIKillData = {}
global table globalToneAPIDeathData = {}
global table globalToneAPIDWEData = {}
global table globalToneAPIMapDeathData = {}
global table globalToneAPIMapDistData = {}
global table globalToneAPIMapKillData = {}
global table globalToneAPIMapMDistData = {}
global table globalToneAPIGamemodeData = {}
global table globalToneAPIAllPlayerKillData = {}
global table globalToneAPIAllPlayerDeathData = {}
global table< table > globalToneAPIGamemodeMapData = {}
string prefix = "default"
string HTTPrequestURL = "default"
string currentVersion = "default"
int weaponEntries = 0
int mapEntries = 0
int gamemodeEntries = 0
int mapGamemodeEntries = 0
void function pulseInit()
{
prefix = "\x1b[38;2;255;178;102m[PULSE]\x1b[0m "
HTTPrequestURL = GetConVarString("ToneURL")
currentVersion = "v" + GetConVarString("MajorVersion") + "." + GetConVarString("MinorVersion") + "." + GetConVarString("PatchVersion")
HttpRequest getVersion
getVersion.method = HttpRequestMethod.GET
getVersion.url = GetConVarString("VersionURL")
void functionref(HttpRequestResponse) onSuccess = void function(HttpRequestResponse response)
{
table JSONDecoded = DecodeJSON(response.body)
if (JSONDecoded["Major"] == GetConVarString("MajorVersion") && JSONDecoded["Minor"] == GetConVarString("MinorVersion") && JSONDecoded["Patch"] == GetConVarString("PatchVersion")) {
print(prefix + currentVersion + " has been loaded and is up to date.")
} else {
print(prefix + currentVersion + " has been loaded. Recommend updating to latest version: v" + JSONDecoded["Major"] + "." + JSONDecoded["Minor"] + "." + JSONDecoded["Patch"] + ".")
}
}
void functionref(HttpRequestFailure) onFailure = void function(HttpRequestFailure failure)
{
print(prefix + currentVersion + " has been loaded and has encountered an error getting latest version data.")
}
NSHttpRequest(getVersion, onSuccess, onFailure)
}
void function GetWeaponStatsFromToneAPI()
{
print(prefix + "Getting kill data...")
weaponEntries = 0
HttpRequest getWeaponStats
getWeaponStats.method = HttpRequestMethod.GET
getWeaponStats.url = HTTPrequestURL + "/weapons"
getWeaponStats.queryParameters["player"] <- [NSGetLocalPlayerUID()]
void functionref(HttpRequestResponse) onSuccess = void function(HttpRequestResponse response)
{
print(prefix + "Kill data received, processing...")
table JSONForConversion = DecodeJSON(response.body)
foreach (var key, var value in JSONForConversion) {
table test = expect table(value)
globalToneAPIKillData[string(key)] <- test.kills
globalToneAPIDeathData[string(key)] <- test.deaths
globalToneAPIDWEData[string(key)] <- test.deaths_while_equipped
weaponEntries += 1
}
print(prefix + "Kill data has been processed with a total of " + string(weaponEntries) + " entries.")
}
void functionref(HttpRequestFailure) onFailure = void function(HttpRequestFailure failure)
{
print(prefix + "Encountered an error getting kill data...")
}
NSHttpRequest(getWeaponStats, onSuccess, onFailure)
}
void function GetGlobalDataFromToneAPI()
{
print(prefix + "Getting global data...")
weaponEntries = 0
HttpRequest getGlobalStats
getGlobalStats.method = HttpRequestMethod.GET
getGlobalStats.url = HTTPrequestURL + "/weapons"
getGlobalStats.queryParameters["player"] <- ["!" + NSGetLocalPlayerUID()]
void functionref(HttpRequestResponse) onSuccess = void function(HttpRequestResponse response)
{
print(prefix + "Global data received, processing...")
table JSONForConversion = DecodeJSON(response.body)
foreach (var key, var value in JSONForConversion) {
table test = expect table(value)
globalToneAPIAllPlayerKillData[string(key)] <- test.kills
globalToneAPIAllPlayerDeathData[string(key)] <- test.deaths
weaponEntries += 1
}
print(prefix + "Global data has been processed with a total of " + string(weaponEntries) + " entries.")
}
void functionref(HttpRequestFailure) onFailure = void function(HttpRequestFailure failure)
{
print(prefix + "Encountered an error getting global data...")
}
NSHttpRequest(getGlobalStats, onSuccess, onFailure)
}
void function GetMapStatsFromToneAPI()
{
print(prefix + "Getting map data...")
mapEntries = 0
HttpRequest getMapStats
getMapStats.method = HttpRequestMethod.GET
getMapStats.url = HTTPrequestURL + "/maps"
getMapStats.queryParameters["player"] <- [NSGetLocalPlayerUID()]
void functionref(HttpRequestResponse) onSuccess = void function(HttpRequestResponse response)
{
print(prefix + "Map data received, processing...")
table JSONForConversion = DecodeJSON(response.body)
foreach (var key, var value in JSONForConversion) {
table test = expect table(value)
globalToneAPIMapKillData[string(key)] <- test.kills
globalToneAPIMapDeathData[string(key)] <- test.deaths
globalToneAPIMapDistData[string(key)] <- test.total_distance
globalToneAPIMapMDistData[string(key)] <- test.max_distance
mapEntries += 1
}
print(prefix + "Map data has been processed with a total of " + string(mapEntries) + " entries.")
}
void functionref(HttpRequestFailure) onFailure = void function(HttpRequestFailure failure)
{
print(prefix + "Encountered an error getting map data...")
}
NSHttpRequest(getMapStats, onSuccess, onFailure)
}
void function GetGamemodeStatsFromToneAPI()
{
print(prefix + "Getting gamemode data...")
gamemodeEntries = 0
HttpRequest getGamemodeStats
getGamemodeStats.method = HttpRequestMethod.GET
getGamemodeStats.url = HTTPrequestURL + "/gamemodes"
getGamemodeStats.queryParameters["player"] <- [NSGetLocalPlayerUID()]
void functionref(HttpRequestResponse) onSuccess = void function(HttpRequestResponse response)
{
print(prefix + "Gamemode data received, processing...")
table JSONForConversion = DecodeJSON(response.body)
foreach (var key, var value in JSONForConversion) {
table test = expect table(value)
globalToneAPIGamemodeData[string(key)] <- test.kills
gamemodeEntries += 1
}
print(prefix + "Gamemode data has been processed with a total of " + string(gamemodeEntries) + " entries.")
}
void functionref(HttpRequestFailure) onFailure = void function(HttpRequestFailure failure)
{
print(prefix + "Encountered an error getting gamemode data...")
}
NSHttpRequest(getGamemodeStats, onSuccess, onFailure)
}
void function fetchGamemodeStatsFromToneAPI(string mapName)
{
mapGamemodeEntries = 0
string mapNameSliced = mapName.slice(3)
HttpRequest getMapGamemodeStats
getMapGamemodeStats.method = HttpRequestMethod.GET
getMapGamemodeStats.url = HTTPrequestURL + "/gamemodes"
getMapGamemodeStats.queryParameters["map"] <- [mapNameSliced]
getMapGamemodeStats.queryParameters["player"] <- [NSGetLocalPlayerUID()]
void functionref(HttpRequestResponse) onSuccess = void function(HttpRequestResponse response):(mapName)
{
table JSONForConversion = DecodeJSON(response.body)
table mapData = {}
foreach (var gamemode, var value in JSONForConversion) {
table test = expect table(value)
mapData[string(gamemode)] <- test.kills
mapGamemodeEntries += 1
}
globalToneAPIGamemodeMapData[mapName] <- mapData
print(prefix + "Map gamemode data has been processed with a total of " + string(mapGamemodeEntries) + " entries.")
}
void functionref(HttpRequestFailure) onFailure = void function(HttpRequestFailure failure)
{
print(prefix + "Encountered an error getting map gamemode data...")
}
NSHttpRequest(getMapGamemodeStats, onSuccess, onFailure)
}
int function getWeaponKillsFromToneAPI(string weaponRef){
if(weaponRef in globalToneAPIKillData){
return expect int(globalToneAPIKillData[weaponRef])
}
return 0
}
int function getNemesisWeaponFromToneAPI(string weaponRef){
if(weaponRef in globalToneAPIDeathData){
return expect int(globalToneAPIDeathData[weaponRef])
}
return 0
}
int function getDWEFromToneAPI(string weaponRef){
if(weaponRef in globalToneAPIDWEData){
return expect int(globalToneAPIDWEData[weaponRef])
}
return 0
}
int function getMapKillFromToneAPI(string mapName){
mapName = mapName.slice(3)
if(mapName in globalToneAPIMapKillData){
return expect int(globalToneAPIMapKillData[mapName])
}
return 0
}
int function getMapDeathFromToneAPI(string mapName){
mapName = mapName.slice(3)
if(mapName in globalToneAPIMapDeathData){
return expect int(globalToneAPIMapDeathData[mapName])
}
return 0
}
int function getMapMDistFromToneAPI(string mapName){
mapName = mapName.slice(3)
if(mapName in globalToneAPIMapMDistData){
return expect int(globalToneAPIMapMDistData[mapName])
}
return 0
}
int function getMapDistFromToneAPI(string mapName){
mapName = mapName.slice(3)
if(mapName in globalToneAPIMapDistData){
return expect int(globalToneAPIMapDistData[mapName])
}
return 0
}
+56
View File
@@ -0,0 +1,56 @@
global function InitViewStatsMenu
struct{
array<string> allMaps
} file
void function InitViewStatsMenu()
{
var menu = GetMenu( "ViewStatsMenu" )
AddMenuEventHandler( menu, eUIEvent.MENU_OPEN, OnViewStats_Open )
var button = Hud_GetChild( menu, "BtnOverview" )
SetButtonRuiText( button, Localize( "#STATS_OVERVIEW" ) )
AddButtonEventHandler( button, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ViewStats_Overview_Menu" ) ) )
button = Hud_GetChild( menu, "BtnTime" )
SetButtonRuiText( button, Localize( "#STATS_TIME" ) )
AddButtonEventHandler( button, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ViewStats_Time_Menu" ) ) )
button = Hud_GetChild( menu, "BtnPilotWeapons" )
SetButtonRuiText( button, Localize( "#STATS_PILOT_WEAPONS" ) )
AddButtonEventHandler( button, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ViewStats_Weapons_Menu" ) ) )
button = Hud_GetChild( menu, "BtnTitans" )
SetButtonRuiText( button, Localize( "#STATS_TITANS" ) )
AddButtonEventHandler( button, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ViewStats_Titans_Menu" ) ) )
button = Hud_GetChild( menu, "BtnMaps" )
SetButtonRuiText( button, Localize( "#STATS_MAPS" ) )
AddButtonEventHandler( button, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ViewStats_Maps_Menu" ) ) )
button = Hud_GetChild( menu, "BtnMisc" )
SetButtonRuiText( button, Localize( "#STATS_MISC" ) )
AddButtonEventHandler( button, UIE_CLICK, AdvanceMenuEventHandler( GetMenu( "ViewStats_Misc_Menu" ) ) )
AddMenuFooterOption( menu, BUTTON_A, "#A_BUTTON_SELECT" )
AddMenuFooterOption( menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
}
void function OnViewStats_Open()
{
foreach(elemNum in file.allMaps){
string mapName = expect string(file.allMaps[ elemNum ])
fetchGamemodeStatsFromToneAPI(mapName)
}
GetWeaponStatsFromToneAPI()
GetGlobalDataFromToneAPI()
GetMapStatsFromToneAPI()
GetGamemodeStatsFromToneAPI()
UI_SetPresentationType( ePresentationType.DEFAULT )
//UpdateViewStatsKillsMenu()
//UpdateViewStatsDistanceMenu()
}
@@ -140,6 +140,8 @@ void function UpdateStatsForMap( string mapName )
Hud_SetText( Hud_GetChild( file.menu, "WeaponName" ), GetMapDisplayName( mapName ) ) Hud_SetText( Hud_GetChild( file.menu, "WeaponName" ), GetMapDisplayName( mapName ) )
fetchGamemodeStatsFromToneAPI(mapName)
// Image // Image
var imageElem = Hud_GetRui( Hud_GetChild( file.menu, "WeaponImageLarge" ) ) var imageElem = Hud_GetRui( Hud_GetChild( file.menu, "WeaponImageLarge" ) )
RuiSetImage( imageElem, "basicImage", GetMapImageForMapName( mapName ) ) RuiSetImage( imageElem, "basicImage", GetMapImageForMapName( mapName ) )
@@ -152,17 +154,17 @@ void function UpdateStatsForMap( string mapName )
//SetStatBoxDisplay( Hud_GetChild( file.menu, "Stat2" ), 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 ) //SetStatBoxDisplay( Hud_GetChild( file.menu, "Stat3" ), Localize( "#STATS_GAMES_PLAYED" ), gamesPlayed )
SetStatsLabelValue( file.menu, "KillsLabel0", Localize("#MAPS_KILLS") ) SetStatsLabelValue( file.menu, "KillsLabel0", "KILLS ON MAP" )
SetStatsLabelValue( file.menu, "KillsValue0", pulseParse("maps", mapName.slice(3), "kills") ) SetStatsLabelValue( file.menu, "KillsValue0", getMapKillFromToneAPI(mapName) )
SetStatsLabelValue( file.menu, "KillsLabel1", Localize("#MAPS_DEATHS") ) SetStatsLabelValue( file.menu, "KillsLabel1", "DEATHS ON MAP" )
SetStatsLabelValue( file.menu, "KillsValue1", pulseParse("maps", mapName.slice(3), "deaths") ) SetStatsLabelValue( file.menu, "KillsValue1", getMapDeathFromToneAPI(mapName) )
SetStatsLabelValue( file.menu, "KillsLabel2", Localize("#MAPS_TSD") ) SetStatsLabelValue( file.menu, "KillsLabel2", "TOTAL SHOT DISTANCE" )
SetStatsLabelValue( file.menu, "KillsValue2", string(int((1.905 * float(pulseParse("maps", mapName.slice(3), "total_distance") ) ) )/100) + "m" ) SetStatsLabelValue( file.menu, "KillsValue2", string(int((1.905 * float(getMapDistFromToneAPI(mapName) ) ) )/100) + "m" )
SetStatsLabelValue( file.menu, "KillsLabel3", Localize("#MAPS_MSD") ) SetStatsLabelValue( file.menu, "KillsLabel3", "MAXIMUM SHOT DISTANCE" )
SetStatsLabelValue( file.menu, "KillsValue3", string(int((1.905 * float(pulseParse("maps", mapName.slice(3), "max_distance") ) ) )/100) + "m" ) SetStatsLabelValue( file.menu, "KillsValue3", string(int((1.905 * float(getMapMDistFromToneAPI(mapName) ) ) )/100) + "m" )
SetStatsLabelValue( file.menu, "KillsLabel4", "--" ) SetStatsLabelValue( file.menu, "KillsLabel4", "--" )
SetStatsLabelValue( file.menu, "KillsValue4", "--" ) SetStatsLabelValue( file.menu, "KillsValue4", "--" )
@@ -183,18 +185,13 @@ void function UpdateStatsForMap( string mapName )
fw = [147, 204, 57, 255], fw = [147, 204, 57, 255],
gg = [14, 87, 132, 255] gg = [14, 87, 132, 255]
} }
table< string, string > customGamemodeNames = {
sns = Localize("#GAMEMODE_sns")
fw = Localize("#GAMEMODE_fw")
gg = Localize("#GAMEMODE_gg")
}
for ( int modeId = 0; modeId < enumCount; modeId++ ) for ( int modeId = 0; modeId < enumCount; modeId++ )
{ {
string modeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeId ) string modeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeId )
if (mapName.slice(3) in pulseData["gamemodesSeparated"]) if ( mapName in globalToneAPIGamemodeMapData && modeName in globalToneAPIGamemodeMapData[mapName] )
{ {
float modePlayedTime = 0 float modePlayedTime = 0
modePlayedTime = float(pulseParse("gamemodesSeparated", mapName.slice(3), modeName, "kills")) modePlayedTime = float(globalToneAPIGamemodeMapData[mapName][modeName])
if ( modePlayedTime > 0 ) { if ( modePlayedTime > 0 ) {
AddPieChartEntry( modes, GameMode_GetName( modeName ), modePlayedTime, GetGameModeDisplayColor( modeName ) ) AddPieChartEntry( modes, GameMode_GetName( modeName ), modePlayedTime, GetGameModeDisplayColor( modeName ) )
} }
@@ -202,12 +199,12 @@ void function UpdateStatsForMap( string mapName )
} }
foreach (string key, array<int> value in customGamemodeList) foreach (string key, array<int> value in customGamemodeList)
{ {
if (mapName.slice(3) in pulseData["gamemodesSeparated"]) if ( mapName in globalToneAPIGamemodeMapData && key in globalToneAPIGamemodeMapData[mapName])
{ {
float modePlayedTime = 0 float modePlayedTime = 0
modePlayedTime = float(pulseParse("gamemodesSeparated", mapName.slice(3), key, "kills")) modePlayedTime = float(globalToneAPIGamemodeMapData[mapName][key])
if ( modePlayedTime > 0 ) { if ( modePlayedTime > 0 ) {
AddPieChartEntry( modes, customGamemodeNames[key], modePlayedTime, value) AddPieChartEntry( modes, key, modePlayedTime, value)
} }
} }
} }
@@ -231,7 +228,7 @@ void function UpdateStatsForMap( string mapName )
PieChartData modesPlayedData PieChartData modesPlayedData
modesPlayedData.entries = modes modesPlayedData.entries = modes
modesPlayedData.labelColor = [ 255, 255, 255, 255 ] modesPlayedData.labelColor = [ 255, 255, 255, 255 ]
SetPieChartData( file.menu, "ModesPieChart", "#KILLS_GAMEMODE", modesPlayedData ) SetPieChartData( file.menu, "ModesPieChart", "KILLS BY GAMEMODE", modesPlayedData )
array<string> fdMaps = GetPlaylistMaps( "fd" ) array<string> fdMaps = GetPlaylistMaps( "fd" )
@@ -6,13 +6,11 @@ struct
{ {
var menu var menu
array<ItemDisplayData> allTitans array<ItemDisplayData> allTitans
array<ItemDisplayData> allPilotWeapons
table<string, array<string> > titanStatLoadout table<string, array<string> > titanStatLoadout
} file } file
const MAX_DOTS_ON_GRAPH = 10 const MAX_DOTS_ON_GRAPH = 10
table <string, int> titanKillData = {} table <string, int> titanKillData = {}
table <string, int> pilotKillData = {}
const IMAGE_TITAN_STRYDER = $"ui/menu/personal_stats/ps_titan_icon_stryder" const IMAGE_TITAN_STRYDER = $"ui/menu/personal_stats/ps_titan_icon_stryder"
const IMAGE_TITAN_ATLAS = $"ui/menu/personal_stats/ps_titan_icon_atlas" const IMAGE_TITAN_ATLAS = $"ui/menu/personal_stats/ps_titan_icon_atlas"
@@ -36,7 +34,7 @@ void function GetTitanKills( string titanName )
{ {
file.allTitans = GetVisibleItemsOfType( eItemTypes.TITAN ) file.allTitans = GetVisibleItemsOfType( eItemTypes.TITAN )
var dataTable = GetDataTable( $"datatable/titan_properties.rpak" ) var dataTable = GetDataTable( $"datatable/titan_properties.rpak" )
foreach ( titan in file.allTitans ) foreach ( titan in file.allTitans )
{ {
file.titanStatLoadout[ titan.ref ] <- [] file.titanStatLoadout[ titan.ref ] <- []
@@ -52,19 +50,10 @@ void function GetTitanKills( string titanName )
foreach ( weaponRef in file.titanStatLoadout[ titanName ]) foreach ( weaponRef in file.titanStatLoadout[ titanName ])
{ {
titanKillData[weaponRef] <- pulseParse("weaponsLocal", weaponRef, "kills") titanKillData[weaponRef] <- getWeaponKillsFromToneAPI(weaponRef)
} }
} }
void function GetAllPilotWeapons()
{
file.allPilotWeapons = GetVisibleItemsOfType( eItemTypes.PILOT_PRIMARY)
file.allPilotWeapons.extend(GetVisibleItemsOfType( eItemTypes.PILOT_SECONDARY) )
file.allPilotWeapons.extend(GetVisibleItemsOfType( eItemTypes.PILOT_ORDNANCE) )
file.allPilotWeapons.extend( GetVisibleItemsOfType( eItemTypes.PILOT_SPECIAL ) )
}
void function OnStatsOverview_Open() void function OnStatsOverview_Open()
{ {
UI_SetPresentationType( ePresentationType.NO_MODELS ) UI_SetPresentationType( ePresentationType.NO_MODELS )
@@ -218,7 +207,7 @@ function UpdateViewStatsOverviewMenu()
Hud_Show( weaponImageElem ) Hud_Show( weaponImageElem )
Hud_SetText( weaponNameElem, nemesisWeapon.printName ) Hud_SetText( weaponNameElem, nemesisWeapon.printName )
Hud_Show( weaponNameElem ) Hud_Show( weaponNameElem )
Hud_SetText( weaponDescElem, Localize("#NEMESIS_WEAPON_VALUE"), nemesisWeapon.val) Hud_SetText( weaponDescElem, nemesisWeapon.val + " DEATHS BY WEAPON" )
Hud_Show( weaponDescElem ) Hud_Show( weaponDescElem )
Hud_Hide( noDataElem ) Hud_Hide( noDataElem )
} }
@@ -243,7 +232,7 @@ function UpdateViewStatsOverviewMenu()
Hud_Show( weaponImageElem ) Hud_Show( weaponImageElem )
Hud_SetText( weaponNameElem, highestKPMData.printName ) Hud_SetText( weaponNameElem, highestKPMData.printName )
Hud_Show( weaponNameElem ) Hud_Show( weaponNameElem )
Hud_SetText( weaponDescElem, "%s1 K/D", highestKPMData.val) Hud_SetText( weaponDescElem, highestKPMData.val + " K/D")
Hud_Show( weaponDescElem ) Hud_Show( weaponDescElem )
Hud_Hide( noDataElem ) Hud_Hide( noDataElem )
} }
@@ -299,45 +288,40 @@ function UpdateViewStatsOverviewMenu()
//######################### //#########################
// Lifetime // Lifetime
table playerweaponData = expect table(pulseData["weaponsLocal"])
table globalweaponData = expect table(pulseData["weaponsGlobal"])
var totalPilotKills = 0
foreach (var key, var value in playerweaponData) { var totalPilotKills = 0
table values = expect table(value)
totalPilotKills += values["kills"] foreach (var key, var value in globalToneAPIKillData) {
totalPilotKills += value
} }
var totalGlobalKills = 0 var totalGlobalKills = 0
foreach (var key, var value in globalweaponData) { foreach (var key, var value in globalToneAPIAllPlayerKillData) {
table values = expect table(value) totalGlobalKills += value
totalGlobalKills += values["kills"]
}
var totalPilotDeaths = 0
foreach (var key, var value in playerweaponData) {
table values = expect table(value)
totalPilotDeaths += values["deaths"]
} }
var totalGlobalDeaths = 0 var totalGlobalDeaths = 0
foreach (var key, var value in globalweaponData) { foreach (var key, var value in globalToneAPIAllPlayerDeathData) {
table values = expect table(value) totalGlobalDeaths += value
totalGlobalDeaths += values["deaths"]
} }
var totalPilotDeaths = 0
foreach (var key, var value in globalToneAPIDeathData) {
totalPilotDeaths += value
}
var killsAsPilot = 0 var killsAsPilot = 0
foreach (var key, var value in playerweaponData) { foreach (var key, var value in globalToneAPIKillData) {
if (key in titanKillData) { if (key in titanKillData) {
continue continue
} else { } else {
killsAsPilot += pulseParse("weaponsLocal", string(key), "kills") killsAsPilot += getWeaponKillsFromToneAPI(string(key))
} }
} }
var killsAsTitan = 0 var killsAsTitan = 0
foreach (var key, var value in playerweaponData) { foreach (var key, var value in globalToneAPIKillData) {
if (key in titanKillData) { if (key in titanKillData) {
killsAsTitan += titanKillData[string(key)] killsAsTitan += titanKillData[string(key)]
} }
@@ -407,27 +391,27 @@ function UpdateViewStatsOverviewMenu()
Hud_SetText( GetElem( file.menu, "KillsAsPilotValue0" ), string( killsAsPilot ) ) Hud_SetText( GetElem( file.menu, "KillsAsPilotValue0" ), string( killsAsPilot ) )
Hud_SetText( GetElem( file.menu, "KillsAsPilotValue1" ), string( GetPlayerStatInt( player, "kills_stats", "titanKillsAsPilot" ) ) ) Hud_SetText( GetElem( file.menu, "KillsAsPilotValue1" ), string( GetPlayerStatInt( player, "kills_stats", "titanKillsAsPilot" ) ) )
Hud_SetText( GetElem( file.menu, "KillsAsPilotValue2" ), string( GetPlayerStatInt( player, "kills_stats", "totalNPC" ) ) ) Hud_SetText( GetElem( file.menu, "KillsAsPilotValue2" ), string( GetPlayerStatInt( player, "kills_stats", "totalNPC" ) ) )
Hud_SetText( GetElem( file.menu, "KillsAsPilotValue3" ), string( pulseParse("weaponsLocal", "pilot_emptyhanded", "kills") ) ) Hud_SetText( GetElem( file.menu, "KillsAsPilotValue3" ), string( getWeaponKillsFromToneAPI("pilot_emptyhanded") ) )
Hud_SetText( GetElem( file.menu, "KillsAsPilotValue4" ), string( pulseParse("weaponsLocal", "human_execution", "kills") ) ) Hud_SetText( GetElem( file.menu, "KillsAsPilotValue4" ), string( getWeaponKillsFromToneAPI("human_execution") ) )
// Hud_SetText( GetElem( file.menu, "KillsAsPilotValue5" ), string( GetPlayerStatInt( player, "kills_stats", "titanFallKill" ) ) ) // Hud_SetText( GetElem( file.menu, "KillsAsPilotValue5" ), string( GetPlayerStatInt( player, "kills_stats", "titanFallKill" ) ) )
var titanMeleeKills = 0 var titanMeleeKills = 0
titanMeleeKills += pulseParse("weaponsLocal", "titan_punch_ion", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("titan_punch_ion")
titanMeleeKills += pulseParse("weaponsLocal", "titan_punch_scorch", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("titan_punch_scorch")
titanMeleeKills += pulseParse("weaponsLocal", "titan_punch_northstar", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("titan_punch_northstar")
titanMeleeKills += pulseParse("weaponsLocal", "titan_sword", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("titan_sword")
titanMeleeKills += pulseParse("weaponsLocal", "titan_punch_tone", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("titan_punch_tone")
titanMeleeKills += pulseParse("weaponsLocal", "titan_punch_legion", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("titan_punch_legion")
titanMeleeKills += pulseParse("weaponsLocal", "titan_punch_vanguard", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("titan_punch_vanguard")
titanMeleeKills += pulseParse("weaponsLocal", "auto_titan_melee", "kills") titanMeleeKills += getWeaponKillsFromToneAPI("auto_titan_melee")
var titanExecutions = pulseParse("weaponsLocal", "titan_execution", "kills") var titanExecutions = getWeaponKillsFromToneAPI("titan_execution")
Hud_SetText( GetElem( file.menu, "KillsAsTitanValue0" ), string( killsAsTitan ) ) Hud_SetText( GetElem( file.menu, "KillsAsTitanValue0" ), string( killsAsTitan ) )
Hud_SetText( GetElem( file.menu, "KillsAsTitanValue1" ), string( GetPlayerStatInt( player, "kills_stats", "titanKillsAsTitan" ) ) ) Hud_SetText( GetElem( file.menu, "KillsAsTitanValue1" ), string( GetPlayerStatInt( player, "kills_stats", "titanKillsAsTitan" ) ) )
Hud_SetText( GetElem( file.menu, "KillsAsTitanValue2" ), string( titanExecutions ) ) Hud_SetText( GetElem( file.menu, "KillsAsTitanValue2" ), string( titanExecutions ) )
Hud_SetText( GetElem( file.menu, "KillsAsTitanValue3" ), string( titanMeleeKills ) ) Hud_SetText( GetElem( file.menu, "KillsAsTitanValue3" ), string( titanMeleeKills ) )
Hud_SetText( GetElem( file.menu, "KillsAsTitanValue4" ), string( pulseParse("weaponsLocal", "damagedef_titan_step", "kills") ) ) Hud_SetText( GetElem( file.menu, "KillsAsTitanValue4" ), string( getWeaponKillsFromToneAPI("damagedef_titan_step") ) )
} }
function PlotKDPointsOnGraph( menu, graphIndex, values, dottedAverage ) function PlotKDPointsOnGraph( menu, graphIndex, values, dottedAverage )
@@ -49,7 +49,7 @@ float function GetTitanKills( string titanName)
float weaponKills = 0 float weaponKills = 0
foreach ( weaponRef in file.titanStatLoadout[ titanName ]) foreach ( weaponRef in file.titanStatLoadout[ titanName ])
{ {
weaponKills += float(pulseParse("weaponsLocal", weaponRef, "kills")) weaponKills += float(getWeaponKillsFromToneAPI(weaponRef))
} }
return weaponKills return weaponKills
} }
@@ -79,13 +79,11 @@ void function UpdateViewStatsTimeMenu()
//######################################### //#########################################
// Time By Class Pie Chart // Time By Class Pie Chart
//######################################### //#########################################
var totalPilotKills = 0 var totalPilotKills = 0
table playerweaponData = expect table(pulseData["weaponsLocal"])
foreach (var key, var value in globalToneAPIKillData) {
foreach (var key, var value in playerweaponData) { totalPilotKills += value
table values = expect table(value)
totalPilotKills += values["kills"]
} }
float hoursAsPilot = float(totalPilotKills) float hoursAsPilot = float(totalPilotKills)
float hoursAsTitan = killsAsTitan float hoursAsTitan = killsAsTitan
@@ -103,7 +101,7 @@ void function UpdateViewStatsTimeMenu()
PieChartData classTimeData PieChartData classTimeData
classTimeData.entries = classes classTimeData.entries = classes
classTimeData.labelColor = [ 255, 255, 255, 255 ] classTimeData.labelColor = [ 255, 255, 255, 255 ]
SetPieChartData( file.menu, "ClassPieChart", Localize("#TIME_KILLS_CLASS"), classTimeData ) SetPieChartData( file.menu, "ClassPieChart", "KILLS BY CLASS", classTimeData )
//######################################### //#########################################
// Time By Chassis Pie Chart // Time By Chassis Pie Chart
@@ -137,7 +135,7 @@ void function UpdateViewStatsTimeMenu()
PieChartData chassisTimeData PieChartData chassisTimeData
chassisTimeData.entries = titans chassisTimeData.entries = titans
chassisTimeData.labelColor = [ 255, 255, 255, 255 ] chassisTimeData.labelColor = [ 255, 255, 255, 255 ]
SetPieChartData( file.menu, "ChassisPieChart", Localize("#TIME_KILLS_TITAN"), chassisTimeData ) SetPieChartData( file.menu, "ChassisPieChart", "KILLS BY TITAN", chassisTimeData )
//######################################### //#########################################
// Time By Mode Pie Chart // Time By Mode Pie Chart
@@ -152,18 +150,13 @@ void function UpdateViewStatsTimeMenu()
fw = [147, 204, 57, 255], fw = [147, 204, 57, 255],
gg = [14, 87, 132, 255] gg = [14, 87, 132, 255]
} }
table< string, string > customGamemodeNames = {
sns = Localize("#GAMEMODE_sns")
fw = Localize("#GAMEMODE_fw")
gg = Localize("#GAMEMODE_gg")
}
for ( int modeId = 0; modeId < enumCount; modeId++ ) for ( int modeId = 0; modeId < enumCount; modeId++ )
{ {
string modeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeId ) string modeName = PersistenceGetEnumItemNameForIndex( "gameModes", modeId )
if ( pulseParse("gamemodesAll", modeName, "kills") != 0 ) if ( modeName in globalToneAPIGamemodeData )
{ {
float modePlayedTime = 0 float modePlayedTime = 0
modePlayedTime = float(pulseParse("gamemodesAll", modeName, "kills")) modePlayedTime = float(globalToneAPIGamemodeData[modeName])
if ( modePlayedTime > 0 ) { if ( modePlayedTime > 0 ) {
AddPieChartEntry( modes, GameMode_GetName( modeName ), modePlayedTime, GetGameModeDisplayColor( modeName ) ) AddPieChartEntry( modes, GameMode_GetName( modeName ), modePlayedTime, GetGameModeDisplayColor( modeName ) )
} }
@@ -171,12 +164,12 @@ void function UpdateViewStatsTimeMenu()
} }
foreach (string key, array<int> value in customGamemodeList) foreach (string key, array<int> value in customGamemodeList)
{ {
if ( pulseParse("gamemodesAll", key, "kills") != 0 ) if ( key in globalToneAPIGamemodeData)
{ {
float modePlayedTime = 0 float modePlayedTime = 0
modePlayedTime = float(pulseParse("gamemodesAll", key, "kills")) modePlayedTime = float(globalToneAPIGamemodeData[key])
if ( modePlayedTime > 0 ) { if ( modePlayedTime > 0 ) {
AddPieChartEntry( modes, customGamemodeNames[key], modePlayedTime, value) AddPieChartEntry( modes, key, modePlayedTime, value)
} }
} }
} }
@@ -184,7 +177,7 @@ void function UpdateViewStatsTimeMenu()
PieChartData modesPlayedData PieChartData modesPlayedData
modesPlayedData.entries = modes modesPlayedData.entries = modes
modesPlayedData.labelColor = [ 255, 255, 255, 255 ] modesPlayedData.labelColor = [ 255, 255, 255, 255 ]
SetPieChartData( file.menu, "ModesPieChart", Localize("#KILLS_GAMEMODE"), modesPlayedData ) SetPieChartData( file.menu, "ModesPieChart", "KILLS BY GAMEMODE", modesPlayedData )
//######################################### //#########################################
// Time Stats // Time Stats
@@ -175,7 +175,7 @@ void function UpdateStatsForTitan( string titanRef, int loadoutIndex )
int aiKills int aiKills
foreach ( weaponRef in file.titanStatLoadout[ titanRef ] ) foreach ( weaponRef in file.titanStatLoadout[ titanRef ] )
{ {
totalKills += pulseParse("weaponsLocal", weaponRef, "kills") totalKills += getWeaponKillsFromToneAPI(weaponRef)
pilotKills += GetPlayerStatInt( player, "weapon_kill_stats", "pilots", weaponRef ) pilotKills += GetPlayerStatInt( player, "weapon_kill_stats", "pilots", weaponRef )
titanKills += GetPlayerStatInt( player, "weapon_kill_stats", "titansTotal", weaponRef ) titanKills += GetPlayerStatInt( player, "weapon_kill_stats", "titansTotal", weaponRef )
aiKills += GetPlayerStatInt( player, "weapon_kill_stats", "ai", weaponRef ) aiKills += GetPlayerStatInt( player, "weapon_kill_stats", "ai", weaponRef )
@@ -338,7 +338,7 @@ table<string, table> function GetOverviewWeaponData()
if ( !PersistenceEnumValueIsValid( "loadoutWeaponsAndAbilities", weaponName ) ) if ( !PersistenceEnumValueIsValid( "loadoutWeaponsAndAbilities", weaponName ) )
continue continue
int val = pulseParse("weaponsLocal", weaponName, "kills") int val = getWeaponKillsFromToneAPI(weaponName)
if ( val > Table[ "most_kills" ].val ) if ( val > Table[ "most_kills" ].val )
{ {
Table[ "most_kills" ].ref = weaponName Table[ "most_kills" ].ref = weaponName
@@ -346,7 +346,7 @@ table<string, table> function GetOverviewWeaponData()
Table[ "most_kills" ].val = val Table[ "most_kills" ].val = val
} }
int nval = pulseParse("weaponsLocal", weaponName, "deaths") int nval = getNemesisWeaponFromToneAPI(weaponName)
if ( nval > Table[ "nemesis_weapon" ].val ) if ( nval > Table[ "nemesis_weapon" ].val )
{ {
Table[ "nemesis_weapon" ].ref = weaponName Table[ "nemesis_weapon" ].ref = weaponName
@@ -357,9 +357,9 @@ table<string, table> function GetOverviewWeaponData()
float kdval = 0 float kdval = 0
float kval = 0 float kval = 0
float dval = 0 float dval = 0
if( pulseParse("weaponsLocal", weaponName, "kills") != 0 && pulseParse("weaponsLocal", weaponName, "deaths_while_equipped") != 0){ if( getWeaponKillsFromToneAPI(weaponName) != 0 && getDWEFromToneAPI(weaponName) != 0){
kval = float(pulseParse("weaponsLocal", weaponName, "kills")) kval = float(getWeaponKillsFromToneAPI(weaponName))
dval = float(pulseParse("weaponsLocal", weaponName, "deaths_while_equipped")) dval = float(getDWEFromToneAPI(weaponName))
if (kval / dval > kdval){ if (kval / dval > kdval){
kdval = kval / dval kdval = kval / dval
} }
@@ -180,8 +180,8 @@ void function UpdateStatsForWeapon( string weaponRef )
} }
// Total Kills Stats // Total Kills Stats
SetStatsLabelValue( file.menu, "KillsValue0", pulseParse("weaponsLocal", weaponRef, "kills") ) //Total kills SetStatsLabelValue( file.menu, "KillsValue0", getWeaponKillsFromToneAPI(weaponRef) )
SetStatsLabelValue( file.menu, "KillsValue1", pulseParse("weaponsLocal", weaponRef, "kills") ) //Pilots SetStatsLabelValue( file.menu, "KillsValue1", getWeaponKillsFromToneAPI(weaponRef) )
SetStatsLabelValue( file.menu, "KillsValue2", GetPlayerStatInt( player, "weapon_kill_stats", "titansTotal", weaponRef ) ) //Titans SetStatsLabelValue( file.menu, "KillsValue2", GetPlayerStatInt( player, "weapon_kill_stats", "titansTotal", weaponRef ) )
SetStatsLabelValue( file.menu, "KillsValue3", pulseParse("weaponsLocal", weaponRef, "deaths_while_equipped")) // Deaths with weapon SetStatsLabelValue( file.menu, "KillsValue3", getDWEFromToneAPI(weaponRef))
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,60 +0,0 @@
globalize_all_functions
global string pulsePrefix
global bool pulseSuccessBool = true
table withMapName = {}
void function pulsePrefixSet() //Sets our prefix
{
pulsePrefix = format("\x1b[38;2;%i;%i;%im[PULSE]\x1b[0m", 255, 178, 102)
}
void function pulsePrintt(string content) //Prints whatever we want printed with given prefix (by standard [PULSE]).
{
printt(format("%s " + content, pulsePrefix))
}
bool function pulseRequestData(string givenURL, table parameterTable, string tablepath) //Gets data from API (see ToneURL within mod.json) and puts it into one table for processing.
{
table<string, array<string> > requestTable
void functionref (HttpRequestResponse) onSuccess = void function(HttpRequestResponse response) : (parameterTable, requestTable, tablepath)
{
table DecodedJSON = DecodeJSON(response.body)
if ("map" in parameterTable)
{
string mapName = expect string(parameterTable["map"])
withMapName[mapName] <- DecodedJSON
pulseData[tablepath] <- withMapName
} else {
pulseData[tablepath] <- DecodedJSON
}
pulseSuccessBool = false
}
void functionref (HttpRequestFailure) onFailure = void function(HttpRequestFailure failure)
{
pulsePrintt("Something went wrong while getting data.")
pulseSuccessBool = true
}
foreach (key, value in parameterTable)
{
requestTable[string(key)] <- [string(value)]
}
return NSHttpGet(givenURL, requestTable, onSuccess, onFailure)
}
int function pulseParse(...) //Returns data from our pulseData table.
{
table parser = pulseData
int result = 0
for (int i; i < vargc; i++) {
if (i < expect int(vargc) - 1) {
if (expect string (vargv[i]) in parser) {
parser = expect table(parser[expect string(vargv[i])])
} else {break}
} else {
result = expect int(parser[expect string(vargv[i])])
}
}
return result
}
@@ -1,30 +0,0 @@
global function pulseInit
global function pulseGetData
global table pulseData = {}
struct {
array<string> allMaps
} file
void function pulseInit()
{
pulsePrefixSet()
file.allMaps = GetPrivateMatchMaps()
pulsePrintt("Initialized.")
}
void function pulseGetData()
{
string URL = GetConVarString("ToneURL")
array <string> URLpath = ["/weapons", "/weapons", "/gamemodes", "/gamemodes", "/maps"]
array <string> tablepath = ["weaponsLocal", "weaponsGlobal", "gamemodesAll", "gamemodesSeparated", "maps"]
pulseRequestData(URL + URLpath[0], {["player"] = NSGetLocalPlayerUID()}, tablepath[0])
pulseRequestData(URL + URLpath[1], {["player"] = "!" + NSGetLocalPlayerUID()}, tablepath[1])
pulseRequestData(URL + URLpath[2], {["player"] = NSGetLocalPlayerUID()}, tablepath[2])
foreach (map in file.allMaps)
{
pulseRequestData(URL + URLpath[3], {["player"] = NSGetLocalPlayerUID(), ["map"] = map.slice(3)}, tablepath[3])
}
pulseRequestData(URL + URLpath[4], {["player"] = NSGetLocalPlayerUID()}, tablepath[4])
}
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
{
"Major": "1",
"Minor": "2",
"Patch": "1"
}