diff --git a/Daybreak.API/Serialization/ApiJsonSerializerContext.cs b/Daybreak.API/Serialization/ApiJsonSerializerContext.cs index 8b75cd45..68c211cb 100644 --- a/Daybreak.API/Serialization/ApiJsonSerializerContext.cs +++ b/Daybreak.API/Serialization/ApiJsonSerializerContext.cs @@ -1,5 +1,6 @@ using Daybreak.API.Models; using Daybreak.Shared.Models.Api; +using Daybreak.Shared.Models.Guildwars; using System.Text.Json; using System.Text.Json.Serialization; @@ -35,6 +36,8 @@ namespace Daybreak.API.Serialization; [JsonSerializable(typeof(LoginInfo))] [JsonSerializable(typeof(MainPlayerBuildContext))] [JsonSerializable(typeof(InventoryInformation))] +[JsonSerializable(typeof(ItemProperty))] +[JsonSourceGenerationOptions(UseStringEnumConverter = true)] public partial class ApiJsonSerializerContext : JsonSerializerContext { } diff --git a/Daybreak.API/Services/InventoryService.cs b/Daybreak.API/Services/InventoryService.cs index 08905bba..b7bfbe24 100644 --- a/Daybreak.API/Services/InventoryService.cs +++ b/Daybreak.API/Services/InventoryService.cs @@ -1,7 +1,9 @@ using Daybreak.API.Interop.GuildWars; using Daybreak.API.Services.Interop; using Daybreak.Shared.Models.Api; +using Daybreak.Shared.Models.Guildwars; using System.Extensions.Core; +using System.Text; namespace Daybreak.API.Services; @@ -37,7 +39,7 @@ public sealed class InventoryService( return default; } - var itemTuples = new List<(BagType Type,List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, int Quantity, uint[] Modifiers, ItemType ItemType)>)>(); + var itemTuples = new List<(BagType Type,List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType)>)>(); foreach (var bag in inventory.Pointer->Bags) { if (bag is null) @@ -45,7 +47,7 @@ public sealed class InventoryService( continue; } - var retBag = new List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, int Quantity, uint[] Modifiers, ItemType ItemType)>(); + var retBag = new List<(uint ModelId, string EncodedCompleteName, string EncodedSingleName, string EncodedName, bool Inscribable, int Quantity, uint[] Modifiers, ItemType ItemType)>(); itemTuples.Add((bag->Type, retBag)); if (bag->ItemsCount is 0) { @@ -68,7 +70,7 @@ public sealed class InventoryService( modifiers[j] = item.Pointer->Modifiers[j].Mod; } - retBag.Add((item.Pointer->ModelId, completeNameEncoded, singleItemName, nameEncoded, item.Pointer->Quantity, modifiers, item.Pointer->Type)); + retBag.Add((item.Pointer->ModelId, completeNameEncoded, singleItemName, nameEncoded, item.Pointer->Inscribable, item.Pointer->Quantity, modifiers, item.Pointer->Type)); } } @@ -88,7 +90,7 @@ public sealed class InventoryService( var decodedName = await this.uIService.DecodeString(item.EncodedName, Language.English, cancellationToken); var decodedCompleteName = await this.uIService.DecodeString(item.EncodedCompleteName, Language.English, cancellationToken); var decodedSingleName = await this.uIService.DecodeString(item.EncodedSingleName, Language.English, cancellationToken); - return (item.ModelId, item.EncodedName, DecodedName: decodedName, item.EncodedSingleName, DecodedSingleName: decodedSingleName, item.EncodedCompleteName, DecodedCompleteName: decodedCompleteName, item.Quantity, item.Modifiers, item.ItemType); + return (item.ModelId, item.EncodedName, DecodedName: decodedName, item.EncodedSingleName, DecodedSingleName: decodedSingleName, item.EncodedCompleteName, DecodedCompleteName: decodedCompleteName, item.Inscribable, item.Quantity, item.Modifiers, item.ItemType); })); return (tuple.Type, Items: decodedItems.ToList()); })); @@ -106,15 +108,15 @@ public sealed class InventoryService( DecodedSingleName: item.DecodedSingleName ?? string.Empty, EncodedCompleteName: ToBase64(item.EncodedCompleteName), DecodedCompleteName: item.DecodedCompleteName ?? string.Empty, + Inscribable: item.Inscribable, Quantity: item.Quantity, Modifiers: item.Modifiers, + Properties: [.. ItemProperty.ParseItemModifiers([.. item.Modifiers.Select(m => (Shared.Models.Guildwars.ItemModifier)m)])], ItemType: item.ItemType.ToString()))]))]); } private static string ToBase64(string encoded) { - var bytes = new byte[encoded.Length * sizeof(char)]; - Buffer.BlockCopy(encoded.ToCharArray(), 0, bytes, 0, bytes.Length); - return Convert.ToBase64String(bytes); + return Convert.ToBase64String(Encoding.Unicode.GetBytes(encoded)); } } diff --git a/Daybreak.Shared/Converters/AttributeJsonConverter.cs b/Daybreak.Shared/Converters/AttributeJsonConverter.cs deleted file mode 100644 index 942e2abc..00000000 --- a/Daybreak.Shared/Converters/AttributeJsonConverter.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class AttributeJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Models.Guildwars.Attribute))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Models.Guildwars.Attribute.TryParse(name, out var namedAttribute)) - { - return default; - } - - return namedAttribute; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Models.Guildwars.Attribute.TryParse((int)id.Value, out var parsedAttribute)) - { - return default; - } - - return parsedAttribute; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Models.Guildwars.Attribute attribute) - { - return; - } - - writer.WriteValue(attribute.Id); - } -} diff --git a/Daybreak.Shared/Converters/CampaignJsonConverter.cs b/Daybreak.Shared/Converters/CampaignJsonConverter.cs deleted file mode 100644 index da730a8b..00000000 --- a/Daybreak.Shared/Converters/CampaignJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class CampaignJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Campaign))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Campaign.TryParse(name, out var namedCampaign)) - { - return default; - } - - return namedCampaign; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Campaign.TryParse((int)id.Value, out var parsedCampaign)) - { - return default; - } - - return parsedCampaign; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Campaign campaign) - { - return; - } - - writer.WriteValue(campaign.Id); - } -} diff --git a/Daybreak.Shared/Converters/ContinentJsonConverter.cs b/Daybreak.Shared/Converters/ContinentJsonConverter.cs deleted file mode 100644 index cdb9f35d..00000000 --- a/Daybreak.Shared/Converters/ContinentJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class ContinentJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Continent))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Continent.TryParse(name, out var namedContinent)) - { - return default; - } - - return namedContinent; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Continent.TryParse((int)id.Value, out var parsedContinent)) - { - return default; - } - - return parsedContinent; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Continent continent) - { - return; - } - - writer.WriteValue(continent.Id); - } -} diff --git a/Daybreak.Shared/Converters/HexIntConverter.cs b/Daybreak.Shared/Converters/HexIntConverter.cs new file mode 100644 index 00000000..94cad947 --- /dev/null +++ b/Daybreak.Shared/Converters/HexIntConverter.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Converters; + +public sealed class HexIntConverter : JsonConverter +{ + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var hexString = reader.GetString(); + if (hexString is not null && hexString.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + return Convert.ToInt32(hexString[2..], 16); + } + } + + return reader.GetInt32(); + } + + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) + { + writer.WriteStringValue($"0x{value:X4}"); + } +} diff --git a/Daybreak.Shared/Converters/HexUintArrayConverter.cs b/Daybreak.Shared/Converters/HexUintArrayConverter.cs new file mode 100644 index 00000000..99a6c8eb --- /dev/null +++ b/Daybreak.Shared/Converters/HexUintArrayConverter.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Converters; + +public sealed class HexUIntArrayJsonConverter : JsonConverter +{ + private static readonly HexUintConverter ElementConverter = new(); + + public override uint[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException(); + } + + var list = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + list.Add(ElementConverter.Read(ref reader, typeof(uint), options)); + } + + return [.. list]; + } + + public override void Write(Utf8JsonWriter writer, uint[] value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (var item in value) + { + ElementConverter.Write(writer, item, options); + } + + writer.WriteEndArray(); + } +} diff --git a/Daybreak.Shared/Converters/HexUintConverter.cs b/Daybreak.Shared/Converters/HexUintConverter.cs new file mode 100644 index 00000000..be6b9f54 --- /dev/null +++ b/Daybreak.Shared/Converters/HexUintConverter.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Converters; + +public sealed class HexUintConverter : JsonConverter +{ + public override uint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var hexString = reader.GetString(); + if (hexString is not null && hexString.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + return Convert.ToUInt32(hexString[2..], 16); + } + } + + return reader.GetUInt32(); + } + + public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOptions options) + { + writer.WriteStringValue($"0x{value:X4}"); + } +} diff --git a/Daybreak.Shared/Converters/ItemBaseJsonConverter.cs b/Daybreak.Shared/Converters/ItemBaseJsonConverter.cs deleted file mode 100644 index 2b33e455..00000000 --- a/Daybreak.Shared/Converters/ItemBaseJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class ItemBaseJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(ItemBase))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !ItemBase.TryParse(name, out var namedItem)) - { - return default; - } - - return namedItem; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !ItemBase.TryParse((int)id.Value, null, out var parsedItem)) - { - return default; - } - - return parsedItem; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not ItemBase itemBase) - { - return; - } - - writer.WriteValue(itemBase.Id); - } -} diff --git a/Daybreak.Shared/Converters/MapJsonConverter.cs b/Daybreak.Shared/Converters/MapJsonConverter.cs deleted file mode 100644 index a797e434..00000000 --- a/Daybreak.Shared/Converters/MapJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class MapJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Map))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Map.TryParse(name, out var namedMap)) - { - return default; - } - - return namedMap; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Map.TryParse((int)id.Value, out var parsedMap)) - { - return default; - } - - return parsedMap; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Map map) - { - return; - } - - writer.WriteValue(map.Id); - } -} diff --git a/Daybreak.Shared/Converters/NpcJsonConverter.cs b/Daybreak.Shared/Converters/NpcJsonConverter.cs deleted file mode 100644 index 03a268c8..00000000 --- a/Daybreak.Shared/Converters/NpcJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class NpcJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Npc))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Npc.TryParse(name, out var namedNpc)) - { - return default; - } - - return namedNpc; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Npc.TryParse((int)id.Value, out var parsedNpc)) - { - return default; - } - - return parsedNpc; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Npc npc) - { - return; - } - - writer.WriteValue(npc.Ids.FirstOrDefault()); - } -} diff --git a/Daybreak.Shared/Converters/ProfessionJsonConverter.cs b/Daybreak.Shared/Converters/ProfessionJsonConverter.cs deleted file mode 100644 index 08232e2e..00000000 --- a/Daybreak.Shared/Converters/ProfessionJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class ProfessionJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Profession))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Profession.TryParse(name, out var namedProfession)) - { - return default; - } - - return namedProfession; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Profession.TryParse((int)id.Value, out var parsedProfession)) - { - return default; - } - - return parsedProfession; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Profession profession) - { - return; - } - - writer.WriteValue(profession.Id); - } -} diff --git a/Daybreak.Shared/Converters/QuestJsonConverter.cs b/Daybreak.Shared/Converters/QuestJsonConverter.cs deleted file mode 100644 index 640bcc2f..00000000 --- a/Daybreak.Shared/Converters/QuestJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class QuestJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Quest))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Quest.TryParse(name, out var namedQuest)) - { - return default; - } - - return namedQuest; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Quest.TryParse((int)id.Value, out var parsedQuest)) - { - return default; - } - - return parsedQuest; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Quest quest) - { - return; - } - - writer.WriteValue(quest.Id); - } -} diff --git a/Daybreak.Shared/Converters/RegionJsonConverter.cs b/Daybreak.Shared/Converters/RegionJsonConverter.cs deleted file mode 100644 index 29d711d2..00000000 --- a/Daybreak.Shared/Converters/RegionJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class RegionJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Region))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Region.TryParse(name, out var namedRegion)) - { - return default; - } - - return namedRegion; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Region.TryParse((int)id.Value, out var parsedRegion)) - { - return default; - } - - return parsedRegion; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Region region) - { - return; - } - - writer.WriteValue(region.Id); - } -} diff --git a/Daybreak.Shared/Converters/SkillJsonConverter.cs b/Daybreak.Shared/Converters/SkillJsonConverter.cs deleted file mode 100644 index 185a4e56..00000000 --- a/Daybreak.Shared/Converters/SkillJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class SkillJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Skill))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Skill.TryParse(name, out var namedSkill)) - { - return default; - } - - return namedSkill; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Skill.TryParse((int)id.Value, out var parsedSkill)) - { - return default; - } - - return parsedSkill; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Skill skill) - { - return; - } - - writer.WriteValue(skill.Id); - } -} diff --git a/Daybreak.Shared/Converters/ThemeJsonConverter.cs b/Daybreak.Shared/Converters/ThemeJsonConverter.cs index a5125bf3..21463ef4 100644 --- a/Daybreak.Shared/Converters/ThemeJsonConverter.cs +++ b/Daybreak.Shared/Converters/ThemeJsonConverter.cs @@ -1,31 +1,26 @@ using Daybreak.Shared.Models.Themes; using Daybreak.Shared.Services.Themes; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Converters; public sealed class ThemeJsonConverter : JsonConverter { - public override Theme? ReadJson(JsonReader reader, Type objectType, Theme? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override Theme? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType is JsonToken.String) + return reader.TokenType switch { - var themeName = reader.Value?.ToString(); - if (themeName is not null) - { - if (themeName is GameScreenshotsTheme.ThemeName) - { - return Theme.Themes.FirstOrDefault(t => t is GameScreenshotsTheme); - } - - return Theme.Themes.FirstOrDefault(t => t.Name.Equals(themeName, StringComparison.OrdinalIgnoreCase)); - } - } - - throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing theme. Expected a string representing the theme name."); + JsonTokenType.String => reader.GetString() is string themeName + ? themeName is GameScreenshotsTheme.ThemeName + ? Theme.Themes.FirstOrDefault(t => t is GameScreenshotsTheme) + : Theme.Themes.FirstOrDefault(t => t.Name.Equals(themeName, StringComparison.OrdinalIgnoreCase)) + : default, + _ => default + }; } - public override void WriteJson(JsonWriter writer, Theme? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, Theme value, JsonSerializerOptions options) { - writer.WriteValue(value?.Name ?? string.Empty); + writer.WriteStringValue(value.Name); } } diff --git a/Daybreak.Shared/Converters/TitleJsonConverter.cs b/Daybreak.Shared/Converters/TitleJsonConverter.cs deleted file mode 100644 index 2f2a74a5..00000000 --- a/Daybreak.Shared/Converters/TitleJsonConverter.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Daybreak.Shared.Models.Guildwars; -using Newtonsoft.Json; - -namespace Daybreak.Shared.Converters; -public sealed class TitleJsonConverter : JsonConverter -{ - public override bool CanConvert(Type objectType) => true; - - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (!objectType.IsAssignableTo(typeof(Title))) - { - return default; - } - - switch (reader.TokenType) - { - case JsonToken.String: - var name = reader.ReadAsString(); - if (name is null || - !Title.TryParse(name, out var namedTitle)) - { - return default; - } - - return namedTitle; - case JsonToken.Integer: - var id = reader.Value as long?; - if (id is not long || - !Title.TryParse((int)id.Value, out var parsedTitle)) - { - return default; - } - - return parsedTitle; - - default: - return default; - } - } - - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) - { - if (value is not Title title) - { - return; - } - - writer.WriteValue(title.Id); - } -} diff --git a/Daybreak.Shared/Converters/TradeAlertConverter.cs b/Daybreak.Shared/Converters/TradeAlertConverter.cs index 65ba7e91..36ef95e8 100644 --- a/Daybreak.Shared/Converters/TradeAlertConverter.cs +++ b/Daybreak.Shared/Converters/TradeAlertConverter.cs @@ -1,73 +1,78 @@ using Daybreak.Shared.Models.Trade; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Converters; public sealed class TradeAlertConverter : JsonConverter { - public override ITradeAlert? ReadJson(JsonReader reader, Type objectType, ITradeAlert? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override ITradeAlert? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var jObject = JObject.Load(reader).ToObject() ?? throw new InvalidOperationException($"Unable to deserialize {nameof(ITradeAlert)}"); - if (jObject[nameof(TradeAlert.MessageCheck)] is null) + if (reader.TokenType != JsonTokenType.StartObject) { - return new QuoteAlert + throw new JsonException("Expected StartObject token"); + } + + using var jsonDoc = JsonDocument.ParseValue(ref reader); + var root = jsonDoc.RootElement; + + // Discriminate based on presence of MessageCheck property + if (root.TryGetProperty(nameof(TradeAlert.MessageCheck), out _)) + { + return new TradeAlert { - Name = jObject[nameof(QuoteAlert.Name)], - Id = jObject[nameof(QuoteAlert.Id)], - Enabled = jObject[nameof(QuoteAlert.Enabled)], - UpperPriceTarget = jObject[nameof(QuoteAlert.UpperPriceTarget)], - LowerPriceTarget = jObject[nameof(QuoteAlert.LowerPriceTarget)], - UpperPriceTargetEnabled = jObject[nameof(QuoteAlert.UpperPriceTargetEnabled)], - LowerPriceTargetEnabled = jObject[nameof(QuoteAlert.LowerPriceTargetEnabled)], - ItemId = jObject[nameof(QuoteAlert.ItemId)] + Name = root.GetProperty(nameof(TradeAlert.Name)).GetString() ?? string.Empty, + Id = root.GetProperty(nameof(TradeAlert.Id)).GetString() ?? string.Empty, + Enabled = root.GetProperty(nameof(TradeAlert.Enabled)).GetBoolean(), + MessageCheck = root.TryGetProperty(nameof(TradeAlert.MessageCheck), out var msgCheck) ? msgCheck.GetString() : null, + MessageRegexCheck = root.TryGetProperty(nameof(TradeAlert.MessageRegexCheck), out var msgRegex) && msgRegex.GetBoolean(), + SenderCheck = root.TryGetProperty(nameof(TradeAlert.SenderCheck), out var senderCheck) ? senderCheck.GetString() : null, + SenderRegexCheck = root.TryGetProperty(nameof(TradeAlert.SenderRegexCheck), out var senderRegex) && senderRegex.GetBoolean() }; } else { - return new TradeAlert + return new QuoteAlert { - Name = jObject[nameof(TradeAlert.Name)], - Id = jObject[nameof(TradeAlert.Id)], - Enabled = jObject[nameof(TradeAlert.Enabled)], - MessageCheck = jObject[nameof(TradeAlert.MessageCheck)], - MessageRegexCheck = jObject[nameof(TradeAlert.MessageRegexCheck)], - SenderCheck = jObject[nameof(TradeAlert.SenderCheck)], - SenderRegexCheck = jObject[nameof(TradeAlert.SenderRegexCheck)] + Name = root.GetProperty(nameof(QuoteAlert.Name)).GetString() ?? string.Empty, + Id = root.GetProperty(nameof(QuoteAlert.Id)).GetString() ?? string.Empty, + Enabled = root.GetProperty(nameof(QuoteAlert.Enabled)).GetBoolean(), + ItemId = root.TryGetProperty(nameof(QuoteAlert.ItemId), out var itemId) ? itemId.GetInt32() : 0, + TraderQuoteType = root.TryGetProperty(nameof(QuoteAlert.TraderQuoteType), out var quoteType) + ? Enum.Parse(quoteType.GetString() ?? nameof(TraderQuoteType.Buy)) + : default, + UpperPriceTarget = root.TryGetProperty(nameof(QuoteAlert.UpperPriceTarget), out var upperPrice) ? upperPrice.GetInt32() : 0, + UpperPriceTargetEnabled = root.TryGetProperty(nameof(QuoteAlert.UpperPriceTargetEnabled), out var upperEnabled) && upperEnabled.GetBoolean(), + LowerPriceTarget = root.TryGetProperty(nameof(QuoteAlert.LowerPriceTarget), out var lowerPrice) ? lowerPrice.GetInt32() : 0, + LowerPriceTargetEnabled = root.TryGetProperty(nameof(QuoteAlert.LowerPriceTargetEnabled), out var lowerEnabled) && lowerEnabled.GetBoolean() }; } } - public override void WriteJson(JsonWriter writer, ITradeAlert? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, ITradeAlert value, JsonSerializerOptions options) { - if (value is null) - { - serializer.Serialize(writer, null); - return; - } + writer.WriteStartObject(); + + writer.WriteString(nameof(ITradeAlert.Id), value.Id); + writer.WriteString(nameof(ITradeAlert.Name), value.Name); + writer.WriteBoolean(nameof(ITradeAlert.Enabled), value.Enabled); - var jObject = new JObject - { - [nameof(ITradeAlert.Name)] = value.Name, - [nameof(ITradeAlert.Enabled)] = value.Enabled, - [nameof(ITradeAlert.Id)] = value.Id - }; if (value is TradeAlert tradeAlert) { - jObject[nameof(TradeAlert.MessageCheck)] = tradeAlert.MessageCheck; - jObject[nameof(TradeAlert.MessageRegexCheck)] = tradeAlert.MessageRegexCheck; - jObject[nameof(TradeAlert.SenderCheck)] = tradeAlert.SenderCheck; - jObject[nameof(TradeAlert.SenderRegexCheck)] = tradeAlert.SenderRegexCheck; + writer.WriteString(nameof(TradeAlert.MessageCheck), tradeAlert.MessageCheck); + writer.WriteBoolean(nameof(TradeAlert.MessageRegexCheck), tradeAlert.MessageRegexCheck); + writer.WriteString(nameof(TradeAlert.SenderCheck), tradeAlert.SenderCheck); + writer.WriteBoolean(nameof(TradeAlert.SenderRegexCheck), tradeAlert.SenderRegexCheck); } else if (value is QuoteAlert quoteAlert) { - jObject[nameof(QuoteAlert.TraderQuoteType)] = quoteAlert.TraderQuoteType.ToString(); - jObject[nameof(QuoteAlert.ItemId)] = quoteAlert.ItemId; - jObject[nameof(QuoteAlert.UpperPriceTarget)] = quoteAlert.UpperPriceTarget; - jObject[nameof(QuoteAlert.UpperPriceTargetEnabled)] = quoteAlert.UpperPriceTargetEnabled; - jObject[nameof(QuoteAlert.LowerPriceTarget)] = quoteAlert.LowerPriceTarget; - jObject[nameof(QuoteAlert.LowerPriceTargetEnabled)] = quoteAlert.LowerPriceTargetEnabled; + writer.WriteString(nameof(QuoteAlert.TraderQuoteType), quoteAlert.TraderQuoteType.ToString()); + writer.WriteNumber(nameof(QuoteAlert.ItemId), quoteAlert.ItemId); + writer.WriteNumber(nameof(QuoteAlert.UpperPriceTarget), quoteAlert.UpperPriceTarget); + writer.WriteBoolean(nameof(QuoteAlert.UpperPriceTargetEnabled), quoteAlert.UpperPriceTargetEnabled); + writer.WriteNumber(nameof(QuoteAlert.LowerPriceTarget), quoteAlert.LowerPriceTarget); + writer.WriteBoolean(nameof(QuoteAlert.LowerPriceTargetEnabled), quoteAlert.LowerPriceTargetEnabled); } - serializer.Serialize(writer, jObject); + writer.WriteEndObject(); } } diff --git a/Daybreak.Shared/Converters/UnixDateTimeConverter.cs b/Daybreak.Shared/Converters/UnixDateTimeConverter.cs new file mode 100644 index 00000000..f4595568 --- /dev/null +++ b/Daybreak.Shared/Converters/UnixDateTimeConverter.cs @@ -0,0 +1,25 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Converters; + +public sealed class UnixDateTimeConverter : JsonConverter +{ + private static readonly DateTime UnixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.Number => UnixEpoch.AddSeconds(reader.GetInt64()), + JsonTokenType.String when long.TryParse(reader.GetString(), out var seconds) => UnixEpoch.AddSeconds(seconds), + _ => throw new JsonException($"Unable to convert {reader.TokenType} to Unix DateTime") + }; + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + var unixTime = (long)(value.ToUniversalTime() - UnixEpoch).TotalSeconds; + writer.WriteNumberValue(unixTime); + } +} diff --git a/Daybreak.Shared/Daybreak.Shared.csproj b/Daybreak.Shared/Daybreak.Shared.csproj index c53996ef..5a363f03 100644 --- a/Daybreak.Shared/Daybreak.Shared.csproj +++ b/Daybreak.Shared/Daybreak.Shared.csproj @@ -22,5 +22,4 @@ - \ No newline at end of file diff --git a/Daybreak.Shared/Models/Api/ItemEntry.cs b/Daybreak.Shared/Models/Api/ItemEntry.cs index db68cb3e..98858ebb 100644 --- a/Daybreak.Shared/Models/Api/ItemEntry.cs +++ b/Daybreak.Shared/Models/Api/ItemEntry.cs @@ -1,4 +1,8 @@ -namespace Daybreak.Shared.Models.Api; +using Daybreak.Shared.Converters; +using Daybreak.Shared.Models.Guildwars; +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Models.Api; public sealed record ItemEntry( uint ItemModelId, @@ -9,7 +13,9 @@ public sealed record ItemEntry( string EncodedCompleteName, string DecodedCompleteName, string ItemType, + bool Inscribable, int Quantity, - uint[] Modifiers) + [property: JsonConverter(typeof(HexUIntArrayJsonConverter))] uint[] Modifiers, + ItemProperty[] Properties) { } diff --git a/Daybreak.Shared/Models/Browser/BrowserHistory.cs b/Daybreak.Shared/Models/Browser/BrowserHistory.cs deleted file mode 100644 index 9b1487a9..00000000 --- a/Daybreak.Shared/Models/Browser/BrowserHistory.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Daybreak.Shared.Models.Browser; -public sealed class BrowserHistory -{ - public List History { get; set; } = []; - public int CurrentPosition { get; set; } = -1; -} diff --git a/Daybreak.Shared/Models/Browser/BrowserPayload.cs b/Daybreak.Shared/Models/Browser/BrowserPayload.cs deleted file mode 100644 index 1a515cf0..00000000 --- a/Daybreak.Shared/Models/Browser/BrowserPayload.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace Daybreak.Shared.Models.Browser; - -public class BrowserPayload -{ - [JsonConverter(typeof(StringEnumConverter))] - public enum PayloadKeys - { - None, - ContextMenu, - XButton1Pressed, - XButton2Pressed - } - - [JsonProperty(nameof(Key))] - public PayloadKeys Key { get; set; } -} - -public sealed class BrowserPayload : BrowserPayload -{ - [JsonProperty(nameof(Value))] - public T? Value { get; set; } -} diff --git a/Daybreak.Shared/Models/Browser/DownloadPayload.cs b/Daybreak.Shared/Models/Browser/DownloadPayload.cs deleted file mode 100644 index 4739d059..00000000 --- a/Daybreak.Shared/Models/Browser/DownloadPayload.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Daybreak.Shared.Models.Browser; - -public sealed class DownloadPayload -{ - public string? ResultingFilePath { get; init; } - public bool CanDownload { get; set; } -} diff --git a/Daybreak.Shared/Models/Browser/OnContextMenuPayload.cs b/Daybreak.Shared/Models/Browser/OnContextMenuPayload.cs deleted file mode 100644 index 23c4379d..00000000 --- a/Daybreak.Shared/Models/Browser/OnContextMenuPayload.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Newtonsoft.Json; - -namespace Daybreak.Shared.Models.Browser; - -public sealed class OnContextMenuPayload -{ - [JsonProperty(nameof(X))] - public double X { get; set; } - [JsonProperty(nameof(Y))] - public double Y { get; set; } - [JsonProperty(nameof(Selection))] - public string? Selection { get; set; } - [JsonProperty(nameof(Url))] - public string? Url { get; set; } -} diff --git a/Daybreak.Shared/Models/Github/GithubRefTag.cs b/Daybreak.Shared/Models/Github/GithubRefTag.cs index d1bd3c32..d162c110 100644 --- a/Daybreak.Shared/Models/Github/GithubRefTag.cs +++ b/Daybreak.Shared/Models/Github/GithubRefTag.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Github; public sealed class GithubRefTag { - [JsonProperty("ref")] - public string? Ref; - [JsonProperty("node_id")] - public string? NodeId; - [JsonProperty("url")] - public string? Url; + [JsonPropertyName("ref")] + public string? Ref { get; set; } + [JsonPropertyName("node_id")] + public string? NodeId { get; set; } + [JsonPropertyName("url")] + public string? Url { get; set; } } diff --git a/Daybreak.Shared/Models/Guildwars/Attribute.cs b/Daybreak.Shared/Models/Guildwars/Attribute.cs index ce4ef413..8ab66387 100644 --- a/Daybreak.Shared/Models/Guildwars/Attribute.cs +++ b/Daybreak.Shared/Models/Guildwars/Attribute.cs @@ -1,10 +1,8 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(AttributeJsonConverter))] public sealed class Attribute { public static readonly Attribute None = new() { Name = "None", Id = -1, Profession = Profession.None }; @@ -137,10 +135,18 @@ public sealed class Attribute return attribute; } + [JsonPropertyName("id")] public int Id { get; private set; } + + [JsonPropertyName("name")] public string? Name { get; private set; } + + [JsonPropertyName("alternativeName")] public string? AlternativeName { get; private set; } + + [JsonPropertyName("profession")] public Profession? Profession { get; private set; } + public override string ToString() => this.Name ?? string.Empty; private Attribute() { diff --git a/Daybreak.Shared/Models/Guildwars/BuildEntryBase.cs b/Daybreak.Shared/Models/Guildwars/BuildEntryBase.cs index 39df3d74..0df1753c 100644 --- a/Daybreak.Shared/Models/Guildwars/BuildEntryBase.cs +++ b/Daybreak.Shared/Models/Guildwars/BuildEntryBase.cs @@ -1,7 +1,7 @@ using Daybreak.Shared.Models.Builds; using Daybreak.Shared.Utils; -using Newtonsoft.Json; using System.ComponentModel; +using System.Text.Json; namespace Daybreak.Shared.Models.Guildwars; @@ -102,7 +102,7 @@ public abstract class BuildEntryBase : INotifyPropertyChanged, IBuildEntry if (this.Metadata?.TryGetValue(nameof(this.PartyComposition), out var serializedPartyComposition) is true && !string.IsNullOrWhiteSpace(serializedPartyComposition)) { - return JsonConvert.DeserializeObject>(serializedPartyComposition); + return JsonSerializer.Deserialize>(serializedPartyComposition); } return default; @@ -112,7 +112,7 @@ public abstract class BuildEntryBase : INotifyPropertyChanged, IBuildEntry this.Metadata ??= []; this.Metadata[nameof(this.PartyComposition)] = value is null ? string.Empty - : JsonConvert.SerializeObject(value, Formatting.None); + : JsonSerializer.Serialize(value); this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.PartyComposition))); } } diff --git a/Daybreak.Shared/Models/Guildwars/Campaign.cs b/Daybreak.Shared/Models/Guildwars/Campaign.cs index 60e7fba1..f578f9f8 100644 --- a/Daybreak.Shared/Models/Guildwars/Campaign.cs +++ b/Daybreak.Shared/Models/Guildwars/Campaign.cs @@ -1,10 +1,8 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(CampaignJsonConverter))] public sealed class Campaign { public static Campaign None { get; } = new() @@ -137,8 +135,15 @@ public sealed class Campaign { } + [JsonPropertyName("id")] public int Id { get; init; } + + [JsonPropertyName("name")] public string? Name { get; init; } + + [JsonPropertyName("wikiUrl")] public string? WikiUrl { get; init; } + + [JsonPropertyName("continents")] public IReadOnlyList? Continents { get; init; } } diff --git a/Daybreak.Shared/Models/Guildwars/ConditionType.cs b/Daybreak.Shared/Models/Guildwars/ConditionType.cs new file mode 100644 index 00000000..5e4e35ea --- /dev/null +++ b/Daybreak.Shared/Models/Guildwars/ConditionType.cs @@ -0,0 +1,8 @@ +namespace Daybreak.Shared.Models.Guildwars; + +public enum ConditionType +{ + Bleed, + Blind, + Cripple = 3 +} diff --git a/Daybreak.Shared/Models/Guildwars/Continent.cs b/Daybreak.Shared/Models/Guildwars/Continent.cs index 275ee2b8..141c5708 100644 --- a/Daybreak.Shared/Models/Guildwars/Continent.cs +++ b/Daybreak.Shared/Models/Guildwars/Continent.cs @@ -1,9 +1,7 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(ContinentJsonConverter))] public sealed class Continent { public static Continent Tyria { get; } = new Continent @@ -145,8 +143,15 @@ public sealed class Continent { } + [JsonPropertyName("id")] public int Id { get; init; } + + [JsonPropertyName("name")] public string? Name { get; init; } + + [JsonPropertyName("wikiUrl")] public string? WikiUrl { get; init; } + + [JsonPropertyName("regions")] public IReadOnlyList? Regions { get; init; } } diff --git a/Daybreak.Shared/Models/Guildwars/DamageType.cs b/Daybreak.Shared/Models/Guildwars/DamageType.cs new file mode 100644 index 00000000..a7cbab81 --- /dev/null +++ b/Daybreak.Shared/Models/Guildwars/DamageType.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Models.Guildwars; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum DamageType +{ + Blunt, + Piercing, + Slashing, + Icy, + Shocking, + Fiery, + Chaotic, + Unholy, + Holy, + Wooden, + Sacrificial, + Ebon, + Magical, + UnholyDupe, + Count, + None = 0xff +} diff --git a/Daybreak.Shared/Models/Guildwars/ItemBaneSpecies.cs b/Daybreak.Shared/Models/Guildwars/ItemBaneSpecies.cs new file mode 100644 index 00000000..b31be592 --- /dev/null +++ b/Daybreak.Shared/Models/Guildwars/ItemBaneSpecies.cs @@ -0,0 +1,17 @@ +namespace Daybreak.Shared.Models.Guildwars; + +public enum ItemBaneSpecies +{ + Deathbane, + Charrslaying, + Trollslaying, + Pruning, + Skeletonslaying, + Giantslaying, + Dwarslaying, + Tenguslaying, + Demonslaying, + Dragonslaying, + Ogreslaying, + Unknown = -1, +} diff --git a/Daybreak.Shared/Models/Guildwars/ItemBase.cs b/Daybreak.Shared/Models/Guildwars/ItemBase.cs index cff19b38..d8c1b03c 100644 --- a/Daybreak.Shared/Models/Guildwars/ItemBase.cs +++ b/Daybreak.Shared/Models/Guildwars/ItemBase.cs @@ -1,9 +1,7 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(ItemBaseJsonConverter))] public abstract class ItemBase { public static IReadOnlyCollection AllItems { get; } = Enumerable.Empty() @@ -134,10 +132,13 @@ public abstract class ItemBase .Where(filter) .FirstOrDefault(); + [JsonPropertyName("id")] public int Id { get; init; } + [JsonPropertyName("name")] public string? Name { get; init; } + [JsonPropertyName("modifiers")] public IEnumerable? Modifiers { get; init; } public sealed class Unknown : ItemBase diff --git a/Daybreak.Shared/Models/Guildwars/ItemModifier.cs b/Daybreak.Shared/Models/Guildwars/ItemModifier.cs index 04307cd1..23e8a89e 100644 --- a/Daybreak.Shared/Models/Guildwars/ItemModifier.cs +++ b/Daybreak.Shared/Models/Guildwars/ItemModifier.cs @@ -1,11 +1,88 @@ namespace Daybreak.Shared.Models.Guildwars; + public readonly struct ItemModifier { public uint Modifier { get; init; } - public uint Identifier => this.Modifier >> 16; - public uint Argument1 => (this.Modifier & 0x0000FF00) >> 8; - public uint Argument2 => this.Modifier & 0x000000FF; + public uint Flags => (this.Modifier >> 30) & 0xC; + public ItemModifierIdentifier Identifier => (ItemModifierIdentifier)((this.Modifier >> 20) & 0x3FF); + public ItemModifierParam Param => (ItemModifierParam)((this.Modifier >> 16) & 0xF); + public uint UpgradeId => this.Modifier & 0xFFFF; + public uint Argument1 => (this.Modifier >> 8) & 0xFF; + public uint Argument2 => this.Modifier & 0xFF; public static implicit operator uint(ItemModifier modifier) => modifier.Modifier; public static implicit operator ItemModifier(uint modifier) => new(){ Modifier = modifier }; } + +public enum ItemModifierIdentifier : uint +{ + BaneSpecies = 0x008, // Dmg increased vs species (arg1) + Attribute = 0x279, // Requires points in attribute (arg2) (arg1) + Damage = 0x27A, // Min damage - Max damage (arg2) (arg1) + Armor1 = 0x27B, // Armor value (arg1) + Armor2 = 0x23C, // Armor value (arg1) + Energy = 0x27C, // Energy value (arg1) + Upgrade1 = 0x21E, // Suffix/Prefix/Inscription upgrade (upgrade id) + Upgrade2 = 0x240, // Suffix/Prefix/Inscription upgrade (upgrade id) + OfTheProfession = 0x28A, // +5 to primary of profession (arg2) (arg1) + DamageType = 0x24B, // Damage type (arg1) + + DamagePlusCustomized = 0x249, // 120 for +20% damage (Customized) (arg2) + + DamagePlus = 0x223, // +20% damage (arg2) + DamagePlusVsHexed = 0x225, // +15% damage vs hexed (arg2) + DamagePlusEnchanted = 0x226, // +15% damage while enchanted (arg2) + DamagePlusWhileUp = 0x227, // +10% damage while above 50% HP (arg2) + DamagePlusWhileDown = 0x228, // +10% damage while below 50% HP (arg2) + DamagePlusHexed = 0x229, // +15% damage while hexed (arg2) + DamagePlusStance = 0x22A, // +15% damage while in stance (arg2) + + HalvesCastingTimeGeneral = 0x220, // 10 for 10% chance to halve casting time for any skill (arg1) + HalvesCastingTimeAttribute = 0x221, // 20 for 20% chance to halve casting time for attribute skill (arg1) (arg2) + HalvesCastingTimeItemAttribute = 0x280, // 10 for 10% chance to halve casting time for attribute of equipped item (arg1) + + HalvesSkillRechargeGeneral = 0x23A, // 10 for 10% skill recharge for any skill (arg1) + HalvesSkillRechargeAttribute = 0x239, // 20 for 20% skill recharge for attribute skill (arg1) (arg2) + HalvesSkillRechargeItemAttribute = 0x282, // 10 for 10% skill recharge for attribute of equipped item (arg1) + + EnergyPlus = 0x22D, // 15 for +15 energy (arg2) + EnergyPlusEnchanted = 0x22F, // 10 for +10 energy while enchanted (arg2) + EnergyPlusHexed = 0x232, // 10 for +10 energy while hexed (arg2) + + EnergyMinus = 0x20B, // -5 energy (arg2) + EnergyDegen = 0x20C, // -1 energy regen (arg2) + EnergyRegen = 0x262, // 1 energy regen (arg2) + + ArmorPlus = 0x210, // +5 armor (arg2) + ArmorPlusVsDamage = 0x211, // +10 armor vs damage type attacks (arg2) (arg1) + ArmorPlusVsDamage2 = 0xA11, // +10 armor vs damage type attacks (arg2) (arg1) + ArmorPlusVsSpecies = 0x214, // +10 armor vs species attacks (arg2) (arg1) + ArmorPlusAttacking = 0x217, // +5 armor while attacking (arg2) + ArmorPlusCasting = 0x218, // +5 armor while casting (arg2) + ArmorPlusEnchanted = 0x219, // +5 armor while enchanted (arg2) + ArmorPlusHexed = 0x21A, // +5 armor while hexed (arg2) + ArmorPlusWhileDown = 0x21B, // +5 armor while < 50% HP (arg2) + + ArmorMinusAttacking = 0x201, // -5 armor while attacking (arg2) + + HealthPlus = 0x289, // +60 hp (arg2) + HealthPlusWhileDown = 0x230, // +20 health while below 50% HP (arg2) (arg1) + HealthMinus = 0x20D, // -20 health (arg2) + + ReceiveLessDamage = 0x207, // Receive 5 less damage (20%) (arg2) (arg1) + ReceiveLessPhysDamageEnchanted = 0x208, // Receive 2 less physical damage while enchanted (arg2) + ReceiveLessPhysDamageHexed = 0x209, // Receive 3 less physical damage while hexed (arg2) + ReceiveLessPhysDamageStance = 0x20A, // Receive 2 less physical damage while in stance (arg2) + + AttributePlusOne = 0x241, // +1 to attribute (20% chance) (arg1) (arg2) + AttributePlusOneItem = 0x283, // +1 to attribute of equipped item (20% chance) (arg1) + + ReduceConditionDuration = 0x285, // Reduce condition duration by 20% (arg1) +} + +// Parameter values for ItemModifier. Description only currently known. +public enum ItemModifierParam : uint +{ + LabelInName = 0x0, + Description = 0x8 +} diff --git a/Daybreak.Shared/Models/Guildwars/ItemProperty.cs b/Daybreak.Shared/Models/Guildwars/ItemProperty.cs new file mode 100644 index 00000000..34c4272d --- /dev/null +++ b/Daybreak.Shared/Models/Guildwars/ItemProperty.cs @@ -0,0 +1,511 @@ +using System.Collections.Immutable; +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Models.Guildwars; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "propertyType")] +[JsonDerivedType(typeof(BaneProperty), "Bane")] +[JsonDerivedType(typeof(RequirementProperty), "Requirement")] +[JsonDerivedType(typeof(DamageProperty), "Damage")] +[JsonDerivedType(typeof(ArmorProperty), "Armor")] +[JsonDerivedType(typeof(EnergyProperty), "Energy")] +[JsonDerivedType(typeof(SuffixProperty), "Suffix")] +[JsonDerivedType(typeof(PrefixProperty), "Prefix")] +[JsonDerivedType(typeof(InscriptionProperty), "Inscription")] +[JsonDerivedType(typeof(UpgradesRuneProperty), "UpgradesRune")] +[JsonDerivedType(typeof(AppliesToRuneProperty), "AppliesToRune")] +[JsonDerivedType(typeof(OfTheProfessionProperty), "OfTheProfession")] +[JsonDerivedType(typeof(CustomizedProperty), "Customized")] +[JsonDerivedType(typeof(DamageTypeProperty), "DamageType")] +[JsonDerivedType(typeof(DamagePlusProperty), "DamagePlus")] +[JsonDerivedType(typeof(DamagePlusVsHexedProperty), "DamagePlusVsHexed")] +[JsonDerivedType(typeof(DamagePlusWhileEnchantedProperty), "DamagePlusWhileEnchanted")] +[JsonDerivedType(typeof(DamagePlusWhileUpProperty), "DamagePlusWhileUp")] +[JsonDerivedType(typeof(DamagePlusWhileDownProperty), "DamagePlusWhileDown")] +[JsonDerivedType(typeof(DamagePlusWhileHexedProperty), "DamagePlusWhileHexed")] +[JsonDerivedType(typeof(DamagePlusWhileInStanceProperty), "DamagePlusWhileInStance")] +[JsonDerivedType(typeof(HalvesCastingTimeGeneralProperty), "HalvesCastingTimeGeneral")] +[JsonDerivedType(typeof(HalvesCastingTimeAttributeProperty), "HalvesCastingTimeAttribute")] +[JsonDerivedType(typeof(HalvesCastingTimeItemAttributeProperty), "HalvesCastingTimeItemAttribute")] +[JsonDerivedType(typeof(HalvesSkillRechargeGeneralProperty), "HalvesSkillRechargeGeneral")] +[JsonDerivedType(typeof(HalvesSkillRechargeAttributeProperty), "HalvesSkillRechargeAttribute")] +[JsonDerivedType(typeof(HalvesSkillRechargeItemAttributeProperty), "HalvesSkillRechargeItemAttribute")] +[JsonDerivedType(typeof(EnergyPlusProperty), "EnergyPlus")] +[JsonDerivedType(typeof(EnergyPlusWhileEnchantedProperty), "EnergyPlusWhileEnchanted")] +[JsonDerivedType(typeof(EnergyPlusWhileHexedProperty), "EnergyPlusWhileHexed")] +[JsonDerivedType(typeof(EnergyMinusProperty), "EnergyMinus")] +[JsonDerivedType(typeof(EnergyDegenProperty), "EnergyDegen")] +[JsonDerivedType(typeof(EnergyRegenProperty), "EnergyRegen")] +[JsonDerivedType(typeof(ArmorPlusProperty), "ArmorPlus")] +[JsonDerivedType(typeof(ArmorPlusVsDamageProperty), "ArmorPlusVsDamage")] +[JsonDerivedType(typeof(ArmorPlusVsSpeciesProperty), "ArmorPlusVsSpecies")] +[JsonDerivedType(typeof(ArmorPlusWhileAttackingProperty), "ArmorPlusWhileAttacking")] +[JsonDerivedType(typeof(ArmorPlusWhileCastingProperty), "ArmorPlusWhileCasting")] +[JsonDerivedType(typeof(ArmorPlusWhileEnchantedProperty), "ArmorPlusWhileEnchanted")] +[JsonDerivedType(typeof(ArmorPlusWhileHexedProperty), "ArmorPlusWhileHexed")] +[JsonDerivedType(typeof(ArmorPlusWhileDownProperty), "ArmorPlusWhileDown")] +[JsonDerivedType(typeof(ArmorMinusWhileAttackingProperty), "ArmorMinusWhileAttacking")] +[JsonDerivedType(typeof(HealthPlusProperty), "HealthPlus")] +[JsonDerivedType(typeof(HealthPlusWhileDownProperty), "HealthPlusWhileDown")] +[JsonDerivedType(typeof(HealthMinusProperty), "HealthMinus")] +[JsonDerivedType(typeof(ReceiveLessDamageProperty), "ReceiveLessDamage")] +[JsonDerivedType(typeof(ReceiveLessPhysDamageWhileEnchantedProperty), "ReceiveLessPhysDamageWhileEnchanted")] +[JsonDerivedType(typeof(ReceiveLessPhysDamageWhileHexedProperty), "ReceiveLessPhysDamageWhileHexed")] +[JsonDerivedType(typeof(ReceiveLessPhysDamageWhileStanceProperty), "ReceiveLessPhysDamageWhileStance")] +[JsonDerivedType(typeof(AttributePlusOneProperty), "AttributePlusOne")] +[JsonDerivedType(typeof(AttributePlusOneItemProperty), "AttributePlusOneItem")] +[JsonDerivedType(typeof(ReduceConditionDurationProperty), "ReduceConditionDuration")] +[JsonDerivedType(typeof(UnknownUpgradeProperty), "Unknown")] +public abstract class ItemProperty +{ + public static ImmutableArray ParseItemModifiers(ImmutableArray modifiers) + { + return [.. modifiers.Select( + m => m.Identifier switch + { + ItemModifierIdentifier.BaneSpecies => new BaneProperty { BaneSpecies = (ItemBaneSpecies)m.Argument1 }, + ItemModifierIdentifier.Attribute => new RequirementProperty { Attribute = ParseAttributeName(m.Argument1), Requirement = (int)m.Argument2 }, + ItemModifierIdentifier.Damage => new DamageProperty { MinDamage = (int)m.Argument2, MaxDamage = (int)m.Argument1 }, + ItemModifierIdentifier.Armor1 => new ArmorProperty { Armor =(int)m.Argument1 }, + ItemModifierIdentifier.Armor2 => new ArmorProperty { Armor = (int)m.Argument1 }, + ItemModifierIdentifier.Energy => new EnergyProperty { Energy = (int)m.Argument1 }, + ItemModifierIdentifier.Upgrade1 => ParseUpgradeProperty(m), + ItemModifierIdentifier.Upgrade2 => ParseUpgradeProperty(m), + ItemModifierIdentifier.OfTheProfession => ParseOfTheProfessionProperty(m), + ItemModifierIdentifier.DamageType => new DamageTypeProperty { DamageType = (DamageType)m.Argument1 }, + ItemModifierIdentifier.DamagePlusCustomized when m.Argument1 is 120 => new CustomizedProperty(), + + ItemModifierIdentifier.DamagePlus => new DamagePlusProperty { Percentage = (int)m.Argument2 }, + ItemModifierIdentifier.DamagePlusVsHexed => new DamagePlusVsHexedProperty { Percentage = (int)m.Argument2 }, + ItemModifierIdentifier.DamagePlusEnchanted => new DamagePlusWhileEnchantedProperty { Percentage = (int)m.Argument2 }, + ItemModifierIdentifier.DamagePlusWhileUp => new DamagePlusWhileUpProperty { Percentage = (int)m.Argument2 }, + ItemModifierIdentifier.DamagePlusWhileDown => new DamagePlusWhileDownProperty { Percentage = (int)m.Argument2 }, + ItemModifierIdentifier.DamagePlusHexed => new DamagePlusVsHexedProperty { Percentage = (int)m.Argument2 }, + ItemModifierIdentifier.DamagePlusStance => new DamagePlusVsHexedProperty { Percentage = (int)m.Argument2 }, + + ItemModifierIdentifier.HalvesCastingTimeGeneral => new HalvesCastingTimeGeneralProperty { Chance = (int)m.Argument1 }, + ItemModifierIdentifier.HalvesCastingTimeAttribute => new HalvesCastingTimeAttributeProperty { Chance = (int)m.Argument1, Attribute = ParseAttributeName(m.Argument2) }, + ItemModifierIdentifier.HalvesCastingTimeItemAttribute => new HalvesCastingTimeItemAttributeProperty { Chance = (int)m.Argument1 }, + + ItemModifierIdentifier.HalvesSkillRechargeGeneral => new HalvesSkillRechargeGeneralProperty { Chance = (int)m.Argument1 }, + ItemModifierIdentifier.HalvesSkillRechargeAttribute => new HalvesSkillRechargeAttributeProperty { Chance = (int)m.Argument1, Attribute = ParseAttributeName(m.Argument2) }, + ItemModifierIdentifier.HalvesSkillRechargeItemAttribute => new HalvesSkillRechargeItemAttributeProperty { Chance = (int)m.Argument1 }, + + ItemModifierIdentifier.EnergyPlus => new EnergyPlusProperty { Energy = (int)m.Argument2 }, + ItemModifierIdentifier.EnergyPlusEnchanted => new EnergyPlusWhileEnchantedProperty { Energy = (int)m.Argument2 }, + ItemModifierIdentifier.EnergyPlusHexed => new EnergyPlusWhileHexedProperty { Energy = (int)m.Argument2 }, + + ItemModifierIdentifier.EnergyMinus => new EnergyMinusProperty { Energy = (int)m.Argument2 }, + ItemModifierIdentifier.EnergyDegen => new EnergyDegenProperty { EnergyDegen = (int)m.Argument2 }, + ItemModifierIdentifier.EnergyRegen => new EnergyRegenProperty { EnergyRegen = (int)m.Argument2 }, + + ItemModifierIdentifier.ArmorPlus => new ArmorPlusProperty { Armor = (int)m.Argument2 }, + ItemModifierIdentifier.ArmorPlusVsDamage => new ArmorPlusVsDamageProperty { Armor = (int)m.Argument2, DamageType = (DamageType)m.Argument1 }, + ItemModifierIdentifier.ArmorPlusVsDamage2 => new ArmorPlusVsDamageProperty { Armor = (int)m.Argument2, DamageType = (DamageType)m.Argument1 }, + ItemModifierIdentifier.ArmorPlusVsSpecies => new ArmorPlusVsSpeciesProperty { Armor = (int)m.Argument2, Species = (ItemBaneSpecies)m.Argument1 }, + ItemModifierIdentifier.ArmorPlusAttacking => new ArmorPlusWhileAttackingProperty { Armor = (int)m.Argument2 }, + ItemModifierIdentifier.ArmorPlusCasting => new ArmorPlusWhileCastingProperty { Armor = (int)m.Argument2 }, + ItemModifierIdentifier.ArmorPlusEnchanted => new ArmorPlusWhileEnchantedProperty { Armor = (int)m.Argument2 }, + ItemModifierIdentifier.ArmorPlusHexed => new ArmorPlusWhileHexedProperty { Armor = (int)m.Argument2 }, + ItemModifierIdentifier.ArmorPlusWhileDown => new ArmorPlusWhileDownProperty { Armor = (int)m.Argument2 }, + + ItemModifierIdentifier.ArmorMinusAttacking => new ArmorMinusWhileAttackingProperty { Armor = (int)m.Argument2 }, + + ItemModifierIdentifier.HealthPlus => new HealthPlusProperty { Health = (int)m.Argument2 }, + ItemModifierIdentifier.HealthPlusWhileDown => new HealthPlusWhileDownProperty { Health = (int)m.Argument2, HealthThreshold = (int)m.Argument1 }, + ItemModifierIdentifier.HealthMinus => new HealthMinusProperty { Health = (int)m.Argument2 }, + + ItemModifierIdentifier.ReceiveLessDamage => new ReceiveLessDamageProperty { LessDamage = (int)m.Argument2, Chance = (int)m.Argument1 }, + ItemModifierIdentifier.ReceiveLessPhysDamageEnchanted => new ReceiveLessPhysDamageWhileEnchantedProperty { LessDamage = (int)m.Argument2 }, + ItemModifierIdentifier.ReceiveLessPhysDamageHexed => new ReceiveLessPhysDamageWhileHexedProperty { LessDamage = (int)m.Argument2 }, + ItemModifierIdentifier.ReceiveLessPhysDamageStance => new ReceiveLessPhysDamageWhileStanceProperty { LessDamage = (int)m.Argument2 }, + + ItemModifierIdentifier.AttributePlusOne => new AttributePlusOneProperty { Attribute = ParseAttributeName(m.Argument1), Chance = (int)m.Argument2 }, + ItemModifierIdentifier.AttributePlusOneItem => new AttributePlusOneItemProperty { Chance = (int)m.Argument1 }, + + ItemModifierIdentifier.ReduceConditionDuration => new ReduceConditionDurationProperty { Condition = (ConditionType)m.Argument1 }, + _ => default + }) + .OfType()]; + } + + private static OfTheProfessionProperty ParseOfTheProfessionProperty(ItemModifier m) + { + if (!Attribute.TryParse((int)m.Argument1, out var attribute) || + attribute == Attribute.None) + { + return new OfTheProfessionProperty { Profession = Profession.None.Name ?? string.Empty, Attribute = Attribute.None.Name ?? string.Empty, Value = 0 }; + } + + return new OfTheProfessionProperty { Profession = (attribute.Profession ?? Profession.None).Name ?? string.Empty, Attribute = attribute.Name ?? string.Empty, Value = (int)m.Argument2 }; + } + + private static ItemProperty ParseUpgradeProperty(ItemModifier m) + { + if (!ItemUpgrade.TryParse((int)m.UpgradeId, out var upgrade) || + upgrade == ItemUpgrade.Unknown) + { + return new UnknownUpgradeProperty { RawModifier = m }; + } + + return upgrade.Type switch + { + ItemUpgradeType.Suffix => new SuffixProperty { Upgrade = upgrade }, + ItemUpgradeType.Prefix => new PrefixProperty { Upgrade = upgrade }, + ItemUpgradeType.Inscription => new InscriptionProperty { Upgrade = upgrade }, + ItemUpgradeType.UpgradeRune => new UpgradesRuneProperty { Upgrade = upgrade }, + ItemUpgradeType.AppliesToRune => new AppliesToRuneProperty { Upgrade = upgrade }, + _ => new UnknownUpgradeProperty { RawModifier = m } + }; + } + + private static string ParseAttributeName(uint arg) + { + return Attribute.TryParse((int)arg, out var attribute) + ? attribute.Name ?? string.Empty + : arg is 45 ? "Any Casting Primary" : Attribute.None.Name ?? string.Empty; + } +} + +public sealed class UnknownUpgradeProperty : ItemProperty +{ + [JsonPropertyName("rawModifier")] + public required ItemModifier RawModifier { get; init; } +} + +public sealed class BaneProperty : ItemProperty +{ + [JsonPropertyName("baneSpecies")] + public required ItemBaneSpecies BaneSpecies { get; init; } +} + +public sealed class RequirementProperty : ItemProperty +{ + [JsonPropertyName("attribute")] + public required string Attribute { get; init; } + + [JsonPropertyName("requirement")] + public required int Requirement { get; init; } +} + +public sealed class DamageProperty : ItemProperty +{ + [JsonPropertyName("minDamage")] + public required int MinDamage { get; init; } + + [JsonPropertyName("maxDamage")] + public required int MaxDamage { get; init; } +} + +public sealed class ArmorProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class EnergyProperty : ItemProperty +{ + [JsonPropertyName("energy")] + public required int Energy { get; init; } +} + +public sealed class SuffixProperty : ItemProperty +{ + [JsonPropertyName("upgrade")] + public required ItemUpgrade Upgrade { get; init; } +} + +public sealed class PrefixProperty : ItemProperty +{ + [JsonPropertyName("upgrade")] + public required ItemUpgrade Upgrade { get; init; } +} + +public sealed class InscriptionProperty : ItemProperty +{ + [JsonPropertyName("upgrade")] + public required ItemUpgrade Upgrade { get; init; } +} + +public sealed class UpgradesRuneProperty : ItemProperty +{ + [JsonPropertyName("upgrade")] + public required ItemUpgrade Upgrade { get; init; } +} + +public sealed class AppliesToRuneProperty : ItemProperty +{ + [JsonPropertyName("upgrade")] + public required ItemUpgrade Upgrade { get; init; } +} + +public sealed class OfTheProfessionProperty : ItemProperty +{ + [JsonPropertyName("profession")] + public required string Profession { get; init; } + + [JsonPropertyName("attribute")] + public required string Attribute { get; init; } + + [JsonPropertyName("value")] + public required int Value { get; init; } +} + +public sealed class CustomizedProperty : ItemProperty +{ +} + +public sealed class DamageTypeProperty : ItemProperty +{ + [JsonPropertyName("damageType")] + public required DamageType DamageType { get; init; } +} + +public sealed class DamagePlusProperty : ItemProperty +{ + [JsonPropertyName("percentage")] + public required int Percentage { get; init; } +} + +public sealed class DamagePlusVsHexedProperty : ItemProperty +{ + [JsonPropertyName("percentage")] + public required int Percentage { get; init; } +} + +public sealed class DamagePlusWhileEnchantedProperty : ItemProperty +{ + [JsonPropertyName("percentage")] + public required int Percentage { get; init; } +} + +public sealed class DamagePlusWhileUpProperty : ItemProperty +{ + [JsonPropertyName("percentage")] + public required int Percentage { get; init; } +} + +public sealed class DamagePlusWhileDownProperty : ItemProperty +{ + [JsonPropertyName("percentage")] + public required int Percentage { get; init; } +} + +public sealed class DamagePlusWhileHexedProperty : ItemProperty +{ + [JsonPropertyName("percentage")] + public required int Percentage { get; init; } +} + +public sealed class DamagePlusWhileInStanceProperty : ItemProperty +{ + [JsonPropertyName("percentage")] + public required int Percentage { get; init; } +} + +public sealed class HalvesCastingTimeGeneralProperty : ItemProperty +{ + [JsonPropertyName("chance")] + public required int Chance { get; init; } +} + +public sealed class HalvesCastingTimeAttributeProperty : ItemProperty +{ + [JsonPropertyName("attribute")] + public required string Attribute { get; init; } + + [JsonPropertyName("chance")] + public required int Chance { get; init; } +} + +public sealed class HalvesCastingTimeItemAttributeProperty : ItemProperty +{ + [JsonPropertyName("chance")] + public required int Chance { get; init; } +} + +public sealed class HalvesSkillRechargeGeneralProperty : ItemProperty +{ + [JsonPropertyName("chance")] + public required int Chance { get; init; } +} + +public sealed class HalvesSkillRechargeAttributeProperty : ItemProperty +{ + [JsonPropertyName("attribute")] + public required string Attribute { get; init; } + + [JsonPropertyName("chance")] + public required int Chance { get; init; } +} + +public sealed class HalvesSkillRechargeItemAttributeProperty : ItemProperty +{ + [JsonPropertyName("chance")] + public required int Chance { get; init; } +} + +public sealed class EnergyPlusProperty : ItemProperty +{ + [JsonPropertyName("energy")] + public required int Energy { get; init; } +} + +public sealed class EnergyPlusWhileEnchantedProperty : ItemProperty +{ + [JsonPropertyName("energy")] + public required int Energy { get; init; } +} + +public sealed class EnergyPlusWhileHexedProperty : ItemProperty +{ + [JsonPropertyName("energy")] + public required int Energy { get; init; } +} + +public sealed class EnergyMinusProperty : ItemProperty +{ + [JsonPropertyName("energy")] + public required int Energy { get; init; } +} + +public sealed class EnergyDegenProperty : ItemProperty +{ + [JsonPropertyName("energyRegen")] + public required int EnergyDegen { get; init; } +} + +public sealed class EnergyRegenProperty : ItemProperty +{ + [JsonPropertyName("energyRegen")] + public required int EnergyRegen { get; init; } +} + +public sealed class ArmorPlusProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorPlusVsDamageProperty : ItemProperty +{ + [JsonPropertyName("damageType")] + public required DamageType DamageType { get; init; } + + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorPlusVsSpeciesProperty : ItemProperty +{ + [JsonPropertyName("species")] + public required ItemBaneSpecies Species { get; init; } + + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorPlusWhileAttackingProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorPlusWhileCastingProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorPlusWhileEnchantedProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorPlusWhileHexedProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorPlusWhileDownProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class ArmorMinusWhileAttackingProperty : ItemProperty +{ + [JsonPropertyName("armor")] + public required int Armor { get; init; } +} + +public sealed class HealthPlusProperty : ItemProperty +{ + [JsonPropertyName("health")] + public required int Health { get; init; } +} + +public sealed class HealthPlusWhileDownProperty : ItemProperty +{ + [JsonPropertyName("health")] + public required int Health { get; init; } + + [JsonPropertyName("healthThreshold")] + public required int HealthThreshold { get; init; } +} + +public sealed class HealthMinusProperty : ItemProperty +{ + [JsonPropertyName("health")] + public required int Health { get; init; } +} + +public sealed class ReceiveLessDamageProperty : ItemProperty +{ + [JsonPropertyName("chance")] + public required int Chance { get; init; } + + [JsonPropertyName("lessDamage")] + public required int LessDamage { get; init; } +} + +public sealed class ReceiveLessPhysDamageWhileEnchantedProperty : ItemProperty +{ + [JsonPropertyName("lessDamage")] + public required int LessDamage { get; init; } +} + +public sealed class ReceiveLessPhysDamageWhileHexedProperty : ItemProperty +{ + [JsonPropertyName("lessDamage")] + public required int LessDamage { get; init; } +} + +public sealed class ReceiveLessPhysDamageWhileStanceProperty : ItemProperty +{ + [JsonPropertyName("lessDamage")] + public required int LessDamage { get; init; } +} + +public sealed class AttributePlusOneProperty : ItemProperty +{ + [JsonPropertyName("chance")] + public required int Chance { get; init; } + + [JsonPropertyName("attribute")] + public required string Attribute { get; init; } +} + +public sealed class AttributePlusOneItemProperty : ItemProperty +{ + [JsonPropertyName("chance")] + public required int Chance { get; init; } +} + +public sealed class ReduceConditionDurationProperty : ItemProperty +{ + [JsonPropertyName("conditionType")] + public required ConditionType Condition { get; init; } +} diff --git a/Daybreak.Shared/Models/Guildwars/ItemUpgrade.cs b/Daybreak.Shared/Models/Guildwars/ItemUpgrade.cs new file mode 100644 index 00000000..d83e9b48 --- /dev/null +++ b/Daybreak.Shared/Models/Guildwars/ItemUpgrade.cs @@ -0,0 +1,988 @@ +using Daybreak.Shared.Converters; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Models.Guildwars; + +public sealed class ItemUpgrade +{ + public static readonly ItemUpgrade Unknown = new(-1, "Unknown", ItemUpgradeType.Unknown); + public static readonly ItemUpgrade Icy_Axe = new(0x0081, "Icy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Ebon_Axe = new(0x0082, "Ebon", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shocking_Axe = new(0x0083, "Shocking", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Fiery_Axe = new(0x0084, "Fiery", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Barbed_Axe = new(0x0092, "Barbed", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Crippling_Axe = new(0x0094, "Crippling", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Cruel_Axe = new(0x0096, "Cruel", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Furious_Axe = new(0x0099, "Furious", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Poisonous_Axe = new(0x009E, "Poisonous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Heavy_Axe = new(0x00A1, "Heavy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Zealous_Axe = new(0x00A3, "Zealous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vampiric_Axe = new(0x00A7, "Vampiric", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sundering_Axe = new(0x00AB, "Sundering", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfDefense_Axe = new(0x00C5, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfWarding_Axe = new(0x00C7, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Axe = new(0x00CD, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSlaying_Axe = new(0x00D4, "of ____slaying", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Axe = new(0x00D9, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Axe = new(0x00DE, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfAxeMastery = new(0x00E8, "of Axe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfTheProfession_Axe = new(0x0226, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Icy_Bow = new(0x0085, "Icy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Ebon_Bow = new(0x0086, "Ebon", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shocking_Bow = new(0x0087, "Shocking", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Fiery_Bow = new(0x0088, "Fiery", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Poisonous_Bow = new(0x009F, "Poisonous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Zealous_Bow = new(0x00A5, "Zealous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vampiric_Bow = new(0x00A9, "Vampiric", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sundering_Bow = new(0x00AD, "Sundering", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfDefense_Bow = new(0x00C6, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfWarding_Bow = new(0x00C8, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Bow = new(0x00CE, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSlaying_Bow = new(0x00D5, "of _____slaying", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Bow = new(0x00DA, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Bow = new(0x00DF, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMarksmanship = new(0x00E9, "of Marksmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Barbed_Bow = new(0x0147, "Barbed", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Crippling_Bow = new(0x0148, "Crippling", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Silencing_Bow = new(0x0149, "Silencing", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfTheProfession_Bow = new(0x0227, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Icy_Daggers = new(0x012E, "Icy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Ebon_Daggers = new(0x012F, "Ebon", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Fiery_Daggers = new(0x0130, "Fiery", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shocking_Daggers = new(0x0131, "Shocking", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Zealous_Daggers = new(0x0132, "Zealous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vampiric_Daggers = new(0x0133, "Vampiric", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sundering_Daggers = new(0x0134, "Sundering", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Barbed_Daggers = new(0x0135, "Barbed", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Crippling_Daggers = new(0x0136, "Crippling", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Cruel_Daggers = new(0x0137, "Cruel", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Poisonous_Daggers = new(0x0138, "Poisonous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Silencing_Daggers = new(0x0139, "Silencing", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Furious_Daggers = new(0x013A, "Furious", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfDefense_Daggers = new(0x0141, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfWarding_Daggers = new(0x0142, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Daggers = new(0x0143, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Daggers = new(0x0144, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Daggers = new(0x0145, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfDaggerMastery = new(0x0146, "of Dagger Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfTheProfession_Daggers = new(0x0228, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfAptitude_Focus = new(0x0217, "of Aptitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Focus = new(0x0218, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfDevotion_Focus = new(0x0219, "of Devotion", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfValor_Focus = new(0x021A, "of Valor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEndurance_Focus = new(0x021B, "of Endurance", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSwiftness_Focus = new(0x021C, "of Swiftness", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Icy_Hammer = new(0x0089, "Icy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Ebon_Hammer = new(0x008A, "Ebon", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shocking_Hammer = new(0x008B, "Shocking", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Fiery_Hammer = new(0x008C, "Fiery", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Cruel_Hammer = new(0x0097, "Cruel", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Furious_Hammer = new(0x009A, "Furious", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Heavy_Hammer = new(0x00A2, "Heavy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Zealous_Hammer = new(0x00A4, "Zealous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vampiric_Hammer = new(0x00A8, "Vampiric", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sundering_Hammer = new(0x00AC, "Sundering", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfWarding_Hammer = new(0x00C9, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfDefense_Hammer = new(0x00CC, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Hammer = new(0x00CF, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSlaying_Hammer = new(0x00D6, "of _____slaying", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Hammer = new(0x00DB, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Hammer = new(0x00E0, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfHammerMastery = new(0x00EA, "of Hammer Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfTheProfession_Hammer = new(0x0229, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade IHaveThePower = new(0x015C, "\"I have the power!\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade LetTheMemoryLiveAgain = new(0x015E, "\"Let the Memory Live Again!\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade TooMuchInformation = new(0x0163, "\"Too Much Information\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade GuidedByFate = new(0x0164, "\"Guided by Fate\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade StrengthAndHonor = new(0x0165, "\"Strength and Honor\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade VengeanceIsMine = new(0x0166, "\"Vengeance is Mine\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade DontFearTheReaper = new(0x0167, "\"Don't Fear the Reaper\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade DanceWithDeath = new(0x0168, "\"Dance with Death\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade BrawnOverBrains = new(0x0169, "\"Brawn over Brains\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade ToThePain = new(0x016A, "\"To The Pain!\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade IgnoranceIsBliss = new(0x01B6, "\"Ignorance is Bliss\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade LifeIsPain = new(0x01B7, "\"Life is Pain\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade ManForAllSeasons = new(0x01B8, "\"Man for All Seasons\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade SurvivalOfTheFittest = new(0x01B9, "\"Survival of the Fittest\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade MightMakesRight = new(0x01BA, "\"Might makes Right!\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade KnowingIsHalfTheBattle = new(0x01BB, "\"Knowing is Half the Battle.\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade FaithIsMy = new(0x01BC, "\"Faith is My \"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade DownButNotOut = new(0x01BD, "\"Down But Not Out\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade HailToTheKing = new(0x01BE, "\"Hail to the King\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade BeJustAndFearNot = new(0x01BF, "\"Be Just and Fear Not\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade LiveForToday = new(0x01C0, "\"Live for Today\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade SerenityNow = new(0x01C1, "\"Serenity Now\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade ForgetMeNot = new(0x01C2, "\"Forget Me Not\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade NotTheFace = new(0x01C3, "\"Not the face!\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade LeafOnTheWind = new(0x01C4, "\"Leaf on the Wind\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade LikeARollingStone = new(0x01C5, "\"Like a Rolling Stone\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade RidersOnTheStorm = new(0x01C6, "\"Riders on the Storm\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade SleepNowInTheFire = new(0x01C7, "\"Sleep Now in the Fire\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade ThroughThickAndThin = new(0x01C8, "\"Through Thick and Thin\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade TheRiddleOfSteel = new(0x01C9, "\"The Riddle of Steel\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade FearCutsDeeper = new(0x01CA, "\"Fear Cuts Deeper\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade ICanSeeClearlyNow = new(0x01CB, "\"I Can See Clearly Now\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade SwiftAsTheWind = new(0x01CC, "\"Swift as the Wind\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade StrengthOfBody = new(0x01CD, "\"Strength of Body\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade CastOutTheUnclean = new(0x01CE, "\"Cast Out the Unclean\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade PureOfHeart = new(0x01CF, "\"Pure of Heart\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade SoundnessOfMind = new(0x01D0, "\"Soundness of Mind\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade OnlyTheStrongSurvive = new(0x01D1, "\"Only the Strong Survive\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade LuckOfTheDraw = new(0x01D2, "\"Luck of the Draw\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade ShelteredByFaith = new(0x01D3, "\"Sheltered by Faith\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade NothingToFear = new(0x01D4, "\"Nothing to Fear\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade RunForYourLife = new(0x01D5, "\"Run For Your Life!\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade MasterOfMyDomain = new(0x01D6, "\"Master of My Domain\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade AptitudeNotAttitude = new(0x01D7, "\"Aptitude not Attitude\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade SeizeTheDay = new(0x01D8, "\"Seize the Day\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade HaveFaith = new(0x01D9, "\"Have Faith\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade HaleAndHearty = new(0x01DA, "\"Hale and Hearty\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade DontCallItAComeback = new(0x01DB, "\"Don't call it a comeback!\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade IAmSorrow = new(0x01DC, "\"I am Sorrow.\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade DontThinkTwice = new(0x01DD, "\"Don't Think Twice\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade ShowMeTheMoney = new(0x021E, "\"Show me the money\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade MeasureForMeasure = new(0x021F, "\"Measure for Measure\"", ItemUpgradeType.Inscription); + public static readonly ItemUpgrade Icy_Scythe = new(0x016B, "Icy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Ebon_Scythe = new(0x016C, "Ebon", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Zealous_Scythe = new(0x016F, "Zealous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vampiric_Scythe = new(0x0171, "Vampiric", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sundering_Scythe = new(0x0173, "Sundering", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Barbed_Scythe = new(0x0174, "Barbed", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Crippling_Scythe = new(0x0175, "Crippling", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Cruel_Scythe = new(0x0176, "Cruel", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Poisonous_Scythe = new(0x0177, "Poisonous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Heavy_Scythe = new(0x0178, "Heavy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Furious_Scythe = new(0x0179, "Furious", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfDefense_Scythe = new(0x0188, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfWarding_Scythe = new(0x0189, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Scythe = new(0x018A, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Scythe = new(0x018B, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Scythe = new(0x018C, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfScytheMastery = new(0x018D, "of Scythe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Fiery_Scythe = new(0x020B, "Fiery", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shocking_Scythe = new(0x020C, "Shocking", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfTheProfession_Scythe = new(0x022C, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfValor_Shield = new(0x0151, "of Valor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEndurance_Shield = new(0x0152, "of Endurance", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Shield = new(0x0161, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfDevotion_Shield = new(0x0162, "of Devotion", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Fiery_Spear = new(0x016D, "Fiery", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shocking_Spear = new(0x016E, "Shocking", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Zealous_Spear = new(0x0170, "Zealous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vampiric_Spear = new(0x0172, "Vampiric", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sundering_Spear = new(0x017A, "Sundering", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Barbed_Spear = new(0x017B, "Barbed", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Crippling_Spear = new(0x017C, "Crippling", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Cruel_Spear = new(0x017D, "Cruel", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Poisonous_Spear = new(0x017E, "Poisonous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Silencing_Spear = new(0x017F, "Silencing", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Furious_Spear = new(0x0180, "Furious", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Heavy_Spear = new(0x0181, "Heavy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfDefense_Spear = new(0x018E, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfWarding_Spear = new(0x018F, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Spear = new(0x0190, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Spear = new(0x0191, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Spear = new(0x0192, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSpearMastery = new(0x0193, "of Spear Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Icy_Spear = new(0x020D, "Icy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Ebon_Spear = new(0x020E, "Ebon", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfTheProfession_Spear = new(0x022D, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Defensive_Staff = new(0x0091, "Defensive", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Insightful_Staff = new(0x009C, "Insightful", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Hale_Staff = new(0x009D, "Hale", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfAttribute_Staff = new(0x00C3, "of ", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfWarding_Staff = new(0x00CA, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Staff = new(0x00D0, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfDefense_Staff = new(0x00D2, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSlaying_Staff = new(0x00D7, "of _____slaying", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Staff = new(0x00DC, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Staff = new(0x00E1, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMastery_Staff = new(0x0153, "of Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfDevotion_Staff = new(0x0154, "of Devotion", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfValor_Staff = new(0x0155, "of Valor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEndurance_Staff = new(0x0156, "of Endurance", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Swift_Staff = new(0x020F, "Swift", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Adept_Staff = new(0x0210, "Adept", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfTheProfession_Staff = new(0x022B, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Icy_Sword = new(0x008D, "Icy", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Ebon_Sword = new(0x008E, "Ebon", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shocking_Sword = new(0x008F, "Shocking", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Fiery_Sword = new(0x0090, "Fiery", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Barbed_Sword = new(0x0093, "Barbed", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Crippling_Sword = new(0x0095, "Crippling", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Cruel_Sword = new(0x0098, "Cruel", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Furious_Sword = new(0x009B, "Furious", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Poisonous_Sword = new(0x00A0, "Poisonous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Zealous_Sword = new(0x00A6, "Zealous", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vampiric_Sword = new(0x00AA, "Vampiric", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sundering_Sword = new(0x00AE, "Sundering", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfWarding_Sword = new(0x00CB, "of Warding", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfShelter_Sword = new(0x00D1, "of Shelter", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfDefense_Sword = new(0x00D3, "of Defense", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSlaying_Sword = new(0x00D8, "of _____slaying", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfFortitude_Sword = new(0x00DD, "of Fortitude", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfEnchanting_Sword = new(0x00E2, "of Enchanting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSwordsmanship = new(0x00EB, "of Swordsmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfTheProfession_Sword = new(0x022E, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMemory_Wand = new(0x015F, "of Memory", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfQuickening_Wand = new(0x0160, "of Quickening", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfTheProfession_Wand = new(0x022A, "of the Profession", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade Survivor = new(0x01E6, "Survivor", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Radiant = new(0x01E5, "Radiant", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Stalwart = new(0x01E7, "Stalwart", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Brawlers = new(0x01E8, "Brawler's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Blessed = new(0x01E9, "Blessed", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Heralds = new(0x01EA, "Herald's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sentrys = new(0x01EB, "Sentry's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Knights = new(0x01F9, "Knight's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Lieutenants = new(0x0208, "Lieutenant's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Stonefist = new(0x0209, "Stonefist", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Dreadnought = new(0x01FA, "Dreadnought", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Sentinels = new(0x01FB, "Sentinel's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Frostbound = new(0x01FC, "Frostbound", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Pyrebound = new(0x01FE, "Pyrebound", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Stormbound = new(0x01FF, "Stormbound", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Scouts = new(0x0201, "Scout's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Earthbound = new(0x01FD, "Earthbound", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Beastmasters = new(0x0200, "Beastmaster's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Wanderers = new(0x01F6, "Wanderer's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Disciples = new(0x01F7, "Disciple's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Anchorites = new(0x01F8, "Anchorite's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Bloodstained = new(0x020A, "Bloodstained", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Tormentors = new(0x01EC, "Tormentor's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Bonelace = new(0x01EE, "Bonelace", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade MinionMasters = new(0x01EF, "Minion Master's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Blighters = new(0x01F0, "Blighter's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Undertakers = new(0x01ED, "Undertaker's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Virtuosos = new(0x01E4, "Virtuoso's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Artificers = new(0x01E2, "Artificer's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Prodigys = new(0x01E3, "Prodigy's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Hydromancer = new(0x01F2, "Hydromancer", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Geomancer = new(0x01F3, "Geomancer", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Pyromancer = new(0x01F4, "Pyromancer", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Aeromancer = new(0x01F5, "Aeromancer", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Prismatic = new(0x01F1, "Prismatic", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Vanguards = new(0x01DE, "Vanguard's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Infiltrators = new(0x01DF, "Infiltrator's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Saboteurs = new(0x01E0, "Saboteur's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Nightstalkers = new(0x01E1, "Nightstalker's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Shamans = new(0x0204, "Shaman's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade GhostForge = new(0x0205, "Ghost Forge", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Mystics = new(0x0206, "Mystic's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Windwalker = new(0x0202, "Windwalker", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Forsaken = new(0x0203, "Forsaken", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade Centurions = new(0x0207, "Centurion's", ItemUpgradeType.Prefix); + public static readonly ItemUpgrade OfAttunement = new(0x0211, "of Attunement", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfRecovery = new(0x0213, "of Recovery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfRestoration = new(0x0214, "of Restoration", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfClarity = new(0x0215, "of Clarity", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfPurity = new(0x0216, "of Purity", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorVigor = new(0x00FF, "of Minor Vigor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorVigor2 = new(0x00C2, "of Minor Vigor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorVigor = new(0x0101, "of Superior Vigor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorVigor = new(0x0100, "of Major Vigor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfVitae = new(0x0212, "of Vitae", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorAbsorption = new(0x00FC, "of Minor Absorption", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorTactics = new(0x1501, "of Minor Tactics", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorStrength = new(0x1101, "of Minor Strength", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorAxeMastery = new(0x1201, "of Minor Axe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorHammerMastery = new(0x1301, "of Minor Hammer Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorSwordsmanship = new(0x1401, "of Minor Swordsmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorAbsorption = new(0x00FD, "of Major Absorption", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorTactics = new(0x1502, "of Major Tactics", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorStrength = new(0x1102, "of Major Strength", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorAxeMastery = new(0x1202, "of Major Axe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorHammerMastery = new(0x1302, "of Major Hammer Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorSwordsmanship = new(0x1402, "of Major Swordsmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorAbsorption = new(0x00FE, "of Superior Absorption", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorTactics = new(0x1503, "of Superior Tactics", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorStrength = new(0x1103, "of Superior Strength", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorAxeMastery = new(0x1203, "of Superior Axe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorHammerMastery = new(0x1303, "of Superior Hammer Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorSwordsmanship = new(0x1403, "of Superior Swordsmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorWildernessSurvival = new(0x1801, "of Minor Wilderness Survival", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorExpertise = new(0x1701, "of Minor Expertise", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorBeastMastery = new(0x1601, "of Minor Beast Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorMarksmanship = new(0x1901, "of Minor Marksmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorWildernessSurvival = new(0x1802, "of Major Wilderness Survival", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorExpertise = new(0x1702, "of Major Expertise", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorBeastMastery = new(0x1602, "of Major Beast Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorMarksmanship = new(0x1902, "of Major Marksmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorWildernessSurvival = new(0x1803, "of Superior Wilderness Survival", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorExpertise = new(0x1703, "of Superior Expertise", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorBeastMastery = new(0x1603, "of Superior Beast Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorMarksmanship = new(0x1903, "of Superior Marksmanship", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorHealingPrayers = new(0x0D01, "of Minor Healing Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorSmitingPrayers = new(0x0E01, "of Minor Smiting Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorProtectionPrayers = new(0x0F01, "of Minor Protection Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorDivineFavor = new(0x1001, "of Minor Divine Favor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorHealingPrayers = new(0x0D02, "of Major Healing Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorSmitingPrayers = new(0x0E02, "of Major Smiting Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorProtectionPrayers = new(0x0F02, "of Major Protection Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorDivineFavor = new(0x1002, "of Major Divine Favor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorHealingPrayers = new(0x0D03, "of Superior Healing Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorSmitingPrayers = new(0x0E03, "of Superior Smiting Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorProtectionPrayers = new(0x0F03, "of Superior Protection Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorDivineFavor = new(0x1003, "of Superior Divine Favor", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorBloodMagic = new(0x0401, "of Minor Blood Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorDeathMagic = new(0x0501, "of Minor Death Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorCurses = new(0x0701, "of Minor Curses", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorSoulReaping = new(0x0601, "of Minor Soul Reaping", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorBloodMagic = new(0x0402, "of Major Blood Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorDeathMagic = new(0x0502, "of Major Death Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorCurses = new(0x0702, "of Major Curses", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorSoulReaping = new(0x0602, "of Major Soul Reaping", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorBloodMagic = new(0x0403, "of Superior Blood Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorDeathMagic = new(0x0503, "of Superior Death Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorCurses = new(0x0703, "of Superior Curses", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorSoulReaping = new(0x0603, "of Superior Soul Reaping", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorFastCasting = new(0x0001, "of Minor Fast Casting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorDominationMagic = new(0x0201, "of Minor Domination Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorIllusionMagic = new(0x0101, "of Minor Illusion Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorInspirationMagic = new(0x0301, "of Minor Inspiration Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorFastCasting = new(0x0002, "of Major Fast Casting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorDominationMagic = new(0x0202, "of Major Domination Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorIllusionMagic = new(0x0102, "of Major Illusion Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorInspirationMagic = new(0x0302, "of Major Inspiration Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorFastCasting = new(0x0003, "of Superior Fast Casting", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorDominationMagic = new(0x0203, "of Superior Domination Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorIllusionMagic = new(0x0103, "of Superior Illusion Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorInspirationMagic = new(0x0303, "of Superior Inspiration Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorEnergyStorage = new(0x0C01, "of Minor Energy Storage", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorFireMagic = new(0x0A01, "of Minor Fire Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorAirMagic = new(0x0801, "of Minor Air Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorEarthMagic = new(0x0901, "of Minor Earth Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorWaterMagic = new(0x0B01, "of Minor Water Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorEnergyStorage = new(0x0C02, "of Major Energy Storage", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorFireMagic = new(0x0A02, "of Major Fire Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorAirMagic = new(0x0802, "of Major Air Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorEarthMagic = new(0x0902, "of Major Earth Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorWaterMagic = new(0x0B02, "of Major Water Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorEnergyStorage = new(0x0C03, "of Superior Energy Storage", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorFireMagic = new(0x0A03, "of Superior Fire Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorAirMagic = new(0x0803, "of Superior Air Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorEarthMagic = new(0x0903, "of Superior Earth Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorWaterMagic = new(0x0B03, "of Superior Water Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorCriticalStrikes = new(0x2301, "of Minor Critical Strikes", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorDaggerMastery = new(0x1D01, "of Minor Dagger Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorDeadlyArts = new(0x1E01, "of Minor Deadly Arts", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorShadowArts = new(0x1F01, "of Minor Shadow Arts", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorCriticalStrikes = new(0x2302, "of Major Critical Strikes", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorDaggerMastery = new(0x1D02, "of Major Dagger Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorDeadlyArts = new(0x1E02, "of Major Deadly Arts", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorShadowArts = new(0x1F02, "of Major Shadow Arts", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorCriticalStrikes = new(0x2303, "of Superior Critical Strikes", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorDaggerMastery = new(0x1D03, "of Superior Dagger Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorDeadlyArts = new(0x1E03, "of Superior Deadly Arts", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorShadowArts = new(0x1F03, "of Superior Shadow Arts", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorChannelingMagic = new(0x2201, "of Minor Channeling Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorRestorationMagic = new(0x2101, "of Minor Restoration Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorCommuning = new(0x2001, "of Minor Communing", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorSpawningPower = new(0x2401, "of Minor Spawning Power", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorChannelingMagic = new(0x2202, "of Major Channeling Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorRestorationMagic = new(0x2102, "of Major Restoration Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorCommuning = new(0x2002, "of Major Communing", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorSpawningPower = new(0x2402, "of Major Spawning Power", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorChannelingMagic = new(0x2203, "of Superior Channeling Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorRestorationMagic = new(0x2103, "of Superior Restoration Magic", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorCommuning = new(0x2003, "of Superior Communing", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorSpawningPower = new(0x2403, "of Superior Spawning Power", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorMysticism = new(0x2C01, "of Minor Mysticism", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorEarthPrayers = new(0x2B01, "of Minor Earth Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorScytheMastery = new(0x2901, "of Minor Scythe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorWindPrayers = new(0x2A01, "of Minor Wind Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorMysticism = new(0x2C02, "of Major Mysticism", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorEarthPrayers = new(0x2B02, "of Major Earth Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorScytheMastery = new(0x2902, "of Major Scythe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorWindPrayers = new(0x2A02, "of Major Wind Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorMysticism = new(0x2C03, "of Superior Mysticism", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorEarthPrayers = new(0x2B03, "of Superior Earth Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorScytheMastery = new(0x2903, "of Superior Scythe Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorWindPrayers = new(0x2A03, "of Superior Wind Prayers", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorLeadership = new(0x2801, "of Minor Leadership", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorMotivation = new(0x2701, "of Minor Motivation", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorCommand = new(0x2601, "of Minor Command", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMinorSpearMastery = new(0x2501, "of Minor Spear Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorLeadership = new(0x2802, "of Major Leadership", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorMotivation = new(0x2702, "of Major Motivation", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorCommand = new(0x2602, "of Major Command", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfMajorSpearMastery = new(0x2502, "of Major Spear Mastery", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorLeadership = new(0x2803, "of Superior Leadership", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorMotivation = new(0x2703, "of Superior Motivation", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorCommand = new(0x2603, "of Superior Command", ItemUpgradeType.Suffix); + public static readonly ItemUpgrade OfSuperiorSpearMastery = new(0x2503, "of Superior Spear Mastery", ItemUpgradeType.Suffix); + + public static readonly ItemUpgrade UpgradeMinorRune_Warrior = new(0x00B3, "Upgrade warrior minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Warrior = new(0x0167, "Applies to warrior minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Warrior = new(0x00B9, "Upgrade warrior major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Warrior = new(0x0173, "Applies to warrior major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Warrior = new(0x00BF, "Upgrade warrior superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Warrior = new(0x017F, "Applies to warrior superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Ranger = new(0x00B4, "Upgrade ranger minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Ranger = new(0x0169, "Applies to ranger minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Ranger = new(0x00BA, "Upgrade ranger major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Ranger = new(0x0175, "Applies to ranger major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Ranger = new(0x00C0, "Upgrade ranger superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Ranger = new(0x0181, "Applies to ranger superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Monk = new(0x00B2, "Upgrade monk major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Monk = new(0x0165, "Applies to monk minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Monk = new(0x00B8, "Upgrade monk major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Monk = new(0x0171, "Applies to monk major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Monk = new(0x00BE, "Upgrade monk superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Monk = new(0x017D, "Applies to monk superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Necromancer = new(0x00B0, "Upgrade necromancer minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Necromancer = new(0x0161, "Applies to necromancer minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Necromancer = new(0x00B6, "Upgrade necromancer major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Necromancer = new(0x016D, "Applies to necromancer major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Necromancer = new(0x00BC, "Upgrade necromancer superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Necromancer = new(0x0179, "Applies to necromancer superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Mesmer = new(0x00AF, "Upgrade mesmer minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Mesmer = new(0x015F, "Applies to mesmer minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Mesmer = new(0x00B5, "Upgrade mesmer major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Mesmer = new(0x016B, "Applies to mesmer major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Mesmer = new(0x00BB, "Upgrade mesmer superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Mesmer = new(0x0177, "Applies to mesmer superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Elementalist = new(0x00B1, "Upgrade elementalist minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Elementalist = new(0x0163, "Applies to elementalist minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Elementalist = new(0x00B7, "Upgrade elementalist major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Elementalist = new(0x016F, "Applies to elementalist major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Elementalist = new(0x00BD, "Upgrade elementalist superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Elementalist = new(0x017B, "Applies to elementalist superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Assassin = new(0x013B, "Upgrade assassin minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Assassin = new(0x0277, "Applies to assassin minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Assassin = new(0x014C, "Upgrade assassin major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Assassin = new(0x0279, "Applies to assassin major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Assassin = new(0x013D, "Upgrade assassin superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Assassin = new(0x027B, "Applies to assassin superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Ritualist = new(0x013E, "Upgrade ritualist minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Ritualist = new(0x027D, "Applies to ritualist minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Ritualist = new(0x013F, "Upgrade ritualist major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Ritualist = new(0x027F, "Applies to ritualist major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Ritualist = new(0x0140, "Upgrade ritualist superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Ritualist = new(0x0281, "Applies to ritualist superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Dervish = new(0x0182, "Upgrade dervish minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Dervish = new(0x0305, "Applies to dervish minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Dervish = new(0x0183, "Upgrade dervish major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Dervish = new(0x0307, "Applies to dervish major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Dervish = new(0x0184, "Upgrade dervish superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Dervish = new(0x0309, "Applies to dervish superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ItemUpgrade UpgradeMinorRune_Paragon = new(0x0185, "Upgrade paragon minor rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMinorRune_Paragon = new(0x030B, "Applies to paragon minor rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeMajorRune_Paragon = new(0x0186, "Upgrade paragon major rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToMajorRune_Paragon = new(0x030D, "Applies to paragon major rune", ItemUpgradeType.AppliesToRune); + public static readonly ItemUpgrade UpgradeSuperiorRune_Paragon = new(0x0187, "Upgrade paragon superior rune", ItemUpgradeType.UpgradeRune); + public static readonly ItemUpgrade AppliesToSuperiorRune_Paragon = new(0x030F, "Applies to paragon superior rune", ItemUpgradeType.AppliesToRune); + + public static readonly ImmutableArray ItemUpgrades = [ + Unknown, + Icy_Axe, + Ebon_Axe, + Shocking_Axe, + Fiery_Axe, + Barbed_Axe, + Crippling_Axe, + Cruel_Axe, + Furious_Axe, + Poisonous_Axe, + Heavy_Axe, + Zealous_Axe, + Vampiric_Axe, + Sundering_Axe, + OfDefense_Axe, + OfWarding_Axe, + OfShelter_Axe, + OfSlaying_Axe, + OfFortitude_Axe, + OfEnchanting_Axe, + OfAxeMastery, + Icy_Bow, + Ebon_Bow, + Shocking_Bow, + Fiery_Bow, + Poisonous_Bow, + Zealous_Bow, + Vampiric_Bow, + Sundering_Bow, + OfDefense_Bow, + OfWarding_Bow, + OfShelter_Bow, + OfSlaying_Bow, + OfFortitude_Bow, + OfEnchanting_Bow, + OfMarksmanship, + Barbed_Bow, + Crippling_Bow, + Silencing_Bow, + Icy_Daggers, + Ebon_Daggers, + Fiery_Daggers, + Shocking_Daggers, + Zealous_Daggers, + Vampiric_Daggers, + Sundering_Daggers, + Barbed_Daggers, + Crippling_Daggers, + Cruel_Daggers, + Poisonous_Daggers, + Silencing_Daggers, + Furious_Daggers, + OfDefense_Daggers, + OfWarding_Daggers, + OfShelter_Daggers, + OfEnchanting_Daggers, + OfFortitude_Daggers, + OfDaggerMastery, + OfAptitude_Focus, + OfFortitude_Focus, + OfDevotion_Focus, + OfValor_Focus, + OfEndurance_Focus, + OfSwiftness_Focus, + Icy_Hammer, + Ebon_Hammer, + Shocking_Hammer, + Fiery_Hammer, + Cruel_Hammer, + Furious_Hammer, + Heavy_Hammer, + Zealous_Hammer, + Vampiric_Hammer, + Sundering_Hammer, + OfWarding_Hammer, + OfDefense_Hammer, + OfShelter_Hammer, + OfSlaying_Hammer, + OfFortitude_Hammer, + OfEnchanting_Hammer, + OfHammerMastery, + IHaveThePower, + LetTheMemoryLiveAgain, + TooMuchInformation, + GuidedByFate, + StrengthAndHonor, + VengeanceIsMine, + DontFearTheReaper, + DanceWithDeath, + BrawnOverBrains, + ToThePain, + IgnoranceIsBliss, + LifeIsPain, + ManForAllSeasons, + SurvivalOfTheFittest, + MightMakesRight, + KnowingIsHalfTheBattle, + FaithIsMy, + DownButNotOut, + HailToTheKing, + BeJustAndFearNot, + LiveForToday, + SerenityNow, + ForgetMeNot, + NotTheFace, + LeafOnTheWind, + LikeARollingStone, + RidersOnTheStorm, + SleepNowInTheFire, + ThroughThickAndThin, + TheRiddleOfSteel, + FearCutsDeeper, + ICanSeeClearlyNow, + SwiftAsTheWind, + StrengthOfBody, + CastOutTheUnclean, + PureOfHeart, + SoundnessOfMind, + OnlyTheStrongSurvive, + LuckOfTheDraw, + ShelteredByFaith, + NothingToFear, + RunForYourLife, + MasterOfMyDomain, + AptitudeNotAttitude, + SeizeTheDay, + HaveFaith, + HaleAndHearty, + DontCallItAComeback, + IAmSorrow, + DontThinkTwice, + ShowMeTheMoney, + MeasureForMeasure, + Icy_Scythe, + Ebon_Scythe, + Zealous_Scythe, + Vampiric_Scythe, + Sundering_Scythe, + Barbed_Scythe, + Crippling_Scythe, + Cruel_Scythe, + Poisonous_Scythe, + Heavy_Scythe, + Furious_Scythe, + OfDefense_Scythe, + OfWarding_Scythe, + OfShelter_Scythe, + OfEnchanting_Scythe, + OfFortitude_Scythe, + OfScytheMastery, + Fiery_Scythe, + Shocking_Scythe, + OfValor_Shield, + OfEndurance_Shield, + OfFortitude_Shield, + OfDevotion_Shield, + Fiery_Spear, + Shocking_Spear, + Zealous_Spear, + Vampiric_Spear, + Sundering_Spear, + Barbed_Spear, + Crippling_Spear, + Cruel_Spear, + Poisonous_Spear, + Silencing_Spear, + Furious_Spear, + Heavy_Spear, + OfDefense_Spear, + OfWarding_Spear, + OfShelter_Spear, + OfEnchanting_Spear, + OfFortitude_Spear, + OfSpearMastery, + Icy_Spear, + Ebon_Spear, + Defensive_Staff, + Insightful_Staff, + Hale_Staff, + OfAttribute_Staff, + OfWarding_Staff, + OfShelter_Staff, + OfDefense_Staff, + OfSlaying_Staff, + OfFortitude_Staff, + OfEnchanting_Staff, + OfMastery_Staff, + OfDevotion_Staff, + OfValor_Staff, + OfEndurance_Staff, + Swift_Staff, + Adept_Staff, + Icy_Sword, + Ebon_Sword, + Shocking_Sword, + Fiery_Sword, + Barbed_Sword, + Crippling_Sword, + Cruel_Sword, + Furious_Sword, + Poisonous_Sword, + Zealous_Sword, + Vampiric_Sword, + Sundering_Sword, + OfWarding_Sword, + OfShelter_Sword, + OfDefense_Sword, + OfSlaying_Sword, + OfFortitude_Sword, + OfEnchanting_Sword, + OfSwordsmanship, + OfMemory_Wand, + OfQuickening_Wand, + OfTheProfession_Axe, + OfTheProfession_Bow, + OfTheProfession_Daggers, + OfTheProfession_Hammer, + OfTheProfession_Wand, + OfTheProfession_Staff, + OfTheProfession_Scythe, + OfTheProfession_Spear, + OfTheProfession_Sword, + Survivor, + Radiant, + Stalwart, + Brawlers, + Blessed, + Heralds, + Sentrys, + Knights, + Lieutenants, + Stonefist, + Dreadnought, + Sentinels, + Frostbound, + Pyrebound, + Stormbound, + Scouts, + Earthbound, + Beastmasters, + Wanderers, + Disciples, + Anchorites, + Bloodstained, + Tormentors, + Bonelace, + MinionMasters, + Blighters, + Undertakers, + Virtuosos, + Artificers, + Prodigys, + Hydromancer, + Geomancer, + Pyromancer, + Aeromancer, + Prismatic, + Vanguards, + Infiltrators, + Saboteurs, + Nightstalkers, + Shamans, + GhostForge, + Mystics, + Windwalker, + Forsaken, + Centurions, + OfAttunement, + OfRecovery, + OfRestoration, + OfClarity, + OfPurity, + OfMinorVigor, + OfMinorVigor2, + OfSuperiorVigor, + OfMajorVigor, + OfVitae, + OfMinorAbsorption, + OfMinorTactics, + OfMinorStrength, + OfMinorAxeMastery, + OfMinorHammerMastery, + OfMinorSwordsmanship, + OfMajorAbsorption, + OfMajorTactics, + OfMajorStrength, + OfMajorAxeMastery, + OfMajorHammerMastery, + OfMajorSwordsmanship, + OfSuperiorAbsorption, + OfSuperiorTactics, + OfSuperiorStrength, + OfSuperiorAxeMastery, + OfSuperiorHammerMastery, + OfSuperiorSwordsmanship, + OfMinorWildernessSurvival, + OfMinorExpertise, + OfMinorBeastMastery, + OfMinorMarksmanship, + OfMajorWildernessSurvival, + OfMajorExpertise, + OfMajorBeastMastery, + OfMajorMarksmanship, + OfSuperiorWildernessSurvival, + OfSuperiorExpertise, + OfSuperiorBeastMastery, + OfSuperiorMarksmanship, + OfMinorHealingPrayers, + OfMinorSmitingPrayers, + OfMinorProtectionPrayers, + OfMinorDivineFavor, + OfMajorHealingPrayers, + OfMajorSmitingPrayers, + OfMajorProtectionPrayers, + OfMajorDivineFavor, + OfSuperiorHealingPrayers, + OfSuperiorSmitingPrayers, + OfSuperiorProtectionPrayers, + OfSuperiorDivineFavor, + OfMinorBloodMagic, + OfMinorDeathMagic, + OfMinorCurses, + OfMinorSoulReaping, + OfMajorBloodMagic, + OfMajorDeathMagic, + OfMajorCurses, + OfMajorSoulReaping, + OfSuperiorBloodMagic, + OfSuperiorDeathMagic, + OfSuperiorCurses, + OfSuperiorSoulReaping, + OfMinorFastCasting, + OfMinorDominationMagic, + OfMinorIllusionMagic, + OfMinorInspirationMagic, + OfMajorFastCasting, + OfMajorDominationMagic, + OfMajorIllusionMagic, + OfMajorInspirationMagic, + OfSuperiorFastCasting, + OfSuperiorDominationMagic, + OfSuperiorIllusionMagic, + OfSuperiorInspirationMagic, + OfMinorEnergyStorage, + OfMinorFireMagic, + OfMinorAirMagic, + OfMinorEarthMagic, + OfMinorWaterMagic, + OfMajorEnergyStorage, + OfMajorFireMagic, + OfMajorAirMagic, + OfMajorEarthMagic, + OfMajorWaterMagic, + OfSuperiorEnergyStorage, + OfSuperiorFireMagic, + OfSuperiorAirMagic, + OfSuperiorEarthMagic, + OfSuperiorWaterMagic, + OfMinorCriticalStrikes, + OfMinorDaggerMastery, + OfMinorDeadlyArts, + OfMinorShadowArts, + OfMajorCriticalStrikes, + OfMajorDaggerMastery, + OfMajorDeadlyArts, + OfMajorShadowArts, + OfSuperiorCriticalStrikes, + OfSuperiorDaggerMastery, + OfSuperiorDeadlyArts, + OfSuperiorShadowArts, + OfMinorChannelingMagic, + OfMinorRestorationMagic, + OfMinorCommuning, + OfMinorSpawningPower, + OfMajorChannelingMagic, + OfMajorRestorationMagic, + OfMajorCommuning, + OfMajorSpawningPower, + OfSuperiorChannelingMagic, + OfSuperiorRestorationMagic, + OfSuperiorCommuning, + OfSuperiorSpawningPower, + OfMinorMysticism, + OfMinorEarthPrayers, + OfMinorScytheMastery, + OfMinorWindPrayers, + OfMajorMysticism, + OfMajorEarthPrayers, + OfMajorScytheMastery, + OfMajorWindPrayers, + OfSuperiorMysticism, + OfSuperiorEarthPrayers, + OfSuperiorScytheMastery, + OfSuperiorWindPrayers, + OfMinorLeadership, + OfMinorMotivation, + OfMinorCommand, + OfMinorSpearMastery, + OfMajorLeadership, + OfMajorMotivation, + OfMajorCommand, + OfMajorSpearMastery, + OfSuperiorLeadership, + OfSuperiorMotivation, + OfSuperiorCommand, + OfSuperiorSpearMastery, + UpgradeMinorRune_Warrior, + AppliesToMinorRune_Warrior, + UpgradeMajorRune_Warrior, + AppliesToMajorRune_Warrior, + UpgradeSuperiorRune_Warrior, + AppliesToSuperiorRune_Warrior, + UpgradeMinorRune_Ranger, + AppliesToMinorRune_Ranger, + UpgradeMajorRune_Ranger, + AppliesToMajorRune_Ranger, + UpgradeSuperiorRune_Ranger, + AppliesToSuperiorRune_Ranger, + UpgradeMinorRune_Monk, + AppliesToMinorRune_Monk, + UpgradeMajorRune_Monk, + AppliesToMajorRune_Monk, + UpgradeSuperiorRune_Monk, + AppliesToSuperiorRune_Monk, + UpgradeMinorRune_Mesmer, + AppliesToMinorRune_Mesmer, + UpgradeMajorRune_Mesmer, + AppliesToMajorRune_Mesmer, + UpgradeSuperiorRune_Mesmer, + AppliesToSuperiorRune_Mesmer, + UpgradeMinorRune_Necromancer, + AppliesToMinorRune_Necromancer, + UpgradeMajorRune_Necromancer, + AppliesToMajorRune_Necromancer, + UpgradeSuperiorRune_Necromancer, + AppliesToSuperiorRune_Necromancer, + UpgradeMinorRune_Elementalist, + AppliesToMinorRune_Elementalist, + UpgradeMajorRune_Elementalist, + AppliesToMajorRune_Elementalist, + UpgradeSuperiorRune_Elementalist, + AppliesToSuperiorRune_Elementalist, + UpgradeMinorRune_Assassin, + AppliesToMinorRune_Assassin, + UpgradeMajorRune_Assassin, + AppliesToMajorRune_Assassin, + UpgradeSuperiorRune_Assassin, + AppliesToSuperiorRune_Assassin, + UpgradeMinorRune_Ritualist, + AppliesToMinorRune_Ritualist, + UpgradeMajorRune_Ritualist, + AppliesToMajorRune_Ritualist, + UpgradeSuperiorRune_Ritualist, + AppliesToSuperiorRune_Ritualist, + UpgradeMinorRune_Dervish, + AppliesToMinorRune_Dervish, + UpgradeMajorRune_Dervish, + AppliesToMajorRune_Dervish, + UpgradeSuperiorRune_Dervish, + AppliesToSuperiorRune_Dervish, + UpgradeMinorRune_Paragon, + AppliesToMinorRune_Paragon, + UpgradeMajorRune_Paragon, + AppliesToMajorRune_Paragon, + UpgradeSuperiorRune_Paragon, + AppliesToSuperiorRune_Paragon, + ]; + + public static ItemUpgrade Parse(int id) + { + if (!TryParse(id, out var upgrade)) + { + throw new InvalidOperationException($"Could not find {nameof(ItemUpgrade)} by id {id}"); + } + + return upgrade; + } + + public static bool TryParse(int id, [NotNullWhen(true)] out ItemUpgrade? upgrade) + { + upgrade = ItemUpgrades.FirstOrDefault(u => u.Id == id); + return upgrade is not null; + } + + [JsonPropertyName("id")] + [JsonConverter(typeof(HexIntConverter))] + public int Id { get; } + + [JsonPropertyName("name")] + public string Name { get; } + + [JsonPropertyName("type")] + public ItemUpgradeType Type { get; } + + private ItemUpgrade(int id, string name, ItemUpgradeType type) + { + this.Id = id; + this.Name = name; + this.Type = type; + } +} diff --git a/Daybreak.Shared/Models/Guildwars/ItemUpgradeType.cs b/Daybreak.Shared/Models/Guildwars/ItemUpgradeType.cs new file mode 100644 index 00000000..8cb8f380 --- /dev/null +++ b/Daybreak.Shared/Models/Guildwars/ItemUpgradeType.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Daybreak.Shared.Models.Guildwars; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ItemUpgradeType +{ + Unknown, + Prefix, + Suffix, + Inscription, + UpgradeRune, + AppliesToRune +} diff --git a/Daybreak.Shared/Models/Guildwars/Map.cs b/Daybreak.Shared/Models/Guildwars/Map.cs index 37c80447..a6256956 100644 --- a/Daybreak.Shared/Models/Guildwars/Map.cs +++ b/Daybreak.Shared/Models/Guildwars/Map.cs @@ -1,13 +1,16 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(MapJsonConverter))] public sealed class Map : IWikiEntity { + [JsonPropertyName("id")] public int Id { get; private set; } + + [JsonPropertyName("name")] public string? Name { get; private set; } + + [JsonPropertyName("wikiUrl")] public string? WikiUrl { get; private set; } public static readonly Map None = new() { Id = 0, Name = "", WikiUrl = "https://wiki.guildwars.com/wiki/" }; diff --git a/Daybreak.Shared/Models/Guildwars/Npc.cs b/Daybreak.Shared/Models/Guildwars/Npc.cs index 62d943f4..b8cd95c7 100644 --- a/Daybreak.Shared/Models/Guildwars/Npc.cs +++ b/Daybreak.Shared/Models/Guildwars/Npc.cs @@ -1,10 +1,8 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; // TODO: Add missing npcs -[JsonConverter(typeof(NpcJsonConverter))] public sealed class Npc { public static readonly Npc Unknown = new() { Ids = [0], Name = "Unknown" }; @@ -3032,8 +3030,13 @@ public sealed class Npc return npc; } + [JsonPropertyName("ids")] public int[] Ids { get; private set; } = []; + + [JsonPropertyName("name")] public string Name { get; private set; } = string.Empty; + + [JsonPropertyName("wikiUrl")] public string WikiUrl { get; private set; } = string.Empty; private Npc() diff --git a/Daybreak.Shared/Models/Guildwars/PartyCompositionMetadataEntry.cs b/Daybreak.Shared/Models/Guildwars/PartyCompositionMetadataEntry.cs index 9d29d479..3f3a032e 100644 --- a/Daybreak.Shared/Models/Guildwars/PartyCompositionMetadataEntry.cs +++ b/Daybreak.Shared/Models/Guildwars/PartyCompositionMetadataEntry.cs @@ -1,6 +1,5 @@ using Daybreak.Shared.Models.Api; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; @@ -10,14 +9,14 @@ public sealed class PartyCompositionMetadataEntry public required int Index { get; init; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? HeroId { get; init; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public HeroBehavior? Behavior { get; init; } } -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] public enum PartyCompositionMemberType { Unknown, diff --git a/Daybreak.Shared/Models/Guildwars/Profession.cs b/Daybreak.Shared/Models/Guildwars/Profession.cs index fcf8a175..6275197f 100644 --- a/Daybreak.Shared/Models/Guildwars/Profession.cs +++ b/Daybreak.Shared/Models/Guildwars/Profession.cs @@ -1,11 +1,10 @@ using Daybreak.Shared.Converters; -using Newtonsoft.Json; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(ProfessionJsonConverter))] [TypeConverter(typeof(ProfessionTypeConverter))] public sealed class Profession : IWikiEntity { @@ -163,13 +162,27 @@ public sealed class Profession : IWikiEntity return profession; } + [JsonPropertyName("wikiUrl")] public string? WikiUrl { get; init; } = string.Empty; + + [JsonPropertyName("buildsUrl")] public string? BuildsUrl { get; init; } + + [JsonPropertyName("alias")] public string? Alias { get; init; } + + [JsonPropertyName("name")] public string? Name { get; init; } + + [JsonPropertyName("id")] public int Id { get; set; } + + [JsonPropertyName("primaryAttribute")] public Attribute? PrimaryAttribute { get; private set; } + + [JsonPropertyName("attributes")] public List Attributes { get; private set; } = []; + private Profession() { } diff --git a/Daybreak.Shared/Models/Guildwars/Quest.cs b/Daybreak.Shared/Models/Guildwars/Quest.cs index 16d1ef55..89e73c1b 100644 --- a/Daybreak.Shared/Models/Guildwars/Quest.cs +++ b/Daybreak.Shared/Models/Guildwars/Quest.cs @@ -1,13 +1,16 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(QuestJsonConverter))] public sealed class Quest : IWikiEntity { + [JsonPropertyName("id")] public int Id { get; init; } + + [JsonPropertyName("name")] public string? Name { get; init; } + + [JsonPropertyName("wikiUrl")] public string? WikiUrl { get; init; } public static readonly Quest TheAscalonSettlement = new() { Id = 0, Name = "The Ascalon Settlement", WikiUrl = "https://wiki.guildwars.com/wiki/The_Ascalon_Settlement" }; diff --git a/Daybreak.Shared/Models/Guildwars/Region.cs b/Daybreak.Shared/Models/Guildwars/Region.cs index 9f2a9175..8639bccf 100644 --- a/Daybreak.Shared/Models/Guildwars/Region.cs +++ b/Daybreak.Shared/Models/Guildwars/Region.cs @@ -1,9 +1,7 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(RegionJsonConverter))] public sealed class Region : IWikiEntity { public static readonly Region Kryta = new() @@ -888,9 +886,16 @@ public sealed class Region : IWikiEntity private Region() { } - + + [JsonPropertyName("id")] public int Id { get; init; } + + [JsonPropertyName("name")] public string? Name { get; init; } + + [JsonPropertyName("wikiUrl")] public string? WikiUrl { get; init; } + + [JsonPropertyName("maps")] public IReadOnlyList? Maps { get; init; } } diff --git a/Daybreak.Shared/Models/Guildwars/Skill.cs b/Daybreak.Shared/Models/Guildwars/Skill.cs index 09ed9509..accaf522 100644 --- a/Daybreak.Shared/Models/Guildwars/Skill.cs +++ b/Daybreak.Shared/Models/Guildwars/Skill.cs @@ -1,6 +1,5 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; @@ -12,7 +11,6 @@ namespace Daybreak.Shared.Models.Guildwars; /// https://wiki.guildwars.com/wiki/Gallery_of_high_resolution_skill_icons/large /// Details can be fetched using /// -[JsonConverter(typeof(SkillJsonConverter))] public sealed class Skill : IIconUrlEntity { public static readonly Skill ATouchofGuile = new() { Id = 2357, Name = "A Touch of Guile", Elite = false, PvEOnly = true, PvP = false, Campaign = Campaign.EyeOfTheNorth, Profession = Profession.None, Attribute = Attribute.None, Type = SkillType.Touch | SkillType.Hex | SkillType.Spell, Energy = "5", Activation = "³⁄₄", Recharge = "15", Overcast = "", Adrenaline = "", Sacrifice = "", Upkeep = "", Description = "Hex Spell. Target touched foe takes 44…80 damage. If target foe was knocked down, that foe cannot attack for 5…8 seconds.", ConciseDescription = "Touch Hex Spell. Deals 44…80 damage. Target foe cannot attack (5…8 seconds) if it was knocked-down.", IconUrl = "https://wiki.guildwars.com/images/2/2d/A_Touch_of_Guile.jpg" }; @@ -3032,26 +3030,65 @@ public sealed class Skill : IIconUrlEntity return skill; } + [JsonPropertyName("name")] public required string Name { get; init; } + + [JsonPropertyName("id")] public required int Id { get; init; } + + [JsonPropertyName("profession")] public required Profession Profession { get; init; } + + [JsonPropertyName("attribute")] public required Attribute Attribute { get; init; } + + [JsonPropertyName("campaign")] public required Campaign Campaign { get; init; } + + [JsonPropertyName("elite")] public required bool Elite { get; init; } + + [JsonPropertyName("pveOnly")] public required bool PvEOnly { get; init; } + + [JsonPropertyName("pvp")] public required bool PvP { get; init; } + + [JsonPropertyName("type")] public required SkillType Type { get; init; } + + [JsonPropertyName("energy")] public required string Energy { get; init; } + + [JsonPropertyName("activation")] public required string Activation { get; init; } + + [JsonPropertyName("recharge")] public required string Recharge { get; init; } + + [JsonPropertyName("overcast")] public required string Overcast { get; init; } + + [JsonPropertyName("adrenaline")] public required string Adrenaline { get; init; } + + [JsonPropertyName("sacrifice")] public required string Sacrifice { get; init; } + + [JsonPropertyName("description")] public required string Description { get; init; } + + [JsonPropertyName("conciseDescription")] public required string ConciseDescription { get; init; } + + [JsonPropertyName("iconUrl")] public required string IconUrl { get; init; } + + [JsonPropertyName("upkeep")] public required string Upkeep { get; init; } + public override string ToString() => this.Name ?? nameof(Skill); + private Skill() { } diff --git a/Daybreak.Shared/Models/Guildwars/Title.cs b/Daybreak.Shared/Models/Guildwars/Title.cs index e1afcfd5..2d4b32da 100644 --- a/Daybreak.Shared/Models/Guildwars/Title.cs +++ b/Daybreak.Shared/Models/Guildwars/Title.cs @@ -1,9 +1,7 @@ -using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Guildwars; -[JsonConverter(typeof(TitleJsonConverter))] public sealed class Title : IWikiEntity { public static readonly Title None = new() { Id = 0xFF, }; @@ -142,9 +140,16 @@ public sealed class Title : IWikiEntity return title; } + [JsonPropertyName("id")] public int Id { get; init; } + + [JsonPropertyName("name")] public string? Name { get; init; } + + [JsonPropertyName("wikiUrl")] public string? WikiUrl { get; init; } + + [JsonPropertyName("tiers")] public List? Tiers { get; init; } private Title() diff --git a/Daybreak.Shared/Models/LaunchConfigurations/LaunchConfiguration.cs b/Daybreak.Shared/Models/LaunchConfigurations/LaunchConfiguration.cs index 62978779..b211048f 100644 --- a/Daybreak.Shared/Models/LaunchConfigurations/LaunchConfiguration.cs +++ b/Daybreak.Shared/Models/LaunchConfigurations/LaunchConfiguration.cs @@ -1,24 +1,24 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.LaunchConfigurations; public sealed class LaunchConfiguration { - [JsonProperty(nameof(Name))] + [JsonPropertyName(nameof(Name))] public string? Name { get; set; } - [JsonProperty(nameof(Identifier))] + [JsonPropertyName(nameof(Identifier))] public string? Identifier { get; set; } - [JsonProperty(nameof(Executable))] + [JsonPropertyName(nameof(Executable))] public string? Executable { get; set; } - [JsonProperty(nameof(Arguments))] + [JsonPropertyName(nameof(Arguments))] public string? Arguments { get; set; } - [JsonProperty(nameof(CredentialsIdentifier))] + [JsonPropertyName(nameof(CredentialsIdentifier))] public string? CredentialsIdentifier { get; set; } - [JsonProperty(nameof(SteamSupport))] + [JsonPropertyName(nameof(SteamSupport))] public bool? SteamSupport { get; set; } = true; } diff --git a/Daybreak.Shared/Models/ProtectedLoginCredentials.cs b/Daybreak.Shared/Models/ProtectedLoginCredentials.cs index efe7e808..8428a593 100644 --- a/Daybreak.Shared/Models/ProtectedLoginCredentials.cs +++ b/Daybreak.Shared/Models/ProtectedLoginCredentials.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models; public sealed class ProtectedLoginCredentials { - [JsonProperty(nameof(Identifier))] + [JsonPropertyName(nameof(Identifier))] public string? Identifier { get; set; } - [JsonProperty(nameof(ProtectedUsername))] + [JsonPropertyName(nameof(ProtectedUsername))] public string? ProtectedUsername { get; set; } - [JsonProperty(nameof(ProtectedPassword))] + [JsonPropertyName(nameof(ProtectedPassword))] public string? ProtectedPassword { get; set; } } diff --git a/Daybreak.Shared/Models/SecureString.cs b/Daybreak.Shared/Models/SecureString.cs deleted file mode 100644 index 49ab439a..00000000 --- a/Daybreak.Shared/Models/SecureString.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Daybreak.Shared.Utils; -using Newtonsoft.Json; -using System.Core.Extensions; -using System.Security.Cryptography; - -namespace Daybreak.Shared.Models; - -[Serializable] -public sealed class SecureString -{ - public static SecureString Empty { get => new(string.Empty); } - - private byte[]? encryptedBytes; - private readonly byte[] key; - - private byte[]? DecryptedValue - { - get => this.encryptedBytes?.DecryptBytes(this.key); - set => this.encryptedBytes = value?.EncryptBytes(this.key); - } - [JsonProperty("value")] - public string? Value - { - get - { - return this.encryptedBytes?.DecryptBytes(this.key).AsString(); - } - set - { - this.encryptedBytes = value?.AsBytes().EncryptBytes(this.key); - } - } - private SecureString(byte[] value) - { - this.key = new byte[32]; - using var crypto = RandomNumberGenerator.Create(); - crypto.GetBytes(this.key); - this.DecryptedValue = value; - } - public SecureString(string value) - { - this.key = new byte[32]; - using var crypto = RandomNumberGenerator.Create(); - crypto.GetBytes(this.key); - this.Value = value; - } - - public static implicit operator string?(SecureString? ss) => ss is null ? string.Empty : ss.Value; - public static implicit operator SecureString(string s) => new(s); - public static SecureString operator +(SecureString? ss1, SecureString? ss2) - { - return new SecureString([.. ss1.ThrowIfNull().DecryptedValue!, .. ss2.ThrowIfNull().DecryptedValue!]); - } - public static SecureString operator +(SecureString? ss1, string s2) - { - return new SecureString([.. ss1.ThrowIfNull().DecryptedValue!, .. s2.AsBytes()]); - } - public static SecureString operator +(SecureString? ss1, char c) - { - return new SecureString([.. ss1.ThrowIfNull().DecryptedValue!, Convert.ToByte(c)]); - } - public static bool operator ==(SecureString? ss1, SecureString? ss2) - { - if (ss1 is null && ss2 is null) return true; - if (ss1 is null) return false; - if (ss2 is null) return false; - - var ss1Value = ss1.DecryptedValue; - var ss2Value = ss2.DecryptedValue; - - if (ss1Value!.Length != ss2Value!.Length) return false; - - for (int i = 0; i < ss1Value.Length; i++) - { - if (ss1Value[i] != ss2Value[i]) return false; - } - - return true; - } - public static bool operator !=(SecureString? ss1, SecureString? ss2) - { - return !(ss1 == ss2); - } - public static bool operator ==(SecureString? ss1, string s2) - { - if (ss1 is null) return false; - - return ss1.Value == s2; - } - public static bool operator !=(SecureString? ss1, string s2) - { - if (ss1 is null) return true; - - return !(ss1.Value == s2); - } - - public override bool Equals(object? obj) - { - if (obj is string) - { - return this == (obj as string)!; - } - else if (obj is SecureString) - { - return this == (obj as SecureString)!; - } - else - { - return base.Equals(obj); - } - } - - public override int GetHashCode() - { - return this.Value!.GetHashCode(); - } - - public override string ToString() - { - return this.Value!; - } -} diff --git a/Daybreak.Shared/Models/Themes/Theme.cs b/Daybreak.Shared/Models/Themes/Theme.cs index 4769c13b..35514931 100644 --- a/Daybreak.Shared/Models/Themes/Theme.cs +++ b/Daybreak.Shared/Models/Themes/Theme.cs @@ -1,6 +1,6 @@ using Daybreak.Shared.Converters; using Daybreak.Shared.Models.ColorPalette; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Themes; [JsonConverter(typeof(ThemeJsonConverter))] diff --git a/Daybreak.Shared/Models/Trade/ITradeAlert.cs b/Daybreak.Shared/Models/Trade/ITradeAlert.cs index a6759f30..e4635ed3 100644 --- a/Daybreak.Shared/Models/Trade/ITradeAlert.cs +++ b/Daybreak.Shared/Models/Trade/ITradeAlert.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Converters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Shared.Models.Trade; diff --git a/Daybreak.Shared/Services/BuildTemplates/BuildTemplateManager.cs b/Daybreak.Shared/Services/BuildTemplates/BuildTemplateManager.cs index 5f5cb505..49ecf445 100644 --- a/Daybreak.Shared/Services/BuildTemplates/BuildTemplateManager.cs +++ b/Daybreak.Shared/Services/BuildTemplates/BuildTemplateManager.cs @@ -4,11 +4,11 @@ using Daybreak.Shared.Models.Builds; using Daybreak.Shared.Models.Guildwars; using Daybreak.Shared.Services.BuildTemplates.Models; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; +using System.Diagnostics.CodeAnalysis; using System.Extensions; using System.Extensions.Core; -using System.IO; using System.Text; +using System.Text.Json; using AttributeEntry = Daybreak.Shared.Models.Builds.AttributeEntry; using Convert = System.Convert; @@ -299,7 +299,7 @@ public sealed class BuildTemplateManager( var metadata = buildEntry.Metadata is not null ? Convert.ToBase64String( Encoding.UTF8.GetBytes( - JsonConvert.SerializeObject(buildEntry.Metadata, Formatting.None))) + JsonSerializer.Serialize(buildEntry.Metadata))) : string.Empty; File.WriteAllText(newPath, $"{encodedBuild.ToString().Trim()}\n{metadata}"); @@ -320,20 +320,20 @@ public sealed class BuildTemplateManager( } } - public async Task> GetBuild(string name) + public async Task GetBuild(string name) { // Use the cache as fallback for newly created builds that are not yet saved to the disk var path = Path.Combine(BuildsPath, $"{name}.txt"); var result = await this.LoadBuildFromFile(path, name); - if (result.TryExtractFailure(out var exception) && exception is not null) + if (result is null) { var maybeCachedBuild = this.BuildMemoryCache.FirstOrDefault(b => b.Name == name); if (maybeCachedBuild is not null) { - return Result.Success(maybeCachedBuild); + return maybeCachedBuild; } - return Result.Failure(exception); + return default; } return result; @@ -358,9 +358,9 @@ public sealed class BuildTemplateManager( { var name = Path.GetRelativePath(BuildsPath, file).Replace(".txt", string.Empty); var result = await this.LoadBuildFromFile(file, name); - if (result.TryExtractSuccess(out var buildEntry) && buildEntry is not null) + if (result is not null) { - yield return buildEntry; + yield return result; } } } @@ -368,9 +368,10 @@ public sealed class BuildTemplateManager( public IBuildEntry DecodeTemplate(string template) { var randomName = Guid.NewGuid().ToString(); - var maybeTemplate = this.DecodeTemplatesInner(template); - return maybeTemplate.Switch( - onSuccess: builds => (IBuildEntry)(builds.Length == 1 ? + var builds = this.DecodeTemplatesInner(template); + return builds is null + ? throw new InvalidOperationException("Failed to decode build template") + : builds.Length == 1 ? new SingleBuildEntry { Name = randomName, @@ -393,35 +394,30 @@ public sealed class BuildTemplateManager( Attributes = b.Attributes, Skills = b.Skills })] - }), - onFailure: exception => throw exception); + }; } - public bool TryDecodeTemplate(string template, out IBuildEntry build) + public bool TryDecodeTemplate(string template, [NotNullWhen(true)] out IBuildEntry? build) { var maybeBuilds = this.DecodeTemplatesInner(template); - (var result, var parsedBuilds) = maybeBuilds.Switch( - onSuccess: parsedBuild => (true, parsedBuild), - onFailure: _ => (false, default!)); - - if (result) + if (maybeBuilds is not null) { var randomName = Guid.NewGuid().ToString(); - build = parsedBuilds.Length == 1 ? + build = maybeBuilds.Length == 1 ? new SingleBuildEntry { Name = randomName, PreviousName = randomName, - Primary = parsedBuilds[0].Primary, - Secondary = parsedBuilds[0].Secondary, - Attributes = parsedBuilds[0].Attributes, - Skills = parsedBuilds[0].Skills + Primary = maybeBuilds[0].Primary, + Secondary = maybeBuilds[0].Secondary, + Attributes = maybeBuilds[0].Attributes, + Skills = maybeBuilds[0].Skills } : new TeamBuildEntry { Name = randomName, PreviousName = randomName, - Builds = parsedBuilds.Select(b => new SingleBuildEntry + Builds = maybeBuilds.Select(b => new SingleBuildEntry { Name = randomName, PreviousName = randomName, @@ -432,11 +428,11 @@ public sealed class BuildTemplateManager( }).ToList() }; - return result; + return true; } - build = default!; - return result; + build = default; + return false; } public string EncodeTemplate(IBuildEntry build) @@ -554,22 +550,26 @@ public sealed class BuildTemplateManager( return singleBuildEntry; } - private async Task> LoadBuildFromFile(string path, string buildName) + private async Task LoadBuildFromFile(string path, string buildName) { + var scopedLogger = this.logger.CreateScopedLogger(); if (File.Exists(path) is false) { - return new InvalidOperationException("Unable to find build file"); + scopedLogger.LogError("Unable to find build file at path {path}", path); + return default; } var content = await File.ReadAllLinesAsync(path); if (content.Length == 0) { - return new InvalidOperationException("File does not contain a valid template code"); + scopedLogger.LogError("File at path {path} does not contain a valid template code", path); + return default; } if (this.TryDecodeTemplate(content.First(), out var build) is false) { - return new InvalidOperationException("Unable to parse build file"); + scopedLogger.LogError("Unable to parse build file at path {path}", path); + return default; } build.Name = buildName; @@ -586,13 +586,14 @@ public sealed class BuildTemplateManager( try { build.Metadata = - JsonConvert.DeserializeObject>( + JsonSerializer.Deserialize>( Encoding.UTF8.GetString( Convert.FromBase64String(content[1]))); } catch(Exception ex) { - return new InvalidOperationException("Failed to parse build metadata", ex); + scopedLogger.LogError(ex, "Failed to parse build metadata at path {path}", path); + return default; } } } @@ -602,34 +603,33 @@ public sealed class BuildTemplateManager( build.CreationTime = new FileInfo(path).CreationTimeUtc; } - return Result.Success(build); + return build; } - private Result DecodeTemplatesInner(string template) + private Build[]? DecodeTemplatesInner(string template) { var buildTemplates = template.Split(' ').Where(s => !s.IsNullOrWhiteSpace()).ToArray(); if (buildTemplates.Length == 0) { - return new InvalidOperationException("Invalid build template"); + return []; } var builds = new Build[buildTemplates.Length]; for(var i = 0; i < builds.Length; i++) { var result = this.DecodeTemplateInner(buildTemplates[i]); - if (result.TryExtractFailure(out var exception)) + if (result is null) { - return exception!; + return default; } - result.TryExtractSuccess(out var maybeBuild); - builds[i] = maybeBuild!; + builds[i] = result; } return builds; } - private Result DecodeTemplateInner(string template) + private Build? DecodeTemplateInner(string template) { var scopedLogger = this.logger.CreateScopedLogger(); scopedLogger.LogDebug("Attempting to decode template"); @@ -638,7 +638,7 @@ public sealed class BuildTemplateManager( if (buildMetadata.VersionNumber != 0) { scopedLogger.LogError("Expected version number to be 0 but found {buildMetadata.VersionNumber}", buildMetadata.VersionNumber); - return new InvalidOperationException($"Failed to parse template"); + return default; } var build = new Build() @@ -650,14 +650,14 @@ public sealed class BuildTemplateManager( if (Profession.TryParse(buildMetadata.PrimaryProfessionId, out var primaryProfession) is false) { scopedLogger.LogError("Failed to parse profession with id {buildMetadata.PrimaryProfessionId}", buildMetadata.PrimaryProfessionId); - return new InvalidOperationException($"Failed to parse template"); + return default; } build.Primary = primaryProfession; if (Profession.TryParse(buildMetadata.SecondaryProfessionId, out var secondaryProfession) is false) { scopedLogger.LogError("Failed to parse profession with id {buildMetadata.SecondaryProfessionId}", buildMetadata.SecondaryProfessionId); - return new InvalidOperationException($"Failed to parse template"); + return default; } build.Secondary = secondaryProfession; @@ -682,9 +682,8 @@ public sealed class BuildTemplateManager( var maybeAttribute = build.Attributes.FirstOrDefault(a => a.Attribute!.Id == attributeId); if (maybeAttribute is null) { - var msg = $"Failed to parse attribute with id {attributeId} for professions {primaryProfession.Name}/{secondaryProfession.Name}"; - scopedLogger.LogError(msg); - return new InvalidOperationException(msg); + scopedLogger.LogError("Failed to parse attribute with id {attributeId} for professions {primaryProfession.Name}/{secondaryProfession.Name}", attributeId, primaryProfession.Name ?? string.Empty, secondaryProfession.Name ?? string.Empty); + return default; } maybeAttribute!.Points = buildMetadata.AttributePoints[i]; @@ -695,7 +694,7 @@ public sealed class BuildTemplateManager( if (Skill.TryParse(buildMetadata.SkillIds[i], out var skill) is false) { scopedLogger.LogError("Failed to parse skill with id {skillId}", buildMetadata.SkillIds[i]); - return new InvalidOperationException($"Failed to parse template"); + return default; } build.Skills.Add(skill); diff --git a/Daybreak.Shared/Services/BuildTemplates/IBuildTemplateManager.cs b/Daybreak.Shared/Services/BuildTemplates/IBuildTemplateManager.cs index 424c6c6d..1aba879a 100644 --- a/Daybreak.Shared/Services/BuildTemplates/IBuildTemplateManager.cs +++ b/Daybreak.Shared/Services/BuildTemplates/IBuildTemplateManager.cs @@ -1,7 +1,7 @@ using Daybreak.Shared.Models; using Daybreak.Shared.Models.Api; using Daybreak.Shared.Models.Builds; -using System.Extensions; +using System.Diagnostics.CodeAnalysis; namespace Daybreak.Shared.Services.BuildTemplates; @@ -27,8 +27,8 @@ public interface IBuildTemplateManager void SaveBuild(IBuildEntry buildEntry); void RemoveBuild(IBuildEntry buildEntry); IAsyncEnumerable GetBuilds(); - Task> GetBuild(string name); + Task GetBuild(string name); IBuildEntry DecodeTemplate(string template); - bool TryDecodeTemplate(string template, out IBuildEntry build); + bool TryDecodeTemplate(string template, [NotNullWhen(true)] out IBuildEntry? build); string EncodeTemplate(IBuildEntry buildEntry); } diff --git a/Daybreak.Shared/Services/Options/IOptionsProvider.cs b/Daybreak.Shared/Services/Options/IOptionsProvider.cs index 09b0c8e7..e053e2ae 100644 --- a/Daybreak.Shared/Services/Options/IOptionsProvider.cs +++ b/Daybreak.Shared/Services/Options/IOptionsProvider.cs @@ -1,17 +1,17 @@ using Daybreak.Shared.Models.Options; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Daybreak.Shared.Services.Options; public interface IOptionsProvider { - JObject? TryGetKeyedOptions(string key); + JsonDocument? TryGetKeyedOptions(string key); IEnumerable GetRegisteredOptionInstances(); OptionInstance GetRegisteredOptionInstance(string optionName); IEnumerable GetRegisteredOptionDefinitions(); void SaveRegisteredOptions(object options); void SaveRegisteredOptions(OptionInstance optionInstance); - void SaveRegisteredOptions(string name, JObject options); + void SaveRegisteredOptions(string name, JsonDocument options); void SaveOption(TOptions options) where TOptions : notnull; } diff --git a/Daybreak.Shared/Services/Options/IOptionsSynchronizationService.cs b/Daybreak.Shared/Services/Options/IOptionsSynchronizationService.cs index badc1f73..31a0b7aa 100644 --- a/Daybreak.Shared/Services/Options/IOptionsSynchronizationService.cs +++ b/Daybreak.Shared/Services/Options/IOptionsSynchronizationService.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Daybreak.Shared.Services.Options; public interface IOptionsSynchronizationService { - Task> GetLocalOptions(CancellationToken cancellationToken); + Task> GetLocalOptions(CancellationToken cancellationToken); - Task?> GetRemoteOptions(CancellationToken cancellationToken); + Task?> GetRemoteOptions(CancellationToken cancellationToken); /// /// Back up options on remote diff --git a/Daybreak/Configuration/Options/BuildSynchronizationOptions.cs b/Daybreak/Configuration/Options/BuildSynchronizationOptions.cs index aaa44a09..a82399e4 100644 --- a/Daybreak/Configuration/Options/BuildSynchronizationOptions.cs +++ b/Daybreak/Configuration/Options/BuildSynchronizationOptions.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -8,8 +8,8 @@ namespace Daybreak.Configuration.Options; [OptionsSynchronizationIgnore] internal sealed class SynchronizationOptions { - [JsonProperty(nameof(ProtectedGraphAccessToken))] + [JsonPropertyName(nameof(ProtectedGraphAccessToken))] public string? ProtectedGraphAccessToken { get; set; } - [JsonProperty(nameof(ProtectedGraphRefreshToken))] + [JsonPropertyName(nameof(ProtectedGraphRefreshToken))] public string? ProtectedGraphRefreshToken { get; set; } } diff --git a/Daybreak/Configuration/Options/CredentialManagerOptions.cs b/Daybreak/Configuration/Options/CredentialManagerOptions.cs index 6a148291..7dc90be8 100644 --- a/Daybreak/Configuration/Options/CredentialManagerOptions.cs +++ b/Daybreak/Configuration/Options/CredentialManagerOptions.cs @@ -1,6 +1,6 @@ using Daybreak.Shared.Attributes; using Daybreak.Shared.Models; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -8,6 +8,6 @@ namespace Daybreak.Configuration.Options; [OptionsName(Name = "Credentials")] internal sealed class CredentialManagerOptions { - [JsonProperty(nameof(ProtectedLoginCredentials))] + [JsonPropertyName(nameof(ProtectedLoginCredentials))] public List ProtectedLoginCredentials { get; set; } = []; } diff --git a/Daybreak/Configuration/Options/DSOALOptions.cs b/Daybreak/Configuration/Options/DSOALOptions.cs index e48c99d9..83ef304d 100644 --- a/Daybreak/Configuration/Options/DSOALOptions.cs +++ b/Daybreak/Configuration/Options/DSOALOptions.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -7,7 +7,7 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] internal sealed class DSOALOptions { - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] [OptionName(Name = "Enabled", Description = "If true, the launcher will also launch DSOAL when launching GuildWars")] public bool Enabled { get; set; } } diff --git a/Daybreak/Configuration/Options/DXVKOptions.cs b/Daybreak/Configuration/Options/DXVKOptions.cs index ee81ebc7..66bfcce7 100644 --- a/Daybreak/Configuration/Options/DXVKOptions.cs +++ b/Daybreak/Configuration/Options/DXVKOptions.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -7,12 +7,12 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] internal sealed class DXVKOptions { - [JsonProperty(nameof(Version))] + [JsonPropertyName(nameof(Version))] [OptionSynchronizationIgnore] [OptionIgnore] public string? Version { get; set; } - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] [OptionName(Name = "Enabled", Description = "If true, the launcher will also launch DXVK when launching GuildWars")] public bool Enabled { get; set; } } diff --git a/Daybreak/Configuration/Options/DirectSongOptions.cs b/Daybreak/Configuration/Options/DirectSongOptions.cs index 0fec2754..e28716f4 100644 --- a/Daybreak/Configuration/Options/DirectSongOptions.cs +++ b/Daybreak/Configuration/Options/DirectSongOptions.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -7,7 +7,7 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] internal sealed class DirectSongOptions { - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] [OptionName(Name = "Enabled", Description = "If true, Daybreak will setup DirectSong when launching Guild Wars")] public bool Enabled { get; set; } = false; } diff --git a/Daybreak/Configuration/Options/FocusViewOptions.cs b/Daybreak/Configuration/Options/FocusViewOptions.cs index 0f21b257..d69b493f 100644 --- a/Daybreak/Configuration/Options/FocusViewOptions.cs +++ b/Daybreak/Configuration/Options/FocusViewOptions.cs @@ -1,6 +1,6 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; using Daybreak.Shared.Models.FocusView; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -8,39 +8,39 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] public sealed class FocusViewOptions { - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] [OptionName(Name = "Enabled", Description = "If true, the focus view is enabled, showing live information from the game")] public bool Enabled { get; set; } = false; - [JsonProperty(nameof(ExperienceDisplay))] + [JsonPropertyName(nameof(ExperienceDisplay))] [OptionName(Name = "Experience Display Mode", Description = "Sets how should the experience display show the information")] public ExperienceDisplay ExperienceDisplay { get; set; } - [JsonProperty(nameof(KurzickPointsDisplay))] + [JsonPropertyName(nameof(KurzickPointsDisplay))] [OptionName(Name = "Kurzick Points Display Mode", Description = "Sets how should the kurzick points display show the information")] public PointsDisplay KurzickPointsDisplay { get; set; } - [JsonProperty(nameof(LuxonPointsDisplay))] + [JsonPropertyName(nameof(LuxonPointsDisplay))] [OptionName(Name = "Luxon Points Display Mode", Description = "Sets how should the luxon points display show the information")] public PointsDisplay LuxonPointsDisplay { get; set; } - [JsonProperty(nameof(BalthazarPointsDisplay))] + [JsonPropertyName(nameof(BalthazarPointsDisplay))] [OptionName(Name = "Balthazar Points Display Mode", Description = "Sets how should the balthazar points display show the information")] public PointsDisplay BalthazarPointsDisplay { get; set; } - [JsonProperty(nameof(ImperialPointsDisplay))] + [JsonPropertyName(nameof(ImperialPointsDisplay))] [OptionName(Name = "Imperial Points Display Mode", Description = "Sets how should the imperial points display show the information")] public PointsDisplay ImperialPointsDisplay { get; set; } - [JsonProperty(nameof(VanquishingDisplay))] + [JsonPropertyName(nameof(VanquishingDisplay))] [OptionName(Name = "Vanquishing Display Mode", Description = "Sets how should the vanquishing display show the information")] public PointsDisplay VanquishingDisplay { get; set; } - [JsonProperty(nameof(HealthDisplay))] + [JsonPropertyName(nameof(HealthDisplay))] [OptionName(Name = "Health Display Mode", Description = "Sets how should the health display show the information")] public PointsDisplay HealthDisplay { get; set; } - [JsonProperty(nameof(EnergyDisplay))] + [JsonPropertyName(nameof(EnergyDisplay))] [OptionName(Name = "Energy Display Mode", Description = "Sets how should the energy display show the information")] public PointsDisplay EnergyDisplay { get; set; } } diff --git a/Daybreak/Configuration/Options/GuildWarsScreenPlacerOptions.cs b/Daybreak/Configuration/Options/GuildWarsScreenPlacerOptions.cs index 27dce428..97d316d9 100644 --- a/Daybreak/Configuration/Options/GuildWarsScreenPlacerOptions.cs +++ b/Daybreak/Configuration/Options/GuildWarsScreenPlacerOptions.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -7,9 +7,9 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] internal sealed class GuildWarsScreenPlacerOptions { - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] public bool Enabled { get; set; } - [JsonProperty(nameof(DesiredScreen))] + [JsonPropertyName(nameof(DesiredScreen))] public int DesiredScreen { get; set; } } diff --git a/Daybreak/Configuration/Options/GuildwarsExecutableOptions.cs b/Daybreak/Configuration/Options/GuildwarsExecutableOptions.cs index 9dfc42d0..07b0767d 100644 --- a/Daybreak/Configuration/Options/GuildwarsExecutableOptions.cs +++ b/Daybreak/Configuration/Options/GuildwarsExecutableOptions.cs @@ -1,11 +1,11 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; [OptionsIgnore] [OptionsSynchronizationIgnore] internal sealed class GuildwarsExecutableOptions { - [JsonProperty(nameof(ExecutablePaths))] + [JsonPropertyName(nameof(ExecutablePaths))] public List ExecutablePaths { get; set; } = []; } diff --git a/Daybreak/Configuration/Options/LaunchConfigurationServiceOptions.cs b/Daybreak/Configuration/Options/LaunchConfigurationServiceOptions.cs index f7c04a98..032c7851 100644 --- a/Daybreak/Configuration/Options/LaunchConfigurationServiceOptions.cs +++ b/Daybreak/Configuration/Options/LaunchConfigurationServiceOptions.cs @@ -1,6 +1,6 @@ using Daybreak.Shared.Attributes; using Daybreak.Shared.Models.LaunchConfigurations; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -8,6 +8,6 @@ namespace Daybreak.Configuration.Options; [OptionsSynchronizationIgnore] internal sealed class LaunchConfigurationServiceOptions { - [JsonProperty(nameof(LaunchConfigurations))] + [JsonPropertyName(nameof(LaunchConfigurations))] public List LaunchConfigurations { get; set; } = []; } diff --git a/Daybreak/Configuration/Options/LauncherOptions.cs b/Daybreak/Configuration/Options/LauncherOptions.cs index efc11bea..c464572c 100644 --- a/Daybreak/Configuration/Options/LauncherOptions.cs +++ b/Daybreak/Configuration/Options/LauncherOptions.cs @@ -1,42 +1,42 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; [OptionsName(Name = "Launcher")] public sealed class LauncherOptions { - [JsonProperty(nameof(LaunchGuildwarsAsCurrentUser))] + [JsonPropertyName(nameof(LaunchGuildwarsAsCurrentUser))] [OptionName(Name = "Launch GuildWars As Current User", Description = "If true, will attempt to launch GuildWars as the current user. Otherwise will attempt to launch as system user")] public bool LaunchGuildwarsAsCurrentUser { get; set; } = true; - [JsonProperty(nameof(ShortcutLocation))] + [JsonPropertyName(nameof(ShortcutLocation))] [OptionName(Name = "Shortcut Location", Description = "Location where the shortcut will be placed")] [OptionSynchronizationIgnore] public string? ShortcutLocation { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); - [JsonProperty(nameof(PlaceShortcut))] + [JsonPropertyName(nameof(PlaceShortcut))] [OptionName(Name = "Shortcut Placed", Description = "If true, the shortcut will be placed. If false, the shortcut will be deleted")] public bool PlaceShortcut { get; set; } - [JsonProperty(nameof(AutoCheckUpdate))] + [JsonPropertyName(nameof(AutoCheckUpdate))] [OptionName(Name = "Auto-Check For Updates", Description = "Automatically check for updates")] public bool AutoCheckUpdate { get; set; } = true; - [JsonProperty(nameof(MultiLaunchSupport))] + [JsonPropertyName(nameof(MultiLaunchSupport))] [OptionName(Name = "Multi-Launch Support", Description = "If true, the launcher will support multiple executables being launched at the same time")] public bool MultiLaunchSupport { get; set; } - [JsonProperty(nameof(ModStartupTimeout))] + [JsonPropertyName(nameof(ModStartupTimeout))] [OptionName(Name = "Mod Startup Timeout", Description = "Amount of seconds that Daybreak will wait for each mod to start-up before cancelling the tasks")] [OptionRange(MinValue = 30, MaxValue = 300)] public double ModStartupTimeout { get; set; } = 30; - [JsonProperty(nameof(BetaUpdate))] + [JsonPropertyName(nameof(BetaUpdate))] [OptionName(Name = "Beta Update", Description = "If true, the launcher will use the new update procedure")] public bool BetaUpdate { get; set; } = true; - [JsonProperty(nameof(AutoBackupSettings))] + [JsonPropertyName(nameof(AutoBackupSettings))] [OptionName(Name = "Auto Backup Settings", Description = "If true, the launcher will attempt to backup settings periodically")] public bool AutoBackupSettings { get; set; } = false; } diff --git a/Daybreak/Configuration/Options/ReShadeOptions.cs b/Daybreak/Configuration/Options/ReShadeOptions.cs index ed38d19f..30d449f1 100644 --- a/Daybreak/Configuration/Options/ReShadeOptions.cs +++ b/Daybreak/Configuration/Options/ReShadeOptions.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -7,11 +7,11 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] internal sealed class ReShadeOptions { - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] [OptionName(Name = "Enabled", Description = "If true, Daybreak will attempt to inject ReShade into the starting Guild Wars executable")] public bool Enabled { get; set; } = false; - [JsonProperty(nameof(AutoUpdate))] + [JsonPropertyName(nameof(AutoUpdate))] [OptionName(Name = "Auto-update", Description = "If true, Daybreak will periodically check ReShade for updates")] public bool AutoUpdate { get; set; } = true; } diff --git a/Daybreak/Configuration/Options/ToolboxOptions.cs b/Daybreak/Configuration/Options/ToolboxOptions.cs index 8f10d19d..a0bbb9d3 100644 --- a/Daybreak/Configuration/Options/ToolboxOptions.cs +++ b/Daybreak/Configuration/Options/ToolboxOptions.cs @@ -1,5 +1,5 @@ using Daybreak.Shared.Attributes; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -7,7 +7,7 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] internal sealed class ToolboxOptions { - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] [OptionName(Name = "Enabled", Description = "If true, Daybreak will also launch GWToolboxdll when launching GuildWars")] public bool Enabled { get; set; } } diff --git a/Daybreak/Configuration/Options/UModOptions.cs b/Daybreak/Configuration/Options/UModOptions.cs index c1b8e768..4a7bee8c 100644 --- a/Daybreak/Configuration/Options/UModOptions.cs +++ b/Daybreak/Configuration/Options/UModOptions.cs @@ -1,6 +1,6 @@ using Daybreak.Shared.Attributes; using Daybreak.Shared.Models.UMod; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Configuration.Options; @@ -8,15 +8,15 @@ namespace Daybreak.Configuration.Options; [OptionsIgnore] public sealed class UModOptions { - [JsonProperty(nameof(Enabled))] + [JsonPropertyName(nameof(Enabled))] [OptionName(Name = "Enabled", Description = "If true, Daybreak will also launch uMod when launching GuildWars")] public bool Enabled { get; set; } - [JsonProperty(nameof(AutoEnableMods))] + [JsonPropertyName(nameof(AutoEnableMods))] [OptionName(Name = "Auto-Enable Mods", Description = "If true, mods loaded into Daybreak will be enabled by default")] public bool AutoEnableMods { get; set; } = true; - [JsonProperty(nameof(Mods))] + [JsonPropertyName(nameof(Mods))] [OptionIgnore] [OptionSynchronizationIgnore] public List Mods { get; set; } = []; diff --git a/Daybreak/Configuration/SecretManager.cs b/Daybreak/Configuration/SecretManager.cs index 4c813815..ec4852e1 100644 --- a/Daybreak/Configuration/SecretManager.cs +++ b/Daybreak/Configuration/SecretManager.cs @@ -1,30 +1,40 @@ -using Newtonsoft.Json.Linq; -using System.Extensions; +using System.Extensions; using System.Reflection; +using System.Text.Json; namespace Daybreak.Configuration; public static class SecretManager { - private static JObject? SecretsHolder; + private static JsonDocument? SecretsHolder; public static string GetSecret(SecretKeys secretKey) { SecretsHolder ??= LoadSecrets(); - return SecretsHolder.Value(secretKey.Key) ?? throw new InvalidOperationException($"Could not find secret by key {secretKey.Key}"); + if (SecretsHolder.RootElement.TryGetProperty(secretKey.Key, out var element)) + { + return element.GetString() ?? throw new InvalidOperationException($"Could not find secret by key {secretKey.Key}"); + } + + throw new InvalidOperationException($"Could not find secret by key {secretKey.Key}"); } public static T GetSecret(SecretKeys secretKey) { SecretsHolder ??= LoadSecrets(); - return SecretsHolder.Value(secretKey.Key) ?? throw new InvalidOperationException($"Could not find secret by key {secretKey.Key}"); + if (SecretsHolder.RootElement.TryGetProperty(secretKey.Key, out var element)) + { + return element.Deserialize() ?? throw new InvalidOperationException($"Could not find secret by key {secretKey.Key}"); + } + + throw new InvalidOperationException($"Could not find secret by key {secretKey.Key}"); } - private static JObject LoadSecrets() + private static JsonDocument LoadSecrets() { var serializedSecrets = Assembly.GetExecutingAssembly().GetManifestResourceStream("Daybreak.secrets.json")?.ReadAllBytes().GetString() ?? throw new InvalidOperationException("Could not load Daybreak.secrets.json from assembly"); serializedSecrets = TrimUnwantedCharacters(serializedSecrets); - return JObject.Parse(serializedSecrets); + return JsonDocument.Parse(serializedSecrets); } private static string TrimUnwantedCharacters(string s) diff --git a/Daybreak/Launch/Launcher.cs b/Daybreak/Launch/Launcher.cs index b9216ec9..223b73ed 100644 --- a/Daybreak/Launch/Launcher.cs +++ b/Daybreak/Launch/Launcher.cs @@ -1,5 +1,6 @@ using Daybreak.Configuration; using Daybreak.Services.Initialization; +using Daybreak.Shared.Models.Guildwars; using Daybreak.Shared.Models.Menu; using Daybreak.Shared.Models.Plugins; using Daybreak.Shared.Models.Themes; diff --git a/Daybreak/Services/Credentials/CredentialManager.cs b/Daybreak/Services/Credentials/CredentialManager.cs index b19dfa03..9694d545 100644 --- a/Daybreak/Services/Credentials/CredentialManager.cs +++ b/Daybreak/Services/Credentials/CredentialManager.cs @@ -48,8 +48,6 @@ internal sealed class CredentialManager( return [.. config .ProtectedLoginCredentials .Select(this.UnprotectCredentials) - .Where(this.CredentialsUnprotected) - .Select(this.ExtractCredentials) .OfType()]; } @@ -59,8 +57,6 @@ internal sealed class CredentialManager( var options = this.liveOptions.CurrentValue; options.ProtectedLoginCredentials = [.. loginCredentials .Select(this.ProtectCredentials) - .Where(this.CredentialsProtected) - .Select(this.ExtractProtectedCredentials) .OfType()]; this.optionsProvider.SaveOption(options); } @@ -75,7 +71,7 @@ internal sealed class CredentialManager( }; } - private Optional UnprotectCredentials(ProtectedLoginCredentials protectedLoginCredentials) + private LoginCredentials? UnprotectCredentials(ProtectedLoginCredentials protectedLoginCredentials) { var scopedLogger = this.logger.CreateScopedLogger(); try @@ -92,11 +88,11 @@ internal sealed class CredentialManager( catch (Exception e) { scopedLogger.LogError(e, "Unable to retrieve credentials"); - return Optional.None(); + return default; } } - private Optional ProtectCredentials(LoginCredentials loginCredentials) + private ProtectedLoginCredentials? ProtectCredentials(LoginCredentials loginCredentials) { var scopedLogger = this.logger.CreateScopedLogger(); try @@ -113,31 +109,7 @@ internal sealed class CredentialManager( catch(Exception e) { scopedLogger.LogError(e, "Unable to encrypt credentials"); - return Optional.None(); + return default; } } - - private bool CredentialsUnprotected(Optional optional) - { - return optional - .Switch(onSome: _ => true, onNone: () => false) - .ExtractValue(); - } - - private bool CredentialsProtected(Optional optional) - { - return optional - .Switch(onSome: _ => true, onNone: () => false) - .ExtractValue(); - } - - private ProtectedLoginCredentials? ExtractProtectedCredentials(Optional optional) - { - return optional.ExtractValue(); - } - - private LoginCredentials? ExtractCredentials(Optional optional) - { - return optional.ExtractValue(); - } } diff --git a/Daybreak/Services/Graph/BlazorGraphClient.cs b/Daybreak/Services/Graph/BlazorGraphClient.cs index c1911a0f..6ca2fc54 100644 --- a/Daybreak/Services/Graph/BlazorGraphClient.cs +++ b/Daybreak/Services/Graph/BlazorGraphClient.cs @@ -7,11 +7,11 @@ using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Identity.Client; -using Newtonsoft.Json; using System.Core.Extensions; using System.Extensions; using System.Extensions.Core; using System.Net.Http.Headers; +using System.Text.Json; namespace Daybreak.Services.Graph; @@ -61,9 +61,9 @@ internal sealed class BlazorGraphClient : IGraphClient this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); } - public async Task> PerformAuthorizationFlow(CancellationToken cancellationToken = default) + public async Task PerformAuthorizationFlow(CancellationToken cancellationToken = default) { - if (await this.GetValidAccessToken() is string) + if (await this.GetValidAccessToken() is not null) { return true; } @@ -79,19 +79,21 @@ internal sealed class BlazorGraphClient : IGraphClient catch (Exception ex) { this.logger.LogError(ex, "OAuth flow failed"); - return ex; + return false; } } - public async Task> GetUserProfile() + public async Task GetUserProfile() where TViewType : ComponentBase { + var scopedLogger = this.logger.CreateScopedLogger(); try { var accessToken = await this.GetValidAccessToken(); if (string.IsNullOrEmpty(accessToken)) { - return new InvalidOperationException("Client is not authorized"); + scopedLogger.LogError("Client is not authorized"); + return default; } this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -99,46 +101,48 @@ internal sealed class BlazorGraphClient : IGraphClient if (!response.IsSuccessStatusCode) { - return new InvalidOperationException($"Failed to load profile. Response status code [{response.StatusCode}]"); + scopedLogger.LogError("Failed to load profile. Status code: {StatusCode}", response.StatusCode); + return default; } - var profile = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - return profile!; + var profile = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + return profile; } catch (Exception ex) { - this.logger.LogError(ex, "Failed to get user profile"); - return ex; + scopedLogger.LogError(ex, "Failed to get user profile"); + return default; } } - public async Task> UploadBuilds() + public async Task UploadBuilds() { var builds = await this.buildTemplateManager.GetBuilds().ToListAsync(); return await this.PutBuilds(builds); } - public async Task> DownloadBuilds() + public async Task DownloadBuilds() { + var scopedLogger = this.logger.CreateScopedLogger(); var retrieveBuildsResponse = await this.RetrieveBuildsList(); - if (retrieveBuildsResponse.TryExtractFailure(out var failure)) + if (retrieveBuildsResponse is null) { - this.logger.LogError(failure, "Unable to download builds"); - return false; - } - - if (retrieveBuildsResponse.TryExtractSuccess(out var builds) is false) - { - this.logger.LogError("Unexpected error occurred"); + scopedLogger.LogError("Unable to download builds"); return false; } this.buildTemplateManager.ClearBuilds(); var compiledBuilds = this.buildsCache?.Select(buildFile => { - if (this.buildTemplateManager.TryDecodeTemplate(buildFile.TemplateCode!, out var build) is false) + if (buildFile is null || + buildFile.TemplateCode is null) { - return null; + return default; + } + + if (this.buildTemplateManager.TryDecodeTemplate(buildFile.TemplateCode, out var build) is false) + { + return default; } build.SourceUrl = buildFile.SourceUrl; @@ -151,18 +155,13 @@ internal sealed class BlazorGraphClient : IGraphClient return true; } - public async Task> DownloadBuild(string buildName) + public async Task DownloadBuild(string buildName) { + var scopedLogger = this.logger.CreateScopedLogger(); var retrieveBuildsResponse = await this.RetrieveBuildsList(); - if (retrieveBuildsResponse.TryExtractFailure(out var failure)) + if (retrieveBuildsResponse is null) { - this.logger.LogError(failure, "Unable to download builds"); - return false; - } - - if (retrieveBuildsResponse.TryExtractSuccess(out var builds) is false) - { - this.logger.LogError("Unexpected error occurred"); + scopedLogger.LogError("Unable to download builds"); return false; } @@ -185,33 +184,36 @@ internal sealed class BlazorGraphClient : IGraphClient return true; } - public async Task> UploadBuild(string buildName) + public async Task UploadBuild(string buildName) { + var scopedLogger = this.logger.CreateScopedLogger(); var getBuildResult = await this.buildTemplateManager.GetBuild(buildName); - if (getBuildResult.TryExtractSuccess(out var buildEntry) is false) + if (getBuildResult is null) { - return getBuildResult.SwitchAny(onFailure: exception => exception)!; + scopedLogger.LogError("Build {BuildName} not found for upload", buildName); + return false; } - return await this.PutBuild(buildEntry!); + return await this.PutBuild(getBuildResult); } - public async Task, Exception>> RetrieveBuildsList() + public async Task?> RetrieveBuildsList() { var maybeBuildsBackup = await this.GetBuildsBackup(); - var buildsBackup = maybeBuildsBackup.ExtractValue(); - this.buildsCache = buildsBackup; - return buildsBackup!; + this.buildsCache = maybeBuildsBackup; + return maybeBuildsBackup; } - public async Task> UploadSettings(string settings, CancellationToken cancellationToken) + public async Task UploadSettings(string settings, CancellationToken cancellationToken) { + var scopedLogger = this.logger.CreateScopedLogger(); try { var accessToken = await this.GetValidAccessToken(); if (string.IsNullOrEmpty(accessToken)) { - return new InvalidOperationException("Client is not authorized"); + scopedLogger.LogError("Client is not authorized"); + return false; } this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -221,19 +223,21 @@ internal sealed class BlazorGraphClient : IGraphClient } catch (Exception e) { - this.logger.LogError(e, "Failed to upload settings"); - return e; + scopedLogger.LogError(e, "Failed to upload settings"); + return false; } } - public async Task> DownloadSettings(CancellationToken cancellationToken) + public async Task DownloadSettings(CancellationToken cancellationToken) { + var scopedLogger = this.logger.CreateScopedLogger(); try { var accessToken = await this.GetValidAccessToken(); if (string.IsNullOrEmpty(accessToken)) { - return new InvalidOperationException("Client is not authorized"); + scopedLogger.LogError("Client is not authorized"); + return default; } this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -241,7 +245,7 @@ internal sealed class BlazorGraphClient : IGraphClient if (fileItemResponse.IsSuccessStatusCode is false) { - return string.Empty; + return default; } var driveItemContent = await fileItemResponse.Content.ReadAsStringAsync(cancellationToken); @@ -249,15 +253,15 @@ internal sealed class BlazorGraphClient : IGraphClient } catch (Exception e) { - this.logger.LogError(e, "Failed to download settings"); - return e; + scopedLogger.LogError(e, "Failed to download settings"); + return default; } } - public Task> LogOut() + public Task LogOut() { this.ResetAuthorization(); - return Task.FromResult(Result.Success(true)); + return Task.FromResult(true); } public void ResetAuthorization() @@ -321,7 +325,7 @@ internal sealed class BlazorGraphClient : IGraphClient buildList = [.. buildList.OrderBy(b => b.FileName)]; this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - using var stringContent = new StringContent(JsonConvert.SerializeObject(buildList)); + using var stringContent = new StringContent(JsonSerializer.Serialize(buildList)); var response = await this.httpClient.PutAsync(BuildsSyncFileUri, stringContent); if (response.IsSuccessStatusCode is false) @@ -361,7 +365,7 @@ internal sealed class BlazorGraphClient : IGraphClient buildList = [.. buildList.OrderBy(b => b.FileName)]; this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - using var stringContent = new StringContent(JsonConvert.SerializeObject(buildList)); + using var stringContent = new StringContent(JsonSerializer.Serialize(buildList)); var response = await this.httpClient.PutAsync(BuildsSyncFileUri, stringContent); if (response.IsSuccessStatusCode is false) @@ -379,14 +383,16 @@ internal sealed class BlazorGraphClient : IGraphClient } } - private async Task>> GetBuildsBackup() + private async Task?> GetBuildsBackup() { + var scopedLogger = this.logger.CreateScopedLogger(); try { var accessToken = await this.GetValidAccessToken(); if (string.IsNullOrEmpty(accessToken)) { - return Optional.None>(); + scopedLogger.LogError("Client is not authorized"); + return default; } this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -394,15 +400,17 @@ internal sealed class BlazorGraphClient : IGraphClient if (fileItemResponse.IsSuccessStatusCode is false) { - return Optional.None>(); + scopedLogger.LogError("Failed to retrieve builds backup. Status code: {StatusCode}", fileItemResponse.StatusCode); + return default; } var driveItemContent = await fileItemResponse.Content.ReadAsStringAsync(); - var backup = JsonConvert.DeserializeObject>(driveItemContent); + var backup = JsonSerializer.Deserialize>(driveItemContent); if (backup is null) { - return Optional.None>(); + scopedLogger.LogError("Deserialized builds backup is null"); + return default; } return backup; @@ -410,7 +418,7 @@ internal sealed class BlazorGraphClient : IGraphClient catch (Exception ex) { this.logger.LogError(ex, "Failed to get builds backup"); - return Optional.None>(); + return default; } } } diff --git a/Daybreak/Services/Graph/IGraphClient.cs b/Daybreak/Services/Graph/IGraphClient.cs index 53c4a9d3..f7de6782 100644 --- a/Daybreak/Services/Graph/IGraphClient.cs +++ b/Daybreak/Services/Graph/IGraphClient.cs @@ -1,21 +1,20 @@ using Daybreak.Services.Graph.Models; using Microsoft.AspNetCore.Components; -using System.Extensions; namespace Daybreak.Services.Graph; public interface IGraphClient { - Task> GetUserProfile() + Task GetUserProfile() where TViewType : ComponentBase; - Task> PerformAuthorizationFlow(CancellationToken cancellationToken = default); - Task> LogOut(); - Task> UploadBuilds(); - Task> DownloadBuilds(); - Task> UploadBuild(string buildName); - Task> DownloadBuild(string buildName); - Task> UploadSettings(string settings, CancellationToken cancellationToken); - Task> DownloadSettings(CancellationToken cancellationToken); - Task, Exception>> RetrieveBuildsList(); + Task PerformAuthorizationFlow(CancellationToken cancellationToken = default); + Task LogOut(); + Task UploadBuilds(); + Task DownloadBuilds(); + Task UploadBuild(string buildName); + Task DownloadBuild(string buildName); + Task UploadSettings(string settings, CancellationToken cancellationToken); + Task DownloadSettings(CancellationToken cancellationToken); + Task?> RetrieveBuildsList(); void ResetAuthorization(); } diff --git a/Daybreak/Services/Graph/Models/FileItem.cs b/Daybreak/Services/Graph/Models/FileItem.cs index 4bf7fe06..ba12c5aa 100644 --- a/Daybreak/Services/Graph/Models/FileItem.cs +++ b/Daybreak/Services/Graph/Models/FileItem.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.Graph.Models; public sealed class FileItem { - [JsonProperty("@microsoft.graph.downloadUrl")] + [JsonPropertyName("@microsoft.graph.downloadUrl")] public string? DownloadUrl { get; set; } - [JsonProperty("name")] + [JsonPropertyName("name")] public string? Name { get; set; } - [JsonProperty("lastModifiedDateTime")] + [JsonPropertyName("lastModifiedDateTime")] public DateTime LastModifiedDateTime { get; set; } } diff --git a/Daybreak/Services/Graph/Models/FolderItem.cs b/Daybreak/Services/Graph/Models/FolderItem.cs index 10031b29..c4602263 100644 --- a/Daybreak/Services/Graph/Models/FolderItem.cs +++ b/Daybreak/Services/Graph/Models/FolderItem.cs @@ -1,9 +1,9 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.Graph.Models; public sealed class FolderItem { - [JsonProperty("value")] + [JsonPropertyName("value")] public List? Files { get; set; } } diff --git a/Daybreak/Services/Graph/Models/TokenResponse.cs b/Daybreak/Services/Graph/Models/TokenResponse.cs index 73f5e219..f66d00c9 100644 --- a/Daybreak/Services/Graph/Models/TokenResponse.cs +++ b/Daybreak/Services/Graph/Models/TokenResponse.cs @@ -1,17 +1,17 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.Graph.Models; public sealed class TokenResponse { - [JsonProperty("token_type")] + [JsonPropertyName("token_type")] public string? TokenType { get; set; } - [JsonProperty("scope")] + [JsonPropertyName("scope")] public string? Scope { get; set; } - [JsonProperty("expires_in")] + [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } - [JsonProperty("access_token")] + [JsonPropertyName("access_token")] public string? AccessToken { get; set; } - [JsonProperty("refresh_token")] + [JsonPropertyName("refresh_token")] public string? RefreshToken { get; set; } } diff --git a/Daybreak/Services/Graph/Models/User.cs b/Daybreak/Services/Graph/Models/User.cs index 69c337a6..52f26098 100644 --- a/Daybreak/Services/Graph/Models/User.cs +++ b/Daybreak/Services/Graph/Models/User.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.Graph.Models; public sealed class User { - [JsonProperty("displayName")] + [JsonPropertyName("displayName")] public string? DisplayName { get; set; } - [JsonProperty("mail")] + [JsonPropertyName("mail")] public string? Email { get; set; } } diff --git a/Daybreak/Services/Guildwars/Utils/GuildwarsFileStream.cs b/Daybreak/Services/Guildwars/Utils/GuildwarsFileStream.cs index cc9a4a7f..7fb09418 100644 --- a/Daybreak/Services/Guildwars/Utils/GuildwarsFileStream.cs +++ b/Daybreak/Services/Guildwars/Utils/GuildwarsFileStream.cs @@ -1,7 +1,6 @@ using Daybreak.Services.Guildwars.Models; using Daybreak.Services.Guildwars.Utils; using System.Core.Extensions; -using System.IO; namespace Daybreak.Services.GuildWars.Utils; internal sealed class GuildwarsFileStream(GuildWarsClientContext guildwarsClientContext, GuildWarsClient guildwarsClient, int fileId, int sizeCompressed, int sizeDecompressed, int crc) : Stream @@ -89,7 +88,7 @@ internal sealed class GuildwarsFileStream(GuildWarsClientContext guildwarsClient public override int Read(byte[] buffer, int offset, int count) { - return System.Extensions.TaskExtensions.RunSync(() => this.ReadAsync(buffer, offset, count)); + return Task.Run(() => this.ReadAsync(buffer, offset, count)).Result; } public override long Seek(long offset, SeekOrigin origin) diff --git a/Daybreak/Services/Options/OptionsManager.cs b/Daybreak/Services/Options/OptionsManager.cs index 6c030660..7d0463c2 100644 --- a/Daybreak/Services/Options/OptionsManager.cs +++ b/Daybreak/Services/Options/OptionsManager.cs @@ -4,12 +4,11 @@ using Daybreak.Shared.Models.Options; using Daybreak.Shared.Services.Options; using Daybreak.Shared.Utils; using Daybreak.Shared.Validators; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System.ComponentModel; using System.Core.Extensions; using System.Extensions; using System.Reflection; +using System.Text.Json; namespace Daybreak.Services.Options; @@ -39,7 +38,7 @@ internal sealed class OptionsManager : IOptionsProvider } else { - this.optionsCache = JsonConvert.DeserializeObject>(optionsFileContent) ?? + this.optionsCache = JsonSerializer.Deserialize>(optionsFileContent) ?? throw new InvalidOperationException("Unable to load options. Operation failed during deserialization"); } @@ -58,7 +57,7 @@ internal sealed class OptionsManager : IOptionsProvider throw new InvalidOperationException($"No registered or existing options found for {optionsName}"); } - return JsonConvert.DeserializeObject(value) ?? + return JsonSerializer.Deserialize(value) ?? throw new InvalidOperationException($"Failed to deserialize options {optionsName}"); } @@ -100,7 +99,7 @@ internal sealed class OptionsManager : IOptionsProvider continue; } - var instance = JsonConvert.DeserializeObject(value, type.Type) ?? Activator.CreateInstance(type.Type); + var instance = JsonSerializer.Deserialize(value, type.Type) ?? Activator.CreateInstance(type.Type); yield return new OptionInstance { Reference = instance!, Type = type }; } } @@ -122,7 +121,7 @@ internal sealed class OptionsManager : IOptionsProvider throw new InvalidOperationException($"No existing options found for {optionName}"); } - var instance = JsonConvert.DeserializeObject(value, type.Type) ?? Activator.CreateInstance(type.Type); + var instance = JsonSerializer.Deserialize(value, type.Type) ?? Activator.CreateInstance(type.Type); return new OptionInstance { Reference = instance!, Type = type }; } @@ -144,7 +143,7 @@ internal sealed class OptionsManager : IOptionsProvider this.SaveOptions(optionInstance.Type.Type, optionInstance.Reference); } - public void SaveRegisteredOptions(string name, JObject options) + public void SaveRegisteredOptions(string name, JsonDocument options) { options.ThrowIfNull(); @@ -167,14 +166,14 @@ internal sealed class OptionsManager : IOptionsProvider this.SaveOptions(registeredType, options); } - public JObject? TryGetKeyedOptions(string key) + public JsonDocument? TryGetKeyedOptions(string key) { if (!this.optionsCache.TryGetValue(key, out var value)) { return default; } - return JsonConvert.DeserializeObject(value); + return JsonSerializer.Deserialize(value); } private void RegisterOptions(Type optionType) @@ -182,7 +181,7 @@ internal sealed class OptionsManager : IOptionsProvider var optionsName = GetOptionsName(optionType); if (this.optionsCache.ContainsKey(optionsName) is false) { - this.optionsCache.Add(optionsName, JsonConvert.SerializeObject(Activator.CreateInstance(optionType))); + this.optionsCache.Add(optionsName, JsonSerializer.Serialize(Activator.CreateInstance(optionType))); } this.optionsTypes.Add(optionType); @@ -206,14 +205,14 @@ internal sealed class OptionsManager : IOptionsProvider throw new InvalidOperationException($"No registered or existing options found for {optionsName}"); } - this.optionsCache[optionsName] = JsonConvert.SerializeObject(value); + this.optionsCache[optionsName] = JsonSerializer.Serialize(value); this.SaveOptions(); this.CallHooks(type); } private void SaveOptions() { - File.WriteAllText(OptionsFile, JsonConvert.SerializeObject(this.optionsCache)); + File.WriteAllText(OptionsFile, JsonSerializer.Serialize(this.optionsCache)); } private void CallHooks(Type type) diff --git a/Daybreak/Services/Options/OptionsSynchronizationService.cs b/Daybreak/Services/Options/OptionsSynchronizationService.cs index 536ad4dd..bf6d3db1 100644 --- a/Daybreak/Services/Options/OptionsSynchronizationService.cs +++ b/Daybreak/Services/Options/OptionsSynchronizationService.cs @@ -1,18 +1,20 @@ using Daybreak.Configuration.Options; using Daybreak.Services.Graph; +using Daybreak.Shared.Services.Notifications; using Daybreak.Shared.Services.Options; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System.Core.Extensions; using System.Extensions; using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.Options; public sealed class OptionsSynchronizationService( + INotificationService notificationService, IOptionsProvider optionsProvider, IGraphClient graphClient, IOptionsMonitor liveOptions, @@ -21,6 +23,7 @@ public sealed class OptionsSynchronizationService( private static readonly TimeSpan StartupDelay = TimeSpan.FromMinutes(1); private static readonly TimeSpan BackupFrequency = TimeSpan.FromSeconds(15); + private readonly INotificationService notificationService = notificationService.ThrowIfNull(); private readonly IOptionsProvider optionsProvider = optionsProvider.ThrowIfNull(); private readonly IGraphClient graphClient = graphClient.ThrowIfNull(); private readonly IOptionsMonitor liveOptions = liveOptions.ThrowIfNull(); @@ -33,8 +36,8 @@ public sealed class OptionsSynchronizationService( { await Task.Delay(BackupFrequency, cancellationToken); var remoteOptions = await this.GetRemoteOptionsInternal(cancellationToken); - var remoteOptionsSerialized = JsonConvert.SerializeObject(remoteOptions); - var currentOptions = JsonConvert.SerializeObject(this.GetCurrentOptionsInternal()); + var remoteOptionsSerialized = JsonSerializer.Serialize(remoteOptions); + var currentOptions = JsonSerializer.Serialize(this.GetCurrentOptionsInternal()); if (remoteOptions is not null && currentOptions != remoteOptionsSerialized && this.liveOptions.CurrentValue.AutoBackupSettings) @@ -55,12 +58,12 @@ public sealed class OptionsSynchronizationService( return Task.CompletedTask; } - public Task> GetLocalOptions(CancellationToken cancellationToken) + public Task> GetLocalOptions(CancellationToken cancellationToken) { return Task.FromResult(this.GetCurrentOptionsInternal()); } - public async Task?> GetRemoteOptions(CancellationToken cancellationToken) + public async Task?> GetRemoteOptions(CancellationToken cancellationToken) { return await this.GetRemoteOptionsInternal(cancellationToken); } @@ -68,13 +71,14 @@ public sealed class OptionsSynchronizationService( public async Task BackupOptions(CancellationToken cancellationToken) { var currentOptions = this.GetCurrentOptionsInternal(); - var serializedOptions = JsonConvert.SerializeObject(currentOptions); + var serializedOptions = JsonSerializer.Serialize(currentOptions); var result = await this.graphClient.UploadSettings(serializedOptions, cancellationToken); - result.DoAny( - onFailure: exception => - { - throw exception; - }); + if (result is false) + { + this.notificationService.NotifyError( + title: "Failed to backup settings", + description: "Encountered an error while backing up your settings to the cloud. Check logs for details."); + } } public async Task RestoreOptions(CancellationToken cancellationToken) @@ -91,41 +95,35 @@ public sealed class OptionsSynchronizationService( } } - private async Task?> GetRemoteOptionsInternal(CancellationToken cancellationToken) + private async Task?> GetRemoteOptionsInternal(CancellationToken cancellationToken) { var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetRemoteOptionsInternal), string.Empty); var maybeContent = await this.graphClient.DownloadSettings(cancellationToken); - return maybeContent.Switch( - onSuccess: content => - { - try - { - return JsonConvert.DeserializeObject>(content); - } - catch(Exception e) - { - scopedLogger.LogError(e, "Encountered exception when deserializing content"); - return default; - } - }, - onFailure: ex => - { - scopedLogger.LogError(ex, "Encountered exception when retrieving remote options"); - return default; - }); + if (maybeContent is null) + { + scopedLogger.LogError("Failed to download remote settings"); + return default; + } + + return JsonSerializer.Deserialize>(maybeContent); } - private Dictionary GetCurrentOptionsInternal() + private Dictionary GetCurrentOptionsInternal() { return this.optionsProvider.GetRegisteredOptionInstances() .Where(o => o.Type.IsSynchronized) - .Select(o => (o, JObject.FromObject(o.Reference))) + .Select(o => (o, JsonSerializer.SerializeToDocument(o.Reference))) .Select(t => { - var jObject = t.Item2; + var jsonDocument = t.Item2; var objectType = t.o.Type.Type; var properties = t.o.Type.Properties; - foreach(var property in properties) + + // Create a mutable dictionary from the JSON + var dict = JsonSerializer.Deserialize>(jsonDocument.RootElement.GetRawText()) + ?? []; + + foreach (var property in properties) { if (property.Type.GetCustomAttributes().Any(a => a is JsonIgnoreAttribute)) { @@ -135,17 +133,18 @@ public sealed class OptionsSynchronizationService( if (!property.IsSynchronized) { var name = property.Name; - if (property.Type.GetCustomAttribute() is JsonPropertyAttribute jsonPropertyAttribute && - jsonPropertyAttribute.PropertyName is string propertyName) + if (property.Type.GetCustomAttribute() is JsonPropertyNameAttribute jsonPropertyNameAttribute) { - name = propertyName; + name = jsonPropertyNameAttribute.Name; } - jObject.Remove(name); + dict.Remove(name); } } - return (t.o.Type.Name, t.Item2); + // Convert back to JsonDocument + var filteredDocument = JsonSerializer.SerializeToDocument(dict); + return (t.o.Type.Name, filteredDocument); }) .ToDictionary(); } diff --git a/Daybreak/Services/Registry/RegistryService.cs b/Daybreak/Services/Registry/RegistryService.cs index 7941c1dc..2994da3d 100644 --- a/Daybreak/Services/Registry/RegistryService.cs +++ b/Daybreak/Services/Registry/RegistryService.cs @@ -1,9 +1,8 @@ using Daybreak.Shared.Services.Registry; using Microsoft.Win32; -using Newtonsoft.Json; using System.Core.Extensions; using System.Extensions; -using System.IO; +using System.Text.Json; namespace Daybreak.Services.Registry; @@ -41,7 +40,7 @@ internal sealed class RegistryService : IRegistryService return RegistryKeyExecute(key, (destinationRegistryKey, mappingKey) => { - destinationRegistryKey.SetValue(mappingKey, JsonConvert.SerializeObject(value)); + destinationRegistryKey.SetValue(mappingKey, JsonSerializer.Serialize(value)); return true; }); } @@ -69,7 +68,7 @@ internal sealed class RegistryService : IRegistryService return false; } - value = JsonConvert.DeserializeObject(serializedValue); + value = JsonSerializer.Deserialize(serializedValue); return value is not null; } diff --git a/Daybreak/Services/Startup/Actions/CredentialsOptionsMigrator.cs b/Daybreak/Services/Startup/Actions/CredentialsOptionsMigrator.cs index 5e28aad3..735b91a9 100644 --- a/Daybreak/Services/Startup/Actions/CredentialsOptionsMigrator.cs +++ b/Daybreak/Services/Startup/Actions/CredentialsOptionsMigrator.cs @@ -3,10 +3,11 @@ using Daybreak.Shared.Attributes; using Daybreak.Shared.Models; using Daybreak.Shared.Services.Options; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; using System.Core.Extensions; using System.Extensions; +using System.Extensions.Core; using System.Reflection; +using System.Text.Json; namespace Daybreak.Services.Startup.Actions; internal sealed class CredentialsOptionsMigrator( @@ -21,20 +22,20 @@ internal sealed class CredentialsOptionsMigrator( public override void ExecuteOnStartup() { var newOptionsKey = GetNewOptionsKey(); - var scopedLogger = this.logger.CreateScopedLogger(nameof(this.ExecuteOnStartup), string.Empty); + var scopedLogger = this.logger.CreateScopedLogger(); if (this.optionsProvider.TryGetKeyedOptions(newOptionsKey) is not null) { - scopedLogger.LogDebug($"Found [{newOptionsKey}]. No migration needed"); + scopedLogger.LogDebug("Found [{newOptionsKey}]. No migration needed", newOptionsKey); return; } - if (this.optionsProvider.TryGetKeyedOptions(OldOptionsKey) is not JObject options) + if (this.optionsProvider.TryGetKeyedOptions(OldOptionsKey) is not JsonDocument options) { - scopedLogger.LogDebug($"Could not find [{OldOptionsKey}]. No migration possible"); + scopedLogger.LogDebug("Could not find [{OldOptionsKey}]. No migration possible", OldOptionsKey); return; } - this.logger.LogDebug($"Found [{OldOptionsKey}]. Migrating options to [{newOptionsKey}]"); + this.logger.LogDebug("Found [{OldOptionsKey}]. Migrating options to [{newOptionsKey}]", OldOptionsKey, newOptionsKey); this.optionsProvider.SaveRegisteredOptions(newOptionsKey, options); } diff --git a/Daybreak/Services/Toolbox/Utilities/ToolboxClient.cs b/Daybreak/Services/Toolbox/Utilities/ToolboxClient.cs index 18a1d881..8bdf747d 100644 --- a/Daybreak/Services/Toolbox/Utilities/ToolboxClient.cs +++ b/Daybreak/Services/Toolbox/Utilities/ToolboxClient.cs @@ -6,8 +6,8 @@ using Microsoft.Extensions.Logging; using System.Core.Extensions; using System.Extensions; using System.Extensions.Core; -using System.IO; -using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; namespace Daybreak.Services.Toolbox.Utilities; internal sealed class ToolboxClient( @@ -92,8 +92,7 @@ internal sealed class ToolboxClient( return default; } - var responseString = await getListResponse.Content.ReadAsStringAsync(cancellationToken); - var releasesList = responseString.Deserialize>(); + var releasesList = await getListResponse.Content.ReadFromJsonAsync>(cancellationToken); var latestReleaseTuple = releasesList?.Where(t => t.Ref?.Contains("Release") is true) .Select(t => t.Ref?.Replace("refs/tags/", "")) .OfType() diff --git a/Daybreak/Services/TradeChat/Models/TraderMessageQueryResponse.cs b/Daybreak/Services/TradeChat/Models/TraderMessageQueryResponse.cs index a9ce42c4..bd79373e 100644 --- a/Daybreak/Services/TradeChat/Models/TraderMessageQueryResponse.cs +++ b/Daybreak/Services/TradeChat/Models/TraderMessageQueryResponse.cs @@ -1,15 +1,15 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.TradeChat.Models; internal sealed class TraderMessageQueryResponse { - [JsonProperty("query")] + [JsonPropertyName("query")] public string? Query { get; set; } - [JsonProperty("num_results")] + [JsonPropertyName("num_results")] public int ResultCount { get; set; } - [JsonProperty("messages")] + [JsonPropertyName("messages")] public List? Messages { get; set; } } diff --git a/Daybreak/Services/TradeChat/Models/TraderMessageResponse.cs b/Daybreak/Services/TradeChat/Models/TraderMessageResponse.cs index a6222945..5073c635 100644 --- a/Daybreak/Services/TradeChat/Models/TraderMessageResponse.cs +++ b/Daybreak/Services/TradeChat/Models/TraderMessageResponse.cs @@ -1,19 +1,21 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.TradeChat.Models; internal sealed class TraderMessageResponse { - [JsonProperty("m")] + [JsonPropertyName("m")] public string? Message { get; set; } - [JsonProperty("s")] + [JsonPropertyName("s")] public string? Sender { get; set; } - [JsonProperty("t")] + [JsonPropertyName("t")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public long? Timestamp { get; set; } // Replaces the message identified with this value - [JsonProperty("r")] + [JsonPropertyName("r")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public long? ReplaceTimestamp { get; set; } } diff --git a/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs b/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs index e4ed5bb8..58a0b30f 100644 --- a/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs +++ b/Daybreak/Services/TradeChat/Models/TraderQuotePayload.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using Daybreak.Shared.Converters; +using System.Text.Json.Serialization; namespace Daybreak.Services.TradeChat.Models; internal sealed class TraderQuotePayload { - [JsonProperty("p")] + [JsonPropertyName("p")] public int Price { get; set; } - [JsonProperty("t")] + [JsonPropertyName("t")] [JsonConverter(typeof(UnixDateTimeConverter))] public DateTime TimeStamp { get; set; } - [JsonProperty("s")] + [JsonPropertyName("s")] public int Type { get; set; } } diff --git a/Daybreak/Services/TradeChat/Models/TraderQuotesResponse.cs b/Daybreak/Services/TradeChat/Models/TraderQuotesResponse.cs index 358a8fe7..57ac67c0 100644 --- a/Daybreak/Services/TradeChat/Models/TraderQuotesResponse.cs +++ b/Daybreak/Services/TradeChat/Models/TraderQuotesResponse.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.TradeChat.Models; internal sealed class TraderQuotesResponse { - [JsonProperty("buy")] + [JsonPropertyName("buy")] public Dictionary BuyQuotes { get; set; } = default!; - [JsonProperty("sell")] + [JsonPropertyName("sell")] public Dictionary SellQuotes { get; set; } = default!; } diff --git a/Daybreak/Services/TradeChat/Notifications/TradeMessageNotificationHandler.cs b/Daybreak/Services/TradeChat/Notifications/TradeMessageNotificationHandler.cs index 31a1b84a..c8b89188 100644 --- a/Daybreak/Services/TradeChat/Notifications/TradeMessageNotificationHandler.cs +++ b/Daybreak/Services/TradeChat/Notifications/TradeMessageNotificationHandler.cs @@ -2,9 +2,9 @@ using Daybreak.Shared.Models.Notifications.Handling; using Daybreak.Shared.Models.Trade; using Daybreak.Views.Trade; -using Newtonsoft.Json; using System.Core.Extensions; using System.Extensions; +using System.Text.Json; using TrailBlazr.Services; namespace Daybreak.Services.TradeChat.Notifications; @@ -21,7 +21,7 @@ internal sealed class TradeMessageNotificationHandler(IViewManager viewManager) return; } - var trade = JsonConvert.DeserializeObject(notification.Metadata); + var trade = JsonSerializer.Deserialize(notification.Metadata); if (trade is null) { return; diff --git a/Daybreak/Services/TradeChat/TradeAlertingService.cs b/Daybreak/Services/TradeChat/TradeAlertingService.cs index 54bfe571..9961d639 100644 --- a/Daybreak/Services/TradeChat/TradeAlertingService.cs +++ b/Daybreak/Services/TradeChat/TradeAlertingService.cs @@ -10,10 +10,10 @@ using Daybreak.Shared.Services.TradeChat; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using System.Core.Extensions; using System.Extensions; using System.Extensions.Core; +using System.Text.Json; using System.Text.RegularExpressions; namespace Daybreak.Services.TradeChat; @@ -257,7 +257,7 @@ internal sealed class TradeAlertingService : ITradeAlertingService, IHostedServi this.notificationService.NotifyInformation( title: $"{source} Trader Alert", description: $"{alert.Name} has matched on a trader message. Sender: {traderMessageDTO.Sender}. Message: {traderMessageDTO.Message}", - metaData: JsonConvert.SerializeObject(traderMessage), + metaData: JsonSerializer.Serialize(traderMessage), expirationTime: DateTime.Now + TimeSpan.FromDays(1)); } diff --git a/Daybreak/Services/TradeChat/TradeChatService.cs b/Daybreak/Services/TradeChat/TradeChatService.cs index 35d98e03..d6afd315 100644 --- a/Daybreak/Services/TradeChat/TradeChatService.cs +++ b/Daybreak/Services/TradeChat/TradeChatService.cs @@ -4,7 +4,6 @@ using Daybreak.Shared.Models.Trade; using Daybreak.Shared.Services.TradeChat; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using System.Core.Extensions; using System.Extensions; using System.Extensions.Core; @@ -12,6 +11,7 @@ using System.Logging; using System.Net.WebSockets; using System.Runtime.CompilerServices; using System.Text; +using System.Text.Json; namespace Daybreak.Services.TradeChat; @@ -89,7 +89,7 @@ internal sealed class TradeChatService : ITradeChatService>(content) ?? Array.Empty().As>(); + var responses = JsonSerializer.Deserialize>(content) ?? Array.Empty().As>(); return responses!.Select(t => new TraderMessage { Message = t.Message ?? string.Empty, @@ -125,9 +125,9 @@ internal sealed class TradeChatService : ITradeChatService(responseString) ?? default; + return JsonSerializer.Deserialize(responseString) ?? default; } - catch (JsonSerializationException ex) + catch (JsonException ex) { scopedLogger.LogError(ex, "Encountered serialization exception. Returning default response"); return default; diff --git a/Daybreak/Services/TradeChat/TraderQuoteService.cs b/Daybreak/Services/TradeChat/TraderQuoteService.cs index ad0151a7..e4809dcc 100644 --- a/Daybreak/Services/TradeChat/TraderQuoteService.cs +++ b/Daybreak/Services/TradeChat/TraderQuoteService.cs @@ -5,10 +5,10 @@ using Daybreak.Shared.Models.Trade; using Daybreak.Shared.Services.TradeChat; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using System.Core.Extensions; using System.Extensions; using System.Logging; +using System.Text.Json; namespace Daybreak.Services.TradeChat; @@ -55,7 +55,7 @@ internal sealed class TraderQuoteService : ITraderQuoteService try { var content = await this.GetAsync(TraderQuotesUri, default, scopedLogger, cancellationToken); - var response = JsonConvert.DeserializeObject(content); + var response = JsonSerializer.Deserialize(content); var responseList = new List(); foreach (var buyQuote in response?.BuyQuotes!) { @@ -101,7 +101,7 @@ internal sealed class TraderQuoteService : ITraderQuoteService try { var content = await this.GetAsync(TraderQuotesUri, default, scopedLogger, cancellationToken); - var response = JsonConvert.DeserializeObject(content); + var response = JsonSerializer.Deserialize(content); var responseList = new List(); foreach (var sellQuote in response?.SellQuotes!) { diff --git a/Daybreak/Services/UMod/UModService.cs b/Daybreak/Services/UMod/UModService.cs index fe988c6c..c7caa0fc 100644 --- a/Daybreak/Services/UMod/UModService.cs +++ b/Daybreak/Services/UMod/UModService.cs @@ -18,6 +18,7 @@ using System.Core.Extensions; using System.Diagnostics; using System.Extensions; using System.Extensions.Core; +using System.Net.Http.Json; using TrailBlazr.Services; namespace Daybreak.Services.UMod; @@ -331,8 +332,7 @@ internal sealed class UModService( return new DownloadLatestOperation.NonSuccessStatusCode((int)getListResponse.StatusCode); } - var responseString = await getListResponse.Content.ReadAsStringAsync(cancellationToken); - var releasesList = responseString.Deserialize>(); + var releasesList = await getListResponse.Content.ReadFromJsonAsync>(cancellationToken); var latestRelease = releasesList? .Select(t => t.Ref?.Replace("refs/tags/", "")) .OfType() @@ -367,8 +367,7 @@ internal sealed class UModService( return default; } - var responseString = await getListResponse.Content.ReadAsStringAsync(cancellationToken); - var releasesList = responseString.Deserialize>(); + var releasesList = await getListResponse.Content.ReadFromJsonAsync>(cancellationToken); var latestRelease = releasesList? .Select(t => t.Ref?.Replace("refs/tags/", "")) .OfType() diff --git a/Daybreak/Services/Updater/ApplicationUpdater.cs b/Daybreak/Services/Updater/ApplicationUpdater.cs index 6795f155..96512ac7 100644 --- a/Daybreak/Services/Updater/ApplicationUpdater.cs +++ b/Daybreak/Services/Updater/ApplicationUpdater.cs @@ -189,8 +189,7 @@ internal sealed class ApplicationUpdater( var response = await this.httpClient.GetAsync(VersionListUrl, cancellationToken); if (response.IsSuccessStatusCode) { - var serializedList = await response.Content.ReadAsStringAsync(cancellationToken); - var versionList = serializedList.Deserialize(); + var versionList = await response.Content.ReadFromJsonAsync(cancellationToken); return versionList!.Select(v => v.Ref![(RefTagPrefix.Length - 1)..]) .Select(v => { diff --git a/Daybreak/Services/Updater/Models/Metadata.cs b/Daybreak/Services/Updater/Models/Metadata.cs index 6d525a0e..78f43f41 100644 --- a/Daybreak/Services/Updater/Models/Metadata.cs +++ b/Daybreak/Services/Updater/Models/Metadata.cs @@ -1,17 +1,17 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Daybreak.Services.Updater.Models; internal sealed class Metadata { - [JsonProperty(nameof(RelativePath))] + [JsonPropertyName(nameof(RelativePath))] public string? RelativePath { get; set; } - [JsonProperty(nameof(Name))] + [JsonPropertyName(nameof(Name))] public string? Name { get; set; } - [JsonProperty(nameof(Size))] + [JsonPropertyName(nameof(Size))] public int Size { get; set; } - [JsonProperty(nameof(VersionInfo))] + [JsonPropertyName(nameof(VersionInfo))] public string? VersionInfo { get; set; } } diff --git a/Daybreak/Services/Wiki/WikiService.cs b/Daybreak/Services/Wiki/WikiService.cs index a92ce9f5..a4ca9bb8 100644 --- a/Daybreak/Services/Wiki/WikiService.cs +++ b/Daybreak/Services/Wiki/WikiService.cs @@ -1,10 +1,9 @@ using Daybreak.Shared.Models.Guildwars; using Daybreak.Shared.Services.Wiki; using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; using System.Collections.Concurrent; -using System.Net.Http; using System.Text.Encodings.Web; +using System.Text.Json; using System.Text.RegularExpressions; using Attribute = Daybreak.Shared.Models.Guildwars.Attribute; @@ -89,25 +88,39 @@ public sealed partial class WikiService( { try { - var json = JObject.Parse(jsonContent); - if (json["query"]?["pages"] is not JObject pages) + using var json = JsonDocument.Parse(jsonContent); + var root = json.RootElement; + + if (!root.TryGetProperty("query", out var query) || + !query.TryGetProperty("pages", out var pages)) { return null; } - if (pages.Properties().FirstOrDefault()?.Value is not JObject page) + // Get the first property (page) from pages object + using var pagesEnumerator = pages.EnumerateObject(); + if (!pagesEnumerator.MoveNext()) { return null; } - var revisions = page["revisions"] as JArray; - if (revisions == null || revisions.Count == 0) + var page = pagesEnumerator.Current.Value; + + if (!page.TryGetProperty("revisions", out var revisions) || + revisions.GetArrayLength() == 0) { return null; } - var content = revisions[0]?["slots"]?["main"]?["*"]?.Value(); - return content; + var firstRevision = revisions[0]; + if (firstRevision.TryGetProperty("slots", out var slots) && + slots.TryGetProperty("main", out var main) && + main.TryGetProperty("*", out var content)) + { + return content.GetString(); + } + + return null; } catch (Exception) { diff --git a/Daybreak/Views/BuildRoutingView.razor.cs b/Daybreak/Views/BuildRoutingView.razor.cs index 47988627..bf7412fb 100644 --- a/Daybreak/Views/BuildRoutingView.razor.cs +++ b/Daybreak/Views/BuildRoutingView.razor.cs @@ -19,28 +19,26 @@ public sealed class BuildRoutingViewModel( public override async ValueTask ParametersSet(BuildRoutingView view, CancellationToken cancellationToken) { var build = await this.buildTemplateManager.GetBuild(view.BuildName); - build.Do( - onSuccess: parsedBuild => - { - if (parsedBuild is SingleBuildEntry singleBuildEntry) - { - this.viewManager.ShowView((nameof(SingleBuildTemplateView.BuildName), singleBuildEntry.Name ?? string.Empty)); - } - else if (parsedBuild is TeamBuildEntry teamBuildEntry) - { - this.viewManager.ShowView((nameof(TeamBuildTemplateView.BuildName), teamBuildEntry.Name ?? string.Empty)); - } - else - { - throw new Exception($"Unexpected build entry type: {parsedBuild.GetType().Name}"); - } - }, - onFailure: failure => - { - this.notificationService.NotifyError( + if (build is null) + { + this.notificationService.NotifyError( title: "Failed to load build", description: $"Encountered an error while loading build {view.BuildName}. Check logs for details"); - this.viewManager.ShowView(); - }); + this.viewManager.ShowView(); + return; + } + + if (build is SingleBuildEntry singleBuildEntry) + { + this.viewManager.ShowView((nameof(SingleBuildTemplateView.BuildName), singleBuildEntry.Name ?? string.Empty)); + } + else if (build is TeamBuildEntry teamBuildEntry) + { + this.viewManager.ShowView((nameof(TeamBuildTemplateView.BuildName), teamBuildEntry.Name ?? string.Empty)); + } + else + { + throw new Exception($"Unexpected build entry type: {build.GetType().Name}"); + } } } diff --git a/Daybreak/Views/BuildTemplateViewModelBase.cs b/Daybreak/Views/BuildTemplateViewModelBase.cs index d5a5fae9..00c32ac1 100644 --- a/Daybreak/Views/BuildTemplateViewModelBase.cs +++ b/Daybreak/Views/BuildTemplateViewModelBase.cs @@ -82,12 +82,7 @@ public abstract class BuildTemplateViewModelBase( public override sealed async ValueTask ParametersSet(TView view, CancellationToken cancellationToken) { var buildLoad = await this.buildTemplateManager.GetBuild(view.BuildName); - if (!buildLoad.TryExtractSuccess(out var buildEntry)) - { - throw new InvalidOperationException($"Failed to load build by name {view.BuildName}"); - } - - this.LoadBuild(buildEntry); + this.LoadBuild(buildLoad); this.UpdateBuildCode(); this.FilterSkillsByProfessionsAndString(); await this.RefreshViewAsync(); diff --git a/Daybreak/Views/FocusView.razor.cs b/Daybreak/Views/FocusView.razor.cs index e49b7661..42dfe69d 100644 --- a/Daybreak/Views/FocusView.razor.cs +++ b/Daybreak/Views/FocusView.razor.cs @@ -91,7 +91,17 @@ public sealed class FocusViewModel( return; } - this.process = Process.GetProcessById(processId); + try + { + this.process = Process.GetProcessById(processId); + } + catch(Exception ex) + { + scopedLogger.LogError(ex, "Encountered exception when fetching process with id {processId}", processId); + this.viewManager.ShowView(); + return; + } + var apiContext = await this.daybreakApiService.GetDaybreakApiContext(this.process, cancellationToken); if (apiContext is null) { diff --git a/Daybreak/Views/SettingsSynchronizationView.razor.cs b/Daybreak/Views/SettingsSynchronizationView.razor.cs index de90a9a2..ca373e20 100644 --- a/Daybreak/Views/SettingsSynchronizationView.razor.cs +++ b/Daybreak/Views/SettingsSynchronizationView.razor.cs @@ -4,9 +4,8 @@ using Daybreak.Shared.Services.Options; using DiffPlex; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System.Core.Extensions; +using System.Text.Json; using TrailBlazr.ViewModels; namespace Daybreak.Views; @@ -16,12 +15,14 @@ public sealed class SettingsSynchronizationViewModel( IOptionsSynchronizationService optionsSynchronizationService) : ViewModelBase { + private static readonly JsonSerializerOptions IndentedOptions = new() { WriteIndented = true }; + private readonly INotificationService notificationService = notificationService.ThrowIfNull(); private readonly IGraphClient graphClient = graphClient.ThrowIfNull(); private readonly IOptionsSynchronizationService optionsSynchronizationService = optionsSynchronizationService.ThrowIfNull(); - private Dictionary? remoteOptions; - private Dictionary? localOptions; + private Dictionary? remoteOptions; + private Dictionary? localOptions; public bool Loading { get; set; } @@ -52,11 +53,11 @@ public sealed class SettingsSynchronizationViewModel( { var localOptionsString = this.localOptions is null ? "{}" - : JsonConvert.SerializeObject(this.localOptions, Formatting.Indented); + : JsonSerializer.Serialize(this.localOptions, IndentedOptions); var remoteOptionsString = this.remoteOptions is null ? "{}" - : JsonConvert.SerializeObject(this.remoteOptions, Formatting.Indented); + : JsonSerializer.Serialize(this.remoteOptions, IndentedOptions ); var diffBuilder = new SideBySideDiffBuilder(new Differ()); this.SideBySideDiff = diffBuilder.BuildDiffModel(localOptionsString, remoteOptionsString); diff --git a/Directory.Packages.props b/Directory.Packages.props index 293c1824..9c7f6761 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -63,7 +63,7 @@ - +