feat: add kick/promote and make friend request work
This commit is contained in:
parent
5b31d47d48
commit
3d3ca42a6f
@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
using ChatTwo.Code;
|
using ChatTwo.Code;
|
||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
@ -12,6 +13,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
|
using FFXIVClientStructs.FFXIV.Client.UI.Shell;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Siggingway;
|
using Siggingway;
|
||||||
|
|
||||||
namespace ChatTwo;
|
namespace ChatTwo;
|
||||||
@ -21,6 +23,7 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
internal const string ChatLogRefresh = "40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA";
|
internal const string ChatLogRefresh = "40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA";
|
||||||
internal const string ChangeChannelName = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6";
|
internal const string ChangeChannelName = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6";
|
||||||
internal const string IsMentorA1 = "48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 74 71 0F B6 86";
|
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";
|
internal const string CurrentChatEntryOffset = "8B 77 ?? 8D 46 01 89 47 14 81 FE ?? ?? ?? ?? 72 03 FF 47";
|
||||||
}
|
}
|
||||||
@ -39,12 +42,9 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
[Signature("E8 ?? ?? ?? ?? 33 C0 EB 51", Fallibility = Fallibility.Fallible)]
|
[Signature("E8 ?? ?? ?? ?? 33 C0 EB 51", Fallibility = Fallibility.Fallible)]
|
||||||
private readonly delegate* unmanaged<IntPtr, ulong, byte*, ushort, void> _inviteToParty = null!;
|
private readonly delegate* unmanaged<IntPtr, ulong, byte*, ushort, void> _inviteToParty = null!;
|
||||||
|
|
||||||
[Signature(("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9"), Fallibility = Fallibility.Fallible)]
|
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9", Fallibility = Fallibility.Fallible)]
|
||||||
private readonly delegate* unmanaged<IntPtr, ulong, ushort, byte*, byte> _inviteToNoviceNetwork = null!;
|
private readonly delegate* unmanaged<IntPtr, ulong, ushort, byte*, byte> _inviteToNoviceNetwork = null!;
|
||||||
|
|
||||||
[Signature("40 53 48 83 EC 20 48 8B D9 48 8B 49 10 48 8B 01 FF 90 ?? ?? ?? ?? 48 8B 48 48", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<AgentInterface*, byte> _friendRequestBool = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? EB 35 BA", Fallibility = Fallibility.Fallible)]
|
[Signature("E8 ?? ?? ?? ?? EB 35 BA", Fallibility = Fallibility.Fallible)]
|
||||||
private readonly delegate* unmanaged<uint, uint, ulong, uint, byte, byte> _tryOn = null!;
|
private readonly delegate* unmanaged<uint, uint, ulong, uint, byte, byte> _tryOn = null!;
|
||||||
|
|
||||||
@ -60,12 +60,15 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
[Signature("E8 ?? ?? ?? ?? EB 45 45 33 C9", Fallibility = Fallibility.Fallible)]
|
[Signature("E8 ?? ?? ?? ?? EB 45 45 33 C9", Fallibility = Fallibility.Fallible)]
|
||||||
private readonly delegate* unmanaged<void*, uint, byte, void> _searchForItem = null!;
|
private readonly delegate* unmanaged<void*, uint, byte, void> _searchForItem = null!;
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 84 C0 74 3A", Fallibility = Fallibility.Fallible)]
|
|
||||||
private readonly delegate* unmanaged<AgentInterface*, uint, byte*, ushort, uint, byte, void> _agentContextYesNo = null!;
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 84 C0 74 0D B0 02", Fallibility = Fallibility.Fallible)]
|
[Signature("E8 ?? ?? ?? ?? 84 C0 74 0D B0 02", Fallibility = Fallibility.Fallible)]
|
||||||
private readonly delegate* unmanaged<IntPtr, byte> _isMentor = null!;
|
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
|
#endregion
|
||||||
|
|
||||||
#region Hooks
|
#region Hooks
|
||||||
@ -74,20 +77,29 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
|
|
||||||
private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent);
|
private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent);
|
||||||
|
|
||||||
|
private delegate IntPtr ResolveTextCommandPlaceholderDelegate(IntPtr a1, byte* placeholderText, byte a3, byte a4);
|
||||||
|
|
||||||
[Signature(Signatures.ChatLogRefresh, DetourName = nameof(ChatLogRefreshDetour))]
|
[Signature(Signatures.ChatLogRefresh, DetourName = nameof(ChatLogRefreshDetour))]
|
||||||
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; init; }
|
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; init; }
|
||||||
|
|
||||||
[Signature(Signatures.ChangeChannelName, DetourName = nameof(ChangeChannelNameDetour))]
|
[Signature(Signatures.ChangeChannelName, DetourName = nameof(ChangeChannelNameDetour))]
|
||||||
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; init; }
|
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; init; }
|
||||||
|
|
||||||
|
[Signature(Signatures.ResolveTextCommandPlaceholder, DetourName = nameof(ResolveTextCommandPlaceholderDetour))]
|
||||||
|
private Hook<ResolveTextCommandPlaceholderDelegate>? ResolveTextCommandPlaceholderHook { get; init; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#pragma warning disable 0649
|
||||||
|
|
||||||
[Signature(Signatures.CurrentChatEntryOffset, Offset = 2)]
|
[Signature(Signatures.CurrentChatEntryOffset, Offset = 2)]
|
||||||
private readonly byte? _currentChatEntryOffset;
|
private readonly byte? _currentChatEntryOffset;
|
||||||
|
|
||||||
[Signature(Signatures.IsMentorA1, ScanType = ScanType.StaticAddress)]
|
[Signature(Signatures.IsMentorA1, ScanType = ScanType.StaticAddress)]
|
||||||
private readonly IntPtr? _isMentorA1;
|
private readonly IntPtr? _isMentorA1;
|
||||||
|
|
||||||
|
#pragma warning restore 0649
|
||||||
|
|
||||||
internal const int HqItemOffset = 1_000_000;
|
internal const int HqItemOffset = 1_000_000;
|
||||||
|
|
||||||
private Plugin Plugin { get; }
|
private Plugin Plugin { get; }
|
||||||
@ -105,6 +117,7 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
|
|
||||||
this.ChatLogRefreshHook?.Enable();
|
this.ChatLogRefreshHook?.Enable();
|
||||||
this.ChangeChannelNameHook?.Enable();
|
this.ChangeChannelNameHook?.Enable();
|
||||||
|
this.ResolveTextCommandPlaceholderHook?.Enable();
|
||||||
|
|
||||||
this.Plugin.ClientState.Login += this.Login;
|
this.Plugin.ClientState.Login += this.Login;
|
||||||
this.Login(null, null);
|
this.Login(null, null);
|
||||||
@ -112,9 +125,12 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
this.Plugin.ClientState.Login -= this.Login;
|
this.Plugin.ClientState.Login -= this.Login;
|
||||||
|
this.ResolveTextCommandPlaceholderHook?.Dispose();
|
||||||
this.ChangeChannelNameHook?.Dispose();
|
this.ChangeChannelNameHook?.Dispose();
|
||||||
this.ChatLogRefreshHook?.Dispose();
|
this.ChatLogRefreshHook?.Dispose();
|
||||||
this.ChatActivated = null;
|
this.ChatActivated = null;
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(this._placeholderNamePtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Login(object? sender, EventArgs? e) {
|
private void Login(object? sender, EventArgs? e) {
|
||||||
@ -165,17 +181,14 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal void SendFriendRequest(string name, ushort world) {
|
internal void SendFriendRequest(string name, ushort world) {
|
||||||
if (this._agentContextYesNo == null || this._friendRequestBool == null) {
|
var row = this.Plugin.DataManager.GetExcelSheet<World>()!.GetRow(world);
|
||||||
|
if (row == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Context);
|
var worldName = row.Name.RawString;
|
||||||
var a6 = this._friendRequestBool(agent) == 0 ? (byte) 1 : (byte) 0;
|
this._replacementName = $"{name}@{worldName}";
|
||||||
|
this.Plugin.Common.Functions.Chat.SendMessage($"/friendlist add {this._placeholder}");
|
||||||
fixed (byte* namePtr = name.ToTerminatedBytes()) {
|
|
||||||
// 6.05: 20DA57
|
|
||||||
this._agentContextYesNo(agent, 149, namePtr, world, 10_955, a6);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InviteToNoviceNetwork(string name, ushort world) {
|
internal void InviteToNoviceNetwork(string name, ushort world) {
|
||||||
@ -454,4 +467,57 @@ internal unsafe class GameFunctions : IDisposable {
|
|||||||
|
|
||||||
return this._isMentor(this._isMentorA1.Value) > 0;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,20 +282,33 @@ internal sealed class PayloadHandler {
|
|||||||
this.Log.Activate = true;
|
this.Log.Activate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Invite to Party")) {
|
var party = this.Ui.Plugin.PartyList;
|
||||||
// FIXME: don't show if player is in your party or if you're in their party
|
var leader = (ulong?) party[(int) party.PartyLeaderIndex]?.ContentId;
|
||||||
// FIXME: don't show if in party and not leader
|
var isLeader = party.Length == 0 || this.Ui.Plugin.ClientState.LocalContentId == leader;
|
||||||
|
var member = party.FirstOrDefault(member => member.Name.TextValue == player.PlayerName && member.World.Id == player.World.RowId);
|
||||||
|
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.InviteToParty(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable("Send Friend Request")) {
|
if (isInParty && member != null) {
|
||||||
// FIXME: this shows window, clicking yes doesn't work
|
if (ImGui.Selectable("Promote")) {
|
||||||
// FIXME: only show if not already friend
|
this.Ui.Plugin.Functions.Promote(player.PlayerName, (ulong) member.ContentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Selectable("Kick from Party")) {
|
||||||
|
this.Ui.Plugin.Functions.KickFromParty(player.PlayerName, (ulong) member.ContentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFriend = this.Ui.Plugin.Common.Functions.FriendList.List.Any(friend => friend.Name.TextValue == player.PlayerName && friend.HomeWorld == player.World.RowId);
|
||||||
|
if (!isFriend && ImGui.Selectable("Send Friend Request")) {
|
||||||
this.Ui.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) player.World.RowId);
|
this.Ui.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.Ui.Plugin.Functions.IsMentor() && ImGui.Selectable("Invite to Novice Network")) {
|
if (this.Ui.Plugin.Functions.IsMentor() && ImGui.Selectable("Invite to Novice Network")) {
|
||||||
// FIXME: only show if character is mentor and target is sprout/returner
|
|
||||||
this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId);
|
this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.ClientState.Party;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
@ -38,6 +39,9 @@ public sealed class Plugin : IDalamudPlugin {
|
|||||||
[PluginService]
|
[PluginService]
|
||||||
internal ObjectTable ObjectTable { get; init; }
|
internal ObjectTable ObjectTable { get; init; }
|
||||||
|
|
||||||
|
[PluginService]
|
||||||
|
internal PartyList PartyList { get; init; }
|
||||||
|
|
||||||
[PluginService]
|
[PluginService]
|
||||||
internal SigScanner SigScanner { get; init; }
|
internal SigScanner SigScanner { get; init; }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user