diff --git a/Client.cs b/Client.cs index 641f1de..986b6ff 100644 --- a/Client.cs +++ b/Client.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json.Linq; namespace PartyDamage; public class Client : IDisposable { - private ClientWebSocket WebSocket { get; } + private ClientWebSocket? WebSocket { get; set; } private CancellationTokenSource TokenSource { get; } private Task? MainTask { get; set; } private bool _disposed; @@ -14,7 +14,6 @@ public class Client : IDisposable { internal CombatData? Data { get; private set; } internal Client() { - this.WebSocket = new ClientWebSocket(); this.TokenSource = new CancellationTokenSource(); this.MainTask = Task.Run(async () => { while (!this._disposed) { @@ -37,10 +36,11 @@ public class Client : IDisposable { this._disposed = true; this.TokenSource.Cancel(); - this.WebSocket.Dispose(); + this.WebSocket?.Dispose(); } private async Task Main() { + this.WebSocket = new ClientWebSocket(); await this.WebSocket.ConnectAsync(new Uri("ws://127.0.0.1:10501/ws"), this.TokenSource.Token); const string msg = "{\"call\": \"subscribe\", \"events\": [\"CombatData\"]}"; var msgBytes = Encoding.UTF8.GetBytes(msg); @@ -60,8 +60,13 @@ public class Client : IDisposable { var jobj = JObject.Parse(text); if (jobj["type"]?.Value() == "CombatData") { - var data = JsonConvert.DeserializeObject(text); - this.Data = data; + var data = JsonConvert.DeserializeObject(text); + if (data == null) { + this.Data = null; + continue; + } + + this.Data = new CombatData(data); } } diff --git a/CombatData.cs b/CombatData.cs index ac72ba2..812174c 100644 --- a/CombatData.cs +++ b/CombatData.cs @@ -1,89 +1,38 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - namespace PartyDamage; -[Serializable] public class CombatData { - public Encounter Encounter; + public Encounter Encounter { get; } + public Dictionary Combatants { get; } = []; - [JsonProperty("Combatant")] - public Dictionary Combatants; + public CombatData(RawCombatData raw) { + foreach (var (key, value) in raw.Combatants) { + this.Combatants[key] = new Combatant { + Name = value.Name, + EncDps = TryFloat(value.EncDps), + }; + } + + this.Encounter = new Encounter { + Title = raw.Encounter.Title, + EncDps = TryFloat(raw.Encounter.EncDps), + }; + } + + private static float TryFloat(string input, float def = float.NaN) { + if (!float.TryParse(input, out var result)) { + result = def; + } + + return result; + } } -[Serializable] -[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] public class Encounter { - [JsonProperty("n")] - public char N; - - [JsonProperty("t")] - public char T; - - public string Title; - - public string Duration; - - public string Damage; - - public string Dps; - - [JsonProperty("encdps")] - public string EncDps; - - public string Hits; - - [JsonProperty("crithits")] - public string CritHits; - - public string Misses; - - [JsonProperty("hitfailed")] - public string HitFailed; - - public string Swings; - - public string Healed; - - [JsonProperty("enchps")] - public string EncHps; - - [JsonProperty("CurrentZoneName")] - public string CurrentZoneName; + public string Title { get; init; } + public float EncDps { get; init; } } -[Serializable] -[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] public class Combatant { - public string Name; - - public string Duration; - - public string Damage; - - public string Dps; - - [JsonProperty("encdps")] - public string EncDps; - - // seems like newtonsoft gets confused if we don't also specify this one - [JsonProperty("ENCDPS")] - public string EncDps2; - - public string Hits; - - [JsonProperty("crithits")] - public string CritHits; - - public string Misses; - - [JsonProperty("hitfailed")] - public string HitFailed; - - public string Swings; - - public string Healed; - - [JsonProperty("enchps")] - public string EncHps; -} + public string Name { get; init; } + public float EncDps { get; init; } +} \ No newline at end of file diff --git a/Plugin.cs b/Plugin.cs index 572c7b2..c5f358d 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -52,6 +52,15 @@ public class Plugin : IDalamudPlugin { var numPlayers = list->PartyMembers.Length; foreach (var combatant in data.Combatants.Values) { + if (combatant.Name.EndsWith(" (YOU)")) { + var name = combatant.Name[..^6]; + var nameNode = list->Pet.Name; + if (nameNode != null && nameNode->NodeText.ToString() == name) { + this.UpdateMember(list->Pet, combatant); + continue; + } + } + var idx = combatant.Name == "YOU" ? 0 : names.IndexOf(combatant.Name); @@ -59,18 +68,21 @@ public class Plugin : IDalamudPlugin { continue; } - if (!float.TryParse(combatant.EncDps, out var dps)) { - dps = 0; - } - - var dpsText = dps switch { - float.PositiveInfinity => "0", - float.NegativeInfinity => "0", - < 1_000 => dps.ToString("N1"), - < 1_000_000 => $"{dps / 1_000:N1}K", - _ => $"{dps / 1_000_000:N1}M", - }; - list->PartyMembers[idx].Name->SetText(dpsText); + this.UpdateMember(list->PartyMembers[idx], combatant); } } + + private unsafe void UpdateMember(AddonPartyList.PartyListMemberStruct member, Combatant combatant) { + var dpsText = combatant.EncDps switch { + float.NaN => "?", + float.PositiveInfinity => "0", + float.NegativeInfinity => "0", + < 1_000 => combatant.EncDps.ToString("N1"), + < 1_000_000 => $"{combatant.EncDps / 1_000:N1}K", + < 1_000_000_000 => $"{combatant.EncDps / 1_000_000:N1}M", + _ => combatant.EncDps.ToString("N1"), + }; + + member.Name->SetText(dpsText); + } } diff --git a/RawCombatData.cs b/RawCombatData.cs new file mode 100644 index 0000000..ba7038b --- /dev/null +++ b/RawCombatData.cs @@ -0,0 +1,92 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace PartyDamage; + +[Serializable] +public class RawCombatData { + public RawEncounter Encounter; + + [JsonProperty("Combatant")] + public Dictionary Combatants; +} + +[Serializable] +[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] +public class RawEncounter { + [JsonProperty("n")] + public char N; + + [JsonProperty("t")] + public char T; + + public string Title; + + public string Duration; + + public string Damage; + + public string Dps; + + [JsonProperty("encdps")] + public string EncDps; + + [JsonProperty("ENCDPS")] + public string EncDps2; + + public string Hits; + + [JsonProperty("crithits")] + public string CritHits; + + public string Misses; + + [JsonProperty("hitfailed")] + public string HitFailed; + + public string Swings; + + public string Healed; + + [JsonProperty("enchps")] + public string EncHps; + + [JsonProperty("CurrentZoneName")] + public string CurrentZoneName; +} + +[Serializable] +[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))] +public class RawCombatant { + public string Name; + + public string Duration; + + public string Damage; + + public string Dps; + + [JsonProperty("encdps")] + public string EncDps; + + // seems like newtonsoft gets confused if we don't also specify this one + [JsonProperty("ENCDPS")] + public string EncDps2; + + public string Hits; + + [JsonProperty("crithits")] + public string CritHits; + + public string Misses; + + [JsonProperty("hitfailed")] + public string HitFailed; + + public string Swings; + + public string Healed; + + [JsonProperty("enchps")] + public string EncHps; +}