1f2cb000a2
Hellion Chat is an independent fork with its own assembly name and
plugin slot, but it kept registering the upstream /chat2* slash
commands. That mixed naming caused two friction points: users could
not tell from the in-game help which plugin owned the command, and
running both Hellion Chat and the upstream Chat 2 side by side would
collide on the registration.
Five commands change:
/chat2 -> /hellion (settings + chat toggle)
/chat2Viewer -> /hellionView (database viewer)
/chat2Debugger -> /hellionDebugger (internal, not shown in help)
/chat2SeString -> /hellionSeString (internal, debug-only)
/clearlog2 -> /clearhellion (clear chat log)
Help strings ("Perform various actions with Chat 2.", "Clear the
Chat 2 chat log") are reworded to match. ImGui internal window IDs
(###chat2-settings, ###chat2-dbviewer) are left untouched on purpose
so existing user layouts for those windows do not snap back to
default. Resource files do not reference any of these command names,
so no localisation work needed.
327 lines
12 KiB
C#
327 lines
12 KiB
C#
using System.Globalization;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using ChatTwo.Util;
|
|
using Dalamud.Game.Text.SeStringHandling;
|
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Interface.Windowing;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Utility;
|
|
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
|
|
|
|
namespace ChatTwo.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();
|
|
|
|
var initialByte = payloadData.First();
|
|
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));
|
|
}
|
|
}
|
|
}
|