Skip to content

Commit

Permalink
Add items_game.txt API
Browse files Browse the repository at this point in the history
  • Loading branch information
Citrinate committed Jan 12, 2025
1 parent 4888081 commit 0511cb5
Show file tree
Hide file tree
Showing 14 changed files with 583 additions and 82 deletions.
44 changes: 19 additions & 25 deletions CS2Interface/GameData/GameDataItems.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using CS2Interface.Localization;
using SteamKit2;

namespace CS2Interface {
internal class GameDataItems : GameDataResource {
private KeyValue? Data;
internal KeyValue? Data {get; private set;}

internal GameDataItems(string url) : base(url) {}

Expand All @@ -20,38 +18,34 @@ internal override async Task<bool> Update() {
return false;
}

Data = data;
// Combine any duplicated names
KeyValue mergedData = new();
foreach (KeyValue kv in data.Children) {
if (kv.Name == null) {
continue;
}

if (mergedData[kv.Name] == KeyValue.Invalid) {
mergedData[kv.Name] = kv.Clone();
} else {
mergedData[kv.Name].Merge(kv);
}
}

Data = mergedData;
Updated = true;

return true;
}

internal List<KeyValue>? this[string? key] {
internal KeyValue this[string? key] {
get {
if (key == null || Data == null) {
return null;
return KeyValue.Invalid;
}

return Data.Children.Where(x => x.Name == key).SelectMany(x => x.Children).ToList();
return Data[key];
}
}

internal KeyValue? GetDef(string value, string index, bool suppressErrorLogs = false) {
if (Data == null) {
return null;
}

KeyValue? def = this[value]?.Where(x => x.Name?.ToUpper().Trim() == index.ToUpper().Trim()).FirstOrDefault();

if (def == null) {
if (!suppressErrorLogs) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: {1}[{2}]", Strings.GameDataDefinitionUndefined, value, index));
}

return null;
}

return def;
}
}
}
14 changes: 7 additions & 7 deletions CS2Interface/GameData/GameObjects/Items/Item.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,11 @@ protected override bool SetAdditionalProperties() {
} else if (ItemData.ItemDef["item_slot"].Value == "customplayer") {
locKey = "loc_key_character"; // Agent rarities
}
RarityName = GameData.CsgoEnglish[GameData.ItemsGame["rarities"]?.FirstOrDefault(x => x["value"].Value == Rarity.ToString())?[locKey].Value];
RarityName = GameData.CsgoEnglish[GameData.ItemsGame["rarities"].Children.FirstOrDefault(x => x["value"].Value == Rarity.ToString())?[locKey].Value];
}

TypeName = GameData.CsgoEnglish[ItemData.ItemDef["item_type_name"].Value?.Substring(1)];
QualityName = GameData.CsgoEnglish[GameData.ItemsGame["qualities"]?.FirstOrDefault(x => x["value"].Value == Quality.ToString())?.Name];
QualityName = GameData.CsgoEnglish[GameData.ItemsGame["qualities"].Children.FirstOrDefault(x => x["value"].Value == Quality.ToString())?.Name];
OriginName = GameDataText.GetOriginName(Origin);

// Set the item name, which will be something like: what kind of sticker it is, or the name of the weapon skin, or the type of pin/coin
Expand Down Expand Up @@ -219,18 +219,18 @@ protected override bool SetAdditionalProperties() {

if (NameID != null) {
{ // Determine what set, if any, this item belongs to
KeyValue? setItemDef = GameData.ItemsGame["item_sets"]?.FirstOrDefault(x => x["items"][NameID] != KeyValue.Invalid);
KeyValue? setItemDef = GameData.ItemsGame["item_sets"].Children.FirstOrDefault(x => x["items"][NameID] != KeyValue.Invalid);
if (setItemDef != null) {
SetNameID = setItemDef.Name;
SetName = GameData.CsgoEnglish[setItemDef["name"].Value?.Substring(1)];
}
}

{ // Determine what crate, if any, this item belongs to. Doesn't work for souvenir skins, knives, or gloves
string? lootListName = GameData.ItemsGame["client_loot_lists"]?.FirstOrDefault(x => x[NameID] != KeyValue.Invalid)?.Name;
lootListName = lootListName == null ? null : GameData.ItemsGame["client_loot_lists"]?.FirstOrDefault(x => x[lootListName] != KeyValue.Invalid)?.Name ?? lootListName; // Some lists in client_loot_lists are nested (1 or 2 layers), we want the top-most layer
string? lootListID = lootListName == null ? null : GameData.ItemsGame["revolving_loot_lists"]?.FirstOrDefault(x => x.Value == lootListName)?.Name;
KeyValue? crateItemDef = lootListID == null ? null : GameData.ItemsGame["items"]?.FirstOrDefault(x => x["attributes"]["set supply crate series"]["value"].Value == lootListID);
string? lootListName = GameData.ItemsGame["client_loot_lists"].Children.FirstOrDefault(x => x[NameID] != KeyValue.Invalid)?.Name;
lootListName = lootListName == null ? null : GameData.ItemsGame["client_loot_lists"].Children.FirstOrDefault(x => x[lootListName] != KeyValue.Invalid)?.Name ?? lootListName; // Some lists in client_loot_lists are nested (1 or 2 layers), we want the top-most layer
string? lootListID = lootListName == null ? null : GameData.ItemsGame["revolving_loot_lists"].Children.FirstOrDefault(x => x.Value == lootListName)?.Name;
KeyValue? crateItemDef = lootListID == null ? null : GameData.ItemsGame["items"].Children.FirstOrDefault(x => x["attributes"]["set supply crate series"]["value"].Value == lootListID);
if (crateItemDef != null && crateItemDef.Name != null) {
CrateNameID = crateItemDef["name"].Value;
CrateDefIndex = uint.Parse(crateItemDef.Name);
Expand Down
54 changes: 35 additions & 19 deletions CS2Interface/GameData/GameObjects/Items/ItemData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class ItemData {
public bool ShouldSerializePaintKitDef() => PaintKitDef != null;
public bool ShouldSerializeStickerKitDef() => StickerKitDef != null;
public bool ShouldSerializeMusicDef() => MusicDef != null;
public bool ShouldSerializeKeychainDefDef() => KeychainDef != null;
public bool ShouldSerializeKeychainDef() => KeychainDef != null;

internal ItemData(Item item) {
ItemDef = CreateItemDef(item);
Expand All @@ -46,8 +46,10 @@ internal ItemData(Item item) {
}

private KeyValue CreateItemDef(Item item) {
KeyValue? itemDef = GameData.ItemsGame.GetDef("items", item.DefIndex.ToString());
if (itemDef == null) {
KeyValue itemDef = GameData.ItemsGame["items"][item.DefIndex.ToString()];
if (itemDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: items[{1}]", Strings.GameDataDefinitionUndefined, item.DefIndex));

throw new Exception();
}

Expand All @@ -59,8 +61,10 @@ private KeyValue CreateItemDef(Item item) {
}

// Add default values
KeyValue? defaultItemDef = GameData.ItemsGame.GetDef("items", "default");
if (defaultItemDef == null) {
KeyValue defaultItemDef = GameData.ItemsGame["items"]["default"];
if (defaultItemDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: items[default]", Strings.GameDataDefinitionUndefined));

throw new Exception();
}

Expand All @@ -82,8 +86,8 @@ private bool MergePrefab(KeyValue itemDef, string? prefab) {
bool foundValid = false;

foreach (string prefabName in prefabNames) {
KeyValue? prefabDef = GameData.ItemsGame.GetDef("prefabs", prefabName, suppressErrorLogs: true);
if (prefabDef == null) {
KeyValue prefabDef = GameData.ItemsGame["prefabs"][prefabName];
if (prefabDef == KeyValue.Invalid) {
continue;
}

Expand All @@ -107,14 +111,18 @@ private bool MergePrefab(KeyValue itemDef, string? prefab) {
return null;
}

KeyValue? paintKitDef = GameData.ItemsGame.GetDef("paint_kits", item.PaintIndex.ToString());
if (paintKitDef == null) {
KeyValue paintKitDef = GameData.ItemsGame["paint_kits"][item.PaintIndex.ToString()];
if (paintKitDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: paint_kits[{1}]", Strings.GameDataDefinitionUndefined, item.PaintIndex));

throw new Exception();
}

// Add default values
KeyValue? defaultPaintKitDef = GameData.ItemsGame.GetDef("paint_kits", "0");
if (defaultPaintKitDef == null) {
KeyValue defaultPaintKitDef = GameData.ItemsGame["paint_kits"]["0"];
if (defaultPaintKitDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: paint_kits[0]", Strings.GameDataDefinitionUndefined));

throw new Exception();
}

Expand All @@ -137,14 +145,18 @@ private bool MergePrefab(KeyValue itemDef, string? prefab) {
return null;
}

KeyValue? stickerKitDef = GameData.ItemsGame.GetDef("sticker_kits", item.StickerID.ToString()!);
if (stickerKitDef == null) {
KeyValue stickerKitDef = GameData.ItemsGame["sticker_kits"][item.StickerID.ToString()!];
if (stickerKitDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: sticker_kits[{1}]", Strings.GameDataDefinitionUndefined, item.StickerID));

throw new Exception();
}

// Add default values
KeyValue? defaultStickerKitDef = GameData.ItemsGame.GetDef("sticker_kits", "0");
if (defaultStickerKitDef == null) {
KeyValue defaultStickerKitDef = GameData.ItemsGame["sticker_kits"]["0"];
if (defaultStickerKitDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: sticker_kits[0]", Strings.GameDataDefinitionUndefined));

throw new Exception();
}

Expand All @@ -160,8 +172,10 @@ private bool MergePrefab(KeyValue itemDef, string? prefab) {
return null;
}

KeyValue? musicDef = GameData.ItemsGame.GetDef("music_definitions", item.MusicID.ToString()!);
if (musicDef == null) {
KeyValue musicDef = GameData.ItemsGame["music_definitions"][item.MusicID.ToString()!];
if (musicDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: music_definitions[{1}]", Strings.GameDataDefinitionUndefined, item.MusicID));

throw new Exception();
}

Expand All @@ -179,8 +193,10 @@ private bool MergePrefab(KeyValue itemDef, string? prefab) {
return null;
}

KeyValue? keychainDef = GameData.ItemsGame.GetDef("keychain_definitions", item.KeychainID.ToString()!);
if (keychainDef == null) {
KeyValue keychainDef = GameData.ItemsGame["keychain_definitions"][item.KeychainID.ToString()!];
if (keychainDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: keychain_definitions[{1}]", Strings.GameDataDefinitionUndefined, item.KeychainID));

throw new Exception();
}

Expand Down
14 changes: 9 additions & 5 deletions CS2Interface/GameData/GameObjects/Recipes/Recipe.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using ArchiSteamFarm.Core;
using CS2Interface.Localization;
using SteamKit2;

Expand Down Expand Up @@ -47,8 +49,10 @@ public Recipe(ushort recipeID) {
}

protected override bool SetDefs() {
KeyValue? recipeDef = GameData.ItemsGame.GetDef("recipes", RecipeID.ToString());
if (recipeDef == null) {
KeyValue recipeDef = GameData.ItemsGame["recipes"][RecipeID.ToString()];
if (recipeDef == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: recipes[{1}]", Strings.GameDataDefinitionUndefined, RecipeID));

return false;
}

Expand Down Expand Up @@ -86,13 +90,13 @@ public static async Task<List<Recipe>> GetAll() {
throw new ClientException(EClientExceptionType.Failed, Strings.GameDataLoadingFailed);
}

List<KeyValue>? kvs = GameData.ItemsGame["recipes"];
if (kvs == null) {
KeyValue kvs = GameData.ItemsGame["recipes"];
if (kvs == KeyValue.Invalid) {
return [];
}

List<Recipe> recipes = [];
foreach (KeyValue kv in kvs) {
foreach (KeyValue kv in kvs.Children) {
if (ushort.TryParse(kv.Name, out ushort recipeID)) {
recipes.Add(new Recipe(recipeID));
}
Expand Down
7 changes: 5 additions & 2 deletions CS2Interface/Helpers/Attribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using ArchiSteamFarm.Core;
using CS2Interface.Localization;
using SteamKit2;
using SteamKit2.GC.CSGO.Internal;

Expand Down Expand Up @@ -42,8 +43,10 @@ public static class AttributeParser {
}

foreach (CSOEconItemAttribute attribute in attributes) {
KeyValue? attribute_def = GameData.ItemsGame.GetDef("attributes", attribute.def_index.ToString());
if (attribute_def == null) {
KeyValue attribute_def = GameData.ItemsGame["attributes"][attribute.def_index.ToString()];
if (attribute_def == KeyValue.Invalid) {
ASF.ArchiLogger.LogGenericError(String.Format("{0}: attributes[{1}]", Strings.GameDataDefinitionUndefined, attribute.def_index));

return null;
}

Expand Down
2 changes: 1 addition & 1 deletion CS2Interface/Helpers/KVConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static void ConvertKVObjectToJson (ref Utf8JsonWriter writer, KeyValue vd
return;
}

if (float.TryParse(vdf.Value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out float floatValue)) {
if (float.TryParse(vdf.Value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out float floatValue) && !float.IsInfinity(floatValue)) {
writer.WriteNumberValue(floatValue);

return;
Expand Down
13 changes: 13 additions & 0 deletions CS2Interface/IPC/Api/CS2InterfaceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using ArchiSteamFarm.IPC.Controllers.Api;
using ArchiSteamFarm.IPC.Responses;
using ArchiSteamFarm.Steam;
using CS2Interface.Localization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SteamKit2.GC.CSGO.Internal;
Expand Down Expand Up @@ -347,6 +348,18 @@ public async Task<ActionResult<GenericResponse>> Recipes([FromQuery] bool showDe
return Ok(new GenericResponse<GameData<List<Recipe>>>(true, new GameData<List<Recipe>>(recipes)));
}

[HttpGet("items_game.txt")]
[EndpointSummary("Get the contents of items_game.txt")]
[ProducesResponseType(typeof(GenericResponse<GameDataKV>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> ItemsGame() {
if (!await GameData.IsLoaded(update: false).ConfigureAwait(false) || GameData.ItemsGame.Data == null) {
return BadRequest(new GenericResponse(false, Strings.GameDataLoadingFailed));
}

return Ok(new GenericResponse<GameDataKV>(true, new GameDataKV(GameData.ItemsGame.Data)));
}

private async Task<ActionResult<GenericResponse>> HandleClientException(Bot bot, ClientException e) {
bot.ArchiLogger.LogGenericError(e.Message);
if (e.Type == EClientExceptionType.Timeout) {
Expand Down
Loading

0 comments on commit 0511cb5

Please sign in to comment.