feat: add channel switcher, settings button, better tooltips

This commit is contained in:
Anna
2022-01-02 16:21:31 -05:00
parent 00421587d0
commit b66ccaa1c0
9 changed files with 244 additions and 44 deletions
+21 -3
View File
@@ -1,8 +1,7 @@
namespace ChatTwo.Code; namespace ChatTwo.Code;
internal static class InputChannelExt { internal static class InputChannelExt {
internal static ChatType ToChatType(this InputChannel input) { internal static ChatType ToChatType(this InputChannel input) => input switch {
return input switch {
InputChannel.Tell => ChatType.TellOutgoing, InputChannel.Tell => ChatType.TellOutgoing,
InputChannel.Say => ChatType.Say, InputChannel.Say => ChatType.Say,
InputChannel.Party => ChatType.Party, InputChannel.Party => ChatType.Party,
@@ -30,5 +29,24 @@ internal static class InputChannelExt {
InputChannel.Linkshell8 => ChatType.Linkshell8, InputChannel.Linkshell8 => ChatType.Linkshell8,
_ => throw new ArgumentOutOfRangeException(nameof(input), input, null), _ => throw new ArgumentOutOfRangeException(nameof(input), input, null),
}; };
}
public static uint LinkshellIndex(this InputChannel channel) => channel switch {
InputChannel.Linkshell1 => 0,
InputChannel.Linkshell2 => 1,
InputChannel.Linkshell3 => 2,
InputChannel.Linkshell4 => 3,
InputChannel.Linkshell5 => 4,
InputChannel.Linkshell6 => 5,
InputChannel.Linkshell7 => 6,
InputChannel.Linkshell8 => 7,
InputChannel.CrossLinkshell1 => 0,
InputChannel.CrossLinkshell2 => 1,
InputChannel.CrossLinkshell3 => 2,
InputChannel.CrossLinkshell4 => 3,
InputChannel.CrossLinkshell5 => 4,
InputChannel.CrossLinkshell6 => 5,
InputChannel.CrossLinkshell7 => 6,
InputChannel.CrossLinkshell8 => 7,
_ => uint.MaxValue,
};
} }
+38 -3
View File
@@ -1,10 +1,15 @@
using ChatTwo.Code; using System.Runtime.InteropServices;
using System.Text;
using ChatTwo.Code;
using ChatTwo.Util;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Memory; using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
namespace ChatTwo; namespace ChatTwo;
@@ -13,21 +18,25 @@ internal unsafe class GameFunctions : IDisposable {
private static class Signatures { private static class Signatures {
internal const string ChatLogRefresh = "40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA"; internal const string ChatLogRefresh = "40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA";
internal const string ChangeChannelName = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6"; internal const string ChangeChannelName = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6";
internal const string ChangeChatChannel = "E8 ?? ?? ?? ?? 0F B7 44 37 ??";
} }
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value); private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent); private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent);
private delegate IntPtr ChangeChatChannelDelegate(RaptureShellModule* shell, int channel, uint linkshellIdx, Utf8String* tellTarget, byte one);
internal delegate void ChatActivatedEventDelegate(string? input); internal delegate void ChatActivatedEventDelegate(string? input);
private Plugin Plugin { get; } private Plugin Plugin { get; }
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; } private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; }
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; } private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; }
private readonly ChangeChatChannelDelegate? _changeChatChannel;
internal event ChatActivatedEventDelegate? ChatActivated; internal event ChatActivatedEventDelegate? ChatActivated;
internal (InputChannel channel, string name) ChatChannel { get; private set; } internal (InputChannel channel, List<Chunk> name) ChatChannel { get; private set; }
internal GameFunctions(Plugin plugin) { internal GameFunctions(Plugin plugin) {
this.Plugin = plugin; this.Plugin = plugin;
@@ -42,6 +51,10 @@ internal unsafe class GameFunctions : IDisposable {
this.ChangeChannelNameHook.Enable(); this.ChangeChannelNameHook.Enable();
} }
if (this.Plugin.SigScanner.TryScanText(Signatures.ChangeChatChannel, out var changeChannelPtr)) {
this._changeChatChannel = Marshal.GetDelegateForFunctionPointer<ChangeChatChannelDelegate>(changeChannelPtr);
}
this.Plugin.ClientState.Login += this.Login; this.Plugin.ClientState.Login += this.Login;
this.Login(null, null); this.Login(null, null);
} }
@@ -66,6 +79,23 @@ internal unsafe class GameFunctions : IDisposable {
this.ChangeChannelNameDetour((IntPtr) agent); this.ChangeChannelNameDetour((IntPtr) agent);
} }
internal void SetChatChannel(InputChannel channel, string? tellTarget = null) {
if (this._changeChatChannel == null) {
return;
}
var bytes = Encoding.UTF8.GetBytes(tellTarget ?? "");
var target = new Utf8String();
fixed (byte* tellTargetPtr = bytes) {
var zero = stackalloc byte[1];
zero[0] = 0;
target.StringPtr = tellTargetPtr == null ? zero : tellTargetPtr;
target.StringLength = bytes.Length;
this._changeChatChannel(RaptureShellModule.Instance, (int) (channel + 1), channel.LinkshellIndex(), &target, 1);
}
}
internal static void SetAddonInteractable(string name, bool interactable) { internal static void SetAddonInteractable(string name, bool interactable) {
var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager; var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager;
@@ -217,7 +247,12 @@ internal unsafe class GameFunctions : IDisposable {
return ret; return ret;
} }
this.ChatChannel = ((InputChannel) channel, name.TextValue.TrimStart('\uE01E').Trim()); var nameChunks = ChunkUtil.ToChunks(name, null).ToList();
if (nameChunks.Count > 0 && nameChunks[0] is TextChunk text) {
text.Content = text.Content.TrimStart('\uE01E').TrimStart();
}
this.ChatChannel = ((InputChannel) channel, nameChunks);
return ret; return ret;
} }
+18
View File
@@ -7,6 +7,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Utility; using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
using ImGuiScene;
namespace ChatTwo; namespace ChatTwo;
@@ -109,7 +110,20 @@ internal sealed class PayloadHandler {
} }
} }
private static void InlineIcon(TextureWrap icon) {
var lineHeight = ImGui.CalcTextSize("A").Y;
var cursor = ImGui.GetCursorPos();
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
ImGui.SameLine();
ImGui.SetCursorPos(cursor + new Vector2(icon.Width + 4, (float) icon.Height / 2 - lineHeight / 2));
}
private void HoverStatus(StatusPayload status) { private void HoverStatus(StatusPayload status) {
if (this.Ui.Plugin.TextureCache.GetStatus(status.Status) is { } icon) {
InlineIcon(icon);
}
var name = ChunkUtil.ToChunks(status.Status.Name.ToDalamudString(), null); var name = ChunkUtil.ToChunks(status.Status.Name.ToDalamudString(), null);
this.Log.DrawChunks(name.ToList()); this.Log.DrawChunks(name.ToList());
ImGui.Separator(); ImGui.Separator();
@@ -119,6 +133,10 @@ internal sealed class PayloadHandler {
} }
private void HoverItem(ItemPayload item) { private void HoverItem(ItemPayload item) {
if (this.Ui.Plugin.TextureCache.GetItem(item.Item) is { } icon) {
InlineIcon(icon);
}
var name = ChunkUtil.ToChunks(item.Item.Name.ToDalamudString(), null); var name = ChunkUtil.ToChunks(item.Item.Name.ToDalamudString(), null);
this.Log.DrawChunks(name.ToList()); this.Log.DrawChunks(name.ToList());
ImGui.Separator(); ImGui.Separator();
+3
View File
@@ -46,6 +46,7 @@ public sealed class Plugin : IDalamudPlugin {
internal Configuration Config { get; } internal Configuration Config { get; }
internal XivCommonBase Common { get; } internal XivCommonBase Common { get; }
internal TextureCache TextureCache { get; }
internal GameFunctions Functions { get; } internal GameFunctions Functions { get; }
internal Store Store { get; } internal Store Store { get; }
internal PluginUi Ui { get; } internal PluginUi Ui { get; }
@@ -54,6 +55,7 @@ public sealed class Plugin : IDalamudPlugin {
public Plugin() { public Plugin() {
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration(); this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
this.Common = new XivCommonBase(); this.Common = new XivCommonBase();
this.TextureCache = new TextureCache(this.DataManager!);
this.Functions = new GameFunctions(this); this.Functions = new GameFunctions(this);
this.Store = new Store(this); this.Store = new Store(this);
this.Ui = new PluginUi(this); this.Ui = new PluginUi(this);
@@ -69,6 +71,7 @@ public sealed class Plugin : IDalamudPlugin {
this.Ui.Dispose(); this.Ui.Dispose();
this.Store.Dispose(); this.Store.Dispose();
this.Functions.Dispose(); this.Functions.Dispose();
this.TextureCache.Dispose();
this.Common.Dispose(); this.Common.Dispose();
} }
+4 -1
View File
@@ -9,6 +9,9 @@ namespace ChatTwo;
internal sealed class PluginUi : IDisposable { internal sealed class PluginUi : IDisposable {
internal Plugin Plugin { get; } internal Plugin Plugin { get; }
internal bool SettingsVisible;
internal ImFontPtr? RegularFont { get; private set; } internal ImFontPtr? RegularFont { get; private set; }
internal ImFontPtr? ItalicFont { get; private set; } internal ImFontPtr? ItalicFont { get; private set; }
internal Vector4 DefaultText { get; private set; } internal Vector4 DefaultText { get; private set; }
@@ -55,7 +58,7 @@ internal sealed class PluginUi : IDisposable {
var builder = new ImFontGlyphRangesBuilderPtr(ImGuiNative.ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder()); var builder = new ImFontGlyphRangesBuilderPtr(ImGuiNative.ImFontGlyphRangesBuilder_ImFontGlyphRangesBuilder());
builder.AddRanges(ImGui.GetIO().Fonts.GetGlyphRangesDefault()); builder.AddRanges(ImGui.GetIO().Fonts.GetGlyphRangesDefault());
builder.AddText("←→↑↓《》■※☀★★☆♥♡ヅツッシ☀☁☂℃℉°♀♂♠♣♦♣♧®©™€$£♯♭♪✓√◎◆◇♦■□〇●△▽▼▲‹›≤≥<«“”─"); builder.AddText("←→↑↓《》■※☀★★☆♥♡ヅツッシ☀☁☂℃℉°♀♂♠♣♦♣♧®©™€$£♯♭♪✓√◎◆◇♦■□〇●△▽▼▲‹›≤≥<«“”─\~");
builder.BuildRanges(out this._ranges); builder.BuildRanges(out this._ranges);
var regular = this.GetResource("ChatTwo.fonts.NotoSans-Regular.ttf"); var regular = this.GetResource("ChatTwo.fonts.NotoSans-Regular.ttf");
+59
View File
@@ -0,0 +1,59 @@
using Dalamud.Data;
using ImGuiScene;
using Lumina.Excel.GeneratedSheets;
namespace ChatTwo;
internal class TextureCache : IDisposable {
private DataManager Data { get; }
private readonly Dictionary<uint, TextureWrap> _itemIcons = new();
private readonly Dictionary<uint, TextureWrap> _statusIcons = new();
internal IReadOnlyDictionary<uint, TextureWrap> ItemIcons => this._itemIcons;
internal IReadOnlyDictionary<uint, TextureWrap> StatusIcons => this._statusIcons;
internal TextureCache(DataManager data) {
this.Data = data;
}
public void Dispose() {
var allIcons = this.ItemIcons.Values
.Concat(this.StatusIcons.Values);
foreach (var tex in allIcons) {
tex.Dispose();
}
}
private void AddIcon(IDictionary<uint, TextureWrap> dict, uint icon) {
if (dict.ContainsKey(icon)) {
return;
}
var tex = this.Data.GetImGuiTextureIcon(icon);
if (tex != null) {
dict[icon] = tex;
}
}
internal void AddItem(Item item) {
this.AddIcon(this._itemIcons, item.Icon);
}
internal void AddStatus(Status status) {
this.AddIcon(this._statusIcons, status.Icon);
}
internal TextureWrap? GetItem(Item item) {
this.AddItem(item);
this.ItemIcons.TryGetValue(item.Icon, out var icon);
return icon;
}
internal TextureWrap? GetStatus(Status status) {
this.AddStatus(status);
this.StatusIcons.TryGetValue(status.Icon, out var icon);
return icon;
}
}
+44 -2
View File
@@ -1,12 +1,16 @@
using System.Numerics; using System.Numerics;
using ChatTwo.Code; using ChatTwo.Code;
using ChatTwo.Util; using ChatTwo.Util;
using Dalamud.Interface;
using ImGuiNET; using ImGuiNET;
using ImGuiScene; using ImGuiScene;
using Lumina.Excel.GeneratedSheets;
namespace ChatTwo.Ui; namespace ChatTwo.Ui;
internal sealed class ChatLog : IUiComponent { internal sealed class ChatLog : IUiComponent {
private const string ChatChannelPicker = "chat-channel-picker";
private PluginUi Ui { get; } private PluginUi Ui { get; }
internal bool Activate; internal bool Activate;
@@ -143,7 +147,39 @@ internal sealed class ChatLog : IUiComponent {
ImGui.SetKeyboardFocusHere(); ImGui.SetKeyboardFocusHere();
} }
ImGui.TextUnformatted(this.Ui.Plugin.Functions.ChatChannel.name); ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
try {
this.DrawChunks(this.Ui.Plugin.Functions.ChatChannel.name);
} finally {
ImGui.PopStyleVar();
}
var beforeIcon = ImGui.GetCursorPos();
if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment)) {
ImGui.OpenPopup(ChatChannelPicker);
}
if (ImGui.BeginPopup(ChatChannelPicker)) {
foreach (var channel in Enum.GetValues<InputChannel>()) {
var name = this.Ui.Plugin.DataManager.GetExcelSheet<LogFilter>()!
.FirstOrDefault(row => row.LogKind == (byte) channel.ToChatType())
?.Name
?.RawString ?? channel.ToString();
if (ImGui.Selectable(name)) {
this.Ui.Plugin.Functions.SetChatChannel(channel);
}
}
ImGui.EndPopup();
}
ImGui.SameLine();
var afterIcon = ImGui.GetCursorPos();
var buttonWidth = afterIcon.X - beforeIcon.X;
var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth;
var inputType = this.Ui.Plugin.Functions.ChatChannel.channel.ToChatType(); var inputType = this.Ui.Plugin.Functions.ChatChannel.channel.ToChatType();
var inputColour = this.Ui.Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol) var inputColour = this.Ui.Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol)
@@ -154,7 +190,7 @@ internal sealed class ChatLog : IUiComponent {
ImGui.PushStyleColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(inputColour.Value)); ImGui.PushStyleColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(inputColour.Value));
} }
ImGui.SetNextItemWidth(-1); ImGui.SetNextItemWidth(inputWidth);
const ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags.EnterReturnsTrue const ImGuiInputTextFlags inputFlags = ImGuiInputTextFlags.EnterReturnsTrue
| ImGuiInputTextFlags.CallbackAlways | ImGuiInputTextFlags.CallbackAlways
| ImGuiInputTextFlags.CallbackHistory; | ImGuiInputTextFlags.CallbackHistory;
@@ -174,6 +210,12 @@ internal sealed class ChatLog : IUiComponent {
ImGui.PopStyleColor(); ImGui.PopStyleColor();
} }
ImGui.SameLine();
if (ImGuiUtil.IconButton(FontAwesomeIcon.Cog)) {
this.Ui.SettingsVisible ^= true;
}
ImGui.End(); ImGui.End();
} }
+5 -7
View File
@@ -9,8 +9,6 @@ namespace ChatTwo.Ui;
internal sealed class Settings : IUiComponent { internal sealed class Settings : IUiComponent {
private PluginUi Ui { get; } private PluginUi Ui { get; }
private bool _visible;
private bool _hideChat; private bool _hideChat;
private bool _nativeItemTooltips; private bool _nativeItemTooltips;
private float _fontSize; private float _fontSize;
@@ -29,7 +27,7 @@ internal sealed class Settings : IUiComponent {
} }
private void Command(string command, string args) { private void Command(string command, string args) {
this._visible ^= true; this.Ui.SettingsVisible ^= true;
} }
private void Initialise() { private void Initialise() {
@@ -42,11 +40,11 @@ internal sealed class Settings : IUiComponent {
} }
public void Draw() { public void Draw() {
if (!this._visible) { if (!this.Ui.SettingsVisible) {
return; return;
} }
if (!ImGui.Begin($"{this.Ui.Plugin.Name} settings", ref this._visible)) { if (!ImGui.Begin($"{this.Ui.Plugin.Name} settings", ref this.Ui.SettingsVisible)) {
ImGui.End(); ImGui.End();
return; return;
} }
@@ -148,13 +146,13 @@ internal sealed class Settings : IUiComponent {
if (ImGui.Button("Save and close")) { if (ImGui.Button("Save and close")) {
save = true; save = true;
this._visible = false; this.Ui.SettingsVisible = false;
} }
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Discard")) { if (ImGui.Button("Discard")) {
this._visible = false; this.Ui.SettingsVisible = false;
} }
ImGui.End(); ImGui.End();
+25 -1
View File
@@ -1,5 +1,6 @@
using System.Text; using System.Text;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface;
using ImGuiNET; using ImGuiNET;
namespace ChatTwo.Util; namespace ChatTwo.Util;
@@ -34,14 +35,20 @@ internal static class ImGuiUtil {
PostPayload(payload, handler); PostPayload(payload, handler);
} }
if (csText.Length == 0) {
return;
}
foreach (var part in csText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) { foreach (var part in csText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) {
var bytes = Encoding.UTF8.GetBytes(part); var bytes = Encoding.UTF8.GetBytes(part);
fixed (byte* rawText = bytes) { fixed (byte* rawText = bytes) {
var text = rawText; var text = rawText;
var textEnd = text + bytes.Length; var textEnd = text + bytes.Length;
// idk how this is possible, but it is, I guess // empty string
if (text == null) { if (text == null) {
ImGui.TextUnformatted("");
ImGui.TextUnformatted("");
return; return;
} }
@@ -63,6 +70,8 @@ internal static class ImGuiUtil {
endPrevLine = ImGuiNative.ImFont_CalcWordWrapPositionA(ImGui.GetFont().NativePtr, scale, text, textEnd, widthLeft); endPrevLine = ImGuiNative.ImFont_CalcWordWrapPositionA(ImGui.GetFont().NativePtr, scale, text, textEnd, widthLeft);
if (endPrevLine == null) { if (endPrevLine == null) {
ImGui.TextUnformatted("");
ImGui.TextUnformatted("");
break; break;
} }
@@ -71,4 +80,19 @@ internal static class ImGuiUtil {
} }
} }
} }
internal static bool IconButton(FontAwesomeIcon icon, string? id = null) {
ImGui.PushFont(UiBuilder.IconFont);
var label = icon.ToIconString();
if (id != null) {
label += $"##{id}";
}
var ret = ImGui.Button(label);
ImGui.PopFont();
return ret;
}
} }