699d4ede1d
Add .prettierrc.json, .markdownlint.json, .yamllint.yaml, .gitattributes Run CSharpier, Prettier and markdownlint across the entire codebase. No logic changes — formatting, using order and line endings only.
403 lines
15 KiB
C#
403 lines
15 KiB
C#
using System.Globalization;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Game.Text.SeStringHandling;
|
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Interface.Windowing;
|
|
using Dalamud.Utility;
|
|
using HellionChat.Util;
|
|
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
|
|
|
|
namespace HellionChat.Ui;
|
|
|
|
public class SeStringDebugger : Window
|
|
{
|
|
private readonly Plugin Plugin;
|
|
|
|
public SeStringDebugger(Plugin plugin)
|
|
: base("SeString Debugger###chat2-sestringdebugger")
|
|
{
|
|
Plugin = plugin;
|
|
|
|
SizeConstraints = new WindowSizeConstraints
|
|
{
|
|
MinimumSize = new Vector2(475, 600),
|
|
MaximumSize = new Vector2(float.MaxValue, float.MaxValue),
|
|
};
|
|
|
|
RespectCloseHotkey = false;
|
|
DisableWindowSounds = true;
|
|
|
|
#if DEBUG
|
|
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute += Toggle;
|
|
#endif
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
#if DEBUG
|
|
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute -= Toggle;
|
|
#endif
|
|
}
|
|
|
|
private void Toggle(string _, string __) => Toggle();
|
|
|
|
public override void Draw()
|
|
{
|
|
if (Plugin.MessageManager.LastMessage.Sender == null)
|
|
{
|
|
ImGui.TextUnformatted("Nothing to show");
|
|
return;
|
|
}
|
|
|
|
// TODO: Make SeString freely selectable through chat
|
|
ImGui.TextUnformatted("Sender Content");
|
|
ImGui.Spacing();
|
|
if (Plugin.MessageManager.LastMessage.Sender != null)
|
|
ProcessPayloads(Plugin.MessageManager.LastMessage.Sender.Payloads);
|
|
else
|
|
ImGui.TextUnformatted("Nothing to show");
|
|
|
|
ImGui.TextUnformatted("Message Content");
|
|
ImGui.Spacing();
|
|
if (Plugin.MessageManager.LastMessage.Message != null)
|
|
ProcessPayloads(Plugin.MessageManager.LastMessage.Message.Payloads);
|
|
else
|
|
ImGui.TextUnformatted("Nothing to show");
|
|
}
|
|
|
|
private void ProcessPayloads(List<Payload> payloads)
|
|
{
|
|
foreach (var payload in payloads)
|
|
{
|
|
switch (payload)
|
|
{
|
|
case UIForegroundPayload color:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link ForegroundColor",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Enabled?", color.IsEnabled.ToString() },
|
|
{
|
|
"ColorKey",
|
|
color.IsEnabled ? color.ColorKey.ToString() : "Color Ended"
|
|
},
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case MapLinkPayload map:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link MapLinkPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Map.RowId", map.Map.RowId.ToString() },
|
|
{ "Map.PlaceName", map.Map.Value.PlaceName.Value.Name.ToString() },
|
|
{
|
|
"Map.PlaceNameRegion",
|
|
map.Map.Value.PlaceNameRegion.Value.Name.ToString()
|
|
},
|
|
{
|
|
"Map.PlaceNameSub",
|
|
map.Map.Value.PlaceNameSub.Value.Name.ToString()
|
|
},
|
|
{ "TerritoryType.RowId", map.TerritoryType.RowId.ToString() },
|
|
{ "RawX", map.RawX.ToString() },
|
|
{ "RawY", map.RawY.ToString() },
|
|
{ "XCoord", map.XCoord.ToString(CultureInfo.InvariantCulture) },
|
|
{ "YCoord", map.YCoord.ToString(CultureInfo.InvariantCulture) },
|
|
{ "CoordinateString", map.CoordinateString },
|
|
{ "DataString", map.DataString },
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case QuestPayload quest:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link QuestPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Quest.RowId", quest.Quest.RowId.ToString() },
|
|
{ "Quest.Name", quest.Quest.Value.Name.ToString() },
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case DalamudLinkPayload link:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link DalamudLinkPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "CommandId", link.CommandId.ToString() },
|
|
{ "Plugin", link.Plugin },
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case DalamudPartyFinderPayload pf:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link PartyFinderPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "ListingId", pf.ListingId.ToString() },
|
|
{ "LinkType", EnumName(pf.LinkType) },
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case PlayerPayload player:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link PlayerPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Displayed", player.DisplayedName },
|
|
{ "Player Name", player.PlayerName },
|
|
{ "World Name", player.World.Value.Name.ExtractText() },
|
|
{
|
|
"Data",
|
|
string.Join(" ", player.Encode().Select(b => b.ToString("X2")))
|
|
},
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case ItemPayload item:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link ItemPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "ItemId", item.ItemId.ToString() },
|
|
{ "RawItemId", item.RawItemId.ToString() },
|
|
{ "Kind", EnumName(item.Kind) },
|
|
{ "IsHQ", item.IsHQ.ToString() },
|
|
{
|
|
"Item.Name",
|
|
item.Kind == ItemKind.EventItem
|
|
? Sheets.EventItemSheet.GetRow(item.ItemId).Name.ExtractText()
|
|
: Sheets.ItemSheet.GetRow(item.ItemId).Name.ExtractText()
|
|
},
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case AutoTranslatePayload at:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link AutoTranslatePayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Text", at.Text },
|
|
{ "Key/Group", $"{at.Key}/{at.Group}" },
|
|
{ "Data", string.Join(" ", at.Encode().Select(b => b.ToString("X2"))) },
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case IconPayload icon:
|
|
{
|
|
var found = IconUtil.GfdFileView.TryGetEntry((uint)icon.Icon, out _);
|
|
RenderMetadataDictionary(
|
|
"Link IconPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Found", found.ToString() },
|
|
{ "Icon ID", ((uint)icon.Icon).ToString() },
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
case RawPayload raw:
|
|
{
|
|
var colorPayload = ColorPayload.From(raw.Data);
|
|
if (colorPayload != null)
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link ColorPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Unshifted", colorPayload.UnshiftedColor.ToString("X8") },
|
|
{ "Color", colorPayload.Color.ToString("X8") },
|
|
{ "Enabled?", colorPayload.Enabled.ToString() },
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link RawPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{
|
|
"Data",
|
|
string.Join(" ", raw.Data.Select(b => b.ToString("X2")))
|
|
},
|
|
{ "Type", EnumName(raw.Type) },
|
|
}
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case StatusPayload status:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link StatusPayload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Status.RowId", status.Status.RowId.ToString() },
|
|
{ "Status.Name", status.Status.Value.Name.ExtractText() },
|
|
{ "Status.Icon", status.Status.Value.Icon.ToString() },
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
|
|
case Util.PartyFinderPayload pf:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link PartyFinderPayload",
|
|
new Dictionary<string, string?> { { "Id", pf.Id.ToString() } }
|
|
);
|
|
break;
|
|
}
|
|
case AchievementPayload achievement:
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Link AchievementPayload",
|
|
new Dictionary<string, string?> { { "Id", achievement.Id.ToString() } }
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
var payloadData = payload.Encode();
|
|
|
|
if (payloadData.Length == 0)
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Empty Payload",
|
|
new Dictionary<string, string?> { { "Type", payload.GetType().Name } }
|
|
);
|
|
break;
|
|
}
|
|
|
|
var initialByte = payloadData[0];
|
|
if (initialByte != 0x02)
|
|
{
|
|
RenderMetadataDictionary(
|
|
"Text Payload",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{ "Content", Encoding.UTF8.GetString(payloadData) },
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
var unknown = new RawPayload(payloadData);
|
|
RenderMetadataDictionary(
|
|
"Link Unknown",
|
|
new Dictionary<string, string?>
|
|
{
|
|
{
|
|
"Unknown",
|
|
string.Join(" ", unknown.Data.Select(b => b.ToString("X2")))
|
|
},
|
|
}
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string? EnumName<T>(T? value)
|
|
where T : Enum
|
|
{
|
|
if (value == null)
|
|
{
|
|
return null;
|
|
}
|
|
var rawValue = Convert.ChangeType(value, value.GetTypeCode());
|
|
return (Enum.GetName(value.GetType(), value) ?? "Unknown") + $" ({rawValue})";
|
|
}
|
|
|
|
private static void RenderMetadataDictionary(string name, Dictionary<string, string?> metadata)
|
|
{
|
|
var style = ImGui.GetStyle();
|
|
|
|
ImGui.Text($"{name}:");
|
|
using var indent = ImRaii.PushIndent(style.IndentSpacing);
|
|
using (var table = ImRaii.Table($"##chat2-{name}", 2))
|
|
{
|
|
if (!table.Success)
|
|
return;
|
|
|
|
ImGui.TableSetupColumn($"##chat2-{name}-key", ImGuiTableColumnFlags.WidthStretch, 0.4f);
|
|
ImGui.TableSetupColumn($"##chat2-{name}-value");
|
|
for (var i = 0; i < metadata.Count; i++)
|
|
{
|
|
using var id = ImRaii.PushId(i);
|
|
|
|
var (key, value) = metadata.ElementAt(i);
|
|
ImGui.TableNextColumn();
|
|
ImGui.Text(key);
|
|
|
|
ImGui.TableNextColumn();
|
|
ImGuiTextVisibleWhitespace(value);
|
|
}
|
|
}
|
|
|
|
ImGui.NewLine();
|
|
}
|
|
|
|
// ImGuiTextVisibleWhitespace replaces leading and trailing whitespace with
|
|
// visible characters. The extra characters are rendered with a muted font.
|
|
private static void ImGuiTextVisibleWhitespace(string? original)
|
|
{
|
|
if (string.IsNullOrEmpty(original))
|
|
{
|
|
using var pushedColor = ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 1, 1, 0.5f));
|
|
ImGui.TextUnformatted(original == null ? "(null)" : "(empty)");
|
|
return;
|
|
}
|
|
|
|
var text = original;
|
|
var start = 0;
|
|
var end = text.Length;
|
|
|
|
using var pushedStyle = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0));
|
|
|
|
while (start < end && char.IsWhiteSpace(text[start]))
|
|
start++;
|
|
|
|
if (start > 0)
|
|
{
|
|
using var pushedColor = ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 1, 1, 0.5f));
|
|
ImGui.TextWrapped(new string('_', start));
|
|
|
|
ImGui.SameLine();
|
|
}
|
|
|
|
while (end > start && char.IsWhiteSpace(text[end - 1]))
|
|
end--;
|
|
|
|
ImGui.TextWrapped(text[start..end]);
|
|
|
|
if (end < text.Length)
|
|
{
|
|
ImGui.SameLine();
|
|
|
|
using var pushedColor = ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 1, 1, 0.5f));
|
|
ImGui.TextWrapped(new string('_', text.Length - end));
|
|
}
|
|
}
|
|
}
|