Compare commits
2 Commits
cd85e57940
...
0f790fa319
Author | SHA1 | Date | |
---|---|---|---|
0f790fa319 | |||
c5bbd616f0 |
@ -4,6 +4,7 @@ using Dalamud.Game.Text.SeStringHandling;
|
||||
namespace ChatTwo;
|
||||
|
||||
internal abstract class Chunk {
|
||||
internal Message? Message { get; set; }
|
||||
internal SeString? Source { get; set; }
|
||||
internal Payload? Link { get; set; }
|
||||
|
||||
|
@ -3,7 +3,7 @@ using ChatTwo.Util;
|
||||
namespace ChatTwo.Code;
|
||||
|
||||
internal static class ChatTypeExt {
|
||||
internal static string? Name(this ChatType type) {
|
||||
internal static string Name(this ChatType type) {
|
||||
return type switch {
|
||||
ChatType.Debug => "Debug",
|
||||
ChatType.Urgent => "Urgent",
|
||||
@ -206,4 +206,33 @@ internal static class ChatTypeExt {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static InputChannel? ToInputChannel(this ChatType type) => type switch {
|
||||
ChatType.TellOutgoing => InputChannel.Tell,
|
||||
ChatType.Say => InputChannel.Say,
|
||||
ChatType.Party => InputChannel.Party,
|
||||
ChatType.Alliance => InputChannel.Alliance,
|
||||
ChatType.Yell => InputChannel.Yell,
|
||||
ChatType.Shout => InputChannel.Shout,
|
||||
ChatType.FreeCompany => InputChannel.FreeCompany,
|
||||
ChatType.PvpTeam => InputChannel.PvpTeam,
|
||||
ChatType.NoviceNetwork => InputChannel.NoviceNetwork,
|
||||
ChatType.CrossLinkshell1 => InputChannel.CrossLinkshell1,
|
||||
ChatType.CrossLinkshell2 => InputChannel.CrossLinkshell2,
|
||||
ChatType.CrossLinkshell3 => InputChannel.CrossLinkshell3,
|
||||
ChatType.CrossLinkshell4 => InputChannel.CrossLinkshell4,
|
||||
ChatType.CrossLinkshell5 => InputChannel.CrossLinkshell5,
|
||||
ChatType.CrossLinkshell6 => InputChannel.CrossLinkshell6,
|
||||
ChatType.CrossLinkshell7 => InputChannel.CrossLinkshell7,
|
||||
ChatType.CrossLinkshell8 => InputChannel.CrossLinkshell8,
|
||||
ChatType.Linkshell1 => InputChannel.Linkshell1,
|
||||
ChatType.Linkshell2 => InputChannel.Linkshell2,
|
||||
ChatType.Linkshell3 => InputChannel.Linkshell3,
|
||||
ChatType.Linkshell4 => InputChannel.Linkshell4,
|
||||
ChatType.Linkshell5 => InputChannel.Linkshell5,
|
||||
ChatType.Linkshell6 => InputChannel.Linkshell6,
|
||||
ChatType.Linkshell7 => InputChannel.Linkshell7,
|
||||
ChatType.Linkshell8 => InputChannel.Linkshell8,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
@ -1,523 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
219
ChatTwo/GameFunctions/Chat.cs
Executable file
219
ChatTwo/GameFunctions/Chat.cs
Executable file
@ -0,0 +1,219 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
107
ChatTwo/GameFunctions/Context.cs
Executable file
107
ChatTwo/GameFunctions/Context.cs
Executable file
@ -0,0 +1,107 @@
|
||||
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);
|
||||
}
|
||||
}
|
234
ChatTwo/GameFunctions/GameFunctions.cs
Executable file
234
ChatTwo/GameFunctions/GameFunctions.cs
Executable file
@ -0,0 +1,234 @@
|
||||
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);
|
||||
}
|
||||
}
|
73
ChatTwo/GameFunctions/Party.cs
Executable file
73
ChatTwo/GameFunctions/Party.cs
Executable file
@ -0,0 +1,73 @@
|
||||
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,5 +15,9 @@ internal class Message {
|
||||
this.Code = code;
|
||||
this.Sender = sender;
|
||||
this.Content = content;
|
||||
|
||||
foreach (var chunk in sender.Concat(content)) {
|
||||
chunk.Message = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Ui;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
@ -16,7 +17,7 @@ internal sealed class PayloadHandler {
|
||||
private PluginUi Ui { get; }
|
||||
private ChatLog Log { get; }
|
||||
|
||||
private HashSet<Payload> PopupPayloads { get; set; } = new();
|
||||
private HashSet<(Chunk, Payload)> PopupPayloads { get; set; } = new();
|
||||
|
||||
private bool _handleTooltips;
|
||||
private uint _hoveredItem;
|
||||
@ -32,7 +33,7 @@ internal sealed class PayloadHandler {
|
||||
this.DrawPopups();
|
||||
|
||||
if (this._handleTooltips && ++this._hoverCounter - this._lastHoverCounter > 1) {
|
||||
GameFunctions.CloseItemTooltip();
|
||||
GameFunctions.GameFunctions.CloseItemTooltip();
|
||||
this._hoveredItem = 0;
|
||||
this._hoverCounter = this._lastHoverCounter = 0;
|
||||
this._handleTooltips = false;
|
||||
@ -40,8 +41,8 @@ internal sealed class PayloadHandler {
|
||||
}
|
||||
|
||||
private void DrawPopups() {
|
||||
var newPopups = new HashSet<Payload>();
|
||||
foreach (var payload in this.PopupPayloads) {
|
||||
var newPopups = new HashSet<(Chunk, Payload)>();
|
||||
foreach (var (chunk, payload) in this.PopupPayloads) {
|
||||
var id = PopupId(payload);
|
||||
if (id == null) {
|
||||
continue;
|
||||
@ -51,12 +52,12 @@ internal sealed class PayloadHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
newPopups.Add(payload);
|
||||
newPopups.Add((chunk, payload));
|
||||
ImGui.PushID(id);
|
||||
|
||||
switch (payload) {
|
||||
case PlayerPayload player: {
|
||||
this.DrawPlayerPopup(player);
|
||||
this.DrawPlayerPopup(chunk, player);
|
||||
break;
|
||||
}
|
||||
case ItemPayload item: {
|
||||
@ -78,7 +79,7 @@ internal sealed class PayloadHandler {
|
||||
this.LeftClickPayload(chunk, payload);
|
||||
break;
|
||||
case ImGuiMouseButton.Right:
|
||||
this.RightClickPayload(payload);
|
||||
this.RightClickPayload(chunk, payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -91,7 +92,7 @@ internal sealed class PayloadHandler {
|
||||
}
|
||||
case ItemPayload item: {
|
||||
if (this.Ui.Plugin.Config.NativeItemTooltips) {
|
||||
GameFunctions.OpenItemTooltip(item.ItemId);
|
||||
GameFunctions.GameFunctions.OpenItemTooltip(item.ItemId);
|
||||
|
||||
this._handleTooltips = true;
|
||||
if (this._hoveredItem != item.ItemId) {
|
||||
@ -177,7 +178,7 @@ internal sealed class PayloadHandler {
|
||||
}
|
||||
case RawPayload raw: {
|
||||
if (Equals(raw, ChunkUtil.PeriodicRecruitmentLink)) {
|
||||
GameFunctions.OpenPartyFinder();
|
||||
GameFunctions.GameFunctions.OpenPartyFinder();
|
||||
}
|
||||
|
||||
break;
|
||||
@ -211,11 +212,11 @@ internal sealed class PayloadHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void RightClickPayload(Payload payload) {
|
||||
private void RightClickPayload(Chunk chunk, Payload payload) {
|
||||
switch (payload) {
|
||||
case PlayerPayload:
|
||||
case ItemPayload: {
|
||||
this.PopupPayloads.Add(payload);
|
||||
this.PopupPayloads.Add((chunk, payload));
|
||||
ImGui.OpenPopup(PopupId(payload));
|
||||
break;
|
||||
}
|
||||
@ -236,30 +237,30 @@ internal sealed class PayloadHandler {
|
||||
this.Log.DrawChunks(ChunkUtil.ToChunks(name, null).ToList(), false);
|
||||
ImGui.Separator();
|
||||
|
||||
var realItemId = (uint) (item.ItemId + (item.IsHQ ? GameFunctions.HqItemOffset : 0));
|
||||
var realItemId = (uint) (item.ItemId + (item.IsHQ ? GameFunctions.GameFunctions.HqItemOffset : 0));
|
||||
|
||||
if (item.Item.EquipSlotCategory.Row != 0) {
|
||||
if (ImGui.Selectable("Try On")) {
|
||||
this.Ui.Plugin.Functions.TryOn(realItemId, 0);
|
||||
this.Ui.Plugin.Functions.Context.TryOn(realItemId, 0);
|
||||
}
|
||||
|
||||
if (ImGui.Selectable("Item Comparison")) {
|
||||
this.Ui.Plugin.Functions.OpenItemComparison(realItemId);
|
||||
this.Ui.Plugin.Functions.Context.OpenItemComparison(realItemId);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Item.ItemSearchCategory.Value?.Category == 3) {
|
||||
if (ImGui.Selectable("Search Recipes Using This Material")) {
|
||||
this.Ui.Plugin.Functions.SearchForRecipesUsingItem(item.ItemId);
|
||||
this.Ui.Plugin.Functions.Context.SearchForRecipesUsingItem(item.ItemId);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Selectable("Search for Item")) {
|
||||
this.Ui.Plugin.Functions.SearchForItem(realItemId);
|
||||
this.Ui.Plugin.Functions.Context.SearchForItem(realItemId);
|
||||
}
|
||||
|
||||
if (ImGui.Selectable("Link")) {
|
||||
this.Ui.Plugin.Functions.LinkItem(realItemId);
|
||||
this.Ui.Plugin.Functions.Context.LinkItem(realItemId);
|
||||
}
|
||||
|
||||
if (ImGui.Selectable("Copy Item Name")) {
|
||||
@ -267,7 +268,7 @@ internal sealed class PayloadHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPlayerPopup(PlayerPayload player) {
|
||||
private void DrawPlayerPopup(Chunk chunk, PlayerPayload player) {
|
||||
var name = player.PlayerName;
|
||||
if (player.World.IsPublic) {
|
||||
name += $"{player.World.Name}";
|
||||
@ -289,16 +290,16 @@ internal sealed class PayloadHandler {
|
||||
var isInParty = member != default;
|
||||
if (isLeader) {
|
||||
if (!isInParty && ImGui.Selectable("Invite to Party")) {
|
||||
this.Ui.Plugin.Functions.InviteToParty(player.PlayerName, (ushort) player.World.RowId);
|
||||
this.Ui.Plugin.Functions.Party.Invite(player.PlayerName, (ushort) player.World.RowId);
|
||||
}
|
||||
|
||||
if (isInParty && member != null) {
|
||||
if (ImGui.Selectable("Promote")) {
|
||||
this.Ui.Plugin.Functions.Promote(player.PlayerName, (ulong) member.ContentId);
|
||||
this.Ui.Plugin.Functions.Party.Promote(player.PlayerName, (ulong) member.ContentId);
|
||||
}
|
||||
|
||||
if (ImGui.Selectable("Kick from Party")) {
|
||||
this.Ui.Plugin.Functions.KickFromParty(player.PlayerName, (ulong) member.ContentId);
|
||||
this.Ui.Plugin.Functions.Party.Kick(player.PlayerName, (ulong) member.ContentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -309,10 +310,16 @@ internal sealed class PayloadHandler {
|
||||
}
|
||||
|
||||
if (this.Ui.Plugin.Functions.IsMentor() && ImGui.Selectable("Invite to Novice Network")) {
|
||||
this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId);
|
||||
this.Ui.Plugin.Functions.Context.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) {
|
||||
this.Ui.Plugin.TargetManager.SetTarget(obj);
|
||||
}
|
||||
@ -323,7 +330,6 @@ internal sealed class PayloadHandler {
|
||||
|
||||
// Add to Blacklist 0x1C
|
||||
// View Party Finder 0x2E
|
||||
// Reply in Selected Chat Mode 0x64
|
||||
}
|
||||
|
||||
private PlayerCharacter? FindCharacterForPayload(PlayerPayload payload) {
|
||||
|
@ -51,7 +51,7 @@ public sealed class Plugin : IDalamudPlugin {
|
||||
internal Configuration Config { get; }
|
||||
internal XivCommonBase Common { get; }
|
||||
internal TextureCache TextureCache { get; }
|
||||
internal GameFunctions Functions { get; }
|
||||
internal GameFunctions.GameFunctions Functions { get; }
|
||||
internal Store Store { get; }
|
||||
internal PluginUi Ui { get; }
|
||||
|
||||
@ -60,7 +60,7 @@ public sealed class Plugin : IDalamudPlugin {
|
||||
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
this.Common = new XivCommonBase();
|
||||
this.TextureCache = new TextureCache(this.DataManager!);
|
||||
this.Functions = new GameFunctions(this);
|
||||
this.Functions = new GameFunctions.GameFunctions(this);
|
||||
this.Store = new Store(this);
|
||||
this.Ui = new PluginUi(this);
|
||||
|
||||
@ -70,7 +70,7 @@ public sealed class Plugin : IDalamudPlugin {
|
||||
|
||||
public void Dispose() {
|
||||
this.Framework.Update -= this.FrameworkUpdate;
|
||||
GameFunctions.SetChatInteractable(true);
|
||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
||||
|
||||
this.Ui.Dispose();
|
||||
this.Store.Dispose();
|
||||
@ -97,8 +97,8 @@ public sealed class Plugin : IDalamudPlugin {
|
||||
}
|
||||
|
||||
foreach (var name in ChatAddonNames) {
|
||||
if (GameFunctions.IsAddonInteractable(name)) {
|
||||
GameFunctions.SetAddonInteractable(name, false);
|
||||
if (GameFunctions.GameFunctions.IsAddonInteractable(name)) {
|
||||
GameFunctions.GameFunctions.SetAddonInteractable(name, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ internal sealed class PluginUi : IDisposable {
|
||||
private (GCHandle, int) _jpFont;
|
||||
private (GCHandle, int) _gameSymFont;
|
||||
|
||||
private ImVector _ranges;
|
||||
private readonly ImVector _ranges;
|
||||
|
||||
private GCHandle _jpRange = GCHandle.Alloc(
|
||||
GlyphRangesJapanese.GlyphRanges,
|
||||
|
@ -52,7 +52,7 @@ internal class Store : IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
var contentId = this.Plugin.Functions.GetContentIdForChatLogEntry(entry.Item1);
|
||||
var contentId = this.Plugin.Functions.Chat.GetContentIdForEntry(entry.Item1);
|
||||
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.Ui.Plugin.Functions.ChatActivated += this.ChatActivated;
|
||||
this.Ui.Plugin.Functions.Chat.Activated += this.Activated;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Ui.Plugin.Functions.ChatActivated -= this.ChatActivated;
|
||||
this.Ui.Plugin.Functions.Chat.Activated -= this.Activated;
|
||||
this._fontIcon?.Dispose();
|
||||
}
|
||||
|
||||
private void ChatActivated(string? input) {
|
||||
private void Activated(string? input) {
|
||||
this.Activate = true;
|
||||
if (input != null && !this.Chat.Contains(input)) {
|
||||
this.Chat += input;
|
||||
@ -88,7 +88,7 @@ internal sealed class ChatLog : IUiComponent {
|
||||
if (activeTab is { Channel: { } channel }) {
|
||||
ImGui.TextUnformatted(channel.ToChatType().Name());
|
||||
} else {
|
||||
this.DrawChunks(this.Ui.Plugin.Functions.ChatChannel.name);
|
||||
this.DrawChunks(this.Ui.Plugin.Functions.Chat.Channel.name);
|
||||
}
|
||||
} finally {
|
||||
ImGui.PopStyleVar();
|
||||
@ -114,7 +114,7 @@ internal sealed class ChatLog : IUiComponent {
|
||||
?.RawString ?? channel.ToString();
|
||||
|
||||
if (ImGui.Selectable(name)) {
|
||||
this.Ui.Plugin.Functions.SetChatChannel(channel);
|
||||
this.Ui.Plugin.Functions.Chat.SetChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ internal sealed class ChatLog : IUiComponent {
|
||||
var buttonWidth = afterIcon.X - beforeIcon.X;
|
||||
var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth;
|
||||
|
||||
var inputType = this.Ui.Plugin.Functions.ChatChannel.channel.ToChatType();
|
||||
var inputType = this.Ui.Plugin.Functions.Chat.Channel.channel.ToChatType();
|
||||
var inputColour = this.Ui.Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol)
|
||||
? inputCol
|
||||
: inputType.DefaultColour();
|
||||
|
@ -197,7 +197,7 @@ internal sealed class Settings : IUiComponent {
|
||||
}
|
||||
|
||||
if (!this._hideChat && hideChatChanged) {
|
||||
GameFunctions.SetChatInteractable(true);
|
||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
||||
}
|
||||
|
||||
this.Initialise();
|
||||
|
Loading…
Reference in New Issue
Block a user