diff --git a/Client.cs b/Client.cs index f329d16..641f1de 100644 --- a/Client.cs +++ b/Client.cs @@ -1,5 +1,7 @@ using System.Net.WebSockets; using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace PartyDamage; @@ -9,6 +11,8 @@ public class Client : IDisposable { private Task? MainTask { get; set; } private bool _disposed; + internal CombatData? Data { get; private set; } + internal Client() { this.WebSocket = new ClientWebSocket(); this.TokenSource = new CancellationTokenSource(); @@ -16,6 +20,8 @@ public class Client : IDisposable { while (!this._disposed) { try { await this.Main(); + } catch (TaskCanceledException) { + break; } catch (Exception ex) { Plugin.Log.Error(ex, "Error in WebSocket loop"); await Task.Delay(TimeSpan.FromSeconds(3)); @@ -32,24 +38,30 @@ public class Client : IDisposable { this._disposed = true; this.TokenSource.Cancel(); this.WebSocket.Dispose(); - this.MainTask?.Dispose(); } private async Task Main() { - await this.WebSocket.ConnectAsync(new Uri("http://127.0.0.1:10501/"), this.TokenSource.Token); - var msg = "{\"call\": \"subscribe\", \"events\": [\"CombatData\"]}"; + 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); await this.WebSocket.SendAsync(msgBytes, WebSocketMessageType.Text, true, this.TokenSource.Token); - var received = new List(); + var buf = new byte[1024]; + var bytes = new List(); while (this.WebSocket.State == WebSocketState.Open) { - var bytes = new byte[1024]; - var resp = await this.WebSocket.ReceiveAsync(bytes, this.TokenSource.Token); - received.AddRange(bytes[..resp.Count]); - if (resp.EndOfMessage) { - var text = Encoding.UTF8.GetString(bytes.AsSpan()); - received.Clear(); - Plugin.Log.Info(text); + var resp = await this.WebSocket.ReceiveAsync(buf, this.TokenSource.Token); + bytes.AddRange(buf[..resp.Count]); + if (!resp.EndOfMessage) { + continue; + } + + var text = Encoding.UTF8.GetString(bytes.ToArray()); + bytes.Clear(); + + var jobj = JObject.Parse(text); + if (jobj["type"]?.Value() == "CombatData") { + var data = JsonConvert.DeserializeObject(text); + this.Data = data; } } diff --git a/CombatData.cs b/CombatData.cs index fd274ed..ac72ba2 100644 --- a/CombatData.cs +++ b/CombatData.cs @@ -66,6 +66,10 @@ public class Combatant { [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")] diff --git a/PartyDamage.yaml b/PartyDamage.yaml new file mode 100755 index 0000000..13beb40 --- /dev/null +++ b/PartyDamage.yaml @@ -0,0 +1,6 @@ +name: Party Damage +author: anna +punchline: Show your DPS in the party list +description: |- + Does what it says on the tin. +repo_url: https://git.anna.lgbt/anna/PartyDamage diff --git a/Plugin.cs b/Plugin.cs index e8287e9..572c7b2 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,6 +1,9 @@ using Dalamud.IoC; using Dalamud.Plugin; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.Game.Group; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Component.GUI; namespace PartyDamage; @@ -8,13 +11,66 @@ public class Plugin : IDalamudPlugin { [PluginService] internal static IPluginLog Log { get; private set; } + [PluginService] + private IClientState ClientState { get; init; } + + [PluginService] + private IFramework Framework { get; init; } + private Client Client { get; } public Plugin() { this.Client = new Client(); + this.Framework!.Update += this.OnFramework; } public void Dispose() { + this.Framework!.Update -= this.OnFramework; this.Client.Dispose(); } + + private unsafe void OnFramework(IFramework framework) { + if (this.Client.Data is not { } data) { + return; + } + + if (this.ClientState.LocalPlayer is not { } player) { + return; + } + + var list = (AddonPartyList*) AtkStage.Instance()->RaptureAtkUnitManager->GetAddonByName("_PartyList"); + + var names = new List(); + var group = GroupManager.Instance()->GetGroup(); + if (group == null) { + Plugin.Log.Info("group null"); + } + + for (var i = 0; i < group->MemberCount; i++) { + names.Add(group->PartyMembers[i].NameString); + } + + var numPlayers = list->PartyMembers.Length; + foreach (var combatant in data.Combatants.Values) { + var idx = combatant.Name == "YOU" + ? 0 + : names.IndexOf(combatant.Name); + if (idx == -1 || idx >= numPlayers) { + 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); + } + } }