- Migrate config for API 15
- Migrate database for API 15
- Allow usage of new target source
- Implement first tell target option
This commit is contained in:
Infi
2026-04-30 02:59:58 +02:00
parent 68810e23c1
commit b4cb8b25ec
56 changed files with 1286 additions and 616 deletions
+14 -14
View File
@@ -20,7 +20,7 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
using InteropGenerator.Runtime;
using Lumina.Text.ReadOnly;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
namespace ChatTwo.GameFunctions;
@@ -35,21 +35,21 @@ internal sealed unsafe class Chat : IDisposable
// Client::UI::AddonChatLog.OnRefresh
[Signature("40 53 57 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 4D 8B F8", DetourName = nameof(ChatLogRefreshDetour))]
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; init; }
private Hook<ChatLogRefreshDelegate>? ChatLogRefreshHook = null!;
private delegate byte ChatLogRefreshDelegate(nint log, ushort eventId, AtkValue* value);
// Replace with CS version later
[Signature("48 89 5C 24 ?? 55 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 83 B9", DetourName = nameof(ContextMenuTellInForayDetour))]
private Hook<ContextMenuTellInForayDelegate>? ContextMenuTellInForayHook { get; set; }
private Hook<ContextMenuTellInForayDelegate>? ContextMenuTellInForayHook = null!;
private delegate void ContextMenuTellInForayDelegate(RaptureShellModule* module, Utf8String* playerName, Utf8String* worldName, ushort worldId, ulong accountId, ulong contentId, ushort reason);
private Hook<AgentChatLog.Delegates.ChangeChannelName> ChangeChannelNameHook { get; init; }
private Hook<RaptureShellModule.Delegates.ReplyInSelectedChatMode>? ReplyInSelectedChatModeHook { get; init; }
private Hook<RaptureShellModule.Delegates.SetContextTellTarget>? SetChatLogTellTargetHook { get; init; }
private readonly Hook<AgentChatLog.Delegates.ChangeChannelName>? ChangeChannelNameHook;
private readonly Hook<RaptureShellModule.Delegates.ReplyInSelectedChatMode>? ReplyInSelectedChatModeHook;
private readonly Hook<RaptureShellModule.Delegates.SetContextTellTarget>? SetChatLogTellTargetHook;
// Pointers
[Signature("48 8D 35 ?? ?? ?? ?? 8B 05", ScanType = ScanType.StaticAddress)]
private readonly char* CurrentCharacter = null!;
[Signature("48 8D 1D ?? ?? ?? ?? 8B 05", ScanType = ScanType.StaticAddress)]
private readonly char* LastTypedCharacter = null!;
private Plugin Plugin { get; }
@@ -64,7 +64,7 @@ internal sealed unsafe class Chat : IDisposable
private long LastPlayerNameDisplayTypeRefresh;
private PlayerNameDisplayType CurrentPlayerNameDisplayType = PlayerNameDisplayType.FullName;
internal Chat(Plugin plugin)
public Chat(Plugin plugin)
{
Plugin = plugin;
Plugin.GameInteropProvider.InitializeFromAttributes(this);
@@ -131,7 +131,7 @@ internal sealed unsafe class Chat : IDisposable
// If this function ever returns 0, it returns null instead.
internal uint? GetChannelColor(ChatType type)
{
var parent = new ChatCode((ushort) type).Parent();
var parent = type.Parent();
switch (parent)
{
case ChatType.Debug:
@@ -169,7 +169,7 @@ internal sealed unsafe class Chat : IDisposable
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C))
return ChatLogRefreshHook!.Original(log, eventId, value);
if (Plugin.Functions.KeybindManager.DirectChat && CurrentCharacter != null)
if (Plugin.Functions.KeybindManager.DirectChat && LastTypedCharacter != null)
{
// FIXME: this whole system sucks
// FIXME v2: I hate everything about this, but it works
@@ -177,7 +177,7 @@ internal sealed unsafe class Chat : IDisposable
{
string? input = null;
var utf8Bytes = MemoryHelper.ReadRaw((nint)CurrentCharacter+0x4, 2);
var utf8Bytes = MemoryHelper.ReadRaw((nint)LastTypedCharacter+0x4, 2);
var chars = Encoding.UTF8.GetString(utf8Bytes).ToCharArray();
if (chars.Length == 0)
return;
@@ -230,7 +230,7 @@ internal sealed unsafe class Chat : IDisposable
private CStringPointer ChangeChannelNameDetour(AgentChatLog* agent)
{
var ret = ChangeChannelNameHook.Original(agent);
var ret = ChangeChannelNameHook!.Original(agent);
if (agent == null)
return ret;
@@ -572,6 +572,6 @@ internal sealed unsafe class Chat : IDisposable
// second before the cutscene actually starts, because the game sets
// the cutscene conditions before processing the skip.
var raptureAtkUnitManager = RaptureAtkUnitManager.Instance();
return raptureAtkUnitManager == null || raptureAtkUnitManager->UiFlags.HasFlag(UIModule.UiFlags.Chat);
return raptureAtkUnitManager == null || raptureAtkUnitManager->UiFlags.HasFlag(UiFlags.Chat);
}
}
+4 -3
View File
@@ -1,4 +1,5 @@
using System.Text;
using ChatTwo.Resources;
using Dalamud.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using FFXIVClientStructs.FFXIV.Client.UI;
@@ -18,13 +19,13 @@ public unsafe class ChatBox
{
var bytes = Encoding.UTF8.GetBytes(message);
if (bytes.Length == 0)
throw new ArgumentException("message is empty", nameof(message));
throw new ArgumentException(Language.ChatBox_Error_Empty, nameof(message));
if (bytes.Length > 500)
throw new ArgumentException("message is longer than 500 bytes", nameof(message));
throw new ArgumentException(Language.ChatBox_Error_Too_Long, nameof(message));
if (message.Length != SanitiseText(message).Length)
throw new ArgumentException("message contained invalid characters", nameof(message));
throw new ArgumentException(Language.ChatBox_Error_Invalid, nameof(message));
SendMessageUnsafe(bytes);
}
+3 -3
View File
@@ -14,7 +14,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Info;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
namespace ChatTwo.GameFunctions;
@@ -22,7 +22,7 @@ internal unsafe class GameFunctions : IDisposable
{
#region Hooks
[Signature("E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B D0 49 8D 4F", DetourName = nameof(ResolveTextCommandPlaceholderDetour))]
private Hook<ResolveTextCommandPlaceholderDelegate>? ResolveTextCommandPlaceholderHook { get; init; }
private Hook<ResolveTextCommandPlaceholderDelegate>? ResolveTextCommandPlaceholderHook = null!;
private delegate nint ResolveTextCommandPlaceholderDelegate(nint a1, byte* placeholderText, byte a3, byte a4);
#endregion
@@ -132,7 +132,7 @@ internal unsafe class GameFunctions : IDisposable
agent->AddonId = addon->Id;
// Skips early return
atkStage->TooltipManager.Flag1 |= 2;
atkStage->TooltipManager.TooltipType |= 2;
addon->Show(false, 15);
}
+1 -1
View File
@@ -1,7 +1,7 @@
namespace ChatTwo.GameFunctions.Types;
[Flags]
internal enum ModifierFlag
public enum ModifierFlag
{
None = 0,
Shift = 1 << 0,
+1 -1
View File
@@ -1,6 +1,6 @@
namespace ChatTwo.GameFunctions.Types;
internal enum TellReason
public enum TellReason
{
Direct = 0,
PartyFinder = 1,
+27 -9
View File
@@ -1,13 +1,17 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
namespace ChatTwo.GameFunctions.Types;
internal sealed class TellTarget
[Serializable]
public class TellTarget
{
internal string Name { get; }
internal ushort World { get; }
internal ulong ContentId { get; }
internal TellReason Reason { get; }
public string Name { get; set; }
public uint World { get; set; }
public ulong ContentId { get; private set; }
public TellReason Reason { get; private set; }
internal TellTarget(string name, ushort world, ulong contentId, TellReason reason)
public TellTarget(string name, uint world, ulong contentId, TellReason reason)
{
Name = name;
World = world;
@@ -15,8 +19,22 @@ internal sealed class TellTarget
Reason = reason;
}
public bool IsSet() => Name.Length > 0 && World > 0;
public bool IsSet()
=> Name.Length > 0 && World > 0;
public string ToWorldString() => Sheets.WorldSheet.TryGetRow(World, out var worldRow) ? worldRow.Name.ToString() : string.Empty;
public string ToTargetString() => $"{Name}@{ToWorldString()}";
public string ToWorldString()
=> Sheets.WorldSheet.TryGetRow(World, out var worldRow) ? worldRow.Name.ToString() : string.Empty;
public string ToTargetString()
=> $"{Name}@{ToWorldString()}";
public unsafe void FromTarget(IPlayerCharacter target)
{
Name = target.Name.TextValue;
World = target.HomeWorld.RowId;
ContentId = ((Character*)target.Address)->ContentId;
}
public static TellTarget Empty() => new(string.Empty, 0, 0, TellReason.Direct);
public static TellTarget From(TellTarget t) => new(t.Name, t.World, t.ContentId, t.Reason);
}