Compare commits
3 Commits
c53b4ac3eb
...
15d1575c59
Author | SHA1 | Date | |
---|---|---|---|
15d1575c59 | |||
c2b83116f2 | |||
3c4c47ffd8 |
@ -1,6 +1,8 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using ChatTwo.Code;
|
using ChatTwo.Code;
|
||||||
|
using ChatTwo.GameFunctions.Types;
|
||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
@ -25,6 +27,24 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
[Signature("4C 8B 81 ?? ?? ?? ?? 4D 85 C0 74 17", Fallibility = Fallibility.Fallible)]
|
[Signature("4C 8B 81 ?? ?? ?? ?? 4D 85 C0 74 17", Fallibility = Fallibility.Fallible)]
|
||||||
private readonly delegate* unmanaged<RaptureLogModule*, uint, ulong> _getContentIdForChatEntry = null!;
|
private readonly delegate* unmanaged<RaptureLogModule*, uint, ulong> _getContentIdForChatEntry = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")]
|
||||||
|
private readonly delegate* unmanaged<IntPtr, Utf8String*, IntPtr, uint> _getKeybind = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 48 3B F0 74 35")]
|
||||||
|
private readonly delegate* unmanaged<AtkStage*, IntPtr> _getFocus = null!;
|
||||||
|
|
||||||
|
[Signature("44 8B 89 ?? ?? ?? ?? 4C 8B C1")]
|
||||||
|
private readonly delegate* unmanaged<void*, int, IntPtr> _getTellHistory = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? B8 ?? ?? ?? ?? 48 8D 4D 50")]
|
||||||
|
private readonly delegate* unmanaged<RaptureLogModule*, ushort, Utf8String*, Utf8String*, ulong, ushort, byte, int, byte, void> _printTell = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01")]
|
||||||
|
private readonly delegate* unmanaged<IntPtr, ulong, ushort, Utf8String*, Utf8String*, byte, ulong, byte> _sendTell = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? F6 43 0A 40")]
|
||||||
|
private readonly delegate* unmanaged<Framework*, IntPtr> _getNetworkModule = null!;
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
||||||
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
|
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
|
||||||
@ -33,6 +53,8 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
|
|
||||||
private delegate void ReplyInSelectedChatModeDelegate(AgentInterface* agent);
|
private delegate void ReplyInSelectedChatModeDelegate(AgentInterface* agent);
|
||||||
|
|
||||||
|
private delegate byte SetChatLogTellTarget(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort a6, byte a7);
|
||||||
|
|
||||||
[Signature(
|
[Signature(
|
||||||
"40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA",
|
"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)
|
DetourName = nameof(ChatLogRefreshDetour)
|
||||||
@ -51,6 +73,12 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
)]
|
)]
|
||||||
private Hook<ReplyInSelectedChatModeDelegate>? ReplyInSelectedChatModeHook { get; init; }
|
private Hook<ReplyInSelectedChatModeDelegate>? ReplyInSelectedChatModeHook { get; init; }
|
||||||
|
|
||||||
|
[Signature(
|
||||||
|
"E8 ?? ?? ?? ?? 4C 8B 7C 24 ?? EB 34",
|
||||||
|
DetourName = nameof(SetChatLogTellTargetDetour)
|
||||||
|
)]
|
||||||
|
private Hook<SetChatLogTellTarget>? SetChatLogTellTargetHook { get; init; }
|
||||||
|
|
||||||
// Offsets
|
// Offsets
|
||||||
|
|
||||||
#pragma warning disable 0649
|
#pragma warning disable 0649
|
||||||
@ -58,11 +86,14 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
[Signature("8B B9 ?? ?? ?? ?? 48 8B D9 83 FF FE 0F 84", Offset = 2)]
|
[Signature("8B B9 ?? ?? ?? ?? 48 8B D9 83 FF FE 0F 84", Offset = 2)]
|
||||||
private readonly int? _replyChannelOffset;
|
private readonly int? _replyChannelOffset;
|
||||||
|
|
||||||
|
[Signature("89 83 ?? ?? ?? ?? 48 8B 01 83 FE 13 7C 05 41 8B D4 EB 03 83 CA FF FF 90", Offset = 2)]
|
||||||
|
private readonly int? _shellChannelOffset;
|
||||||
|
|
||||||
#pragma warning restore 0649
|
#pragma warning restore 0649
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
|
|
||||||
internal delegate void ChatActivatedEventDelegate(string? input);
|
internal delegate void ChatActivatedEventDelegate(string? input, ChannelSwitchInfo info, TellReason? reason, TellTarget? target);
|
||||||
|
|
||||||
internal event ChatActivatedEventDelegate? Activated;
|
internal event ChatActivatedEventDelegate? Activated;
|
||||||
|
|
||||||
@ -76,14 +107,18 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
this.ChatLogRefreshHook?.Enable();
|
this.ChatLogRefreshHook?.Enable();
|
||||||
this.ChangeChannelNameHook?.Enable();
|
this.ChangeChannelNameHook?.Enable();
|
||||||
this.ReplyInSelectedChatModeHook?.Enable();
|
this.ReplyInSelectedChatModeHook?.Enable();
|
||||||
|
this.SetChatLogTellTargetHook?.Enable();
|
||||||
|
|
||||||
|
this.Plugin.Framework.Update += this.InterceptKeybinds;
|
||||||
this.Plugin.ClientState.Login += this.Login;
|
this.Plugin.ClientState.Login += this.Login;
|
||||||
this.Login(null, null);
|
this.Login(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
this.Plugin.ClientState.Login -= this.Login;
|
this.Plugin.ClientState.Login -= this.Login;
|
||||||
|
this.Plugin.Framework.Update -= this.InterceptKeybinds;
|
||||||
|
|
||||||
|
this.SetChatLogTellTargetHook?.Dispose();
|
||||||
this.ReplyInSelectedChatModeHook?.Dispose();
|
this.ReplyInSelectedChatModeHook?.Dispose();
|
||||||
this.ChangeChannelNameHook?.Dispose();
|
this.ChangeChannelNameHook?.Dispose();
|
||||||
this.ChatLogRefreshHook?.Dispose();
|
this.ChatLogRefreshHook?.Dispose();
|
||||||
@ -91,6 +126,175 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
this.Activated = null;
|
this.Activated = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<string, Keybind> _keybinds = new();
|
||||||
|
internal IReadOnlyDictionary<string, Keybind> Keybinds => this._keybinds;
|
||||||
|
|
||||||
|
internal static readonly IReadOnlyDictionary<string, ChannelSwitchInfo> KeybindsToIntercept = new Dictionary<string, ChannelSwitchInfo> {
|
||||||
|
["CMD_CHAT"] = new(null),
|
||||||
|
["CMD_COMMAND"] = new(null, text: "/"),
|
||||||
|
["CMD_REPLY"] = new(InputChannel.Tell, rotate: RotateMode.Forward),
|
||||||
|
["CMD_REPLY_REV"] = new(InputChannel.Tell, rotate: RotateMode.Reverse),
|
||||||
|
["CMD_SAY"] = new(InputChannel.Say),
|
||||||
|
["CMD_YELL"] = new(InputChannel.Yell),
|
||||||
|
["CMD_SHOUT"] = new(InputChannel.Shout),
|
||||||
|
["CMD_PARTY"] = new(InputChannel.Party),
|
||||||
|
["CMD_ALLIANCE"] = new(InputChannel.Alliance),
|
||||||
|
["CMD_FREECOM"] = new(InputChannel.FreeCompany),
|
||||||
|
["PVPTEAM_CHAT"] = new(InputChannel.PvpTeam),
|
||||||
|
["CMD_CWLINKSHELL"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Forward),
|
||||||
|
["CMD_CWLINKSHELL_REV"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Reverse),
|
||||||
|
["CMD_CWLINKSHELL_1"] = new(InputChannel.CrossLinkshell1),
|
||||||
|
["CMD_CWLINKSHELL_2"] = new(InputChannel.CrossLinkshell2),
|
||||||
|
["CMD_CWLINKSHELL_3"] = new(InputChannel.CrossLinkshell3),
|
||||||
|
["CMD_CWLINKSHELL_4"] = new(InputChannel.CrossLinkshell4),
|
||||||
|
["CMD_CWLINKSHELL_5"] = new(InputChannel.CrossLinkshell5),
|
||||||
|
["CMD_CWLINKSHELL_6"] = new(InputChannel.CrossLinkshell6),
|
||||||
|
["CMD_CWLINKSHELL_7"] = new(InputChannel.CrossLinkshell7),
|
||||||
|
["CMD_CWLINKSHELL_8"] = new(InputChannel.CrossLinkshell8),
|
||||||
|
["CMD_LINKSHELL"] = new(InputChannel.Linkshell1, rotate: RotateMode.Forward),
|
||||||
|
["CMD_LINKSHELL_REV"] = new(InputChannel.Linkshell1, rotate: RotateMode.Reverse),
|
||||||
|
["CMD_LINKSHELL_1"] = new(InputChannel.Linkshell1),
|
||||||
|
["CMD_LINKSHELL_2"] = new(InputChannel.Linkshell2),
|
||||||
|
["CMD_LINKSHELL_3"] = new(InputChannel.Linkshell3),
|
||||||
|
["CMD_LINKSHELL_4"] = new(InputChannel.Linkshell4),
|
||||||
|
["CMD_LINKSHELL_5"] = new(InputChannel.Linkshell5),
|
||||||
|
["CMD_LINKSHELL_6"] = new(InputChannel.Linkshell6),
|
||||||
|
["CMD_LINKSHELL_7"] = new(InputChannel.Linkshell7),
|
||||||
|
["CMD_LINKSHELL_8"] = new(InputChannel.Linkshell8),
|
||||||
|
["CMD_BEGINNER"] = new(InputChannel.NoviceNetwork),
|
||||||
|
["CMD_REPLY_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Forward),
|
||||||
|
["CMD_REPLY_REV_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Reverse),
|
||||||
|
["CMD_SAY_ALWAYS"] = new(InputChannel.Say, true),
|
||||||
|
["CMD_YELL_ALWAYS"] = new(InputChannel.Yell, true),
|
||||||
|
["CMD_PARTY_ALWAYS"] = new(InputChannel.Party, true),
|
||||||
|
["CMD_ALLIANCE_ALWAYS"] = new(InputChannel.Alliance, true),
|
||||||
|
["CMD_FREECOM_ALWAYS"] = new(InputChannel.FreeCompany, true),
|
||||||
|
["PVPTEAM_CHAT_ALWAYS"] = new(InputChannel.PvpTeam, true),
|
||||||
|
["CMD_CWLINKSHELL_ALWAYS"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Forward),
|
||||||
|
["CMD_CWLINKSHELL_ALWAYS_REV"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Reverse),
|
||||||
|
["CMD_CWLINKSHELL_1_ALWAYS"] = new(InputChannel.CrossLinkshell1, true),
|
||||||
|
["CMD_CWLINKSHELL_2_ALWAYS"] = new(InputChannel.CrossLinkshell2, true),
|
||||||
|
["CMD_CWLINKSHELL_3_ALWAYS"] = new(InputChannel.CrossLinkshell3, true),
|
||||||
|
["CMD_CWLINKSHELL_4_ALWAYS"] = new(InputChannel.CrossLinkshell4, true),
|
||||||
|
["CMD_CWLINKSHELL_5_ALWAYS"] = new(InputChannel.CrossLinkshell5, true),
|
||||||
|
["CMD_CWLINKSHELL_6_ALWAYS"] = new(InputChannel.CrossLinkshell6, true),
|
||||||
|
["CMD_CWLINKSHELL_7_ALWAYS"] = new(InputChannel.CrossLinkshell7, true),
|
||||||
|
["CMD_CWLINKSHELL_8_ALWAYS"] = new(InputChannel.CrossLinkshell8, true),
|
||||||
|
["CMD_LINKSHELL_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Forward),
|
||||||
|
["CMD_LINKSHELL_REV_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Reverse),
|
||||||
|
["CMD_LINKSHELL_1_ALWAYS"] = new(InputChannel.Linkshell1, true),
|
||||||
|
["CMD_LINKSHELL_2_ALWAYS"] = new(InputChannel.Linkshell2, true),
|
||||||
|
["CMD_LINKSHELL_3_ALWAYS"] = new(InputChannel.Linkshell3, true),
|
||||||
|
["CMD_LINKSHELL_4_ALWAYS"] = new(InputChannel.Linkshell4, true),
|
||||||
|
["CMD_LINKSHELL_5_ALWAYS"] = new(InputChannel.Linkshell5, true),
|
||||||
|
["CMD_LINKSHELL_6_ALWAYS"] = new(InputChannel.Linkshell6, true),
|
||||||
|
["CMD_LINKSHELL_7_ALWAYS"] = new(InputChannel.Linkshell7, true),
|
||||||
|
["CMD_LINKSHELL_8_ALWAYS"] = new(InputChannel.Linkshell8, true),
|
||||||
|
["CMD_BEGINNER_ALWAYS"] = new(InputChannel.NoviceNetwork, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
private bool _inputFocused;
|
||||||
|
private int _graceFrames;
|
||||||
|
|
||||||
|
|
||||||
|
private void CheckFocus() {
|
||||||
|
void Decrement() {
|
||||||
|
if (this._graceFrames > 0) {
|
||||||
|
this._graceFrames -= 1;
|
||||||
|
} else {
|
||||||
|
this._inputFocused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var focus = this._getFocus(AtkStage.GetSingleton());
|
||||||
|
if (focus == IntPtr.Zero) {
|
||||||
|
Decrement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = (AtkResNode*) focus;
|
||||||
|
var parent = node->ParentNode;
|
||||||
|
if (parent == null || (uint) parent->Type is not (1007 or 1011)) {
|
||||||
|
Decrement();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._inputFocused = true;
|
||||||
|
this._graceFrames = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateKeybinds() {
|
||||||
|
foreach (var name in KeybindsToIntercept.Keys) {
|
||||||
|
var keybind = this.GetKeybind(name);
|
||||||
|
if (keybind is null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._keybinds[name] = keybind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InterceptKeybinds(Dalamud.Game.Framework framework) {
|
||||||
|
this.CheckFocus();
|
||||||
|
this.UpdateKeybinds();
|
||||||
|
|
||||||
|
if (this._inputFocused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var modifierState = (ModifierFlag) 0;
|
||||||
|
foreach (var modifier in Enum.GetValues<ModifierFlag>()) {
|
||||||
|
var modifierKey = GetKeyForModifier(modifier);
|
||||||
|
if (modifierKey != VirtualKey.NO_KEY && this.Plugin.KeyState[modifierKey]) {
|
||||||
|
modifierState |= modifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
|
||||||
|
foreach (var toIntercept in KeybindsToIntercept.Keys) {
|
||||||
|
if (!this.Keybinds.TryGetValue(toIntercept, out var keybind)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Intercept(VirtualKey key, ModifierFlag modifier) {
|
||||||
|
if (!this.Plugin.KeyState.IsVirtualKeyValid(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modifierState.HasFlag(modifier)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.Plugin.KeyState[key]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bits = NumUtil.NumberOfSetBits((uint) modifier);
|
||||||
|
if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits) {
|
||||||
|
turnedOff[key] = (bits, toIntercept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Intercept(keybind.Key1, keybind.Modifier1);
|
||||||
|
Intercept(keybind.Key2, keybind.Modifier2);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (key, (_, keybind)) in turnedOff) {
|
||||||
|
PluginLog.Log($"intercepting {keybind}");
|
||||||
|
this.Plugin.KeyState[key] = false;
|
||||||
|
|
||||||
|
if (!KeybindsToIntercept.TryGetValue(keybind, out var info)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.Activated?.Invoke(null, info, TellReason.Reply, null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Error in chat Activated event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Login(object? sender, EventArgs? e) {
|
private void Login(object? sender, EventArgs? e) {
|
||||||
if (this.ChangeChannelNameHook == null) {
|
if (this.ChangeChannelNameHook == null) {
|
||||||
return;
|
return;
|
||||||
@ -104,6 +308,31 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
this.ChangeChannelNameDetour((IntPtr) agent);
|
this.ChangeChannelNameDetour((IntPtr) agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) {
|
||||||
|
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C)) {
|
||||||
|
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, new ChannelSwitchInfo(null), null, null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Error in chat Activated event");
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent the game from focusing the chat log
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
private IntPtr ChangeChannelNameDetour(IntPtr agent) {
|
private IntPtr ChangeChannelNameDetour(IntPtr agent) {
|
||||||
// Last ShB patch
|
// Last ShB patch
|
||||||
// +0x40 = chat channel (byte or uint?)
|
// +0x40 = chat channel (byte or uint?)
|
||||||
@ -121,7 +350,10 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel = *(uint*) (shellModule + 0xFD0);
|
var channel = 0u;
|
||||||
|
if (this._shellChannelOffset != null) {
|
||||||
|
channel = *(uint*) (shellModule + this._shellChannelOffset.Value);
|
||||||
|
}
|
||||||
|
|
||||||
// var channel = *(uint*) (agent + 0x40);
|
// var channel = *(uint*) (agent + 0x40);
|
||||||
if (channel is 17 or 18) {
|
if (channel is 17 or 18) {
|
||||||
@ -152,30 +384,6 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) {
|
|
||||||
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C)) {
|
|
||||||
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) {
|
private void ReplyInSelectedChatModeDetour(AgentInterface* agent) {
|
||||||
if (this._replyChannelOffset == null) {
|
if (this._replyChannelOffset == null) {
|
||||||
goto Original;
|
goto Original;
|
||||||
@ -192,6 +400,19 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
this.ReplyInSelectedChatModeHook!.Original(agent);
|
this.ReplyInSelectedChatModeHook!.Original(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte SetChatLogTellTargetDetour(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort reason, byte a7) {
|
||||||
|
if (name != null) {
|
||||||
|
try {
|
||||||
|
var target = new TellTarget(name->ToString(), world, contentId, (TellReason) reason);
|
||||||
|
this.Activated?.Invoke(null, new ChannelSwitchInfo(InputChannel.Tell), (TellReason) reason, target);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Error in chat Activated event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.SetChatLogTellTargetHook!.Original(a1, name, a3, world, contentId, reason, a7);
|
||||||
|
}
|
||||||
|
|
||||||
internal ulong? GetContentIdForEntry(uint index) {
|
internal ulong? GetContentIdForEntry(uint index) {
|
||||||
if (this._getContentIdForChatEntry == null) {
|
if (this._getContentIdForChatEntry == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -222,4 +443,74 @@ internal sealed unsafe class Chat : IDisposable {
|
|||||||
this._changeChatChannel(RaptureShellModule.Instance, (int) channel, idx, &target, 1);
|
this._changeChatChannel(RaptureShellModule.Instance, (int) channel, idx, &target, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static VirtualKey GetKeyForModifier(ModifierFlag modifierFlag) => modifierFlag switch {
|
||||||
|
ModifierFlag.Shift => VirtualKey.SHIFT,
|
||||||
|
ModifierFlag.Ctrl => VirtualKey.CONTROL,
|
||||||
|
ModifierFlag.Alt => VirtualKey.MENU,
|
||||||
|
_ => VirtualKey.NO_KEY,
|
||||||
|
};
|
||||||
|
|
||||||
|
private Keybind? GetKeybind(string id) {
|
||||||
|
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Configkey);
|
||||||
|
if (agent == IntPtr.Zero) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a1 = *(void**) (agent + 0x78);
|
||||||
|
if (a1 == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outData = stackalloc byte[32];
|
||||||
|
var idString = Utf8String.FromString(id);
|
||||||
|
this._getKeybind((IntPtr) a1, idString, (IntPtr) outData);
|
||||||
|
|
||||||
|
var key1 = (VirtualKey) outData[0];
|
||||||
|
if (key1 is VirtualKey.F23) {
|
||||||
|
key1 = VirtualKey.OEM_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key2 = (VirtualKey) outData[2];
|
||||||
|
if (key2 is VirtualKey.F23) {
|
||||||
|
key2 = VirtualKey.OEM_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Keybind {
|
||||||
|
Key1 = key1,
|
||||||
|
Modifier1 = (ModifierFlag) outData[1],
|
||||||
|
Key2 = key2,
|
||||||
|
Modifier2 = (ModifierFlag) outData[3],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TellHistoryInfo? GetTellHistoryInfo(int index) {
|
||||||
|
var acquaintanceModule = Framework.Instance()->GetUiModule()->GetAcquaintanceModule();
|
||||||
|
if (acquaintanceModule == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ptr = this._getTellHistory(acquaintanceModule, index);
|
||||||
|
if (ptr == IntPtr.Zero) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = MemoryHelper.ReadStringNullTerminated(*(IntPtr*) ptr);
|
||||||
|
var world = *(ushort*) (ptr + 0xD0);
|
||||||
|
var contentId = *(ulong*) (ptr + 0xD8);
|
||||||
|
|
||||||
|
return new TellHistoryInfo(name, world, contentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SendTell(TellReason reason, ulong contentId, string name, ushort homeWorld, string message) {
|
||||||
|
var uName = Utf8String.FromString(name);
|
||||||
|
var uMessage = Utf8String.FromString(message);
|
||||||
|
|
||||||
|
var networkModule = this._getNetworkModule(Framework.Instance());
|
||||||
|
var a1 = *(IntPtr*) (networkModule + 8);
|
||||||
|
var logModule = Framework.Instance()->GetUiModule()->GetRaptureLogModule();
|
||||||
|
|
||||||
|
this._printTell(logModule, 33, uName, uMessage, contentId, homeWorld, 255, 0, 0);
|
||||||
|
this._sendTell(a1, contentId, homeWorld, uName, uMessage, (byte) reason, homeWorld);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
17
ChatTwo/GameFunctions/Types/ChannelSwitchInfo.cs
Executable file
17
ChatTwo/GameFunctions/Types/ChannelSwitchInfo.cs
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
using ChatTwo.Code;
|
||||||
|
|
||||||
|
namespace ChatTwo.GameFunctions.Types;
|
||||||
|
|
||||||
|
internal class ChannelSwitchInfo {
|
||||||
|
internal InputChannel? Channel { get; }
|
||||||
|
internal bool Permanent { get; }
|
||||||
|
internal RotateMode Rotate { get; }
|
||||||
|
internal string? Text { get; }
|
||||||
|
|
||||||
|
internal ChannelSwitchInfo(InputChannel? channel, bool permanent = false, RotateMode rotate = RotateMode.None, string? text = null) {
|
||||||
|
this.Channel = channel;
|
||||||
|
this.Permanent = permanent;
|
||||||
|
this.Rotate = rotate;
|
||||||
|
this.Text = text;
|
||||||
|
}
|
||||||
|
}
|
11
ChatTwo/GameFunctions/Types/Keybind.cs
Executable file
11
ChatTwo/GameFunctions/Types/Keybind.cs
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
|
||||||
|
namespace ChatTwo.GameFunctions.Types;
|
||||||
|
|
||||||
|
internal class Keybind {
|
||||||
|
internal VirtualKey Key1 { get; init; }
|
||||||
|
internal ModifierFlag Modifier1 { get; init; }
|
||||||
|
|
||||||
|
internal VirtualKey Key2 { get; init; }
|
||||||
|
internal ModifierFlag Modifier2 { get; init; }
|
||||||
|
}
|
8
ChatTwo/GameFunctions/Types/ModifierFlag.cs
Executable file
8
ChatTwo/GameFunctions/Types/ModifierFlag.cs
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
namespace ChatTwo.GameFunctions.Types;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
internal enum ModifierFlag {
|
||||||
|
Shift = 1 << 0,
|
||||||
|
Ctrl = 1 << 1,
|
||||||
|
Alt = 1 << 2,
|
||||||
|
}
|
7
ChatTwo/GameFunctions/Types/RotateMode.cs
Executable file
7
ChatTwo/GameFunctions/Types/RotateMode.cs
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
namespace ChatTwo.GameFunctions.Types;
|
||||||
|
|
||||||
|
internal enum RotateMode {
|
||||||
|
None,
|
||||||
|
Forward,
|
||||||
|
Reverse,
|
||||||
|
}
|
13
ChatTwo/GameFunctions/Types/TellHistoryInfo.cs
Executable file
13
ChatTwo/GameFunctions/Types/TellHistoryInfo.cs
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
namespace ChatTwo.GameFunctions.Types;
|
||||||
|
|
||||||
|
internal sealed class TellHistoryInfo {
|
||||||
|
internal string Name { get; }
|
||||||
|
internal uint World { get; }
|
||||||
|
internal ulong ContentId { get; }
|
||||||
|
|
||||||
|
internal TellHistoryInfo(string name, uint world, ulong contentId) {
|
||||||
|
this.Name = name;
|
||||||
|
this.World = world;
|
||||||
|
this.ContentId = contentId;
|
||||||
|
}
|
||||||
|
}
|
8
ChatTwo/GameFunctions/Types/TellReason.cs
Executable file
8
ChatTwo/GameFunctions/Types/TellReason.cs
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
namespace ChatTwo.GameFunctions.Types;
|
||||||
|
|
||||||
|
internal enum TellReason {
|
||||||
|
Direct = 0,
|
||||||
|
PartyFinder = 1,
|
||||||
|
Reply = 2,
|
||||||
|
Friend = 3,
|
||||||
|
}
|
15
ChatTwo/GameFunctions/Types/TellTarget.cs
Executable file
15
ChatTwo/GameFunctions/Types/TellTarget.cs
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
namespace ChatTwo.GameFunctions.Types;
|
||||||
|
|
||||||
|
internal sealed class TellTarget {
|
||||||
|
internal string Name { get; }
|
||||||
|
internal ushort World { get; }
|
||||||
|
internal ulong ContentId { get; }
|
||||||
|
internal TellReason Reason { get; }
|
||||||
|
|
||||||
|
internal TellTarget(string name, ushort world, ulong contentId, TellReason reason) {
|
||||||
|
this.Name = name;
|
||||||
|
this.World = world;
|
||||||
|
this.ContentId = contentId;
|
||||||
|
this.Reason = reason;
|
||||||
|
}
|
||||||
|
}
|
@ -154,6 +154,10 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void HoverItem(ItemPayload item) {
|
private void HoverItem(ItemPayload item) {
|
||||||
|
if (item.Item == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.Ui.Plugin.TextureCache.GetItem(item.Item, item.IsHQ) is { } icon) {
|
if (this.Ui.Plugin.TextureCache.GetItem(item.Item, item.IsHQ) is { } icon) {
|
||||||
InlineIcon(icon);
|
InlineIcon(icon);
|
||||||
}
|
}
|
||||||
@ -232,6 +236,10 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void DrawItemPopup(ItemPayload item) {
|
private void DrawItemPopup(ItemPayload item) {
|
||||||
|
if (item.Item == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.Ui.Plugin.TextureCache.GetItem(item.Item, item.IsHQ) is { } icon) {
|
if (this.Ui.Plugin.TextureCache.GetItem(item.Item, item.IsHQ) is { } icon) {
|
||||||
InlineIcon(icon);
|
InlineIcon(icon);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Party;
|
using Dalamud.Game.ClientState.Party;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
@ -36,6 +37,9 @@ public sealed class Plugin : IDalamudPlugin {
|
|||||||
[PluginService]
|
[PluginService]
|
||||||
internal GameGui GameGui { get; init; }
|
internal GameGui GameGui { get; init; }
|
||||||
|
|
||||||
|
[PluginService]
|
||||||
|
internal KeyState KeyState { get; init; }
|
||||||
|
|
||||||
[PluginService]
|
[PluginService]
|
||||||
internal ObjectTable ObjectTable { get; init; }
|
internal ObjectTable ObjectTable { get; init; }
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using ChatTwo.Code;
|
using ChatTwo.Code;
|
||||||
|
using ChatTwo.GameFunctions.Types;
|
||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Logging;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
@ -21,6 +25,9 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
private readonly List<string> _inputBacklog = new();
|
private readonly List<string> _inputBacklog = new();
|
||||||
private int _inputBacklogIdx = -1;
|
private int _inputBacklogIdx = -1;
|
||||||
private int _lastTab;
|
private int _lastTab;
|
||||||
|
private InputChannel? _tempChannel;
|
||||||
|
private TellTarget? _tellTarget;
|
||||||
|
private int _tellIdx;
|
||||||
|
|
||||||
private PayloadHandler PayloadHandler { get; }
|
private PayloadHandler PayloadHandler { get; }
|
||||||
private Dictionary<string, ChatType> TextCommandChannels { get; } = new();
|
private Dictionary<string, ChatType> TextCommandChannels { get; } = new();
|
||||||
@ -46,11 +53,50 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
this.Ui.Plugin.CommandManager.RemoveHandler("/clearlog2");
|
this.Ui.Plugin.CommandManager.RemoveHandler("/clearlog2");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Activated(string? input) {
|
private void Activated(string? input, ChannelSwitchInfo info, TellReason? reason, TellTarget? target) {
|
||||||
this.Activate = true;
|
this.Activate = true;
|
||||||
if (input != null && !this.Chat.Contains(input)) {
|
if (input != null && !this.Chat.Contains(input)) {
|
||||||
this.Chat += input;
|
this.Chat += input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (info.Channel != null) {
|
||||||
|
var prevTemp = this._tempChannel;
|
||||||
|
|
||||||
|
if (info.Permanent) {
|
||||||
|
this.Ui.Plugin.Functions.Chat.SetChannel(info.Channel.Value);
|
||||||
|
} else {
|
||||||
|
this._tempChannel = info.Channel.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Channel is InputChannel.Tell) {
|
||||||
|
if (info.Rotate != RotateMode.None) {
|
||||||
|
this._tellIdx = prevTemp != InputChannel.Tell
|
||||||
|
? 0
|
||||||
|
: info.Rotate == RotateMode.Reverse
|
||||||
|
? -1
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
var tellInfo = this.Ui.Plugin.Functions.Chat.GetTellHistoryInfo(this._tellIdx);
|
||||||
|
if (tellInfo != null && reason != null) {
|
||||||
|
this._tellTarget = new TellTarget(tellInfo.Name, (ushort) tellInfo.World, tellInfo.ContentId, reason.Value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._tellIdx = 0;
|
||||||
|
this._tellTarget = null;
|
||||||
|
|
||||||
|
if (target != null) {
|
||||||
|
this._tellTarget = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._tellIdx = 0;
|
||||||
|
this._tellTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Text != null && this.Chat.Length == 0) {
|
||||||
|
this.Chat = info.Text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearLog(string command, string arguments) {
|
private void ClearLog(string command, string arguments) {
|
||||||
@ -130,6 +176,56 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
|
|
||||||
private unsafe ImGuiViewport* _lastViewport;
|
private unsafe ImGuiViewport* _lastViewport;
|
||||||
|
|
||||||
|
private void HandleKeybinds() {
|
||||||
|
var modifierState = (ModifierFlag) 0;
|
||||||
|
if (ImGui.GetIO().KeyAlt) {
|
||||||
|
modifierState |= ModifierFlag.Alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.GetIO().KeyCtrl) {
|
||||||
|
modifierState |= ModifierFlag.Ctrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.GetIO().KeyShift) {
|
||||||
|
modifierState |= ModifierFlag.Shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
|
||||||
|
foreach (var (toIntercept, keybind) in this.Ui.Plugin.Functions.Chat.Keybinds) {
|
||||||
|
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Intercept(VirtualKey key, ModifierFlag modifier) {
|
||||||
|
if (!ImGui.IsKeyPressed((int) key) || !modifierState.HasFlag(modifier)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bits = NumUtil.NumberOfSetBits((uint) modifier);
|
||||||
|
if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits) {
|
||||||
|
turnedOff[key] = (bits, toIntercept);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Intercept(keybind.Key1, keybind.Modifier1);
|
||||||
|
Intercept(keybind.Key2, keybind.Modifier2);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (_, (_, keybind)) in turnedOff) {
|
||||||
|
PluginLog.Log($"intercepting {keybind}");
|
||||||
|
if (!GameFunctions.Chat.KeybindsToIntercept.TryGetValue(keybind, out var info)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TellReason? reason = info.Channel == InputChannel.Tell ? TellReason.Reply : null;
|
||||||
|
this.Activated(null, info, reason, null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Error in chat Activated event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void Draw() {
|
public unsafe void Draw() {
|
||||||
var flags = ImGuiWindowFlags.None;
|
var flags = ImGuiWindowFlags.None;
|
||||||
if (!this.Ui.Plugin.Config.CanMove) {
|
if (!this.Ui.Plugin.Config.CanMove) {
|
||||||
@ -171,7 +267,23 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
try {
|
try {
|
||||||
if (activeTab is { Channel: { } channel }) {
|
if (this._tempChannel != null) {
|
||||||
|
if (this._tellTarget != null) {
|
||||||
|
var world = this.Ui.Plugin.DataManager.GetExcelSheet<World>()
|
||||||
|
?.GetRow(this._tellTarget.World)
|
||||||
|
?.Name
|
||||||
|
?.RawString ?? "???";
|
||||||
|
|
||||||
|
this.DrawChunks(new List<Chunk> {
|
||||||
|
new TextChunk(null, null, "Tell "),
|
||||||
|
new TextChunk(null, null, this._tellTarget.Name),
|
||||||
|
new IconChunk(null, null, BitmapFontIcon.CrossWorld),
|
||||||
|
new TextChunk(null, null, world),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ImGui.TextUnformatted(this._tempChannel.Value.ToChatType().Name());
|
||||||
|
}
|
||||||
|
} else if (activeTab is { Channel: { } channel }) {
|
||||||
ImGui.TextUnformatted(channel.ToChatType().Name());
|
ImGui.TextUnformatted(channel.ToChatType().Name());
|
||||||
} else {
|
} else {
|
||||||
this.DrawChunks(this.Ui.Plugin.Functions.Chat.Channel.name);
|
this.DrawChunks(this.Ui.Plugin.Functions.Chat.Channel.name);
|
||||||
@ -213,7 +325,7 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
var buttonWidth = afterIcon.X - beforeIcon.X;
|
var buttonWidth = afterIcon.X - beforeIcon.X;
|
||||||
var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth;
|
var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth;
|
||||||
|
|
||||||
var inputType = activeTab?.Channel?.ToChatType() ?? this.Ui.Plugin.Functions.Chat.Channel.channel.ToChatType();
|
var inputType = this._tempChannel?.ToChatType() ?? activeTab?.Channel?.ToChatType() ?? this.Ui.Plugin.Functions.Chat.Channel.channel.ToChatType();
|
||||||
if (this.Chat.Trim().StartsWith('/')) {
|
if (this.Chat.Trim().StartsWith('/')) {
|
||||||
var command = this.Chat.Split(' ')[0];
|
var command = this.Chat.Split(' ')[0];
|
||||||
if (this.TextCommandChannels.TryGetValue(command, out var channel)) {
|
if (this.TextCommandChannels.TryGetValue(command, out var channel)) {
|
||||||
@ -239,16 +351,43 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
this.AddBacklog(trimmed);
|
this.AddBacklog(trimmed);
|
||||||
this._inputBacklogIdx = -1;
|
this._inputBacklogIdx = -1;
|
||||||
|
|
||||||
if (activeTab is { Channel: { } channel } && !trimmed.StartsWith('/')) {
|
if (this._tellTarget != null) {
|
||||||
|
var target = this._tellTarget;
|
||||||
|
var reason = target.Reason;
|
||||||
|
var world = this.Ui.Plugin.DataManager.GetExcelSheet<World>()?.GetRow(target.World);
|
||||||
|
if (world is { IsPublic: true }) {
|
||||||
|
if (reason == TellReason.Reply && this.Ui.Plugin.Common.Functions.FriendList.List.Any(friend => friend.ContentId == target.ContentId)) {
|
||||||
|
reason = TellReason.Friend;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Ui.Plugin.Functions.Chat.SendTell(reason, target.ContentId, target.Name, (ushort) world.RowId, trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
goto Skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._tempChannel != null) {
|
||||||
|
trimmed = $"{this._tempChannel.Value.Prefix()} {trimmed}";
|
||||||
|
} else if (activeTab is { Channel: { } channel } && !trimmed.StartsWith('/')) {
|
||||||
trimmed = $"{channel.Prefix()} {trimmed}";
|
trimmed = $"{channel.Prefix()} {trimmed}";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Ui.Plugin.Common.Functions.Chat.SendMessage(trimmed);
|
this.Ui.Plugin.Common.Functions.Chat.SendMessage(trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Skip:
|
||||||
this.Chat = string.Empty;
|
this.Chat = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemActive()) {
|
||||||
|
this.HandleKeybinds();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.Activate && !ImGui.IsItemActive()) {
|
||||||
|
this._tempChannel = null;
|
||||||
|
this._tellIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (inputColour != null) {
|
if (inputColour != null) {
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
}
|
}
|
||||||
|
5
ChatTwo/Util/DataUtil.cs
Executable file
5
ChatTwo/Util/DataUtil.cs
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
namespace ChatTwo.Util;
|
||||||
|
|
||||||
|
public class DataUtil {
|
||||||
|
|
||||||
|
}
|
9
ChatTwo/Util/NumUtil.cs
Executable file
9
ChatTwo/Util/NumUtil.cs
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
namespace ChatTwo.Util;
|
||||||
|
|
||||||
|
internal static class NumUtil {
|
||||||
|
internal static uint NumberOfSetBits(uint i) {
|
||||||
|
i -= (i >> 1) & 0x55555555;
|
||||||
|
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
|
||||||
|
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user