feat: add reply in selected chat mode
This commit is contained in:
parent
c5bbd616f0
commit
0f790fa319
|
@ -4,6 +4,7 @@ using Dalamud.Game.Text.SeStringHandling;
|
|||
namespace ChatTwo;
|
||||
|
||||
internal abstract class Chunk {
|
||||
internal Message? Message { get; set; }
|
||||
internal SeString? Source { get; set; }
|
||||
internal Payload? Link { get; set; }
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ using ChatTwo.Util;
|
|||
namespace ChatTwo.Code;
|
||||
|
||||
internal static class ChatTypeExt {
|
||||
internal static string? Name(this ChatType type) {
|
||||
internal static string Name(this ChatType type) {
|
||||
return type switch {
|
||||
ChatType.Debug => "Debug",
|
||||
ChatType.Urgent => "Urgent",
|
||||
|
@ -206,4 +206,33 @@ internal static class ChatTypeExt {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static InputChannel? ToInputChannel(this ChatType type) => type switch {
|
||||
ChatType.TellOutgoing => InputChannel.Tell,
|
||||
ChatType.Say => InputChannel.Say,
|
||||
ChatType.Party => InputChannel.Party,
|
||||
ChatType.Alliance => InputChannel.Alliance,
|
||||
ChatType.Yell => InputChannel.Yell,
|
||||
ChatType.Shout => InputChannel.Shout,
|
||||
ChatType.FreeCompany => InputChannel.FreeCompany,
|
||||
ChatType.PvpTeam => InputChannel.PvpTeam,
|
||||
ChatType.NoviceNetwork => InputChannel.NoviceNetwork,
|
||||
ChatType.CrossLinkshell1 => InputChannel.CrossLinkshell1,
|
||||
ChatType.CrossLinkshell2 => InputChannel.CrossLinkshell2,
|
||||
ChatType.CrossLinkshell3 => InputChannel.CrossLinkshell3,
|
||||
ChatType.CrossLinkshell4 => InputChannel.CrossLinkshell4,
|
||||
ChatType.CrossLinkshell5 => InputChannel.CrossLinkshell5,
|
||||
ChatType.CrossLinkshell6 => InputChannel.CrossLinkshell6,
|
||||
ChatType.CrossLinkshell7 => InputChannel.CrossLinkshell7,
|
||||
ChatType.CrossLinkshell8 => InputChannel.CrossLinkshell8,
|
||||
ChatType.Linkshell1 => InputChannel.Linkshell1,
|
||||
ChatType.Linkshell2 => InputChannel.Linkshell2,
|
||||
ChatType.Linkshell3 => InputChannel.Linkshell3,
|
||||
ChatType.Linkshell4 => InputChannel.Linkshell4,
|
||||
ChatType.Linkshell5 => InputChannel.Linkshell5,
|
||||
ChatType.Linkshell6 => InputChannel.Linkshell6,
|
||||
ChatType.Linkshell7 => InputChannel.Linkshell7,
|
||||
ChatType.Linkshell8 => InputChannel.Linkshell8,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,20 +12,27 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
|||
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Siggingway;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
|
||||
internal sealed unsafe class Chat : IDisposable {
|
||||
// Functions
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 0F B7 44 37 ??", Fallibility = Fallibility.Fallible)]
|
||||
private readonly delegate* unmanaged<RaptureShellModule*, int, uint, Utf8String*, byte, void> _changeChatChannel = null!;
|
||||
|
||||
[Signature("4C 8B 81 ?? ?? ?? ?? 4D 85 C0 74 17", Fallibility = Fallibility.Fallible)]
|
||||
private readonly delegate* unmanaged<RaptureLogModule*, uint, ulong> _getContentIdForChatEntry = null!;
|
||||
|
||||
// Hooks
|
||||
|
||||
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
|
||||
|
||||
private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent);
|
||||
|
||||
private delegate void ReplyInSelectedChatModeDelegate(AgentInterface* agent);
|
||||
|
||||
[Signature(
|
||||
"40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA",
|
||||
DetourName = nameof(ChatLogRefreshDetour)
|
||||
|
@ -38,6 +45,23 @@ internal sealed unsafe class Chat : IDisposable {
|
|||
)]
|
||||
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; init; }
|
||||
|
||||
[Signature(
|
||||
"48 89 5C 24 ?? 57 48 83 EC 30 8B B9 ?? ?? ?? ?? 48 8B D9 83 FF FE",
|
||||
DetourName = nameof(ReplyInSelectedChatModeDetour)
|
||||
)]
|
||||
private Hook<ReplyInSelectedChatModeDelegate>? ReplyInSelectedChatModeHook { get; init; }
|
||||
|
||||
// Offsets
|
||||
|
||||
#pragma warning disable 0649
|
||||
|
||||
[Signature("8B B9 ?? ?? ?? ?? 48 8B D9 83 FF FE 0F 84", Offset = 2)]
|
||||
private readonly int? _replyChannelOffset;
|
||||
|
||||
#pragma warning restore 0649
|
||||
|
||||
// Events
|
||||
|
||||
internal delegate void ChatActivatedEventDelegate(string? input);
|
||||
|
||||
internal event ChatActivatedEventDelegate? Activated;
|
||||
|
@ -51,6 +75,7 @@ internal sealed unsafe class Chat : IDisposable {
|
|||
|
||||
this.ChatLogRefreshHook?.Enable();
|
||||
this.ChangeChannelNameHook?.Enable();
|
||||
this.ReplyInSelectedChatModeHook?.Enable();
|
||||
|
||||
this.Plugin.ClientState.Login += this.Login;
|
||||
this.Login(null, null);
|
||||
|
@ -59,6 +84,7 @@ internal sealed unsafe class Chat : IDisposable {
|
|||
public void Dispose() {
|
||||
this.Plugin.ClientState.Login -= this.Login;
|
||||
|
||||
this.ReplyInSelectedChatModeHook?.Dispose();
|
||||
this.ChangeChannelNameHook?.Dispose();
|
||||
this.ChatLogRefreshHook?.Dispose();
|
||||
|
||||
|
@ -127,27 +153,43 @@ internal sealed unsafe class Chat : IDisposable {
|
|||
}
|
||||
|
||||
private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) {
|
||||
if (eventId == 0x31 && value != null && value->UInt is 0x05 or 0x0C) {
|
||||
string? eventInput = null;
|
||||
|
||||
var str = value + 2;
|
||||
if (str != null && str->String != null) {
|
||||
var input = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String);
|
||||
if (input.Length > 0) {
|
||||
eventInput = input;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.Activated?.Invoke(eventInput);
|
||||
} catch (Exception ex) {
|
||||
PluginLog.LogError(ex, "Error in ChatActivated event");
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C)) {
|
||||
return this.ChatLogRefreshHook!.Original(log, eventId, value);
|
||||
}
|
||||
|
||||
return this.ChatLogRefreshHook!.Original(log, eventId, value);
|
||||
string? eventInput = null;
|
||||
|
||||
var str = value + 2;
|
||||
if (str != null && ((int) str->Type & 0xF) == (int) ValueType.String && str->String != null) {
|
||||
var input = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String);
|
||||
if (input.Length > 0) {
|
||||
eventInput = input;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.Activated?.Invoke(eventInput);
|
||||
} catch (Exception ex) {
|
||||
PluginLog.LogError(ex, "Error in ChatActivated event");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void ReplyInSelectedChatModeDetour(AgentInterface* agent) {
|
||||
if (this._replyChannelOffset == null) {
|
||||
goto Original;
|
||||
}
|
||||
|
||||
var replyMode = *(int*) ((IntPtr) agent + this._replyChannelOffset.Value);
|
||||
if (replyMode == -2) {
|
||||
goto Original;
|
||||
}
|
||||
|
||||
this.SetChannel((InputChannel) replyMode);
|
||||
|
||||
Original:
|
||||
this.ReplyInSelectedChatModeHook!.Original(agent);
|
||||
}
|
||||
|
||||
internal ulong? GetContentIdForEntry(uint index) {
|
||||
|
|
|
@ -15,5 +15,9 @@ internal class Message {
|
|||
this.Code = code;
|
||||
this.Sender = sender;
|
||||
this.Content = content;
|
||||
|
||||
foreach (var chunk in sender.Concat(content)) {
|
||||
chunk.Message = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Ui;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
|
@ -16,7 +17,7 @@ internal sealed class PayloadHandler {
|
|||
private PluginUi Ui { get; }
|
||||
private ChatLog Log { get; }
|
||||
|
||||
private HashSet<Payload> PopupPayloads { get; set; } = new();
|
||||
private HashSet<(Chunk, Payload)> PopupPayloads { get; set; } = new();
|
||||
|
||||
private bool _handleTooltips;
|
||||
private uint _hoveredItem;
|
||||
|
@ -40,8 +41,8 @@ internal sealed class PayloadHandler {
|
|||
}
|
||||
|
||||
private void DrawPopups() {
|
||||
var newPopups = new HashSet<Payload>();
|
||||
foreach (var payload in this.PopupPayloads) {
|
||||
var newPopups = new HashSet<(Chunk, Payload)>();
|
||||
foreach (var (chunk, payload) in this.PopupPayloads) {
|
||||
var id = PopupId(payload);
|
||||
if (id == null) {
|
||||
continue;
|
||||
|
@ -51,12 +52,12 @@ internal sealed class PayloadHandler {
|
|||
continue;
|
||||
}
|
||||
|
||||
newPopups.Add(payload);
|
||||
newPopups.Add((chunk, payload));
|
||||
ImGui.PushID(id);
|
||||
|
||||
switch (payload) {
|
||||
case PlayerPayload player: {
|
||||
this.DrawPlayerPopup(player);
|
||||
this.DrawPlayerPopup(chunk, player);
|
||||
break;
|
||||
}
|
||||
case ItemPayload item: {
|
||||
|
@ -78,7 +79,7 @@ internal sealed class PayloadHandler {
|
|||
this.LeftClickPayload(chunk, payload);
|
||||
break;
|
||||
case ImGuiMouseButton.Right:
|
||||
this.RightClickPayload(payload);
|
||||
this.RightClickPayload(chunk, payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -211,11 +212,11 @@ internal sealed class PayloadHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private void RightClickPayload(Payload payload) {
|
||||
private void RightClickPayload(Chunk chunk, Payload payload) {
|
||||
switch (payload) {
|
||||
case PlayerPayload:
|
||||
case ItemPayload: {
|
||||
this.PopupPayloads.Add(payload);
|
||||
this.PopupPayloads.Add((chunk, payload));
|
||||
ImGui.OpenPopup(PopupId(payload));
|
||||
break;
|
||||
}
|
||||
|
@ -267,7 +268,7 @@ internal sealed class PayloadHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawPlayerPopup(PlayerPayload player) {
|
||||
private void DrawPlayerPopup(Chunk chunk, PlayerPayload player) {
|
||||
var name = player.PlayerName;
|
||||
if (player.World.IsPublic) {
|
||||
name += $"{player.World.Name}";
|
||||
|
@ -313,6 +314,12 @@ internal sealed class PayloadHandler {
|
|||
}
|
||||
}
|
||||
|
||||
var inputChannel = chunk.Message?.Code.Type.ToInputChannel();
|
||||
if (inputChannel != null && ImGui.Selectable("Reply in Selected Chat Mode")) {
|
||||
this.Ui.Plugin.Functions.Chat.SetChannel(inputChannel.Value);
|
||||
this.Log.Activate = true;
|
||||
}
|
||||
|
||||
if (ImGui.Selectable("Target") && this.FindCharacterForPayload(player) is { } obj) {
|
||||
this.Ui.Plugin.TargetManager.SetTarget(obj);
|
||||
}
|
||||
|
@ -323,7 +330,6 @@ internal sealed class PayloadHandler {
|
|||
|
||||
// Add to Blacklist 0x1C
|
||||
// View Party Finder 0x2E
|
||||
// Reply in Selected Chat Mode 0x64
|
||||
}
|
||||
|
||||
private PlayerCharacter? FindCharacterForPayload(PlayerPayload payload) {
|
||||
|
|
|
@ -26,7 +26,7 @@ internal sealed class PluginUi : IDisposable {
|
|||
private (GCHandle, int) _jpFont;
|
||||
private (GCHandle, int) _gameSymFont;
|
||||
|
||||
private ImVector _ranges;
|
||||
private readonly ImVector _ranges;
|
||||
|
||||
private GCHandle _jpRange = GCHandle.Alloc(
|
||||
GlyphRangesJapanese.GlyphRanges,
|
||||
|
|
Loading…
Reference in New Issue
Block a user