Compare commits
No commits in common. "0f790fa319affcfb82f9e2d4628ca3ff9e63b0e5" and "cd85e579401ac444534fd758cc1405a27c836df0" have entirely different histories.
0f790fa319
...
cd85e57940
@ -4,7 +4,6 @@ using Dalamud.Game.Text.SeStringHandling;
|
|||||||
namespace ChatTwo;
|
namespace ChatTwo;
|
||||||
|
|
||||||
internal abstract class Chunk {
|
internal abstract class Chunk {
|
||||||
internal Message? Message { get; set; }
|
|
||||||
internal SeString? Source { get; set; }
|
internal SeString? Source { get; set; }
|
||||||
internal Payload? Link { get; set; }
|
internal Payload? Link { get; set; }
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ using ChatTwo.Util;
|
|||||||
namespace ChatTwo.Code;
|
namespace ChatTwo.Code;
|
||||||
|
|
||||||
internal static class ChatTypeExt {
|
internal static class ChatTypeExt {
|
||||||
internal static string Name(this ChatType type) {
|
internal static string? Name(this ChatType type) {
|
||||||
return type switch {
|
return type switch {
|
||||||
ChatType.Debug => "Debug",
|
ChatType.Debug => "Debug",
|
||||||
ChatType.Urgent => "Urgent",
|
ChatType.Urgent => "Urgent",
|
||||||
@ -206,33 +206,4 @@ internal static class ChatTypeExt {
|
|||||||
return null;
|
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
523
ChatTwo/GameFunctions.cs
Executable file
523
ChatTwo/GameFunctions.cs
Executable file
@ -0,0 +1,523 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using ChatTwo.Code;
|
||||||
|
using ChatTwo.Util;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Siggingway;
|
||||||
|
|
||||||
|
namespace ChatTwo;
|
||||||
|
|
||||||
|
internal unsafe class GameFunctions : IDisposable {
|
||||||
|
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 ChangeChannelName = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6";
|
||||||
|
internal const string IsMentorA1 = "48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 74 71 0F B6 86";
|
||||||
|
internal const string ResolveTextCommandPlaceholder = "E8 ?? ?? ?? ?? 49 8D 4F 18 4C 8B E0";
|
||||||
|
|
||||||
|
internal const string CurrentChatEntryOffset = "8B 77 ?? 8D 46 01 89 47 14 81 FE ?? ?? ?? ?? 72 03 FF 47";
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 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!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 8B FD 8B CD", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<IntPtr, uint, IntPtr> _indexer = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 33 C0 EB 51", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<IntPtr, ulong, byte*, ushort, void> _inviteToParty = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<IntPtr, ulong, ushort, byte*, byte> _inviteToNoviceNetwork = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? EB 35 BA", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<uint, uint, ulong, uint, byte, byte> _tryOn = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? EB 7B 49 8B 06", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<AgentInterface*, uint, void> _linkItem = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? EB 3F 83 F8 FE", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<AgentInterface*, ushort, uint, byte, void> _itemComparison = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 41 B4 01", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<IntPtr, uint, void> _searchForRecipesUsingItem = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? EB 45 45 33 C9", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<void*, uint, byte, void> _searchForItem = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 84 C0 74 0D B0 02", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<IntPtr, byte> _isMentor = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 49 8B 56 20", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<AgentInterface*, byte*, ushort, ulong, void> _promote = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? EB 66 49 8B 4E 20", Fallibility = Fallibility.Fallible)]
|
||||||
|
private readonly delegate* unmanaged<AgentInterface*, byte*, ushort, ulong, void> _kick = null!;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Hooks
|
||||||
|
|
||||||
|
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
|
||||||
|
|
||||||
|
private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent);
|
||||||
|
|
||||||
|
private delegate IntPtr ResolveTextCommandPlaceholderDelegate(IntPtr a1, byte* placeholderText, byte a3, byte a4);
|
||||||
|
|
||||||
|
[Signature(Signatures.ChatLogRefresh, DetourName = nameof(ChatLogRefreshDetour))]
|
||||||
|
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; init; }
|
||||||
|
|
||||||
|
[Signature(Signatures.ChangeChannelName, DetourName = nameof(ChangeChannelNameDetour))]
|
||||||
|
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; init; }
|
||||||
|
|
||||||
|
[Signature(Signatures.ResolveTextCommandPlaceholder, DetourName = nameof(ResolveTextCommandPlaceholderDetour))]
|
||||||
|
private Hook<ResolveTextCommandPlaceholderDelegate>? ResolveTextCommandPlaceholderHook { get; init; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#pragma warning disable 0649
|
||||||
|
|
||||||
|
[Signature(Signatures.CurrentChatEntryOffset, Offset = 2)]
|
||||||
|
private readonly byte? _currentChatEntryOffset;
|
||||||
|
|
||||||
|
[Signature(Signatures.IsMentorA1, ScanType = ScanType.StaticAddress)]
|
||||||
|
private readonly IntPtr? _isMentorA1;
|
||||||
|
|
||||||
|
#pragma warning restore 0649
|
||||||
|
|
||||||
|
internal const int HqItemOffset = 1_000_000;
|
||||||
|
|
||||||
|
private Plugin Plugin { get; }
|
||||||
|
|
||||||
|
internal delegate void ChatActivatedEventDelegate(string? input);
|
||||||
|
|
||||||
|
internal event ChatActivatedEventDelegate? ChatActivated;
|
||||||
|
|
||||||
|
internal (InputChannel channel, List<Chunk> name) ChatChannel { get; private set; }
|
||||||
|
|
||||||
|
internal GameFunctions(Plugin plugin) {
|
||||||
|
this.Plugin = plugin;
|
||||||
|
|
||||||
|
Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this);
|
||||||
|
|
||||||
|
this.ChatLogRefreshHook?.Enable();
|
||||||
|
this.ChangeChannelNameHook?.Enable();
|
||||||
|
this.ResolveTextCommandPlaceholderHook?.Enable();
|
||||||
|
|
||||||
|
this.Plugin.ClientState.Login += this.Login;
|
||||||
|
this.Login(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
this.Plugin.ClientState.Login -= this.Login;
|
||||||
|
this.ResolveTextCommandPlaceholderHook?.Dispose();
|
||||||
|
this.ChangeChannelNameHook?.Dispose();
|
||||||
|
this.ChatLogRefreshHook?.Dispose();
|
||||||
|
this.ChatActivated = null;
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(this._placeholderNamePtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Login(object? sender, EventArgs? e) {
|
||||||
|
if (this.ChangeChannelNameHook == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
|
||||||
|
if (agent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ChangeChannelNameDetour((IntPtr) agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal uint? GetCurrentChatLogEntryIndex() {
|
||||||
|
if (this._currentChatEntryOffset == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureLogModule();
|
||||||
|
return *(uint*) (log + this._currentChatEntryOffset.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ulong? GetContentIdForChatLogEntry(uint index) {
|
||||||
|
if (this._getContentIdForChatEntry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InviteToParty(string name, ushort world) {
|
||||||
|
if (this._inviteToParty == null || this._indexer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uiModule = Framework.Instance()->GetUiModule();
|
||||||
|
// 6.05: 20D722
|
||||||
|
var func = (delegate*<UIModule*, IntPtr>) uiModule->vfunc[33];
|
||||||
|
var toIndex = func(uiModule);
|
||||||
|
var a1 = this._indexer(toIndex, 1);
|
||||||
|
|
||||||
|
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
||||||
|
// can specify content id if we have it, but there's no need
|
||||||
|
this._inviteToParty(a1, 0, namePtr, world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SendFriendRequest(string name, ushort world) {
|
||||||
|
var row = this.Plugin.DataManager.GetExcelSheet<World>()!.GetRow(world);
|
||||||
|
if (row == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var worldName = row.Name.RawString;
|
||||||
|
this._replacementName = $"{name}@{worldName}";
|
||||||
|
this.Plugin.Common.Functions.Chat.SendMessage($"/friendlist add {this._placeholder}");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InviteToNoviceNetwork(string name, ushort world) {
|
||||||
|
if (this._inviteToNoviceNetwork == null || this._indexer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uiModule = Framework.Instance()->GetUiModule();
|
||||||
|
// 6.05: 20D722
|
||||||
|
var func = (delegate*<UIModule*, IntPtr>) uiModule->vfunc[33];
|
||||||
|
var toIndex = func(uiModule);
|
||||||
|
// 6.05: 20E4CB
|
||||||
|
var a1 = this._indexer(toIndex, 0x11);
|
||||||
|
|
||||||
|
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
||||||
|
// can specify content id if we have it, but there's no need
|
||||||
|
this._inviteToNoviceNetwork(a1, 0, world, namePtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager;
|
||||||
|
|
||||||
|
var addon = (IntPtr) unitManager->GetAddonByName(name);
|
||||||
|
if (addon == IntPtr.Zero) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = (uint*) (addon + 0x180);
|
||||||
|
if (interactable) {
|
||||||
|
*flags &= ~(1u << 22);
|
||||||
|
} else {
|
||||||
|
*flags |= 1 << 22;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void SetChatInteractable(bool interactable) {
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
SetAddonInteractable($"ChatLogPanel_{i}", interactable);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAddonInteractable("ChatLog", interactable);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsAddonInteractable(string name) {
|
||||||
|
var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager;
|
||||||
|
|
||||||
|
var addon = (IntPtr) unitManager->GetAddonByName(name);
|
||||||
|
if (addon == IntPtr.Zero) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = (uint*) (addon + 0x180);
|
||||||
|
return (*flags & (1 << 22)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OpenItemTooltip(uint id) {
|
||||||
|
var atkStage = AtkStage.GetSingleton();
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail);
|
||||||
|
var addon = atkStage->RaptureAtkUnitManager->GetAddonByName("ItemDetail");
|
||||||
|
if (agent == null || addon == null) {
|
||||||
|
// atkStage ain't gonna be null or we have bigger problems
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agentPtr = (IntPtr) agent;
|
||||||
|
|
||||||
|
// addresses mentioned here are 6.01
|
||||||
|
// see the call near the end of AgentItemDetail.Update
|
||||||
|
// offsets valid as of 6.01
|
||||||
|
|
||||||
|
// 8BFC49: sets some shit
|
||||||
|
*(uint*) (agentPtr + 0x20) = 22;
|
||||||
|
// 8C04C8: switch goes down to default, which is what we want
|
||||||
|
*(byte*) (agentPtr + 0x118) = 1;
|
||||||
|
// 8BFCF6: item id when hovering over item in chat
|
||||||
|
*(uint*) (agentPtr + 0x11C) = id;
|
||||||
|
// 8BFCE4: always 0 when hovering over item in chat
|
||||||
|
*(uint*) (agentPtr + 0x120) = 0;
|
||||||
|
// 8C0B55: skips a check to do with inventory
|
||||||
|
*(byte*) (agentPtr + 0x128) &= 0xEF;
|
||||||
|
// 8BFC7C: when set to 1, lets everything continue (one frame)
|
||||||
|
*(byte*) (agentPtr + 0x146) = 1;
|
||||||
|
// 8BFC89: skips early return
|
||||||
|
*(byte*) (agentPtr + 0x14A) = 0;
|
||||||
|
|
||||||
|
// this just probably needs to be set
|
||||||
|
agent->AddonId = (uint) addon->ID;
|
||||||
|
|
||||||
|
// vcall from E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60
|
||||||
|
var vf5 = (delegate*<AtkUnitBase*, byte, uint, void>*) ((IntPtr) addon->VTable + 40);
|
||||||
|
// E8872D: lets vf5 actually run
|
||||||
|
*(byte*) ((IntPtr) atkStage + 0x2B4) |= 2;
|
||||||
|
(*vf5)(addon, 0, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void CloseItemTooltip() {
|
||||||
|
// hide addon first to prevent the "addon close" sound
|
||||||
|
var addon = AtkStage.GetSingleton()->RaptureAtkUnitManager->GetAddonByName("ItemDetail");
|
||||||
|
if (addon != null) {
|
||||||
|
addon->Hide(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail);
|
||||||
|
if (agent != null) {
|
||||||
|
agent->Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.ChatActivated?.Invoke(eventInput);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
PluginLog.LogError(ex, "Error in ChatActivated event");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ChatLogRefreshHook!.Original(log, eventId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr ChangeChannelNameDetour(IntPtr agent) {
|
||||||
|
// Last ShB patch
|
||||||
|
// +0x40 = chat channel (byte or uint?)
|
||||||
|
// channel is 17 (maybe 18?) for tells
|
||||||
|
// +0x48 = pointer to channel name string
|
||||||
|
var ret = this.ChangeChannelNameHook!.Original(agent);
|
||||||
|
if (agent == IntPtr.Zero) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// E8 ?? ?? ?? ?? 8D 48 F7
|
||||||
|
// RaptureShellModule + 0xFD0
|
||||||
|
var shellModule = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule();
|
||||||
|
if (shellModule == IntPtr.Zero) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var channel = *(uint*) (shellModule + 0xFD0);
|
||||||
|
|
||||||
|
// var channel = *(uint*) (agent + 0x40);
|
||||||
|
if (channel is 17 or 18) {
|
||||||
|
channel = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SeString? name = null;
|
||||||
|
var namePtrPtr = (byte**) (agent + 0x48);
|
||||||
|
if (namePtrPtr != null) {
|
||||||
|
var namePtr = *namePtrPtr;
|
||||||
|
name = MemoryHelper.ReadSeStringNullTerminated((IntPtr) namePtr);
|
||||||
|
if (name.Payloads.Count == 0) {
|
||||||
|
name = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These context menu things come from AgentChatLog.vf0 at the bottom
|
||||||
|
// 0x10000: item comparison
|
||||||
|
// 0x10001: try on
|
||||||
|
// 0x10002: search for item
|
||||||
|
// 0x10003: link
|
||||||
|
// 0x10005: copy item name
|
||||||
|
// 0x10006: search recipes using this material
|
||||||
|
|
||||||
|
internal void TryOn(uint itemId, byte stainId) {
|
||||||
|
if (this._tryOn == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tryOn(0xFF, itemId, stainId, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LinkItem(uint itemId) {
|
||||||
|
if (this._linkItem == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
|
||||||
|
this._linkItem(agent, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OpenItemComparison(uint itemId) {
|
||||||
|
if (this._itemComparison == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemCompare);
|
||||||
|
this._itemComparison(agent, 0x4D, itemId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SearchForRecipesUsingItem(uint itemId) {
|
||||||
|
if (this._searchForRecipesUsingItem == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uiModule = Framework.Instance()->GetUiModule();
|
||||||
|
var vf35 = (delegate* unmanaged<UIModule*, IntPtr>) uiModule->vfunc[35];
|
||||||
|
var a1 = vf35(uiModule);
|
||||||
|
this._searchForRecipesUsingItem(a1, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SearchForItem(uint itemId) {
|
||||||
|
if (this._searchForItem == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemFinder = Framework.Instance()->GetUiModule()->GetItemFinderModule();
|
||||||
|
this._searchForItem(itemFinder, itemId, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void OpenPartyFinder() {
|
||||||
|
// this whole method: 6.05: 84433A
|
||||||
|
var lfg = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.LookingForGroup);
|
||||||
|
if (lfg->IsAgentActive()) {
|
||||||
|
var addonId = lfg->GetAddonID();
|
||||||
|
var atkModule = Framework.Instance()->GetUiModule()->GetRaptureAtkModule();
|
||||||
|
var atkModuleVtbl = (void**) atkModule->AtkModule.vtbl;
|
||||||
|
var vf27 = (delegate* unmanaged<RaptureAtkModule*, ulong, ulong, byte>) atkModuleVtbl[27];
|
||||||
|
vf27(atkModule, addonId, 1);
|
||||||
|
} else {
|
||||||
|
// 6.05: 8443DD
|
||||||
|
if (*(uint*) ((IntPtr) lfg + 0x2AB8) > 0) {
|
||||||
|
lfg->Hide();
|
||||||
|
} else {
|
||||||
|
lfg->Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsMentor() {
|
||||||
|
if (this._isMentor == null || this._isMentorA1 == null || this._isMentorA1.Value == IntPtr.Zero) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._isMentor(this._isMentorA1.Value) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void KickFromParty(string name, ulong contentId) {
|
||||||
|
if (this._kick == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember);
|
||||||
|
if (agent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
||||||
|
this._kick(agent, namePtr, 0, contentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Promote(string name, ulong contentId) {
|
||||||
|
if (this._promote == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember);
|
||||||
|
if (agent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
||||||
|
this._promote(agent, namePtr, 0, contentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IntPtr _placeholderNamePtr = Marshal.AllocHGlobal(128);
|
||||||
|
private readonly string _placeholder = $"<{Guid.NewGuid():N}>";
|
||||||
|
private string? _replacementName;
|
||||||
|
|
||||||
|
private IntPtr ResolveTextCommandPlaceholderDetour(IntPtr a1, byte* placeholderText, byte a3, byte a4) {
|
||||||
|
if (this._replacementName == null) {
|
||||||
|
goto Original;
|
||||||
|
}
|
||||||
|
|
||||||
|
var placeholder = MemoryHelper.ReadStringNullTerminated((IntPtr) placeholderText);
|
||||||
|
if (placeholder != this._placeholder) {
|
||||||
|
goto Original;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryHelper.WriteString(this._placeholderNamePtr, this._replacementName);
|
||||||
|
this._replacementName = null;
|
||||||
|
|
||||||
|
return this._placeholderNamePtr;
|
||||||
|
|
||||||
|
Original:
|
||||||
|
return this.ResolveTextCommandPlaceholderHook!.Original(a1, placeholderText, a3, a4);
|
||||||
|
}
|
||||||
|
}
|
@ -1,219 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using ChatTwo.Code;
|
|
||||||
using ChatTwo.Util;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Memory;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
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)
|
|
||||||
)]
|
|
||||||
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; init; }
|
|
||||||
|
|
||||||
[Signature(
|
|
||||||
"E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6",
|
|
||||||
DetourName = nameof(ChangeChannelNameDetour)
|
|
||||||
)]
|
|
||||||
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;
|
|
||||||
|
|
||||||
private Plugin Plugin { get; }
|
|
||||||
internal (InputChannel channel, List<Chunk> name) Channel { get; private set; }
|
|
||||||
|
|
||||||
internal Chat(Plugin plugin) {
|
|
||||||
this.Plugin = plugin;
|
|
||||||
Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this);
|
|
||||||
|
|
||||||
this.ChatLogRefreshHook?.Enable();
|
|
||||||
this.ChangeChannelNameHook?.Enable();
|
|
||||||
this.ReplyInSelectedChatModeHook?.Enable();
|
|
||||||
|
|
||||||
this.Plugin.ClientState.Login += this.Login;
|
|
||||||
this.Login(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.Plugin.ClientState.Login -= this.Login;
|
|
||||||
|
|
||||||
this.ReplyInSelectedChatModeHook?.Dispose();
|
|
||||||
this.ChangeChannelNameHook?.Dispose();
|
|
||||||
this.ChatLogRefreshHook?.Dispose();
|
|
||||||
|
|
||||||
this.Activated = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Login(object? sender, EventArgs? e) {
|
|
||||||
if (this.ChangeChannelNameHook == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
|
|
||||||
if (agent == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ChangeChannelNameDetour((IntPtr) agent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntPtr ChangeChannelNameDetour(IntPtr agent) {
|
|
||||||
// Last ShB patch
|
|
||||||
// +0x40 = chat channel (byte or uint?)
|
|
||||||
// channel is 17 (maybe 18?) for tells
|
|
||||||
// +0x48 = pointer to channel name string
|
|
||||||
var ret = this.ChangeChannelNameHook!.Original(agent);
|
|
||||||
if (agent == IntPtr.Zero) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// E8 ?? ?? ?? ?? 8D 48 F7
|
|
||||||
// RaptureShellModule + 0xFD0
|
|
||||||
var shellModule = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule();
|
|
||||||
if (shellModule == IntPtr.Zero) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var channel = *(uint*) (shellModule + 0xFD0);
|
|
||||||
|
|
||||||
// var channel = *(uint*) (agent + 0x40);
|
|
||||||
if (channel is 17 or 18) {
|
|
||||||
channel = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SeString? name = null;
|
|
||||||
var namePtrPtr = (byte**) (agent + 0x48);
|
|
||||||
if (namePtrPtr != null) {
|
|
||||||
var namePtr = *namePtrPtr;
|
|
||||||
name = MemoryHelper.ReadSeStringNullTerminated((IntPtr) namePtr);
|
|
||||||
if (name.Payloads.Count == 0) {
|
|
||||||
name = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name == null) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nameChunks = ChunkUtil.ToChunks(name, null).ToList();
|
|
||||||
if (nameChunks.Count > 0 && nameChunks[0] is TextChunk text) {
|
|
||||||
text.Content = text.Content.TrimStart('\uE01E').TrimStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Channel = ((InputChannel) channel, nameChunks);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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) {
|
|
||||||
if (this._getContentIdForChatEntry == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._getContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SetChannel(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
using ChatTwo.Util;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using Siggingway;
|
|
||||||
|
|
||||||
namespace ChatTwo.GameFunctions;
|
|
||||||
|
|
||||||
internal sealed unsafe class Context {
|
|
||||||
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<IntPtr, ulong, ushort, byte*, byte> _inviteToNoviceNetwork = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? EB 35 BA", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<uint, uint, ulong, uint, byte, byte> _tryOn = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? EB 7B 49 8B 06", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<AgentInterface*, uint, void> _linkItem = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? EB 3F 83 F8 FE", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<AgentInterface*, ushort, uint, byte, void> _itemComparison = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 41 B4 01", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<IntPtr, uint, void> _searchForRecipesUsingItem = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? EB 45 45 33 C9", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<void*, uint, byte, void> _searchForItem = null!;
|
|
||||||
|
|
||||||
private Plugin Plugin { get; }
|
|
||||||
|
|
||||||
internal Context(Plugin plugin) {
|
|
||||||
this.Plugin = plugin;
|
|
||||||
Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void InviteToNoviceNetwork(string name, ushort world) {
|
|
||||||
if (this._inviteToNoviceNetwork == null || this.Plugin.Functions.Indexer == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var uiModule = Framework.Instance()->GetUiModule();
|
|
||||||
// 6.05: 20D722
|
|
||||||
var func = (delegate*<UIModule*, IntPtr>) uiModule->vfunc[33];
|
|
||||||
var toIndex = func(uiModule);
|
|
||||||
// 6.05: 20E4CB
|
|
||||||
var a1 = this.Plugin.Functions.Indexer(toIndex, 0x11);
|
|
||||||
|
|
||||||
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
|
||||||
// can specify content id if we have it, but there's no need
|
|
||||||
this._inviteToNoviceNetwork(a1, 0, world, namePtr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These context menu things come from AgentChatLog.vf0 at the bottom
|
|
||||||
// 0x10000: item comparison
|
|
||||||
// 0x10001: try on
|
|
||||||
// 0x10002: search for item
|
|
||||||
// 0x10003: link
|
|
||||||
// 0x10005: copy item name
|
|
||||||
// 0x10006: search recipes using this material
|
|
||||||
|
|
||||||
internal void TryOn(uint itemId, byte stainId) {
|
|
||||||
if (this._tryOn == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._tryOn(0xFF, itemId, stainId, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void LinkItem(uint itemId) {
|
|
||||||
if (this._linkItem == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
|
|
||||||
this._linkItem(agent, itemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void OpenItemComparison(uint itemId) {
|
|
||||||
if (this._itemComparison == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemCompare);
|
|
||||||
this._itemComparison(agent, 0x4D, itemId, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SearchForRecipesUsingItem(uint itemId) {
|
|
||||||
if (this._searchForRecipesUsingItem == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var uiModule = Framework.Instance()->GetUiModule();
|
|
||||||
var vf35 = (delegate* unmanaged<UIModule*, IntPtr>) uiModule->vfunc[35];
|
|
||||||
var a1 = vf35(uiModule);
|
|
||||||
this._searchForRecipesUsingItem(a1, itemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SearchForItem(uint itemId) {
|
|
||||||
if (this._searchForItem == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemFinder = Framework.Instance()->GetUiModule()->GetItemFinderModule();
|
|
||||||
this._searchForItem(itemFinder, itemId, 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,234 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Hooking;
|
|
||||||
using Dalamud.Memory;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Siggingway;
|
|
||||||
|
|
||||||
namespace ChatTwo.GameFunctions;
|
|
||||||
|
|
||||||
internal unsafe class GameFunctions : IDisposable {
|
|
||||||
private static class Signatures {
|
|
||||||
internal const string IsMentorA1 = "48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 74 71 0F B6 86";
|
|
||||||
internal const string ResolveTextCommandPlaceholder = "E8 ?? ?? ?? ?? 49 8D 4F 18 4C 8B E0";
|
|
||||||
|
|
||||||
internal const string CurrentChatEntryOffset = "8B 77 ?? 8D 46 01 89 47 14 81 FE ?? ?? ?? ?? 72 03 FF 47";
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Functions
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 8B FD 8B CD", Fallibility = Fallibility.Fallible)]
|
|
||||||
internal readonly delegate* unmanaged<IntPtr, uint, IntPtr> Indexer = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 84 C0 74 0D B0 02", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<IntPtr, byte> _isMentor = null!;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Hooks
|
|
||||||
|
|
||||||
private delegate IntPtr ResolveTextCommandPlaceholderDelegate(IntPtr a1, byte* placeholderText, byte a3, byte a4);
|
|
||||||
|
|
||||||
[Signature(Signatures.ResolveTextCommandPlaceholder, DetourName = nameof(ResolveTextCommandPlaceholderDetour))]
|
|
||||||
private Hook<ResolveTextCommandPlaceholderDelegate>? ResolveTextCommandPlaceholderHook { get; init; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#pragma warning disable 0649
|
|
||||||
|
|
||||||
[Signature(Signatures.CurrentChatEntryOffset, Offset = 2)]
|
|
||||||
private readonly byte? _currentChatEntryOffset;
|
|
||||||
|
|
||||||
[Signature(Signatures.IsMentorA1, ScanType = ScanType.StaticAddress)]
|
|
||||||
private readonly IntPtr? _isMentorA1;
|
|
||||||
|
|
||||||
#pragma warning restore 0649
|
|
||||||
|
|
||||||
internal const int HqItemOffset = 1_000_000;
|
|
||||||
|
|
||||||
private Plugin Plugin { get; }
|
|
||||||
internal Party Party { get; }
|
|
||||||
internal Chat Chat { get; }
|
|
||||||
internal Context Context { get; }
|
|
||||||
|
|
||||||
internal GameFunctions(Plugin plugin) {
|
|
||||||
this.Plugin = plugin;
|
|
||||||
this.Party = new Party(this.Plugin);
|
|
||||||
this.Chat = new Chat(this.Plugin);
|
|
||||||
this.Context = new Context(this.Plugin);
|
|
||||||
|
|
||||||
Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this);
|
|
||||||
|
|
||||||
this.ResolveTextCommandPlaceholderHook?.Enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() {
|
|
||||||
this.Chat.Dispose();
|
|
||||||
|
|
||||||
this.ResolveTextCommandPlaceholderHook?.Dispose();
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(this._placeholderNamePtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal uint? GetCurrentChatLogEntryIndex() {
|
|
||||||
if (this._currentChatEntryOffset == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var log = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureLogModule();
|
|
||||||
return *(uint*) (log + this._currentChatEntryOffset.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void SendFriendRequest(string name, ushort world) {
|
|
||||||
var row = this.Plugin.DataManager.GetExcelSheet<World>()!.GetRow(world);
|
|
||||||
if (row == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var worldName = row.Name.RawString;
|
|
||||||
this._replacementName = $"{name}@{worldName}";
|
|
||||||
this.Plugin.Common.Functions.Chat.SendMessage($"/friendlist add {this._placeholder}");
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void SetAddonInteractable(string name, bool interactable) {
|
|
||||||
var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager;
|
|
||||||
|
|
||||||
var addon = (IntPtr) unitManager->GetAddonByName(name);
|
|
||||||
if (addon == IntPtr.Zero) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags = (uint*) (addon + 0x180);
|
|
||||||
if (interactable) {
|
|
||||||
*flags &= ~(1u << 22);
|
|
||||||
} else {
|
|
||||||
*flags |= 1 << 22;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void SetChatInteractable(bool interactable) {
|
|
||||||
for (var i = 0; i < 4; i++) {
|
|
||||||
SetAddonInteractable($"ChatLogPanel_{i}", interactable);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetAddonInteractable("ChatLog", interactable);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool IsAddonInteractable(string name) {
|
|
||||||
var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager;
|
|
||||||
|
|
||||||
var addon = (IntPtr) unitManager->GetAddonByName(name);
|
|
||||||
if (addon == IntPtr.Zero) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags = (uint*) (addon + 0x180);
|
|
||||||
return (*flags & (1 << 22)) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void OpenItemTooltip(uint id) {
|
|
||||||
var atkStage = AtkStage.GetSingleton();
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail);
|
|
||||||
var addon = atkStage->RaptureAtkUnitManager->GetAddonByName("ItemDetail");
|
|
||||||
if (agent == null || addon == null) {
|
|
||||||
// atkStage ain't gonna be null or we have bigger problems
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var agentPtr = (IntPtr) agent;
|
|
||||||
|
|
||||||
// addresses mentioned here are 6.01
|
|
||||||
// see the call near the end of AgentItemDetail.Update
|
|
||||||
// offsets valid as of 6.01
|
|
||||||
|
|
||||||
// 8BFC49: sets some shit
|
|
||||||
*(uint*) (agentPtr + 0x20) = 22;
|
|
||||||
// 8C04C8: switch goes down to default, which is what we want
|
|
||||||
*(byte*) (agentPtr + 0x118) = 1;
|
|
||||||
// 8BFCF6: item id when hovering over item in chat
|
|
||||||
*(uint*) (agentPtr + 0x11C) = id;
|
|
||||||
// 8BFCE4: always 0 when hovering over item in chat
|
|
||||||
*(uint*) (agentPtr + 0x120) = 0;
|
|
||||||
// 8C0B55: skips a check to do with inventory
|
|
||||||
*(byte*) (agentPtr + 0x128) &= 0xEF;
|
|
||||||
// 8BFC7C: when set to 1, lets everything continue (one frame)
|
|
||||||
*(byte*) (agentPtr + 0x146) = 1;
|
|
||||||
// 8BFC89: skips early return
|
|
||||||
*(byte*) (agentPtr + 0x14A) = 0;
|
|
||||||
|
|
||||||
// this just probably needs to be set
|
|
||||||
agent->AddonId = (uint) addon->ID;
|
|
||||||
|
|
||||||
// vcall from E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60
|
|
||||||
var vf5 = (delegate*<AtkUnitBase*, byte, uint, void>*) ((IntPtr) addon->VTable + 40);
|
|
||||||
// E8872D: lets vf5 actually run
|
|
||||||
*(byte*) ((IntPtr) atkStage + 0x2B4) |= 2;
|
|
||||||
(*vf5)(addon, 0, 15);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void CloseItemTooltip() {
|
|
||||||
// hide addon first to prevent the "addon close" sound
|
|
||||||
var addon = AtkStage.GetSingleton()->RaptureAtkUnitManager->GetAddonByName("ItemDetail");
|
|
||||||
if (addon != null) {
|
|
||||||
addon->Hide(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail);
|
|
||||||
if (agent != null) {
|
|
||||||
agent->Hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void OpenPartyFinder() {
|
|
||||||
// this whole method: 6.05: 84433A
|
|
||||||
var lfg = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.LookingForGroup);
|
|
||||||
if (lfg->IsAgentActive()) {
|
|
||||||
var addonId = lfg->GetAddonID();
|
|
||||||
var atkModule = Framework.Instance()->GetUiModule()->GetRaptureAtkModule();
|
|
||||||
var atkModuleVtbl = (void**) atkModule->AtkModule.vtbl;
|
|
||||||
var vf27 = (delegate* unmanaged<RaptureAtkModule*, ulong, ulong, byte>) atkModuleVtbl[27];
|
|
||||||
vf27(atkModule, addonId, 1);
|
|
||||||
} else {
|
|
||||||
// 6.05: 8443DD
|
|
||||||
if (*(uint*) ((IntPtr) lfg + 0x2AB8) > 0) {
|
|
||||||
lfg->Hide();
|
|
||||||
} else {
|
|
||||||
lfg->Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsMentor() {
|
|
||||||
if (this._isMentor == null || this._isMentorA1 == null || this._isMentorA1.Value == IntPtr.Zero) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._isMentor(this._isMentorA1.Value) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly IntPtr _placeholderNamePtr = Marshal.AllocHGlobal(128);
|
|
||||||
private readonly string _placeholder = $"<{Guid.NewGuid():N}>";
|
|
||||||
private string? _replacementName;
|
|
||||||
|
|
||||||
private IntPtr ResolveTextCommandPlaceholderDetour(IntPtr a1, byte* placeholderText, byte a3, byte a4) {
|
|
||||||
if (this._replacementName == null) {
|
|
||||||
goto Original;
|
|
||||||
}
|
|
||||||
|
|
||||||
var placeholder = MemoryHelper.ReadStringNullTerminated((IntPtr) placeholderText);
|
|
||||||
if (placeholder != this._placeholder) {
|
|
||||||
goto Original;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryHelper.WriteString(this._placeholderNamePtr, this._replacementName);
|
|
||||||
this._replacementName = null;
|
|
||||||
|
|
||||||
return this._placeholderNamePtr;
|
|
||||||
|
|
||||||
Original:
|
|
||||||
return this.ResolveTextCommandPlaceholderHook!.Original(a1, placeholderText, a3, a4);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
using ChatTwo.Util;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
||||||
using Siggingway;
|
|
||||||
|
|
||||||
namespace ChatTwo.GameFunctions;
|
|
||||||
|
|
||||||
internal sealed unsafe class Party {
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 33 C0 EB 51", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<IntPtr, ulong, byte*, ushort, void> _inviteToParty = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 49 8B 56 20", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<AgentInterface*, byte*, ushort, ulong, void> _promote = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? EB 66 49 8B 4E 20", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<AgentInterface*, byte*, ushort, ulong, void> _kick = null!;
|
|
||||||
|
|
||||||
private Plugin Plugin { get; }
|
|
||||||
|
|
||||||
internal Party(Plugin plugin) {
|
|
||||||
this.Plugin = plugin;
|
|
||||||
Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Invite(string name, ushort world) {
|
|
||||||
if (this._inviteToParty == null || this.Plugin.Functions.Indexer == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var uiModule = Framework.Instance()->GetUiModule();
|
|
||||||
// 6.05: 20D722
|
|
||||||
var func = (delegate*<UIModule*, IntPtr>) uiModule->vfunc[33];
|
|
||||||
var toIndex = func(uiModule);
|
|
||||||
var a1 = this.Plugin.Functions.Indexer(toIndex, 1);
|
|
||||||
|
|
||||||
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
|
||||||
// can specify content id if we have it, but there's no need
|
|
||||||
this._inviteToParty(a1, 0, namePtr, world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Kick(string name, ulong contentId) {
|
|
||||||
if (this._kick == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember);
|
|
||||||
if (agent == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
|
||||||
this._kick(agent, namePtr, 0, contentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Promote(string name, ulong contentId) {
|
|
||||||
if (this._promote == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember);
|
|
||||||
if (agent == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
|
||||||
this._promote(agent, namePtr, 0, contentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,9 +15,5 @@ internal class Message {
|
|||||||
this.Code = code;
|
this.Code = code;
|
||||||
this.Sender = sender;
|
this.Sender = sender;
|
||||||
this.Content = content;
|
this.Content = content;
|
||||||
|
|
||||||
foreach (var chunk in sender.Concat(content)) {
|
|
||||||
chunk.Message = this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using ChatTwo.Code;
|
|
||||||
using ChatTwo.Ui;
|
using ChatTwo.Ui;
|
||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
@ -17,7 +16,7 @@ internal sealed class PayloadHandler {
|
|||||||
private PluginUi Ui { get; }
|
private PluginUi Ui { get; }
|
||||||
private ChatLog Log { get; }
|
private ChatLog Log { get; }
|
||||||
|
|
||||||
private HashSet<(Chunk, Payload)> PopupPayloads { get; set; } = new();
|
private HashSet<Payload> PopupPayloads { get; set; } = new();
|
||||||
|
|
||||||
private bool _handleTooltips;
|
private bool _handleTooltips;
|
||||||
private uint _hoveredItem;
|
private uint _hoveredItem;
|
||||||
@ -33,7 +32,7 @@ internal sealed class PayloadHandler {
|
|||||||
this.DrawPopups();
|
this.DrawPopups();
|
||||||
|
|
||||||
if (this._handleTooltips && ++this._hoverCounter - this._lastHoverCounter > 1) {
|
if (this._handleTooltips && ++this._hoverCounter - this._lastHoverCounter > 1) {
|
||||||
GameFunctions.GameFunctions.CloseItemTooltip();
|
GameFunctions.CloseItemTooltip();
|
||||||
this._hoveredItem = 0;
|
this._hoveredItem = 0;
|
||||||
this._hoverCounter = this._lastHoverCounter = 0;
|
this._hoverCounter = this._lastHoverCounter = 0;
|
||||||
this._handleTooltips = false;
|
this._handleTooltips = false;
|
||||||
@ -41,8 +40,8 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPopups() {
|
private void DrawPopups() {
|
||||||
var newPopups = new HashSet<(Chunk, Payload)>();
|
var newPopups = new HashSet<Payload>();
|
||||||
foreach (var (chunk, payload) in this.PopupPayloads) {
|
foreach (var payload in this.PopupPayloads) {
|
||||||
var id = PopupId(payload);
|
var id = PopupId(payload);
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
continue;
|
continue;
|
||||||
@ -52,12 +51,12 @@ internal sealed class PayloadHandler {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
newPopups.Add((chunk, payload));
|
newPopups.Add(payload);
|
||||||
ImGui.PushID(id);
|
ImGui.PushID(id);
|
||||||
|
|
||||||
switch (payload) {
|
switch (payload) {
|
||||||
case PlayerPayload player: {
|
case PlayerPayload player: {
|
||||||
this.DrawPlayerPopup(chunk, player);
|
this.DrawPlayerPopup(player);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ItemPayload item: {
|
case ItemPayload item: {
|
||||||
@ -79,7 +78,7 @@ internal sealed class PayloadHandler {
|
|||||||
this.LeftClickPayload(chunk, payload);
|
this.LeftClickPayload(chunk, payload);
|
||||||
break;
|
break;
|
||||||
case ImGuiMouseButton.Right:
|
case ImGuiMouseButton.Right:
|
||||||
this.RightClickPayload(chunk, payload);
|
this.RightClickPayload(payload);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +91,7 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
case ItemPayload item: {
|
case ItemPayload item: {
|
||||||
if (this.Ui.Plugin.Config.NativeItemTooltips) {
|
if (this.Ui.Plugin.Config.NativeItemTooltips) {
|
||||||
GameFunctions.GameFunctions.OpenItemTooltip(item.ItemId);
|
GameFunctions.OpenItemTooltip(item.ItemId);
|
||||||
|
|
||||||
this._handleTooltips = true;
|
this._handleTooltips = true;
|
||||||
if (this._hoveredItem != item.ItemId) {
|
if (this._hoveredItem != item.ItemId) {
|
||||||
@ -178,7 +177,7 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
case RawPayload raw: {
|
case RawPayload raw: {
|
||||||
if (Equals(raw, ChunkUtil.PeriodicRecruitmentLink)) {
|
if (Equals(raw, ChunkUtil.PeriodicRecruitmentLink)) {
|
||||||
GameFunctions.GameFunctions.OpenPartyFinder();
|
GameFunctions.OpenPartyFinder();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -212,11 +211,11 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RightClickPayload(Chunk chunk, Payload payload) {
|
private void RightClickPayload(Payload payload) {
|
||||||
switch (payload) {
|
switch (payload) {
|
||||||
case PlayerPayload:
|
case PlayerPayload:
|
||||||
case ItemPayload: {
|
case ItemPayload: {
|
||||||
this.PopupPayloads.Add((chunk, payload));
|
this.PopupPayloads.Add(payload);
|
||||||
ImGui.OpenPopup(PopupId(payload));
|
ImGui.OpenPopup(PopupId(payload));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -237,30 +236,30 @@ internal sealed class PayloadHandler {
|
|||||||
this.Log.DrawChunks(ChunkUtil.ToChunks(name, null).ToList(), false);
|
this.Log.DrawChunks(ChunkUtil.ToChunks(name, null).ToList(), false);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
var realItemId = (uint) (item.ItemId + (item.IsHQ ? GameFunctions.GameFunctions.HqItemOffset : 0));
|
var realItemId = (uint) (item.ItemId + (item.IsHQ ? GameFunctions.HqItemOffset : 0));
|
||||||
|
|
||||||
if (item.Item.EquipSlotCategory.Row != 0) {
|
if (item.Item.EquipSlotCategory.Row != 0) {
|
||||||
if (ImGui.Selectable("Try On")) {
|
if (ImGui.Selectable("Try On")) {
|
||||||
this.Ui.Plugin.Functions.Context.TryOn(realItemId, 0);
|
this.Ui.Plugin.Functions.TryOn(realItemId, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Item Comparison")) {
|
if (ImGui.Selectable("Item Comparison")) {
|
||||||
this.Ui.Plugin.Functions.Context.OpenItemComparison(realItemId);
|
this.Ui.Plugin.Functions.OpenItemComparison(realItemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Item.ItemSearchCategory.Value?.Category == 3) {
|
if (item.Item.ItemSearchCategory.Value?.Category == 3) {
|
||||||
if (ImGui.Selectable("Search Recipes Using This Material")) {
|
if (ImGui.Selectable("Search Recipes Using This Material")) {
|
||||||
this.Ui.Plugin.Functions.Context.SearchForRecipesUsingItem(item.ItemId);
|
this.Ui.Plugin.Functions.SearchForRecipesUsingItem(item.ItemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Search for Item")) {
|
if (ImGui.Selectable("Search for Item")) {
|
||||||
this.Ui.Plugin.Functions.Context.SearchForItem(realItemId);
|
this.Ui.Plugin.Functions.SearchForItem(realItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Link")) {
|
if (ImGui.Selectable("Link")) {
|
||||||
this.Ui.Plugin.Functions.Context.LinkItem(realItemId);
|
this.Ui.Plugin.Functions.LinkItem(realItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Copy Item Name")) {
|
if (ImGui.Selectable("Copy Item Name")) {
|
||||||
@ -268,7 +267,7 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPlayerPopup(Chunk chunk, PlayerPayload player) {
|
private void DrawPlayerPopup(PlayerPayload player) {
|
||||||
var name = player.PlayerName;
|
var name = player.PlayerName;
|
||||||
if (player.World.IsPublic) {
|
if (player.World.IsPublic) {
|
||||||
name += $"{player.World.Name}";
|
name += $"{player.World.Name}";
|
||||||
@ -290,16 +289,16 @@ internal sealed class PayloadHandler {
|
|||||||
var isInParty = member != default;
|
var isInParty = member != default;
|
||||||
if (isLeader) {
|
if (isLeader) {
|
||||||
if (!isInParty && ImGui.Selectable("Invite to Party")) {
|
if (!isInParty && ImGui.Selectable("Invite to Party")) {
|
||||||
this.Ui.Plugin.Functions.Party.Invite(player.PlayerName, (ushort) player.World.RowId);
|
this.Ui.Plugin.Functions.InviteToParty(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInParty && member != null) {
|
if (isInParty && member != null) {
|
||||||
if (ImGui.Selectable("Promote")) {
|
if (ImGui.Selectable("Promote")) {
|
||||||
this.Ui.Plugin.Functions.Party.Promote(player.PlayerName, (ulong) member.ContentId);
|
this.Ui.Plugin.Functions.Promote(player.PlayerName, (ulong) member.ContentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Kick from Party")) {
|
if (ImGui.Selectable("Kick from Party")) {
|
||||||
this.Ui.Plugin.Functions.Party.Kick(player.PlayerName, (ulong) member.ContentId);
|
this.Ui.Plugin.Functions.KickFromParty(player.PlayerName, (ulong) member.ContentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,16 +309,10 @@ internal sealed class PayloadHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.Ui.Plugin.Functions.IsMentor() && ImGui.Selectable("Invite to Novice Network")) {
|
if (this.Ui.Plugin.Functions.IsMentor() && ImGui.Selectable("Invite to Novice Network")) {
|
||||||
this.Ui.Plugin.Functions.Context.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId);
|
this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (ImGui.Selectable("Target") && this.FindCharacterForPayload(player) is { } obj) {
|
||||||
this.Ui.Plugin.TargetManager.SetTarget(obj);
|
this.Ui.Plugin.TargetManager.SetTarget(obj);
|
||||||
}
|
}
|
||||||
@ -330,6 +323,7 @@ internal sealed class PayloadHandler {
|
|||||||
|
|
||||||
// Add to Blacklist 0x1C
|
// Add to Blacklist 0x1C
|
||||||
// View Party Finder 0x2E
|
// View Party Finder 0x2E
|
||||||
|
// Reply in Selected Chat Mode 0x64
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayerCharacter? FindCharacterForPayload(PlayerPayload payload) {
|
private PlayerCharacter? FindCharacterForPayload(PlayerPayload payload) {
|
||||||
|
@ -51,7 +51,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 TextureCache TextureCache { get; }
|
||||||
internal GameFunctions.GameFunctions Functions { get; }
|
internal GameFunctions Functions { get; }
|
||||||
internal Store Store { get; }
|
internal Store Store { get; }
|
||||||
internal PluginUi Ui { get; }
|
internal PluginUi Ui { get; }
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ public sealed class Plugin : IDalamudPlugin {
|
|||||||
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.TextureCache = new TextureCache(this.DataManager!);
|
||||||
this.Functions = new GameFunctions.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);
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ public sealed class Plugin : IDalamudPlugin {
|
|||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
this.Framework.Update -= this.FrameworkUpdate;
|
this.Framework.Update -= this.FrameworkUpdate;
|
||||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
GameFunctions.SetChatInteractable(true);
|
||||||
|
|
||||||
this.Ui.Dispose();
|
this.Ui.Dispose();
|
||||||
this.Store.Dispose();
|
this.Store.Dispose();
|
||||||
@ -97,8 +97,8 @@ public sealed class Plugin : IDalamudPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (var name in ChatAddonNames) {
|
foreach (var name in ChatAddonNames) {
|
||||||
if (GameFunctions.GameFunctions.IsAddonInteractable(name)) {
|
if (GameFunctions.IsAddonInteractable(name)) {
|
||||||
GameFunctions.GameFunctions.SetAddonInteractable(name, false);
|
GameFunctions.SetAddonInteractable(name, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ internal sealed class PluginUi : IDisposable {
|
|||||||
private (GCHandle, int) _jpFont;
|
private (GCHandle, int) _jpFont;
|
||||||
private (GCHandle, int) _gameSymFont;
|
private (GCHandle, int) _gameSymFont;
|
||||||
|
|
||||||
private readonly ImVector _ranges;
|
private ImVector _ranges;
|
||||||
|
|
||||||
private GCHandle _jpRange = GCHandle.Alloc(
|
private GCHandle _jpRange = GCHandle.Alloc(
|
||||||
GlyphRangesJapanese.GlyphRanges,
|
GlyphRangesJapanese.GlyphRanges,
|
||||||
|
@ -52,7 +52,7 @@ internal class Store : IDisposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentId = this.Plugin.Functions.Chat.GetContentIdForEntry(entry.Item1);
|
var contentId = this.Plugin.Functions.GetContentIdForChatLogEntry(entry.Item1);
|
||||||
entry.Item2.ContentId = contentId ?? 0;
|
entry.Item2.ContentId = contentId ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,15 +29,15 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
|
|
||||||
this._fontIcon = this.Ui.Plugin.DataManager.GetImGuiTexture("common/font/fonticon_ps5.tex");
|
this._fontIcon = this.Ui.Plugin.DataManager.GetImGuiTexture("common/font/fonticon_ps5.tex");
|
||||||
|
|
||||||
this.Ui.Plugin.Functions.Chat.Activated += this.Activated;
|
this.Ui.Plugin.Functions.ChatActivated += this.ChatActivated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
this.Ui.Plugin.Functions.Chat.Activated -= this.Activated;
|
this.Ui.Plugin.Functions.ChatActivated -= this.ChatActivated;
|
||||||
this._fontIcon?.Dispose();
|
this._fontIcon?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Activated(string? input) {
|
private void ChatActivated(string? input) {
|
||||||
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;
|
||||||
@ -88,7 +88,7 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
if (activeTab is { Channel: { } channel }) {
|
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.ChatChannel.name);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
ImGui.PopStyleVar();
|
ImGui.PopStyleVar();
|
||||||
@ -114,7 +114,7 @@ internal sealed class ChatLog : IUiComponent {
|
|||||||
?.RawString ?? channel.ToString();
|
?.RawString ?? channel.ToString();
|
||||||
|
|
||||||
if (ImGui.Selectable(name)) {
|
if (ImGui.Selectable(name)) {
|
||||||
this.Ui.Plugin.Functions.Chat.SetChannel(channel);
|
this.Ui.Plugin.Functions.SetChatChannel(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +127,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 = this.Ui.Plugin.Functions.Chat.Channel.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)
|
||||||
? inputCol
|
? inputCol
|
||||||
: inputType.DefaultColour();
|
: inputType.DefaultColour();
|
||||||
|
@ -197,7 +197,7 @@ internal sealed class Settings : IUiComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this._hideChat && hideChatChanged) {
|
if (!this._hideChat && hideChatChanged) {
|
||||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
GameFunctions.SetChatInteractable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Initialise();
|
this.Initialise();
|
||||||
|
Loading…
Reference in New Issue
Block a user