mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Merge pull request #2049 from goatcorp/api11
Dalamud API 11: Echoes of Vana'diel
This commit is contained in:
commit
a93c54340c
127 changed files with 1959 additions and 2062 deletions
|
|
@ -27,8 +27,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lumina" Version="4.2.1" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.0.1" />
|
||||
<PackageReference Include="Lumina" Version="5.2.1" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<DalamudVersion>10.0.0.15</DalamudVersion>
|
||||
<DalamudVersion>11.0.0.0</DalamudVersion>
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
|
|
@ -71,18 +71,18 @@
|
|||
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
||||
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||
<PackageReference Include="Lumina" Version="4.2.1" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.0.1" />
|
||||
<PackageReference Include="Lumina" Version="5.2.1" />
|
||||
<PackageReference Include="Lumina.Excel" Version="7.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MinSharp" Version="1.0.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
@ -160,7 +160,7 @@
|
|||
<Exec Command="echo|set /P ="$(CommitHash)" > $(CommitHashFile)" IgnoreExitCode="true" />
|
||||
<Exec Command="echo|set /P ="$(SCMVersion)" > $(TempVerFile)" IgnoreExitCode="true" />
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="GenerateStubVersionData" BeforeTargets="WriteVersionData" Condition="'$(SCMVersion)'=='' And '$(Configuration)'!='Release'">
|
||||
<!-- stub out version since it takes a while. -->
|
||||
<PropertyGroup>
|
||||
|
|
@ -168,7 +168,7 @@
|
|||
<CommitHashClientStructs>???</CommitHashClientStructs>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="WriteVersionData" BeforeTargets="CoreCompile">
|
||||
<!-- names the obj/.../CustomAssemblyInfo.cs file -->
|
||||
<PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Dalamud.Utility.Timing;
|
|||
using Lumina;
|
||||
using Lumina.Data;
|
||||
using Lumina.Excel;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -28,12 +29,15 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
{
|
||||
private readonly Thread luminaResourceThread;
|
||||
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
||||
private readonly RsvResolver rsvResolver;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DataManager(Dalamud dalamud)
|
||||
{
|
||||
this.Language = (ClientLanguage)dalamud.StartInfo.Language;
|
||||
|
||||
this.rsvResolver = new();
|
||||
|
||||
try
|
||||
{
|
||||
Log.Verbose("Starting data load...");
|
||||
|
|
@ -44,11 +48,8 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
{
|
||||
LoadMultithreaded = true,
|
||||
CacheFileResources = true,
|
||||
#if NEVER // Lumina bug
|
||||
PanicOnSheetChecksumMismatch = true,
|
||||
#else
|
||||
PanicOnSheetChecksumMismatch = false,
|
||||
#endif
|
||||
RsvResolver = this.rsvResolver.TryResolve,
|
||||
DefaultExcelLanguage = this.Language.ToLumina(),
|
||||
};
|
||||
|
||||
|
|
@ -129,12 +130,12 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
#region Lumina Wrappers
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
||||
=> this.Excel.GetSheet<T>();
|
||||
public ExcelSheet<T> GetExcelSheet<T>(ClientLanguage? language = null, string? name = null) where T : struct, IExcelRow<T>
|
||||
=> this.Excel.GetSheet<T>(language?.ToLumina(), name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
||||
=> this.Excel.GetSheet<T>(language.ToLumina());
|
||||
public SubrowExcelSheet<T> GetSubrowExcelSheet<T>(ClientLanguage? language = null, string? name = null) where T : struct, IExcelSubrow<T>
|
||||
=> this.Excel.GetSubrowSheet<T>(language?.ToLumina(), name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileResource? GetFile(string path)
|
||||
|
|
@ -170,6 +171,7 @@ internal sealed class DataManager : IInternalDisposableService, IDataManager
|
|||
{
|
||||
this.luminaCancellationTokenSource.Cancel();
|
||||
this.GameData.Dispose();
|
||||
this.rsvResolver.Dispose();
|
||||
}
|
||||
|
||||
private class LauncherTroubleshootingInfo
|
||||
|
|
|
|||
22
Dalamud/Data/LuminaUtils.cs
Normal file
22
Dalamud/Data/LuminaUtils.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Data;
|
||||
|
||||
/// <summary>
|
||||
/// A helper class to easily resolve Lumina data within Dalamud.
|
||||
/// </summary>
|
||||
internal static class LuminaUtils
|
||||
{
|
||||
private static ExcelModule Module => Service<DataManager>.Get().Excel;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RowRef{T}"/> class using the default <see cref="ExcelModule"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
|
||||
/// <param name="rowId">The id of the row to resolve.</param>
|
||||
/// <returns>A new <see cref="RowRef{T}"/> object.</returns>
|
||||
public static RowRef<T> CreateRef<T>(uint rowId) where T : struct, IExcelRow<T>
|
||||
{
|
||||
return new(Module, rowId);
|
||||
}
|
||||
}
|
||||
51
Dalamud/Data/RsvResolver.cs
Normal file
51
Dalamud/Data/RsvResolver.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace Dalamud.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Provides functionality for resolving RSV strings.
|
||||
/// </summary>
|
||||
internal sealed unsafe class RsvResolver : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("RsvProvider");
|
||||
|
||||
private readonly Hook<LayoutWorld.Delegates.AddRsvString> addRsvStringHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RsvResolver"/> class.
|
||||
/// </summary>
|
||||
public RsvResolver()
|
||||
{
|
||||
this.addRsvStringHook = Hook<LayoutWorld.Delegates.AddRsvString>.FromAddress((nint)LayoutWorld.MemberFunctionPointers.AddRsvString, this.AddRsvStringDetour);
|
||||
|
||||
this.addRsvStringHook.Enable();
|
||||
}
|
||||
|
||||
private Dictionary<ReadOnlySeString, ReadOnlySeString> Lookup { get; } = [];
|
||||
|
||||
/// <summary>Attemps to resolve an RSV string.</summary>
|
||||
/// <inheritdoc cref="Lumina.Excel.ExcelModule.ResolveRsvDelegate"/>
|
||||
public bool TryResolve(ReadOnlySeString rsvString, out ReadOnlySeString resolvedString) =>
|
||||
this.Lookup.TryGetValue(rsvString, out resolvedString);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.addRsvStringHook.Dispose();
|
||||
}
|
||||
|
||||
private bool AddRsvStringDetour(LayoutWorld* @this, byte* rsvString, byte* resolvedString, nuint resolvedStringSize)
|
||||
{
|
||||
var rsv = new ReadOnlySeString(MemoryHelper.ReadRawNullTerminated((nint)rsvString));
|
||||
var resolved = new ReadOnlySeString(new ReadOnlySpan<byte>(resolvedString, (int)resolvedStringSize).ToArray());
|
||||
Log.Debug($"Resolving RSV \"{rsv}\" to \"{resolved}\".");
|
||||
this.Lookup[rsv] = resolved;
|
||||
return this.addRsvStringHook.Original(@this, rsvString, resolvedString, resolvedStringSize);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,6 @@ internal class AddonEventManagerAddressResolver : BaseAddressResolver
|
|||
/// <param name="scanner">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner scanner)
|
||||
{
|
||||
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE");
|
||||
this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ internal unsafe class PluginEventController : IDisposable
|
|||
{
|
||||
var paramKeyMatches = currentEvent->Param == eventEntry.ParamKey;
|
||||
var eventListenerAddressMatches = (nint)currentEvent->Listener == this.EventListener.Address;
|
||||
var eventTypeMatches = currentEvent->Type == eventType;
|
||||
var eventTypeMatches = currentEvent->State.EventType == eventType;
|
||||
|
||||
if (paramKeyMatches && eventListenerAddressMatches && eventTypeMatches)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using CheapLoc;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -27,49 +19,10 @@ namespace Dalamud.Game;
|
|||
/// Chat events and public helper functions.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class ChatHandlers : IServiceType
|
||||
internal partial class ChatHandlers : IServiceType
|
||||
{
|
||||
private static readonly ModuleLog Log = new("CHATHANDLER");
|
||||
private static readonly ModuleLog Log = new("ChatHandlers");
|
||||
|
||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
||||
{
|
||||
{
|
||||
ClientLanguage.Japanese,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.English,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.German,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) für (?<value>[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
|
||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.French,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Dalamud dalamud = Service<Dalamud>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
|
|
@ -92,6 +45,9 @@ internal class ChatHandlers : IServiceType
|
|||
/// </summary>
|
||||
public bool IsAutoUpdateComplete { get; private set; }
|
||||
|
||||
[GeneratedRegex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled)]
|
||||
private static partial Regex CompiledUrlRegex();
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
var textVal = message.TextValue;
|
||||
|
|
@ -100,7 +56,7 @@ internal class ChatHandlers : IServiceType
|
|||
this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||
{
|
||||
// This seems to be in the user block list - let's not show it
|
||||
Log.Debug("Blocklist triggered");
|
||||
Log.Debug("Filtered a message that contained a muted word");
|
||||
isHandled = true;
|
||||
return;
|
||||
}
|
||||
|
|
@ -127,41 +83,10 @@ internal class ChatHandlers : IServiceType
|
|||
return;
|
||||
#endif
|
||||
|
||||
if (type == XivChatType.RetainerSale)
|
||||
{
|
||||
foreach (var regex in this.retainerSaleRegexes[(ClientLanguage)this.dalamud.StartInfo.Language])
|
||||
{
|
||||
var matchInfo = regex.Match(message.TextValue);
|
||||
|
||||
// we no longer really need to do/validate the item matching since we read the id from the byte array
|
||||
// but we'd be checking the main match anyway
|
||||
var itemInfo = matchInfo.Groups["item"];
|
||||
if (!itemInfo.Success)
|
||||
continue;
|
||||
|
||||
var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
|
||||
if (itemLink == default)
|
||||
{
|
||||
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
|
||||
break;
|
||||
}
|
||||
|
||||
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}");
|
||||
|
||||
var valueInfo = matchInfo.Groups["value"];
|
||||
// not sure if using a culture here would work correctly, so just strip symbols instead
|
||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue))
|
||||
continue;
|
||||
|
||||
// Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var messageCopy = message;
|
||||
var senderCopy = sender;
|
||||
|
||||
var linkMatch = this.urlRegex.Match(message.TextValue);
|
||||
var linkMatch = CompiledUrlRegex().Match(message.TextValue);
|
||||
if (linkMatch.Value.Length > 0)
|
||||
this.LastLink = linkMatch.Value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Data;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Aetherytes;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -56,7 +59,7 @@ public interface IAetheryteEntry
|
|||
/// <summary>
|
||||
/// Gets the Aetheryte data related to this aetheryte.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData { get; }
|
||||
RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -103,5 +106,5 @@ internal sealed class AetheryteEntry : IAetheryteEntry
|
|||
public bool IsApartment => this.data.IsApartment;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Aetheryte> AetheryteData => new(this.AetheryteId);
|
||||
public RowRef<Lumina.Excel.Sheets.Aetheryte> AetheryteData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Aetheryte>(this.AetheryteId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
||||
|
|
@ -28,14 +26,9 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private BuddyList()
|
||||
{
|
||||
this.address = this.clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Buddy list address {Util.DescribeAddress(this.address.BuddyList)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -76,14 +69,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
internal IntPtr BuddyListAddress => this.address.BuddyList;
|
||||
|
||||
private static int BuddyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember>();
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => &UIState.Instance()->Buddy;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IBuddyMember? this[int index]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
||||
|
|
@ -45,17 +47,17 @@ public interface IBuddyMember
|
|||
/// <summary>
|
||||
/// Gets the Mount data related to this buddy. It should only be used with companion buddies.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData { get; }
|
||||
RowRef<Lumina.Excel.Sheets.Mount> MountData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Pet data related to this buddy. It should only be used with pet buddies.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData { get; }
|
||||
RowRef<Lumina.Excel.Sheets.Pet> PetData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Trust data related to this buddy. It should only be used with battle buddies.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData { get; }
|
||||
RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -94,13 +96,13 @@ internal unsafe class BuddyMember : IBuddyMember
|
|||
public uint DataID => this.Struct->DataId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData => new(this.DataID);
|
||||
public RowRef<Lumina.Excel.Sheets.Mount> MountData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Mount>(this.DataID);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData => new(this.DataID);
|
||||
public RowRef<Lumina.Excel.Sheets.Pet> PetData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Pet>(this.DataID);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData => new(this.DataID);
|
||||
public RowRef<Lumina.Excel.Sheets.DawnGrowMember> TrustData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.DawnGrowMember>(this.DataID);
|
||||
|
||||
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
|
|
@ -14,12 +13,14 @@ using Dalamud.Logging.Internal;
|
|||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Application.Network;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
using Action = System.Action;
|
||||
|
||||
|
|
@ -35,17 +36,18 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
|
||||
private readonly GameLifecycle lifecycle;
|
||||
private readonly ClientStateAddressResolver address;
|
||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||
private readonly Hook<EventFramework.Delegates.SetTerritoryTypeId> setupTerritoryTypeHook;
|
||||
private readonly Hook<UIModule.Delegates.HandlePacket> uiModuleHandlePacketHook;
|
||||
private readonly Hook<LogoutCallbackInterface.Delegates.OnLogout> onLogoutHook;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
||||
|
||||
|
||||
private bool lastConditionNone = true;
|
||||
private bool lastFramePvP;
|
||||
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private unsafe ClientState(TargetSigScanner sigScanner, Dalamud dalamud, GameLifecycle lifecycle)
|
||||
|
|
@ -58,21 +60,22 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
|
||||
this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language;
|
||||
|
||||
Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}");
|
||||
var setTerritoryTypeAddr = EventFramework.Addresses.SetTerritoryTypeId.Value;
|
||||
Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(setTerritoryTypeAddr)}");
|
||||
|
||||
this.setupTerritoryTypeHook = Hook<SetupTerritoryTypeDelegate>.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
||||
this.setupTerritoryTypeHook = Hook<EventFramework.Delegates.SetTerritoryTypeId>.FromAddress(setTerritoryTypeAddr, this.SetupTerritoryTypeDetour);
|
||||
this.uiModuleHandlePacketHook = Hook<UIModule.Delegates.HandlePacket>.FromAddress((nint)UIModule.StaticVirtualTablePointer->HandlePacket, this.UIModuleHandlePacketDetour);
|
||||
this.onLogoutHook = Hook<LogoutCallbackInterface.Delegates.OnLogout>.FromAddress((nint)LogoutCallbackInterface.StaticVirtualTablePointer->OnLogout, this.OnLogoutDetour);
|
||||
|
||||
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||
|
||||
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||
|
||||
this.setupTerritoryTypeHook.Enable();
|
||||
this.uiModuleHandlePacketHook.Enable();
|
||||
this.onLogoutHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private unsafe delegate void SetupTerritoryTypeDelegate(EventFramework* eventFramework, ushort terriType);
|
||||
private unsafe delegate void ProcessPacketPlayerSetupDelegate(nint a1, nint packet);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<ushort>? TerritoryChanged;
|
||||
|
|
@ -87,7 +90,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
public event Action? Login;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action? Logout;
|
||||
public event IClientState.LogoutDelegate? Logout;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action? EnterPvP;
|
||||
|
|
@ -110,7 +113,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
get
|
||||
{
|
||||
var agentMap = AgentMap.Instance();
|
||||
return agentMap != null ? AgentMap.Instance()->CurrentMapId : 0;
|
||||
return agentMap != null ? agentMap->CurrentMapId : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,10 +121,17 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
public IPlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?[0] as IPlayerCharacter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
|
||||
public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
public unsafe bool IsLoggedIn
|
||||
{
|
||||
get
|
||||
{
|
||||
var agentLobby = AgentLobby.Instance();
|
||||
return agentLobby != null && agentLobby->IsLoggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsPvP { get; private set; }
|
||||
|
|
@ -165,23 +175,47 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
{
|
||||
this.setupTerritoryTypeHook.Dispose();
|
||||
this.uiModuleHandlePacketHook.Dispose();
|
||||
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
||||
this.onLogoutHook.Dispose();
|
||||
|
||||
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
||||
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
||||
}
|
||||
|
||||
private unsafe void SetupTerritoryTypeDetour(EventFramework* eventFramework, ushort territoryType)
|
||||
{
|
||||
Log.Debug("TerritoryType changed: {0}", territoryType);
|
||||
|
||||
this.TerritoryType = territoryType;
|
||||
this.TerritoryChanged?.InvokeSafely(territoryType);
|
||||
|
||||
Log.Debug("TerritoryType changed: {0}", territoryType);
|
||||
var rowRef = LuminaUtils.CreateRef<TerritoryType>(territoryType);
|
||||
if (rowRef.IsValid)
|
||||
{
|
||||
var isPvP = rowRef.Value.IsPvpZone;
|
||||
if (isPvP != this.IsPvP)
|
||||
{
|
||||
this.IsPvP = isPvP;
|
||||
this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
|
||||
|
||||
if (this.IsPvP)
|
||||
{
|
||||
Log.Debug("EnterPvP");
|
||||
this.EnterPvP?.InvokeSafely();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("LeavePvP");
|
||||
this.LeavePvP?.InvokeSafely();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setupTerritoryTypeHook.Original(eventFramework, territoryType);
|
||||
}
|
||||
|
||||
private unsafe void UIModuleHandlePacketDetour(UIModule* thisPtr, UIModulePacketType type, uint uintParam, void* packet)
|
||||
{
|
||||
this.uiModuleHandlePacketHook!.Original(thisPtr, type, uintParam, packet);
|
||||
this.uiModuleHandlePacketHook.Original(thisPtr, type, uintParam, packet);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
|
@ -226,11 +260,6 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
}
|
||||
}
|
||||
|
||||
private void NetworkHandlersOnCfPop(ContentFinderCondition e)
|
||||
{
|
||||
this.CfPop?.InvokeSafely(e);
|
||||
}
|
||||
|
||||
private void FrameworkOnOnUpdateEvent(IFramework framework1)
|
||||
{
|
||||
var condition = Service<Conditions.Condition>.GetNullable();
|
||||
|
|
@ -244,40 +273,58 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
{
|
||||
Log.Debug("Is login");
|
||||
this.lastConditionNone = false;
|
||||
this.IsLoggedIn = true;
|
||||
this.Login?.InvokeSafely();
|
||||
gameGui.ResetUiHideState();
|
||||
|
||||
this.lifecycle.ResetLogout();
|
||||
}
|
||||
}
|
||||
|
||||
if (!condition.Any() && this.lastConditionNone == false)
|
||||
private unsafe void OnLogoutDetour(LogoutCallbackInterface* thisPtr, LogoutCallbackInterface.LogoutParams* logoutParams)
|
||||
{
|
||||
var gameGui = Service<GameGui>.GetNullable();
|
||||
|
||||
if (logoutParams != null)
|
||||
{
|
||||
Log.Debug("Is logout");
|
||||
this.lastConditionNone = true;
|
||||
this.IsLoggedIn = false;
|
||||
this.Logout?.InvokeSafely();
|
||||
gameGui.ResetUiHideState();
|
||||
|
||||
this.lifecycle.SetLogout();
|
||||
}
|
||||
|
||||
this.IsPvP = GameMain.IsInPvPArea();
|
||||
this.IsPvPExcludingDen = this.IsPvP && this.TerritoryType != 250;
|
||||
|
||||
if (this.IsPvP != this.lastFramePvP)
|
||||
{
|
||||
this.lastFramePvP = this.IsPvP;
|
||||
|
||||
if (this.IsPvP)
|
||||
try
|
||||
{
|
||||
this.EnterPvP?.InvokeSafely();
|
||||
var type = logoutParams->Type;
|
||||
var code = logoutParams->Code;
|
||||
|
||||
Log.Debug("Logout: Type {type}, Code {code}", type, code);
|
||||
|
||||
if (this.Logout is { } callback)
|
||||
{
|
||||
foreach (var action in callback.GetInvocationList().Cast<IClientState.LogoutDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
action(type, code);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception during raise of {handler}", action.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gameGui?.ResetUiHideState();
|
||||
this.lastConditionNone = true; // unblock login flag
|
||||
|
||||
this.lifecycle.SetLogout();
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.LeavePvP?.InvokeSafely();
|
||||
Log.Error(ex, "Exception during OnLogoutDetour");
|
||||
}
|
||||
}
|
||||
|
||||
this.onLogoutHook.Original(thisPtr, logoutParams);
|
||||
}
|
||||
|
||||
private void NetworkHandlersOnCfPop(ContentFinderCondition e)
|
||||
{
|
||||
this.CfPop?.InvokeSafely(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,7 +369,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat
|
|||
public event Action? Login;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action? Logout;
|
||||
public event IClientState.LogoutDelegate? Logout;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action? EnterPvP;
|
||||
|
|
@ -394,7 +441,7 @@ internal class ClientStatePluginScoped : IInternalDisposableService, IClientStat
|
|||
|
||||
private void LoginForward() => this.Login?.Invoke();
|
||||
|
||||
private void LogoutForward() => this.Logout?.Invoke();
|
||||
private void LogoutForward(int type, int code) => this.Logout?.Invoke(type, code);
|
||||
|
||||
private void EnterPvPForward() => this.EnterPvP?.Invoke();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,39 +7,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
{
|
||||
// Static offsets
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the actor table.
|
||||
/// </summary>
|
||||
public IntPtr ObjectTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
public IntPtr BuddyList { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of a pointer to the fate table.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a static address to a pointer, not the address of the table itself.
|
||||
/// </remarks>
|
||||
public IntPtr FateTablePtr { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Group Manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the local content id.
|
||||
/// </summary>
|
||||
public IntPtr LocalContentId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of job gauge data.
|
||||
/// </summary>
|
||||
public IntPtr JobGaugeData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the keyboard state.
|
||||
/// </summary>
|
||||
|
|
@ -50,17 +17,12 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
/// </summary>
|
||||
public IntPtr KeyboardStateIndexArray { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the condition flag array.
|
||||
/// </summary>
|
||||
public IntPtr ConditionFlags { get; private set; }
|
||||
|
||||
// Functions
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method which sets the territory type.
|
||||
/// Gets the address of the method which sets up the player.
|
||||
/// </summary>
|
||||
public IntPtr SetupTerritoryType { get; private set; }
|
||||
public IntPtr ProcessPacketPlayerSetup { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the method which polls the gamepads for data.
|
||||
|
|
@ -74,18 +36,7 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ??");
|
||||
|
||||
this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04");
|
||||
|
||||
this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F1 44 0F B7 41");
|
||||
|
||||
this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 77 71");
|
||||
|
||||
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 0D ?? ?? ?? ?? 48 8D 57 08");
|
||||
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8;
|
||||
|
||||
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 57 48 83 EC 20 0F B7 DA");
|
||||
this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3"); // not in cs struct
|
||||
|
||||
// These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
|
||||
// lea rcx, ds:1DB9F74h[rax*4] KeyboardState
|
||||
|
|
@ -93,8 +44,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
|
||||
this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
|
||||
|
||||
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 8B D3 E8 ?? ?? ?? ?? 32 C0 48 83 C4 20");
|
||||
|
||||
this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24");
|
||||
this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); // unnamed in cs
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,9 @@ internal sealed class Condition : IInternalDisposableService, ICondition
|
|||
private bool isDisposed;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Condition(ClientState clientState)
|
||||
private unsafe Condition()
|
||||
{
|
||||
var resolver = clientState.AddressResolver;
|
||||
this.Address = resolver.ConditionFlags;
|
||||
this.Address = (nint)FFXIVClientStructs.FFXIV.Client.Game.Conditions.Instance();
|
||||
|
||||
// Initialization
|
||||
for (var i = 0; i < MaxConditionEntries; i++)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ public interface IFate : IEquatable<IFate>
|
|||
/// <summary>
|
||||
/// Gets game data linked to this Fate.
|
||||
/// </summary>
|
||||
Lumina.Excel.GeneratedSheets.Fate GameData { get; }
|
||||
RowRef<Lumina.Excel.Sheets.Fate> GameData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time this <see cref="Fate"/> started.
|
||||
|
|
@ -71,11 +71,11 @@ public interface IFate : IEquatable<IFate>
|
|||
/// <summary>
|
||||
/// Gets a value indicating whether or not this <see cref="Fate"/> has a EXP bonus.
|
||||
/// </summary>
|
||||
[Api11ToDo("Remove this")]
|
||||
[Obsolete($"Use {nameof(HasBonus)} instead")]
|
||||
bool HasExpBonus { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this <see cref="Fate"/> has a EXP bonus.
|
||||
/// Gets a value indicating whether or not this <see cref="Fate"/> has a bonus.
|
||||
/// </summary>
|
||||
bool HasBonus { get; }
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ public interface IFate : IEquatable<IFate>
|
|||
/// <summary>
|
||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType { get; }
|
||||
RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of this Fate in memory.
|
||||
|
|
@ -192,7 +192,7 @@ internal unsafe partial class Fate : IFate
|
|||
public ushort FateId => this.Struct->FateId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Lumina.Excel.GeneratedSheets.Fate GameData => Service<DataManager>.Get().GetExcelSheet<Lumina.Excel.GeneratedSheets.Fate>().GetRow(this.FateId);
|
||||
public RowRef<Lumina.Excel.Sheets.Fate> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Fate>(this.FateId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int StartTimeEpoch => this.Struct->StartTimeEpoch;
|
||||
|
|
@ -222,8 +222,9 @@ internal unsafe partial class Fate : IFate
|
|||
public byte Progress => this.Struct->Progress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasExpBonus => this.Struct->IsBonus;
|
||||
|
||||
[Obsolete($"Use {nameof(HasBonus)} instead")]
|
||||
public bool HasExpBonus => this.Struct->IsExpBonus;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasBonus => this.Struct->IsBonus;
|
||||
|
||||
|
|
@ -248,5 +249,5 @@ internal unsafe partial class Fate : IFate
|
|||
/// <summary>
|
||||
/// Gets the territory this <see cref="Fate"/> is located in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> TerritoryType => new(this.Struct->TerritoryId);
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> TerritoryType => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ using System.Collections.Generic;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
||||
|
|
@ -20,55 +19,34 @@ namespace Dalamud.Game.ClientState.Fates;
|
|||
#pragma warning restore SA1015
|
||||
internal sealed partial class FateTable : IServiceType, IFateTable
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FateTable(ClientState clientState)
|
||||
private FateTable()
|
||||
{
|
||||
this.address = clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Fate table address {Util.DescribeAddress(this.address.FateTablePtr)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Address => this.address.FateTablePtr;
|
||||
public unsafe IntPtr Address => (nint)CSFateManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
var fateTable = this.FateTableAddress;
|
||||
if (fateTable == IntPtr.Zero)
|
||||
var fateManager = CSFateManager.Instance();
|
||||
if (fateManager == null)
|
||||
return 0;
|
||||
|
||||
// Sonar used this to check if the table was safe to read
|
||||
if (Struct->FateDirector == null)
|
||||
if (fateManager->FateDirector == null)
|
||||
return 0;
|
||||
|
||||
if (Struct->Fates.First == null || Struct->Fates.Last == null)
|
||||
if (fateManager->Fates.First == null || fateManager->Fates.Last == null)
|
||||
return 0;
|
||||
|
||||
return Struct->Fates.Count;
|
||||
return fateManager->Fates.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Fate table.
|
||||
/// </summary>
|
||||
internal unsafe IntPtr FateTableAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.address.FateTablePtr == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return *(IntPtr*)this.address.FateTablePtr;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFate? this[int index]
|
||||
{
|
||||
|
|
@ -99,11 +77,11 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
|||
if (index >= this.Length)
|
||||
return IntPtr.Zero;
|
||||
|
||||
var fateTable = this.FateTableAddress;
|
||||
if (fateTable == IntPtr.Zero)
|
||||
var fateManager = CSFateManager.Instance();
|
||||
if (fateManager == null)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (IntPtr)this.Struct->Fates[index].Value;
|
||||
return (IntPtr)fateManager->Fates[index].Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -16,25 +16,25 @@ public struct GamepadInput
|
|||
/// <summary>
|
||||
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x88)]
|
||||
[FieldOffset(0x78)]
|
||||
public int LeftStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Left analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x8C)]
|
||||
[FieldOffset(0x7C)]
|
||||
public int LeftStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
|
||||
/// </summary>
|
||||
[FieldOffset(0x90)]
|
||||
[FieldOffset(0x80)]
|
||||
public int RightStickX;
|
||||
|
||||
/// <summary>
|
||||
/// Right analogue stick's vertical value, -99 for down, 99 for up.
|
||||
/// </summary>
|
||||
[FieldOffset(0x94)]
|
||||
[FieldOffset(0x84)]
|
||||
public int RightStickY;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -43,7 +43,7 @@ public struct GamepadInput
|
|||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x98)]
|
||||
[FieldOffset(0x88)]
|
||||
public ushort ButtonsRaw;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -52,7 +52,7 @@ public struct GamepadInput
|
|||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0x9C)]
|
||||
[FieldOffset(0x8C)]
|
||||
public ushort ButtonsPressed;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -61,7 +61,7 @@ public struct GamepadInput
|
|||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0xA0)]
|
||||
[FieldOffset(0x90)]
|
||||
public ushort ButtonsReleased;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -70,6 +70,6 @@ public struct GamepadInput
|
|||
/// <remarks>
|
||||
/// This is a bitfield.
|
||||
/// </remarks>
|
||||
[FieldOffset(0xA4)]
|
||||
[FieldOffset(0x94)]
|
||||
public ushort ButtonsRepeat;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ using Dalamud.Game.ClientState.JobGauge.Types;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using CSJobGaugeManager = FFXIVClientStructs.FFXIV.Client.Game.JobGaugeManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge;
|
||||
|
||||
|
|
@ -21,18 +20,15 @@ namespace Dalamud.Game.ClientState.JobGauge;
|
|||
#pragma warning restore SA1015
|
||||
internal class JobGauges : IServiceType, IJobGauges
|
||||
{
|
||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||
private Dictionary<Type, JobGaugeBase> cache = [];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private JobGauges(ClientState clientState)
|
||||
private JobGauges()
|
||||
{
|
||||
this.Address = clientState.AddressResolver.JobGaugeData;
|
||||
|
||||
Log.Verbose($"JobGaugeData address {Util.DescribeAddress(this.Address)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Address { get; }
|
||||
public unsafe IntPtr Address => (nint)(&CSJobGaugeManager.Instance()->CurrentGauge);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Get<T>() where T : JobGaugeBase
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ public unsafe class SMNGauge : JobGaugeBase<SummonerGauge>
|
|||
|
||||
/// <summary>
|
||||
/// Gets the summon that will return after the current summon expires.
|
||||
/// This maps to the <see cref="Lumina.Excel.GeneratedSheets.Pet"/> sheet.
|
||||
/// This maps to the <see cref="Lumina.Excel.Sheets.Pet"/> sheet.
|
||||
/// </summary>
|
||||
public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
|
||||
/// This maps to the <see cref="Lumina.Excel.GeneratedSheets.PetMirage"/> sheet.
|
||||
/// This maps to the <see cref="Lumina.Excel.Sheets.PetMirage"/> sheet.
|
||||
/// </summary>
|
||||
public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,17 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects;
|
||||
|
||||
|
|
@ -29,10 +31,12 @@ namespace Dalamud.Game.ClientState.Objects;
|
|||
#pragma warning restore SA1015
|
||||
internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||
{
|
||||
private const int ObjectTableLength = 599;
|
||||
private static readonly ModuleLog Log = new("ObjectTable");
|
||||
|
||||
private static int objectTableLength;
|
||||
|
||||
private readonly ClientState clientState;
|
||||
private readonly CachedEntry[] cachedObjectTable = new CachedEntry[ObjectTableLength];
|
||||
private readonly CachedEntry[] cachedObjectTable;
|
||||
|
||||
private readonly ObjectPool<Enumerator> multiThreadedEnumerators =
|
||||
new DefaultObjectPoolProvider().Create<Enumerator>();
|
||||
|
|
@ -46,29 +50,30 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
this.clientState = clientState;
|
||||
|
||||
var nativeObjectTableAddress = (CSGameObject**)this.clientState.AddressResolver.ObjectTable;
|
||||
var nativeObjectTable = CSGameObjectManager.Instance()->Objects.IndexSorted;
|
||||
objectTableLength = nativeObjectTable.Length;
|
||||
|
||||
this.cachedObjectTable = new CachedEntry[objectTableLength];
|
||||
for (var i = 0; i < this.cachedObjectTable.Length; i++)
|
||||
this.cachedObjectTable[i] = new(nativeObjectTableAddress, i);
|
||||
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
|
||||
|
||||
for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++)
|
||||
this.frameworkThreadEnumerators[i] = new(this, i);
|
||||
|
||||
Log.Verbose($"Object table address {Util.DescribeAddress(this.clientState.AddressResolver.ObjectTable)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint Address
|
||||
public unsafe nint Address
|
||||
{
|
||||
get
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
|
||||
return this.clientState.AddressResolver.ObjectTable;
|
||||
return (nint)(&CSGameObjectManager.Instance()->Objects);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length => ObjectTableLength;
|
||||
public int Length => objectTableLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? this[int index]
|
||||
|
|
@ -77,7 +82,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
|
||||
return index is >= ObjectTableLength or < 0 ? null : this.cachedObjectTable[index].Update();
|
||||
return (index >= objectTableLength || index < 0) ? null : this.cachedObjectTable[index].Update();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +125,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
|
||||
return index is < 0 or >= ObjectTableLength ? nint.Zero : (nint)this.cachedObjectTable[index].Address;
|
||||
return (index >= objectTableLength || index < 0) ? nint.Zero : (nint)this.cachedObjectTable[index].Address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -172,33 +177,21 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
}
|
||||
|
||||
/// <summary>Stores an object table entry, with preallocated concrete types.</summary>
|
||||
internal readonly unsafe struct CachedEntry
|
||||
/// <remarks>Initializes a new instance of the <see cref="CachedEntry"/> struct.</remarks>
|
||||
/// <param name="gameObjectPtr">A pointer to the object table entry this entry should be pointing to.</param>
|
||||
internal readonly unsafe struct CachedEntry(Pointer<CSGameObject>* gameObjectPtr)
|
||||
{
|
||||
private readonly CSGameObject** gameObjectPtrPtr;
|
||||
private readonly PlayerCharacter playerCharacter;
|
||||
private readonly BattleNpc battleNpc;
|
||||
private readonly Npc npc;
|
||||
private readonly EventObj eventObj;
|
||||
private readonly GameObject gameObject;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="CachedEntry"/> struct.</summary>
|
||||
/// <param name="ownerTable">The object table that this entry should be pointing to.</param>
|
||||
/// <param name="slot">The slot index inside the table.</param>
|
||||
public CachedEntry(CSGameObject** ownerTable, int slot)
|
||||
{
|
||||
this.gameObjectPtrPtr = ownerTable + slot;
|
||||
this.playerCharacter = new(nint.Zero);
|
||||
this.battleNpc = new(nint.Zero);
|
||||
this.npc = new(nint.Zero);
|
||||
this.eventObj = new(nint.Zero);
|
||||
this.gameObject = new(nint.Zero);
|
||||
}
|
||||
private readonly PlayerCharacter playerCharacter = new(nint.Zero);
|
||||
private readonly BattleNpc battleNpc = new(nint.Zero);
|
||||
private readonly Npc npc = new(nint.Zero);
|
||||
private readonly EventObj eventObj = new(nint.Zero);
|
||||
private readonly GameObject gameObject = new(nint.Zero);
|
||||
|
||||
/// <summary>Gets the address of the underlying native object. May be null.</summary>
|
||||
public CSGameObject* Address
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => *this.gameObjectPtrPtr;
|
||||
get => gameObjectPtr->Value;
|
||||
}
|
||||
|
||||
/// <summary>Updates and gets the wrapped game object pointed by this struct.</summary>
|
||||
|
|
@ -284,11 +277,11 @@ internal sealed partial class ObjectTable
|
|||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.index == ObjectTableLength)
|
||||
if (this.index == objectTableLength)
|
||||
return false;
|
||||
|
||||
var cache = this.owner!.cachedObjectTable.AsSpan();
|
||||
for (this.index++; this.index < ObjectTableLength; this.index++)
|
||||
for (this.index++; this.index < objectTableLength; this.index++)
|
||||
{
|
||||
if (cache[this.index].Update() is { } ao)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
|
||||
|
|
@ -16,14 +12,14 @@ namespace Dalamud.Game.ClientState.Objects.SubKinds;
|
|||
public interface IPlayerCharacter : IBattleChara
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
|
||||
/// Gets the current <see cref="RowRef{T}">world</see> of the character.
|
||||
/// </summary>
|
||||
ExcelResolver<World> CurrentWorld { get; }
|
||||
RowRef<World> CurrentWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
|
||||
/// Gets the home <see cref="RowRef{T}">world</see> of the character.
|
||||
/// </summary>
|
||||
ExcelResolver<World> HomeWorld { get; }
|
||||
RowRef<World> HomeWorld { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -42,10 +38,10 @@ internal unsafe class PlayerCharacter : BattleChara, IPlayerCharacter
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExcelResolver<World> CurrentWorld => new(this.Struct->CurrentWorld);
|
||||
public RowRef<World> CurrentWorld => LuminaUtils.CreateRef<World>(this.Struct->CurrentWorld);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExcelResolver<World> HomeWorld => new(this.Struct->HomeWorld);
|
||||
public RowRef<World> HomeWorld => LuminaUtils.CreateRef<World>(this.Struct->HomeWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target actor ID of the PlayerCharacter.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
|
|
@ -61,7 +63,7 @@ public interface ICharacter : IGameObject
|
|||
/// <summary>
|
||||
/// Gets the ClassJob of this Chara.
|
||||
/// </summary>
|
||||
public ExcelResolver<ClassJob> ClassJob { get; }
|
||||
public RowRef<ClassJob> ClassJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this Chara.
|
||||
|
|
@ -87,7 +89,7 @@ public interface ICharacter : IGameObject
|
|||
/// <summary>
|
||||
/// Gets the current online status of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<OnlineStatus> OnlineStatus { get; }
|
||||
public RowRef<OnlineStatus> OnlineStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status flags.
|
||||
|
|
@ -97,14 +99,14 @@ public interface ICharacter : IGameObject
|
|||
/// <summary>
|
||||
/// Gets the current mount for this character. Will be <c>null</c> if the character doesn't have a mount.
|
||||
/// </summary>
|
||||
public ExcelResolver<Mount>? CurrentMount { get; }
|
||||
public RowRef<Mount>? CurrentMount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current minion summoned for this character. Will be <c>null</c> if the character doesn't have a minion.
|
||||
/// This method *will* return information about a spawned (but invisible) minion, e.g. if the character is riding a
|
||||
/// mount.
|
||||
/// </summary>
|
||||
public ExcelResolver<Companion>? CurrentMinion { get; }
|
||||
public RowRef<Companion>? CurrentMinion { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -150,7 +152,7 @@ internal unsafe class Character : GameObject, ICharacter
|
|||
public byte ShieldPercentage => this.Struct->CharacterData.ShieldValue;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExcelResolver<ClassJob> ClassJob => new(this.Struct->CharacterData.ClassJob);
|
||||
public RowRef<ClassJob> ClassJob => LuminaUtils.CreateRef<ClassJob>(this.Struct->CharacterData.ClassJob);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte Level => this.Struct->CharacterData.Level;
|
||||
|
|
@ -159,7 +161,7 @@ internal unsafe class Character : GameObject, ICharacter
|
|||
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString CompanyTag => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->FreeCompanyTag[0]), 6);
|
||||
public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target object ID of the character.
|
||||
|
|
@ -170,7 +172,7 @@ internal unsafe class Character : GameObject, ICharacter
|
|||
public uint NameId => this.Struct->NameId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ExcelResolver<OnlineStatus> OnlineStatus => new(this.Struct->CharacterData.OnlineStatus);
|
||||
public RowRef<OnlineStatus> OnlineStatus => LuminaUtils.CreateRef<OnlineStatus>(this.Struct->CharacterData.OnlineStatus);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status flags.
|
||||
|
|
@ -186,28 +188,28 @@ internal unsafe class Character : GameObject, ICharacter
|
|||
(this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None);
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExcelResolver<Mount>? CurrentMount
|
||||
public RowRef<Mount>? CurrentMount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Struct->IsNotMounted()) return null; // just for safety.
|
||||
|
||||
var mountId = this.Struct->Mount.MountId;
|
||||
return mountId == 0 ? null : new ExcelResolver<Mount>(mountId);
|
||||
return mountId == 0 ? null : LuminaUtils.CreateRef<Mount>(mountId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExcelResolver<Companion>? CurrentMinion
|
||||
public RowRef<Companion>? CurrentMinion
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.Struct->CompanionObject != null)
|
||||
return new ExcelResolver<Companion>(this.Struct->CompanionObject->BaseId);
|
||||
return LuminaUtils.CreateRef<Companion>(this.Struct->CompanionObject->BaseId);
|
||||
|
||||
// this is only present if a minion is summoned but hidden (e.g. the player's on a mount).
|
||||
var hiddenCompanionId = this.Struct->CompanionData.CompanionId;
|
||||
return hiddenCompanionId == 0 ? null : new ExcelResolver<Companion>(hiddenCompanionId);
|
||||
return hiddenCompanionId == 0 ? null : LuminaUtils.CreateRef<Companion>(hiddenCompanionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ internal partial class GameObject
|
|||
internal unsafe partial class GameObject : IGameObject
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->Name[0]), 64);
|
||||
public SeString Name => SeString.Parse(this.Struct->Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong GameObjectId => this.Struct->GetGameObjectId();
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ using System.Runtime.InteropServices;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
|
|
@ -28,14 +27,9 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PartyList()
|
||||
{
|
||||
this.address = this.clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Group manager address {Util.DescribeAddress(this.address.GroupManager)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -48,7 +42,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GroupManagerAddress => this.address.GroupManager;
|
||||
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Game.ClientState.Statuses;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Memory;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -71,12 +73,12 @@ public interface IPartyMember
|
|||
/// <summary>
|
||||
/// Gets the territory this party member is located in.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> Territory { get; }
|
||||
RowRef<Lumina.Excel.Sheets.TerritoryType> Territory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the World this party member resides in.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.World> World { get; }
|
||||
RowRef<Lumina.Excel.Sheets.World> World { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayname of this party member.
|
||||
|
|
@ -91,7 +93,7 @@ public interface IPartyMember
|
|||
/// <summary>
|
||||
/// Gets the classjob of this party member.
|
||||
/// </summary>
|
||||
ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob { get; }
|
||||
RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this party member.
|
||||
|
|
@ -169,17 +171,17 @@ internal unsafe class PartyMember : IPartyMember
|
|||
/// <summary>
|
||||
/// Gets the territory this party member is located in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> Territory => new(this.Struct->TerritoryType);
|
||||
public RowRef<Lumina.Excel.Sheets.TerritoryType> Territory => LuminaUtils.CreateRef<Lumina.Excel.Sheets.TerritoryType>(this.Struct->TerritoryType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the World this party member resides in.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> World => new(this.Struct->HomeWorld);
|
||||
public RowRef<Lumina.Excel.Sheets.World> World => LuminaUtils.CreateRef<Lumina.Excel.Sheets.World>(this.Struct->HomeWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the displayname of this party member.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref Struct->Name[0]), 0x40);
|
||||
public SeString Name => SeString.Parse(this.Struct->Name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sex of this party member.
|
||||
|
|
@ -189,7 +191,7 @@ internal unsafe class PartyMember : IPartyMember
|
|||
/// <summary>
|
||||
/// Gets the classjob of this party member.
|
||||
/// </summary>
|
||||
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob);
|
||||
public RowRef<Lumina.Excel.Sheets.ClassJob> ClassJob => LuminaUtils.CreateRef<Lumina.Excel.Sheets.ClassJob>(this.Struct->ClassJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this party member.
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Resolvers;
|
||||
|
||||
/// <summary>
|
||||
/// This object resolves a rowID within an Excel sheet.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of Lumina sheet to resolve.</typeparam>
|
||||
public class ExcelResolver<T> where T : ExcelRow
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExcelResolver{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the classJob.</param>
|
||||
internal ExcelResolver(uint id)
|
||||
{
|
||||
this.Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID to be resolved.
|
||||
/// </summary>
|
||||
public uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets GameData linked to this excel row.
|
||||
/// </summary>
|
||||
public T? GameData => Service<DataManager>.Get().GetExcelSheet<T>()?.GetRow(this.Id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets GameData linked to this excel row with the specified language.
|
||||
/// </summary>
|
||||
/// <param name="language">The language.</param>
|
||||
/// <returns>The ExcelRow in the specified language.</returns>
|
||||
public T? GetWithLanguage(ClientLanguage language) => Service<DataManager>.Get().GetExcelSheet<T>(language)?.GetRow(this.Id);
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
|
||||
using Lumina.Excel;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Statuses;
|
||||
|
||||
|
|
@ -31,7 +33,7 @@ public unsafe class Status
|
|||
/// <summary>
|
||||
/// Gets the GameData associated with this status.
|
||||
/// </summary>
|
||||
public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver<Lumina.Excel.GeneratedSheets.Status>(this.Struct->StatusId).GameData;
|
||||
public RowRef<Lumina.Excel.Sheets.Status> GameData => LuminaUtils.CreateRef<Lumina.Excel.Sheets.Status>(this.Struct->StatusId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter value of the status.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ internal sealed class GameConfigAddressResolver : BaseAddressResolver
|
|||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner scanner)
|
||||
{
|
||||
this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E");
|
||||
this.ConfigChangeAddress = scanner.ScanText("E8 ?? ?? ?? ?? 48 8B 3F 49 3B 3E"); // unnamed in CS
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ internal class DutyStateAddressResolver : BaseAddressResolver
|
|||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08");
|
||||
this.ContentDirectorNetworkMessage = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 33 FF 48 8B D9 41 0F B7 08"); // unnamed in cs
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -16,6 +15,8 @@ using Dalamud.Logging.Internal;
|
|||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using CSFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -31,11 +32,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
private readonly Stopwatch updateStopwatch = new();
|
||||
private readonly HitchDetector hitchDetector;
|
||||
|
||||
private readonly Hook<OnUpdateDetour> updateHook;
|
||||
private readonly Hook<OnRealDestroyDelegate> destroyHook;
|
||||
private readonly Hook<CSFramework.Delegates.Tick> updateHook;
|
||||
private readonly Hook<CSFramework.Delegates.Destroy> destroyHook;
|
||||
|
||||
private readonly FrameworkAddressResolver addressResolver;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameLifecycle lifecycle = Service<GameLifecycle>.Get();
|
||||
|
||||
|
|
@ -51,13 +50,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
private ulong tickCounter;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Framework(TargetSigScanner sigScanner)
|
||||
private unsafe Framework()
|
||||
{
|
||||
this.hitchDetector = new HitchDetector("FrameworkUpdate", this.configuration.FrameworkUpdateHitch);
|
||||
|
||||
this.addressResolver = new FrameworkAddressResolver();
|
||||
this.addressResolver.Setup(sigScanner);
|
||||
|
||||
this.frameworkDestroy = new();
|
||||
this.frameworkThreadTaskScheduler = new();
|
||||
this.FrameworkThreadTaskFactory = new(
|
||||
|
|
@ -66,23 +62,13 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
TaskContinuationOptions.None,
|
||||
this.frameworkThreadTaskScheduler);
|
||||
|
||||
this.updateHook = Hook<OnUpdateDetour>.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.destroyHook = Hook<OnRealDestroyDelegate>.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
this.updateHook = Hook<CSFramework.Delegates.Tick>.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Tick, this.HandleFrameworkUpdate);
|
||||
this.destroyHook = Hook<CSFramework.Delegates.Destroy>.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Destroy, this.HandleFrameworkDestroy);
|
||||
|
||||
this.updateHook.Enable();
|
||||
this.destroyHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used during the native Framework::destroy.
|
||||
/// </summary>
|
||||
/// <param name="framework">The native Framework address.</param>
|
||||
/// <returns>A value indicating if the call was successful.</returns>
|
||||
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IFramework.OnUpdateDelegate? Update;
|
||||
|
||||
|
|
@ -390,7 +376,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
}
|
||||
}
|
||||
|
||||
private bool HandleFrameworkUpdate(IntPtr framework)
|
||||
private unsafe bool HandleFrameworkUpdate(CSFramework* thisPtr)
|
||||
{
|
||||
this.frameworkThreadTaskScheduler.BoundThread ??= Thread.CurrentThread;
|
||||
|
||||
|
|
@ -483,10 +469,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
this.hitchDetector.Stop();
|
||||
|
||||
original:
|
||||
return this.updateHook.OriginalDisposeSafe(framework);
|
||||
return this.updateHook.OriginalDisposeSafe(thisPtr);
|
||||
}
|
||||
|
||||
private bool HandleFrameworkDestroy(IntPtr framework)
|
||||
private unsafe bool HandleFrameworkDestroy(CSFramework* thisPtr)
|
||||
{
|
||||
this.frameworkDestroy.Cancel();
|
||||
this.DispatchUpdateEvents = false;
|
||||
|
|
@ -504,7 +490,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
ServiceManager.WaitForServiceUnload();
|
||||
Log.Information("Framework::Destroy OK!");
|
||||
|
||||
return this.destroyHook.OriginalDisposeSafe(framework);
|
||||
return this.destroyHook.OriginalDisposeSafe(thisPtr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
internal sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address for the function that is called once the Framework is destroyed.
|
||||
/// </summary>
|
||||
public IntPtr DestroyAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the function that is called once the Framework is free'd.
|
||||
/// </summary>
|
||||
public IntPtr FreeAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function that is called every tick.
|
||||
/// </summary>
|
||||
public IntPtr TickAddress { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.SetupFramework(sig);
|
||||
}
|
||||
|
||||
private void SetupFramework(ISigScanner scanner)
|
||||
{
|
||||
this.DestroyAddress =
|
||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF");
|
||||
|
||||
this.FreeAddress =
|
||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ?? 48 85 C9");
|
||||
|
||||
this.TickAddress =
|
||||
scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,19 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType;
|
||||
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType;
|
||||
using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan;
|
||||
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
|
|
@ -27,14 +35,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
{
|
||||
private static readonly ModuleLog Log = new("ChatGui");
|
||||
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
private readonly Hook<InventoryItem.Delegates.Copy> inventoryItemCopyHook;
|
||||
private readonly Hook<LogViewer.Delegates.HandleLinkClick> handleLinkClickHook;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -42,29 +48,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
private ImmutableDictionary<(string PluginName, uint CommandId), Action<uint, SeString>>? dalamudLinkHandlersCopy;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ChatGui(TargetSigScanner sigScanner)
|
||||
private ChatGui()
|
||||
{
|
||||
this.address = new ChatGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress((nint)RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
||||
this.inventoryItemCopyHook = Hook<InventoryItem.Delegates.Copy>.FromAddress((nint)InventoryItem.StaticVirtualTablePointer->Copy, this.InventoryItemCopyDetour);
|
||||
this.handleLinkClickHook = Hook<LogViewer.Delegates.HandleLinkClick>.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour);
|
||||
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
this.inventoryItemCopyHook.Enable();
|
||||
this.handleLinkClickHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate uint PrintMessageDelegate(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, byte silent);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageDelegate? ChatMessage;
|
||||
|
||||
|
|
@ -78,7 +75,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
public uint LastLinkedItemId { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
|
@ -106,8 +103,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
this.inventoryItemCopyHook.Dispose();
|
||||
this.handleLinkClickHook.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -171,8 +168,10 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
|
||||
var sender = Utf8String.FromSequence(chat.Name.Encode());
|
||||
var message = Utf8String.FromSequence(replacedMessage.BuiltString.Encode());
|
||||
|
||||
var targetChannel = chat.Type ?? this.configuration.GeneralChatType;
|
||||
|
||||
this.HandlePrintMessageDetour(RaptureLogModule.Instance(), chat.Type, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0));
|
||||
this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0));
|
||||
|
||||
sender->Dtor(true);
|
||||
message->Dtor(true);
|
||||
|
|
@ -275,21 +274,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
});
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
|
||||
{
|
||||
this.inventoryItemCopyHook.Original(thisPtr, otherPtr);
|
||||
|
||||
try
|
||||
{
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
this.LastLinkedItemId = otherPtr->ItemId;
|
||||
this.LastLinkedItemFlags = (byte)otherPtr->Flags;
|
||||
|
||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||
// Log.Verbose($"InventoryItemCopyDetour {thisPtr} {otherPtr} - linked:{this.LastLinkedItemId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
Log.Error(ex, "Exception in InventoryItemCopyHook");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -299,58 +297,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
|
||||
try
|
||||
{
|
||||
var originalSenderData = sender->AsSpan().ToArray();
|
||||
var originalMessageData = message->AsSpan().ToArray();
|
||||
var parsedSender = SeString.Parse(sender->AsSpan());
|
||||
var parsedMessage = SeString.Parse(message->AsSpan());
|
||||
|
||||
var parsedSender = SeString.Parse(originalSenderData);
|
||||
var parsedMessage = SeString.Parse(originalMessageData);
|
||||
var terminatedSender = parsedSender.EncodeWithNullTerminator();
|
||||
var terminatedMessage = parsedMessage.EncodeWithNullTerminator();
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
|
||||
var invocationList = this.CheckMessageHandled!.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
if (this.CheckMessageHandled is { } handledCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as IChatGui.OnCheckMessageHandledDelegate;
|
||||
messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isHandled)
|
||||
{
|
||||
invocationList = this.ChatMessage!.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
foreach (var action in handledCallback.GetInvocationList().Cast<IChatGui.OnCheckMessageHandledDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as IChatGui.OnMessageDelegate;
|
||||
messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name);
|
||||
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", action.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var possiblyModifiedSenderData = parsedSender.Encode();
|
||||
var possiblyModifiedMessageData = parsedMessage.Encode();
|
||||
|
||||
if (!Util.FastByteArrayCompare(originalSenderData, possiblyModifiedSenderData))
|
||||
if (!isHandled && this.ChatMessage is { } callback)
|
||||
{
|
||||
Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(originalSenderData)} -> {parsedSender}");
|
||||
foreach (var action in callback.GetInvocationList().Cast<IChatGui.OnMessageDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", action.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var possiblyModifiedSenderData = parsedSender.EncodeWithNullTerminator();
|
||||
var possiblyModifiedMessageData = parsedMessage.EncodeWithNullTerminator();
|
||||
|
||||
if (!terminatedSender.SequenceEqual(possiblyModifiedSenderData))
|
||||
{
|
||||
Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(terminatedSender)} -> {parsedSender}");
|
||||
sender->SetString(possiblyModifiedSenderData);
|
||||
}
|
||||
|
||||
if (!Util.FastByteArrayCompare(originalMessageData, possiblyModifiedMessageData))
|
||||
if (!terminatedMessage.SequenceEqual(possiblyModifiedMessageData))
|
||||
{
|
||||
Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(originalMessageData)} -> {parsedMessage}");
|
||||
Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(terminatedMessage)} -> {parsedMessage}");
|
||||
message->SetString(possiblyModifiedMessageData);
|
||||
}
|
||||
|
||||
|
|
@ -374,42 +371,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
return messageId;
|
||||
}
|
||||
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||
private void HandleLinkClickDetour(LogViewer* thisPtr, LinkData* linkData)
|
||||
{
|
||||
if ((Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.handleLinkClickHook.Original(thisPtr, linkData);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
var sb = LuminaSeStringBuilder.SharedPool.Get();
|
||||
try
|
||||
{
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||
// read until link terminator
|
||||
foreach (var payload in seStringSpan)
|
||||
{
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
sb.Append(payload);
|
||||
|
||||
if (payload.Type == ReadOnlySePayloadType.Macro &&
|
||||
payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link &&
|
||||
payload.TryGetExpression(out var expr1) &&
|
||||
expr1.TryGetInt(out var expr1Val) &&
|
||||
expr1Val == (int)LinkMacroPayloadType.Terminator)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
var seStr = SeString.Parse(sb.ToArray());
|
||||
if (seStr.Payloads.Count == 0 || seStr.Payloads[0] is not DalamudLinkPayload link)
|
||||
return;
|
||||
|
||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||
var seStr = MemoryHelper.ReadSeStringNullTerminated(payloadPtr);
|
||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value))
|
||||
{
|
||||
if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value))
|
||||
{
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
value.Invoke(link.CommandId, new SeString(payloads));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
value.Invoke(link.CommandId, seStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
||||
}
|
||||
finally
|
||||
{
|
||||
LuminaSeStringBuilder.SharedPool.Return(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -451,7 +463,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
|||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
||||
public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags;
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
internal sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native PopulateItemLinkObject method.
|
||||
/// </summary>
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native InteractableLinkClicked method.
|
||||
/// </summary>
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
this.PopulateItemLinkObject = sig.ScanText("E8 ?? ?? ?? ?? 8B 4E FC");
|
||||
|
||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 4B ?? E8 ?? ?? ?? ?? 33 D2");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Gui.ContextMenu;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Game.Network.Structures.InfoProxy;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Gui.ContextMenu;
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ public sealed unsafe class MenuTargetDefault : MenuTarget
|
|||
/// <summary>
|
||||
/// Gets the home world id of the target.
|
||||
/// </summary>
|
||||
public ExcelResolver<World> TargetHomeWorld => new((uint)this.Context->TargetHomeWorldId);
|
||||
public RowRef<World> TargetHomeWorld => LuminaUtils.CreateRef<World>((uint)this.Context->TargetHomeWorldId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently targeted character. Only shows up for specific targets, like friends, party finder listings, or party members.
|
||||
|
|
|
|||
|
|
@ -85,8 +85,7 @@ public interface IDtrBarEntry : IReadOnlyDtrBarEntry
|
|||
/// <summary>
|
||||
/// Class representing an entry in the server info bar.
|
||||
/// </summary>
|
||||
[Api11ToDo(Api11ToDoAttribute.MakeInternal)]
|
||||
public sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
||||
internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry
|
||||
{
|
||||
private readonly DalamudConfiguration configuration;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -8,6 +9,9 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
|
@ -19,62 +23,21 @@ namespace Dalamud.Game.Gui.FlyText;
|
|||
internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
||||
{
|
||||
/// <summary>
|
||||
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
|
||||
/// The hook that fires when the game creates a fly text element.
|
||||
/// </summary>
|
||||
private readonly AddFlyTextDelegate addFlyTextNative;
|
||||
|
||||
/// <summary>
|
||||
/// The hook that fires when the game creates a fly text element. See <see cref="FlyTextGuiAddressResolver.CreateFlyText"/>.
|
||||
/// </summary>
|
||||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||
private readonly Hook<AddonFlyText.Delegates.CreateFlyText> createFlyTextHook;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FlyTextGui(TargetSigScanner sigScanner)
|
||||
private unsafe FlyTextGui(TargetSigScanner sigScanner)
|
||||
{
|
||||
this.Address = new FlyTextGuiAddressResolver();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||
this.createFlyTextHook = Hook<AddonFlyText.Delegates.CreateFlyText>.FromAddress(AddonFlyText.Addresses.CreateFlyText.Value, this.CreateFlyTextDetour);
|
||||
|
||||
this.createFlyTextHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native CreateFlyText function's hook.
|
||||
/// </summary>
|
||||
private delegate IntPtr CreateFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
uint damageTypeIcon,
|
||||
IntPtr text1,
|
||||
float yOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native AddFlyText function pointer.
|
||||
/// </summary>
|
||||
private delegate void AddFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
uint actorIndex,
|
||||
uint messageMax,
|
||||
IntPtr numbers,
|
||||
uint offsetNum,
|
||||
uint offsetNumMax,
|
||||
IntPtr strings,
|
||||
uint offsetStr,
|
||||
uint offsetStrMax,
|
||||
int unknown);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated;
|
||||
|
||||
private FlyTextGuiAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
|
|
@ -87,26 +50,16 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon, uint damageTypeIcon)
|
||||
{
|
||||
// Known valid flytext region within the atk arrays
|
||||
var numIndex = 30;
|
||||
var strIndex = 27;
|
||||
var numOffset = 161u;
|
||||
var strOffset = 28u;
|
||||
|
||||
// Get the UI module and flytext addon pointers
|
||||
var gameGui = Service<GameGui>.GetNullable();
|
||||
if (gameGui == null)
|
||||
return;
|
||||
|
||||
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||
var flytext = gameGui.GetAddonByName("_FlyText");
|
||||
|
||||
if (ui == null || flytext == IntPtr.Zero)
|
||||
var flytext = (AddonFlyText*)RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText");
|
||||
if (flytext == null)
|
||||
return;
|
||||
|
||||
// Get the number and string arrays we need
|
||||
var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder;
|
||||
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||
var numArray = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.FlyText);
|
||||
var strArray = AtkStage.Instance()->GetStringArrayData(StringArrayType.FlyText);
|
||||
|
||||
// Write the values to the arrays using a known valid flytext region
|
||||
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
||||
|
|
@ -120,66 +73,47 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
numArray->IntArray[numOffset + 8] = 0; // Unknown
|
||||
numArray->IntArray[numOffset + 9] = 0; // Unknown, has something to do with yOffset
|
||||
|
||||
strArray->SetValue((int)strOffset + 0, text1.Encode(), false, true, false);
|
||||
strArray->SetValue((int)strOffset + 1, text2.Encode(), false, true, false);
|
||||
|
||||
this.addFlyTextNative(
|
||||
flytext,
|
||||
actorIndex,
|
||||
1,
|
||||
(IntPtr)numArray,
|
||||
numOffset,
|
||||
10,
|
||||
(IntPtr)strArray,
|
||||
strOffset,
|
||||
2,
|
||||
0);
|
||||
strArray->SetValue((int)strOffset + 0, text1.EncodeWithNullTerminator(), false, true, false);
|
||||
strArray->SetValue((int)strOffset + 1, text2.EncodeWithNullTerminator(), false, true, false);
|
||||
|
||||
flytext->AddFlyText(actorIndex, 1, numArray, numOffset, 10, strArray, strOffset, 2, 0);
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private IntPtr CreateFlyTextDetour(
|
||||
IntPtr addonFlyText,
|
||||
FlyTextKind kind,
|
||||
private unsafe nint CreateFlyTextDetour(
|
||||
AddonFlyText* thisPtr,
|
||||
int kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
byte* text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
uint damageTypeIcon,
|
||||
IntPtr text1,
|
||||
byte* text1,
|
||||
float yOffset)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
var retVal = nint.Zero;
|
||||
try
|
||||
{
|
||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
||||
|
||||
var handled = false;
|
||||
|
||||
var tmpKind = kind;
|
||||
var tmpKind = (FlyTextKind)kind;
|
||||
var tmpVal1 = val1;
|
||||
var tmpVal2 = val2;
|
||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
||||
var tmpText1 = text1 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text1);
|
||||
var tmpText2 = text2 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text2);
|
||||
var tmpColor = color;
|
||||
var tmpIcon = icon;
|
||||
var tmpDamageTypeIcon = damageTypeIcon;
|
||||
var tmpYOffset = yOffset;
|
||||
|
||||
var cmpText1 = tmpText1.ToString();
|
||||
var cmpText2 = tmpText2.ToString();
|
||||
var originalText1 = tmpText1.EncodeWithNullTerminator();
|
||||
var originalText2 = tmpText2.EncodeWithNullTerminator();
|
||||
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({(nint)thisPtr:X}) " +
|
||||
$"kind({kind}) val1({val1}) val2({val2}) damageTypeIcon({damageTypeIcon}) " +
|
||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
||||
$"text1({(nint)text1:X}, \"{tmpText1}\") text2({(nint)text2:X}, \"{tmpText2}\") " +
|
||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
||||
Log.Verbose("[FlyText] Calling flytext events!");
|
||||
this.FlyTextCreated?.Invoke(
|
||||
|
|
@ -204,12 +138,15 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var maybeModifiedText1 = tmpText1.EncodeWithNullTerminator();
|
||||
var maybeModifiedText2 = tmpText2.EncodeWithNullTerminator();
|
||||
|
||||
// Check if any values have changed
|
||||
var dirty = tmpKind != kind ||
|
||||
var dirty = (int)tmpKind != kind ||
|
||||
tmpVal1 != val1 ||
|
||||
tmpVal2 != val2 ||
|
||||
tmpText1.ToString() != cmpText1 ||
|
||||
tmpText2.ToString() != cmpText2 ||
|
||||
!maybeModifiedText1.SequenceEqual(originalText1) ||
|
||||
!maybeModifiedText2.SequenceEqual(originalText2) ||
|
||||
tmpDamageTypeIcon != damageTypeIcon ||
|
||||
tmpColor != color ||
|
||||
tmpIcon != icon ||
|
||||
|
|
@ -219,28 +156,26 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
if (!dirty)
|
||||
{
|
||||
Log.Verbose("[FlyText] Calling flytext with original args.");
|
||||
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon,
|
||||
return this.createFlyTextHook.Original(thisPtr, kind, val1, val2, text2, color, icon,
|
||||
damageTypeIcon, text1, yOffset);
|
||||
}
|
||||
|
||||
var terminated1 = Terminate(tmpText1.Encode());
|
||||
var terminated2 = Terminate(tmpText2.Encode());
|
||||
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
||||
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
||||
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
||||
var pText1 = Marshal.AllocHGlobal(maybeModifiedText1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(maybeModifiedText2.Length);
|
||||
Marshal.Copy(maybeModifiedText1, 0, pText1, maybeModifiedText1.Length);
|
||||
Marshal.Copy(maybeModifiedText2, 0, pText2, maybeModifiedText2.Length);
|
||||
Log.Verbose("[FlyText] Allocated and set strings.");
|
||||
|
||||
retVal = this.createFlyTextHook.Original(
|
||||
addonFlyText,
|
||||
tmpKind,
|
||||
thisPtr,
|
||||
(int)tmpKind,
|
||||
tmpVal1,
|
||||
tmpVal2,
|
||||
pText2,
|
||||
(byte*)pText2,
|
||||
tmpColor,
|
||||
tmpIcon,
|
||||
tmpDamageTypeIcon,
|
||||
pText1,
|
||||
(byte*)pText1,
|
||||
tmpYOffset);
|
||||
|
||||
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="FlyTextGui"/> class.
|
||||
/// </summary>
|
||||
internal class FlyTextGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native AddFlyText method, which occurs
|
||||
/// when the game adds fly text elements to the UI. Multiple fly text
|
||||
/// elements can be added in a single AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr AddFlyText { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native CreateFlyText method, which occurs
|
||||
/// when the game creates a new fly text element. This method is called
|
||||
/// once per fly text element, and can be called multiple times per
|
||||
/// AddFlyText call.
|
||||
/// </summary>
|
||||
public IntPtr CreateFlyText { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7");
|
||||
this.CreateFlyText = sig.ScanText("E8 ?? ?? ?? ?? 48 8B F8 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B 18");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -18,7 +17,6 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
using SharpDX;
|
||||
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
using Vector3 = System.Numerics.Vector3;
|
||||
|
|
@ -33,20 +31,18 @@ namespace Dalamud.Game.Gui;
|
|||
internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
||||
{
|
||||
private static readonly ModuleLog Log = new("GameGui");
|
||||
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private readonly GameGuiAddressResolver address;
|
||||
|
||||
private readonly Hook<SetGlobalBgmDelegate> setGlobalBgmHook;
|
||||
private readonly Hook<HandleItemHoverDelegate> handleItemHoverHook;
|
||||
private readonly Hook<HandleItemOutDelegate> handleItemOutHook;
|
||||
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook;
|
||||
private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
|
||||
private readonly Hook<AgentActionDetail.Delegates.HandleActionHover> handleActionHoverHook;
|
||||
private readonly Hook<AgentActionDetail.Delegates.ReceiveEvent> handleActionOutHook;
|
||||
private readonly Hook<HandleImmDelegate> handleImmHook;
|
||||
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
||||
private readonly Hook<Utf8StringFromSequenceDelegate> utf8StringFromSequenceHook;
|
||||
|
||||
private GetUIMapObjectDelegate? getUIMapObject;
|
||||
private OpenMapWithFlagDelegate? openMapWithFlag;
|
||||
private readonly Hook<RaptureAtkModule.Delegates.SetUiVisibility> setUiVisibilityHook;
|
||||
private readonly Hook<Utf8String.Delegates.Ctor_FromSequence> utf8StringFromSequenceHook;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GameGui(TargetSigScanner sigScanner)
|
||||
|
|
@ -57,66 +53,37 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
Log.Verbose("===== G A M E G U I =====");
|
||||
Log.Verbose($"GameGuiManager address {Util.DescribeAddress(this.address.BaseAddress)}");
|
||||
Log.Verbose($"SetGlobalBgm address {Util.DescribeAddress(this.address.SetGlobalBgm)}");
|
||||
Log.Verbose($"HandleItemHover address {Util.DescribeAddress(this.address.HandleItemHover)}");
|
||||
Log.Verbose($"HandleItemOut address {Util.DescribeAddress(this.address.HandleItemOut)}");
|
||||
Log.Verbose($"HandleImm address {Util.DescribeAddress(this.address.HandleImm)}");
|
||||
|
||||
this.setGlobalBgmHook = Hook<SetGlobalBgmDelegate>.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
|
||||
|
||||
this.handleItemHoverHook = Hook<HandleItemHoverDelegate>.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour);
|
||||
this.handleItemOutHook = Hook<HandleItemOutDelegate>.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour);
|
||||
|
||||
this.handleActionHoverHook = Hook<HandleActionHoverDelegate>.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour);
|
||||
this.handleActionOutHook = Hook<HandleActionOutDelegate>.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour);
|
||||
this.handleActionHoverHook = Hook<AgentActionDetail.Delegates.HandleActionHover>.FromAddress(AgentActionDetail.Addresses.HandleActionHover.Value, this.HandleActionHoverDetour);
|
||||
this.handleActionOutHook = Hook<AgentActionDetail.Delegates.ReceiveEvent>.FromAddress((nint)AgentActionDetail.StaticVirtualTablePointer->ReceiveEvent, this.HandleActionOutDetour);
|
||||
|
||||
this.handleImmHook = Hook<HandleImmDelegate>.FromAddress(this.address.HandleImm, this.HandleImmDetour);
|
||||
|
||||
this.toggleUiHideHook = Hook<ToggleUiHideDelegate>.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour);
|
||||
this.setUiVisibilityHook = Hook<RaptureAtkModule.Delegates.SetUiVisibility>.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour);
|
||||
|
||||
this.utf8StringFromSequenceHook = Hook<Utf8StringFromSequenceDelegate>.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
|
||||
this.utf8StringFromSequenceHook = Hook<Utf8String.Delegates.Ctor_FromSequence>.FromAddress(Utf8String.Addresses.Ctor_FromSequence.Value, this.Utf8StringFromSequenceDetour);
|
||||
|
||||
this.setGlobalBgmHook.Enable();
|
||||
this.handleItemHoverHook.Enable();
|
||||
this.handleItemOutHook.Enable();
|
||||
this.handleImmHook.Enable();
|
||||
this.toggleUiHideHook.Enable();
|
||||
this.setUiVisibilityHook.Enable();
|
||||
this.handleActionHoverHook.Enable();
|
||||
this.handleActionOutHook.Enable();
|
||||
this.utf8StringFromSequenceHook.Enable();
|
||||
|
||||
this.framework.Update += this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
// Hooked delegates
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
|
||||
private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag);
|
||||
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, bool uiVisible);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<bool>? UiHideToggled;
|
||||
|
||||
|
|
@ -137,33 +104,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
|
||||
/// <inheritdoc/>
|
||||
public bool OpenMapWithMapLink(MapLinkPayload mapLink)
|
||||
{
|
||||
var uiModule = this.GetUIModule();
|
||||
|
||||
if (uiModule == IntPtr.Zero)
|
||||
{
|
||||
Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.getUIMapObject ??= this.address.GetVirtualFunction<GetUIMapObjectDelegate>(uiModule, 0, 8);
|
||||
|
||||
var uiMapObjectPtr = this.getUIMapObject(uiModule);
|
||||
|
||||
if (uiMapObjectPtr == IntPtr.Zero)
|
||||
{
|
||||
Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.openMapWithFlag ??= this.address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
||||
|
||||
var mapLinkString = mapLink.DataString;
|
||||
|
||||
Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}");
|
||||
|
||||
return this.openMapWithFlag(uiMapObjectPtr, mapLinkString);
|
||||
}
|
||||
=> RaptureAtkModule.Instance()->OpenMapWithMapLink(mapLink.DataString);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
|
||||
|
|
@ -188,7 +129,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
inView = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
pCoords *= MathF.Abs(1.0f / pCoords.W);
|
||||
screenPos = new Vector2(pCoords.X, pCoords.Y);
|
||||
|
||||
|
|
@ -216,7 +157,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
worldPos = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var camera = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CameraManager.Instance()->CurrentCamera;
|
||||
if (camera == null)
|
||||
{
|
||||
|
|
@ -271,7 +212,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon);
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr FindAgentInterface(IntPtr addonPtr)
|
||||
{
|
||||
|
|
@ -311,11 +252,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
/// </summary>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.framework.Update -= this.FrameworkUpdate;
|
||||
|
||||
this.setGlobalBgmHook.Dispose();
|
||||
this.handleItemHoverHook.Dispose();
|
||||
this.handleItemOutHook.Dispose();
|
||||
this.handleImmHook.Dispose();
|
||||
this.toggleUiHideHook.Dispose();
|
||||
this.setUiVisibilityHook.Dispose();
|
||||
this.handleActionHoverHook.Dispose();
|
||||
this.handleActionOutHook.Dispose();
|
||||
this.utf8StringFromSequenceHook.Dispose();
|
||||
|
|
@ -359,69 +300,24 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||
{
|
||||
var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (retVal.ToInt64() == 22)
|
||||
{
|
||||
var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
|
||||
this.HoveredItem = itemId;
|
||||
|
||||
this.HoveredItemChanged?.InvokeSafely(this, itemId);
|
||||
|
||||
Log.Verbose($"HoverItemId:{itemId} this:{hoverState.ToInt64()}");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||
{
|
||||
var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (a3 != IntPtr.Zero && a4 == 1)
|
||||
{
|
||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||
|
||||
if (a3Val == 255)
|
||||
{
|
||||
this.HoveredItem = 0ul;
|
||||
|
||||
try
|
||||
{
|
||||
this.HoveredItemChanged?.Invoke(this, 0ul);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverItemId: 0");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
|
||||
private void HandleActionHoverDetour(AgentActionDetail* hoverState, ActionKind actionKind, uint actionId, int a4, byte a5)
|
||||
{
|
||||
this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
|
||||
this.HoveredAction.ActionKind = actionKind;
|
||||
this.HoveredAction.ActionKind = (HoverActionKind)actionKind;
|
||||
this.HoveredAction.BaseActionID = actionId;
|
||||
this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C);
|
||||
this.HoveredAction.ActionID = hoverState->ActionId;
|
||||
this.HoveredActionChanged?.InvokeSafely(this, this.HoveredAction);
|
||||
|
||||
Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{hoverState.ToInt64():X}");
|
||||
Log.Verbose($"HoverActionId: {actionKind}/{actionId} this:{(nint)hoverState:X}");
|
||||
}
|
||||
|
||||
private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4)
|
||||
private AtkValue* HandleActionOutDetour(AgentActionDetail* agentActionDetail, AtkValue* a2, AtkValue* a3, uint a4, ulong a5)
|
||||
{
|
||||
var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4);
|
||||
var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4, a5);
|
||||
|
||||
if (a3 != IntPtr.Zero && a4 == 1)
|
||||
if (a3 != null && a4 == 1)
|
||||
{
|
||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||
var a3Val = a3->Int;
|
||||
|
||||
if (a3Val == 255)
|
||||
{
|
||||
|
|
@ -445,16 +341,14 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, bool unknownByte)
|
||||
private unsafe void SetUiVisibilityDetour(RaptureAtkModule* thisPtr, bool uiVisible)
|
||||
{
|
||||
var result = this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
||||
this.setUiVisibilityHook.Original(thisPtr, uiVisible);
|
||||
|
||||
this.GameUiHidden = !RaptureAtkModule.Instance()->IsUiVisible;
|
||||
this.UiHideToggled?.InvokeSafely(this, this.GameUiHidden);
|
||||
|
||||
Log.Debug("UiHide toggled: {0}", this.GameUiHidden);
|
||||
|
||||
return result;
|
||||
Log.Debug("GameUiHidden: {0}", this.GameUiHidden);
|
||||
}
|
||||
|
||||
private char HandleImmDetour(IntPtr framework, char a2, byte a3)
|
||||
|
|
@ -477,6 +371,24 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
|
||||
return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe?
|
||||
}
|
||||
|
||||
private unsafe void FrameworkUpdate(IFramework framework)
|
||||
{
|
||||
var agentItemDetail = AgentItemDetail.Instance();
|
||||
if (agentItemDetail != null)
|
||||
{
|
||||
var itemId = agentItemDetail->ItemId;
|
||||
|
||||
if (this.HoveredItem != itemId)
|
||||
{
|
||||
Log.Verbose($"HoveredItem changed: {itemId}");
|
||||
|
||||
this.HoveredItem = itemId;
|
||||
|
||||
this.HoveredItemChanged?.InvokeSafely(this, itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -501,13 +413,13 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
|
|||
this.gameGuiService.HoveredItemChanged += this.HoveredItemForward;
|
||||
this.gameGuiService.HoveredActionChanged += this.HoveredActionForward;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<bool>? UiHideToggled;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<ulong>? HoveredItemChanged;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<HoveredAction>? HoveredActionChanged;
|
||||
|
||||
|
|
@ -523,7 +435,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
|
|||
|
||||
/// <inheritdoc/>
|
||||
public HoveredAction HoveredAction => this.gameGuiService.HoveredAction;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
|
|
@ -535,7 +447,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
|
|||
this.HoveredItemChanged = null;
|
||||
this.HoveredActionChanged = null;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool OpenMapWithMapLink(MapLinkPayload mapLink)
|
||||
=> this.gameGuiService.OpenMapWithMapLink(mapLink);
|
||||
|
|
@ -543,7 +455,7 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
|
|||
/// <inheritdoc/>
|
||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
|
||||
=> this.gameGuiService.WorldToScreen(worldPos, out screenPos);
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos, out bool inView)
|
||||
=> this.gameGuiService.WorldToScreen(worldPos, out screenPos, out inView);
|
||||
|
|
@ -555,26 +467,26 @@ internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
|
|||
/// <inheritdoc/>
|
||||
public IntPtr GetUIModule()
|
||||
=> this.gameGuiService.GetUIModule();
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GetAddonByName(string name, int index = 1)
|
||||
=> this.gameGuiService.GetAddonByName(name, index);
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr FindAgentInterface(string addonName)
|
||||
=> this.gameGuiService.FindAgentInterface(addonName);
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe IntPtr FindAgentInterface(void* addon)
|
||||
public unsafe IntPtr FindAgentInterface(void* addon)
|
||||
=> this.gameGuiService.FindAgentInterface(addon);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr FindAgentInterface(IntPtr addonPtr)
|
||||
public IntPtr FindAgentInterface(IntPtr addonPtr)
|
||||
=> this.gameGuiService.FindAgentInterface(addonPtr);
|
||||
|
||||
private void UiHideToggledForward(object sender, bool toggled) => this.UiHideToggled?.Invoke(sender, toggled);
|
||||
|
||||
|
||||
private void HoveredItemForward(object sender, ulong itemId) => this.HoveredItemChanged?.Invoke(sender, itemId);
|
||||
|
||||
|
||||
private void HoveredActionForward(object sender, HoveredAction hoverAction) => this.HoveredActionChanged?.Invoke(sender, hoverAction);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,52 +15,15 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
|||
/// </summary>
|
||||
public IntPtr SetGlobalBgm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleActionOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleImm method.
|
||||
/// </summary>
|
||||
public IntPtr HandleImm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ToggleUiHide method.
|
||||
/// </summary>
|
||||
public IntPtr ToggleUiHide { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native Utf8StringFromSequence method.
|
||||
/// </summary>
|
||||
public IntPtr Utf8StringFromSequence { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F");
|
||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 6C 24 48 48 8B 74 24 50 4C 89 B7 08 01 00 00");
|
||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 44 0F B6 81");
|
||||
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
||||
this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F"); // unnamed in CS
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); // unnamed in CS
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ public enum HoverActionKind
|
|||
/// </summary>
|
||||
Action = 28,
|
||||
|
||||
/// <summary>
|
||||
/// A crafting action is hovered.
|
||||
/// </summary>
|
||||
CraftingAction = 29,
|
||||
|
||||
/// <summary>
|
||||
/// A general action is hovered.
|
||||
/// </summary>
|
||||
|
|
@ -24,7 +29,7 @@ public enum HoverActionKind
|
|||
/// <summary>
|
||||
/// A companion order type of action is hovered.
|
||||
/// </summary>
|
||||
CompanionOrder = 31,
|
||||
CompanionOrder = 31, // Game Term: BuddyOrder
|
||||
|
||||
/// <summary>
|
||||
/// A main command type of action is hovered.
|
||||
|
|
@ -36,6 +41,11 @@ public enum HoverActionKind
|
|||
/// </summary>
|
||||
ExtraCommand = 33,
|
||||
|
||||
/// <summary>
|
||||
/// A companion action is hovered.
|
||||
/// </summary>
|
||||
Companion = 34,
|
||||
|
||||
/// <summary>
|
||||
/// A pet order type of action is hovered.
|
||||
/// </summary>
|
||||
|
|
@ -45,4 +55,99 @@ public enum HoverActionKind
|
|||
/// A trait is hovered.
|
||||
/// </summary>
|
||||
Trait = 36,
|
||||
|
||||
/// <summary>
|
||||
/// A buddy action is hovered.
|
||||
/// </summary>
|
||||
BuddyAction = 37,
|
||||
|
||||
/// <summary>
|
||||
/// A company action is hovered.
|
||||
/// </summary>
|
||||
CompanyAction = 38,
|
||||
|
||||
/// <summary>
|
||||
/// A mount is hovered.
|
||||
/// </summary>
|
||||
Mount = 39,
|
||||
|
||||
/// <summary>
|
||||
/// A chocobo race action is hovered.
|
||||
/// </summary>
|
||||
ChocoboRaceAction = 40,
|
||||
|
||||
/// <summary>
|
||||
/// A chocobo race item is hovered.
|
||||
/// </summary>
|
||||
ChocoboRaceItem = 41,
|
||||
|
||||
/// <summary>
|
||||
/// A deep dungeon equipment is hovered.
|
||||
/// </summary>
|
||||
DeepDungeonEquipment = 42,
|
||||
|
||||
/// <summary>
|
||||
/// A deep dungeon equipment 2 is hovered.
|
||||
/// </summary>
|
||||
DeepDungeonEquipment2 = 43,
|
||||
|
||||
/// <summary>
|
||||
/// A deep dungeon item is hovered.
|
||||
/// </summary>
|
||||
DeepDungeonItem = 44,
|
||||
|
||||
/// <summary>
|
||||
/// A quick chat is hovered.
|
||||
/// </summary>
|
||||
QuickChat = 45,
|
||||
|
||||
/// <summary>
|
||||
/// An action combo route is hovered.
|
||||
/// </summary>
|
||||
ActionComboRoute = 46,
|
||||
|
||||
/// <summary>
|
||||
/// A pvp trait is hovered.
|
||||
/// </summary>
|
||||
PvPSelectTrait = 47,
|
||||
|
||||
/// <summary>
|
||||
/// A squadron action is hovered.
|
||||
/// </summary>
|
||||
BgcArmyAction = 48,
|
||||
|
||||
/// <summary>
|
||||
/// A perform action is hovered.
|
||||
/// </summary>
|
||||
Perform = 49,
|
||||
|
||||
/// <summary>
|
||||
/// A deep dungeon magic stone is hovered.
|
||||
/// </summary>
|
||||
DeepDungeonMagicStone = 50,
|
||||
|
||||
/// <summary>
|
||||
/// A deep dungeon demiclone is hovered.
|
||||
/// </summary>
|
||||
DeepDungeonDemiclone = 51,
|
||||
|
||||
/// <summary>
|
||||
/// An eureka magia action is hovered.
|
||||
/// </summary>
|
||||
EurekaMagiaAction = 52,
|
||||
|
||||
/// <summary>
|
||||
/// An island sanctuary temporary item is hovered.
|
||||
/// </summary>
|
||||
MYCTemporaryItem = 53,
|
||||
|
||||
/// <summary>
|
||||
/// An ornament is hovered.
|
||||
/// </summary>
|
||||
Ornament = 54,
|
||||
|
||||
/// <summary>
|
||||
/// Glasses are hovered.
|
||||
/// </summary>
|
||||
Glasses = 55,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
|
|
@ -20,16 +20,6 @@ namespace Dalamud.Game.Gui.NamePlate;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
||||
{
|
||||
/// <summary>
|
||||
/// The index for the number array used by the NamePlate addon.
|
||||
/// </summary>
|
||||
public const int NumberArrayIndex = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The index for the string array used by the NamePlate addon.
|
||||
/// </summary>
|
||||
public const int StringArrayIndex = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The index for of the FullUpdate entry in the NamePlate number array.
|
||||
/// </summary>
|
||||
|
|
@ -81,18 +71,11 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
|||
/// <inheritdoc/>
|
||||
public unsafe void RequestRedraw()
|
||||
{
|
||||
var addon = this.gameGui.GetAddonByName("NamePlate");
|
||||
if (addon != 0)
|
||||
var addon = (AddonNamePlate*)this.gameGui.GetAddonByName("NamePlate");
|
||||
if (addon != null)
|
||||
{
|
||||
var raptureAtkModule = RaptureAtkModule.Instance();
|
||||
if (raptureAtkModule == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
((AddonNamePlate*)addon)->DoFullUpdate = 1;
|
||||
var namePlateNumberArrayData = raptureAtkModule->AtkArrayDataHolder.NumberArrays[NumberArrayIndex];
|
||||
namePlateNumberArrayData->SetValue(NumberArrayFullUpdateIndex, 1);
|
||||
addon->DoFullUpdate = 1;
|
||||
AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate)->SetValue(NumberArrayFullUpdateIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,9 +140,9 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext
|
|||
public void ResetState(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
this.Addon = (AddonNamePlate*)addon;
|
||||
this.NumberData = numberArrayData[NamePlateGui.NumberArrayIndex];
|
||||
this.NumberData = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate);
|
||||
this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray;
|
||||
this.StringData = stringArrayData[NamePlateGui.StringArrayIndex];
|
||||
this.StringData = AtkStage.Instance()->GetStringArrayData(StringArrayType.NamePlate);
|
||||
this.HasParts = false;
|
||||
|
||||
this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
internal class PartyFinderAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ReceiveListing method.
|
||||
/// </summary>
|
||||
public IntPtr ReceiveListing { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC ?? 48 8B D9 4C 8B FA");
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder;
|
||||
|
|
@ -15,12 +17,11 @@ namespace Dalamud.Game.Gui.PartyFinder;
|
|||
/// This class handles interacting with the native PartyFinder window.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui
|
||||
internal sealed unsafe class PartyFinderGui : IInternalDisposableService, IPartyFinderGui
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
private readonly nint memory;
|
||||
|
||||
private readonly Hook<ReceiveListingDelegate> receiveListingHook;
|
||||
private readonly Hook<InfoProxyCrossRealm.Delegates.ReceiveListing> receiveListingHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||
|
|
@ -29,18 +30,12 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG
|
|||
[ServiceManager.ServiceConstructor]
|
||||
private PartyFinderGui(TargetSigScanner sigScanner)
|
||||
{
|
||||
this.address = new PartyFinderAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||
|
||||
this.receiveListingHook = Hook<ReceiveListingDelegate>.FromAddress(this.address.ReceiveListing, this.HandleReceiveListingDetour);
|
||||
this.receiveListingHook = Hook<InfoProxyCrossRealm.Delegates.ReceiveListing>.FromAddress(InfoProxyCrossRealm.Addresses.ReceiveListing.Value, this.HandleReceiveListingDetour);
|
||||
this.receiveListingHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ReceiveListingDelegate(nint managerPtr, nint data);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing;
|
||||
|
||||
|
|
@ -61,18 +56,18 @@ internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderG
|
|||
}
|
||||
}
|
||||
|
||||
private void HandleReceiveListingDetour(nint managerPtr, nint data)
|
||||
private void HandleReceiveListingDetour(InfoProxyCrossRealm* infoProxy, nint packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HandleListingEvents(data);
|
||||
this.HandleListingEvents(packet);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on ReceiveListing hook.");
|
||||
}
|
||||
|
||||
this.receiveListingHook.Original(managerPtr, data);
|
||||
this.receiveListingHook.Original(infoProxy, packet);
|
||||
}
|
||||
|
||||
private void HandleListingEvents(nint data)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ using Dalamud.Data;
|
|||
using Dalamud.Game.Gui.PartyFinder.Internal;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Gui.PartyFinder.Types;
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ public interface IPartyFinderListing
|
|||
/// <summary>
|
||||
/// Gets a list of the classes/jobs that are currently present in the party.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
|
||||
IReadOnlyCollection<RowRef<ClassJob>> JobsPresent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID assigned to this listing by the game's server.
|
||||
|
|
@ -73,17 +74,17 @@ public interface IPartyFinderListing
|
|||
/// <summary>
|
||||
/// Gets the world that this listing was created on.
|
||||
/// </summary>
|
||||
Lazy<World> World { get; }
|
||||
RowRef<World> World { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home world of the listing's host.
|
||||
/// </summary>
|
||||
Lazy<World> HomeWorld { get; }
|
||||
RowRef<World> HomeWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current world of the listing's host.
|
||||
/// </summary>
|
||||
Lazy<World> CurrentWorld { get; }
|
||||
RowRef<World> CurrentWorld { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Party Finder category this listing is listed under.
|
||||
|
|
@ -98,7 +99,7 @@ public interface IPartyFinderListing
|
|||
/// <summary>
|
||||
/// Gets the duty this listing is for. May be null for non-duty listings.
|
||||
/// </summary>
|
||||
Lazy<ContentFinderCondition> Duty { get; }
|
||||
RowRef<ContentFinderCondition> Duty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of duty this listing is for.
|
||||
|
|
@ -216,12 +217,12 @@ internal class PartyFinderListing : IPartyFinderListing
|
|||
this.ContentId = listing.ContentId;
|
||||
this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray());
|
||||
this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray());
|
||||
this.World = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.World));
|
||||
this.HomeWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.HomeWorld));
|
||||
this.CurrentWorld = new Lazy<World>(() => dataManager.GetExcelSheet<World>().GetRow(listing.CurrentWorld));
|
||||
this.World = LuminaUtils.CreateRef<World>(listing.World);
|
||||
this.HomeWorld = LuminaUtils.CreateRef<World>(listing.HomeWorld);
|
||||
this.CurrentWorld = LuminaUtils.CreateRef<World>(listing.CurrentWorld);
|
||||
this.Category = (DutyCategory)listing.Category;
|
||||
this.RawDuty = listing.Duty;
|
||||
this.Duty = new Lazy<ContentFinderCondition>(() => dataManager.GetExcelSheet<ContentFinderCondition>().GetRow(listing.Duty));
|
||||
this.Duty = LuminaUtils.CreateRef<ContentFinderCondition>(listing.Duty);
|
||||
this.DutyType = (DutyType)listing.DutyType;
|
||||
this.BeginnersWelcome = listing.BeginnersWelcome == 1;
|
||||
this.SecondsRemaining = listing.SecondsRemaining;
|
||||
|
|
@ -231,10 +232,7 @@ internal class PartyFinderListing : IPartyFinderListing
|
|||
this.SlotsFilled = listing.NumSlotsFilled;
|
||||
this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp;
|
||||
this.JobsPresent = listing.JobsPresent
|
||||
.Select(id => new Lazy<ClassJob>(
|
||||
() => id == 0
|
||||
? null
|
||||
: dataManager.GetExcelSheet<ClassJob>().GetRow(id)))
|
||||
.Select(id => LuminaUtils.CreateRef<ClassJob>(id))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
|
@ -251,13 +249,13 @@ internal class PartyFinderListing : IPartyFinderListing
|
|||
public SeString Description { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Lazy<World> World { get; }
|
||||
public RowRef<World> World { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Lazy<World> HomeWorld { get; }
|
||||
public RowRef<World> HomeWorld { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Lazy<World> CurrentWorld { get; }
|
||||
public RowRef<World> CurrentWorld { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DutyCategory Category { get; }
|
||||
|
|
@ -266,7 +264,7 @@ internal class PartyFinderListing : IPartyFinderListing
|
|||
public ushort RawDuty { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Lazy<ContentFinderCondition> Duty { get; }
|
||||
public RowRef<ContentFinderCondition> Duty { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DutyType DutyType { get; }
|
||||
|
|
@ -314,7 +312,7 @@ internal class PartyFinderListing : IPartyFinderListing
|
|||
public IReadOnlyCollection<byte> RawJobsPresent => this.jobsPresent;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyCollection<Lazy<ClassJob>> JobsPresent { get; }
|
||||
public IReadOnlyCollection<RowRef<ClassJob>> JobsPresent { get; }
|
||||
|
||||
#region Indexers
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ using Dalamud.Game.Text.SeStringHandling;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -17,8 +20,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
{
|
||||
private const uint QuestToastCheckmarkMagic = 60081;
|
||||
|
||||
private readonly ToastGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new();
|
||||
private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new();
|
||||
private readonly Queue<byte[]> errorQueue = new();
|
||||
|
|
@ -30,16 +31,12 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sigScanner">Sig scanner to use.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ToastGui(TargetSigScanner sigScanner)
|
||||
private unsafe ToastGui()
|
||||
{
|
||||
this.address = new ToastGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour);
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour);
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour);
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowWideText, this.HandleNormalToastDetour);
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowText, this.HandleQuestToastDetour);
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowErrorText, this.HandleErrorToastDetour);
|
||||
|
||||
this.showNormalToastHook.Enable();
|
||||
this.showQuestToastHook.Enable();
|
||||
|
|
@ -48,16 +45,16 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
|
||||
#region Marshal delegates
|
||||
|
||||
private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
|
||||
private unsafe delegate void ShowNormalToastDelegate(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId);
|
||||
|
||||
private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
||||
private unsafe delegate void ShowQuestToastDelegate(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
||||
|
||||
private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
|
||||
private unsafe delegate void ShowErrorToastDelegate(UIModule* thisPtr, byte* text, byte respectsHidingMaybe);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnNormalToastDelegate? Toast;
|
||||
|
||||
|
|
@ -102,32 +99,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
this.ShowError(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private SeString ParseString(IntPtr text)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
unsafe
|
||||
{
|
||||
var ptr = (byte*)text;
|
||||
while (*ptr != 0)
|
||||
{
|
||||
bytes.Add(*ptr);
|
||||
ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// call events
|
||||
return SeString.Parse(bytes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -149,36 +120,30 @@ internal sealed partial class ToastGui
|
|||
this.normalQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowNormal(byte[] bytes, ToastOptions? options = null)
|
||||
private unsafe void ShowNormal(byte[] bytes, ToastOptions? options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = bytes.NullTerminate())
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
|
||||
}
|
||||
this.HandleNormalToastDetour(
|
||||
UIModule.Instance(),
|
||||
ptr,
|
||||
5,
|
||||
(byte)options.Position,
|
||||
(byte)options.Speed,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId)
|
||||
private unsafe void HandleNormalToastDetour(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var str = MemoryHelper.ReadSeStringNullTerminated((nint)text);
|
||||
var options = new ToastOptions
|
||||
{
|
||||
Position = (ToastPosition)isTop,
|
||||
|
|
@ -189,18 +154,17 @@ internal sealed partial class ToastGui
|
|||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
return;
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = str.EncodeWithNullTerminator())
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId);
|
||||
}
|
||||
this.showNormalToastHook.Original(
|
||||
thisPtr,
|
||||
ptr,
|
||||
layer,
|
||||
(byte)(options.Position == ToastPosition.Top ? 1 : 0),
|
||||
(byte)(options.Speed == ToastSpeed.Fast ? 1 : 0),
|
||||
logMessageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -224,45 +188,33 @@ internal sealed partial class ToastGui
|
|||
this.questQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowQuest(byte[] bytes, QuestToastOptions? options = null)
|
||||
private unsafe void ShowQuest(byte[] bytes, QuestToastOptions? options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = bytes.NullTerminate())
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleQuestToastDetour(
|
||||
manager!.Value,
|
||||
(int)options.Position,
|
||||
(IntPtr)ptr,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
this.HandleQuestToastDetour(
|
||||
UIModule.Instance(),
|
||||
(int)options.Position,
|
||||
ptr,
|
||||
ioc1,
|
||||
(byte)(options.PlaySound ? 1 : 0),
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound)
|
||||
private unsafe void HandleQuestToastDetour(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var str = SeString.Parse(text);
|
||||
var options = new QuestToastOptions
|
||||
{
|
||||
Position = (QuestToastPosition)position,
|
||||
|
|
@ -275,27 +227,20 @@ internal sealed partial class ToastGui
|
|||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
return;
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = str.EncodeWithNullTerminator())
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showQuestToastHook.Original(
|
||||
manager,
|
||||
(int)options.Position,
|
||||
(IntPtr)message,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
this.showQuestToastHook.Original(
|
||||
UIModule.Instance(),
|
||||
(int)options.Position,
|
||||
ptr,
|
||||
ioc1,
|
||||
(byte)(options.PlaySound ? 1 : 0),
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -324,51 +269,32 @@ internal sealed partial class ToastGui
|
|||
this.errorQueue.Enqueue(message.Encode());
|
||||
}
|
||||
|
||||
private void ShowError(byte[] bytes)
|
||||
private unsafe void ShowError(byte[] bytes)
|
||||
{
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = bytes.NullTerminate())
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0);
|
||||
}
|
||||
this.HandleErrorToastDetour(UIModule.Instance(), ptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe)
|
||||
private unsafe void HandleErrorToastDetour(UIModule* thisPtr, byte* text, byte respectsHidingMaybe)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var str = SeString.Parse(text);
|
||||
|
||||
this.ErrorToast?.Invoke(ref str, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return;
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = str.EncodeWithNullTerminator())
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe);
|
||||
}
|
||||
this.showErrorToastHook.Original(thisPtr, ptr, respectsHidingMaybe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
internal class ToastGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowNormalToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowNormalToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowQuestToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowQuestToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ShowErrorToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowErrorToast { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0");
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,11 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Serilog;
|
||||
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
|
|
@ -20,12 +22,14 @@ namespace Dalamud.Game.Internal;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
||||
{
|
||||
private readonly Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
|
||||
private static readonly ModuleLog Log = new("DalamudAtkTweaks");
|
||||
|
||||
private readonly Hook<AgentHUD.Delegates.OpenSystemMenu> hookAgentHudOpenSystemMenu;
|
||||
|
||||
// TODO: Make this into events in Framework.Gui
|
||||
private readonly Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
|
||||
private readonly Hook<UIModule.Delegates.ExecuteMainCommand> hookUiModuleExecuteMainCommand;
|
||||
|
||||
private readonly Hook<AtkUnitBaseReceiveGlobalEvent> hookAtkUnitBaseReceiveGlobalEvent;
|
||||
private readonly Hook<AtkUnitBase.Delegates.ReceiveGlobalEvent> hookAtkUnitBaseReceiveGlobalEvent;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -41,15 +45,9 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudAtkTweaks(TargetSigScanner sigScanner)
|
||||
{
|
||||
var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CF 4C 89 B4 24 B8 08 00 00");
|
||||
|
||||
this.hookAgentHudOpenSystemMenu = Hook<AgentHudOpenSystemMenuPrototype>.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
|
||||
|
||||
var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
||||
this.hookUiModuleRequestMainCommand = Hook<UiModuleRequestMainCommand>.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour);
|
||||
|
||||
var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 54 41 57");
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent = Hook<AtkUnitBaseReceiveGlobalEvent>.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour);
|
||||
this.hookAgentHudOpenSystemMenu = Hook<AgentHUD.Delegates.OpenSystemMenu>.FromAddress(AgentHUD.Addresses.OpenSystemMenu.Value, this.AgentHudOpenSystemMenuDetour);
|
||||
this.hookUiModuleExecuteMainCommand = Hook<UIModule.Delegates.ExecuteMainCommand>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour);
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent = Hook<AtkUnitBase.Delegates.ReceiveGlobalEvent>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveGlobalEvent, this.AtkUnitBaseReceiveGlobalEventDetour);
|
||||
|
||||
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
|
||||
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
|
||||
|
|
@ -57,18 +55,14 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
// this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
|
||||
|
||||
this.hookAgentHudOpenSystemMenu.Enable();
|
||||
this.hookUiModuleRequestMainCommand.Enable();
|
||||
this.hookUiModuleExecuteMainCommand.Enable();
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.</summary>
|
||||
~DalamudAtkTweaks() => this.Dispose(false);
|
||||
|
||||
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||
|
||||
private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
|
||||
|
||||
private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
|
||||
private delegate void AgentHudOpenSystemMenuPrototype(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService() => this.Dispose(true);
|
||||
|
|
@ -81,7 +75,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
if (disposing)
|
||||
{
|
||||
this.hookAgentHudOpenSystemMenu.Dispose();
|
||||
this.hookUiModuleRequestMainCommand.Dispose();
|
||||
this.hookUiModuleExecuteMainCommand.Dispose();
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
||||
|
||||
// this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
||||
|
|
@ -116,22 +110,19 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
}
|
||||
*/
|
||||
|
||||
private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg)
|
||||
private void AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||
{
|
||||
// Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", MemoryHelper.ReadSeStringAsString(out _, new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus);
|
||||
|
||||
// "SendHotkey"
|
||||
// 3 == Close
|
||||
if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && this.configuration.IsFocusManagementEnabled)
|
||||
if (eventType == AtkEventType.InputReceived && WindowSystem.HasAnyWindowSystemFocus && atkEventData != null && *(int*)atkEventData == 3 && this.configuration.IsFocusManagementEnabled)
|
||||
{
|
||||
Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}");
|
||||
return IntPtr.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg);
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, eventType, eventParam, atkEvent, atkEventData);
|
||||
}
|
||||
|
||||
private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize)
|
||||
private void AgentHudOpenSystemMenuDetour(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize)
|
||||
{
|
||||
if (WindowSystem.HasAnyWindowSystemFocus && this.configuration.IsFocusManagementEnabled)
|
||||
{
|
||||
|
|
@ -213,7 +204,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2);
|
||||
}
|
||||
|
||||
private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId)
|
||||
private unsafe void UiModuleExecuteMainCommandDetour(UIModule* thisPtr, uint commandId)
|
||||
{
|
||||
var dalamudInterface = Service<DalamudInterface>.GetNullable();
|
||||
|
||||
|
|
@ -226,7 +217,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
dalamudInterface?.OpenSettings();
|
||||
break;
|
||||
default:
|
||||
this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId);
|
||||
this.hookUiModuleExecuteMainCommand.Original(thisPtr, commandId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Dalamud.Game.Inventory;
|
|||
/// <summary>
|
||||
/// Dalamud wrapper around a ClientStructs InventoryItem.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = StructSizeInBytes)]
|
||||
[StructLayout(LayoutKind.Explicit, Size = InventoryItem.StructSize)]
|
||||
public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -17,22 +17,12 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
|
|||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
internal readonly InventoryItem InternalItem;
|
||||
|
||||
private const int StructSizeInBytes = 0x40;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The view of the backing data, in <see cref="ulong"/>.
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
private fixed ulong dataUInt64[StructSizeInBytes / 0x8];
|
||||
|
||||
static GameInventoryItem()
|
||||
{
|
||||
Debug.Assert(
|
||||
sizeof(InventoryItem) == StructSizeInBytes,
|
||||
$"Definition of {nameof(InventoryItem)} has been changed. " +
|
||||
$"Update {nameof(StructSizeInBytes)} to {sizeof(InventoryItem)} to accommodate for the size change.");
|
||||
}
|
||||
private fixed ulong dataUInt64[InventoryItem.StructSize / 0x8];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameInventoryItem"/> struct.
|
||||
|
|
@ -63,7 +53,7 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
|
|||
/// <summary>
|
||||
/// Gets the quantity of items in this item stack.
|
||||
/// </summary>
|
||||
public uint Quantity => this.InternalItem.Quantity;
|
||||
public int Quantity => this.InternalItem.Quantity;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spiritbond of this item.
|
||||
|
|
@ -157,7 +147,7 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
|
|||
/// <returns><c>true</c> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <c>false</c>.</returns>
|
||||
public readonly bool Equals(in GameInventoryItem other)
|
||||
{
|
||||
for (var i = 0; i < StructSizeInBytes / 8; i++)
|
||||
for (var i = 0; i < InventoryItem.StructSize / 8; i++)
|
||||
{
|
||||
if (this.dataUInt64[i] != other.dataUInt64[i])
|
||||
return false;
|
||||
|
|
@ -173,7 +163,7 @@ public unsafe struct GameInventoryItem : IEquatable<GameInventoryItem>
|
|||
public override int GetHashCode()
|
||||
{
|
||||
var k = 0x5a8447b91aff51b4UL;
|
||||
for (var i = 0; i < StructSizeInBytes / 8; i++)
|
||||
for (var i = 0; i < InventoryItem.StructSize / 8; i++)
|
||||
k ^= this.dataUInt64[i];
|
||||
return unchecked((int)(k ^ (k >> 32)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ using Dalamud.IoC;
|
|||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Network;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Network;
|
||||
|
|
@ -14,10 +17,10 @@ namespace Dalamud.Game.Network;
|
|||
/// This class handles interacting with game network events.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
|
||||
internal sealed unsafe class GameNetwork : IInternalDisposableService, IGameNetwork
|
||||
{
|
||||
private readonly GameNetworkAddressResolver address;
|
||||
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
|
||||
private readonly Hook<PacketDispatcher.Delegates.OnReceivePacket> processZonePacketDownHook;
|
||||
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
|
||||
|
||||
private readonly HitchDetector hitchDetectorUp;
|
||||
|
|
@ -25,11 +28,9 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
|
|||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
private IntPtr baseAddress;
|
||||
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GameNetwork(TargetSigScanner sigScanner)
|
||||
private unsafe GameNetwork(TargetSigScanner sigScanner)
|
||||
{
|
||||
this.hitchDetectorUp = new HitchDetector("GameNetworkUp", this.configuration.GameNetworkUpHitch);
|
||||
this.hitchDetectorDown = new HitchDetector("GameNetworkDown", this.configuration.GameNetworkDownHitch);
|
||||
|
|
@ -37,20 +38,19 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
|
|||
this.address = new GameNetworkAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
var onReceivePacketAddress = (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket;
|
||||
|
||||
Log.Verbose("===== G A M E N E T W O R K =====");
|
||||
Log.Verbose($"ProcessZonePacketDown address {Util.DescribeAddress(this.address.ProcessZonePacketDown)}");
|
||||
Log.Verbose($"OnReceivePacket address {Util.DescribeAddress(onReceivePacketAddress)}");
|
||||
Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}");
|
||||
|
||||
this.processZonePacketDownHook = Hook<ProcessZonePacketDownDelegate>.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour);
|
||||
this.processZonePacketDownHook = Hook<PacketDispatcher.Delegates.OnReceivePacket>.FromAddress(onReceivePacketAddress, this.ProcessZonePacketDownDetour);
|
||||
this.processZonePacketUpHook = Hook<ProcessZonePacketUpDelegate>.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
|
||||
|
||||
this.processZonePacketDownHook.Enable();
|
||||
this.processZonePacketUpHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
|
||||
|
||||
|
|
@ -64,10 +64,8 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
|
|||
this.processZonePacketUpHook.Dispose();
|
||||
}
|
||||
|
||||
private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr)
|
||||
private void ProcessZonePacketDownDetour(PacketDispatcher* dispatcher, uint targetId, IntPtr dataPtr)
|
||||
{
|
||||
this.baseAddress = a;
|
||||
|
||||
this.hitchDetectorDown.Start();
|
||||
|
||||
// Go back 0x10 to get back to the start of the packet header
|
||||
|
|
@ -78,7 +76,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
|
|||
// Call events
|
||||
this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown);
|
||||
|
||||
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10);
|
||||
this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -96,7 +94,7 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
|
|||
|
||||
Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header);
|
||||
|
||||
this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10);
|
||||
this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10);
|
||||
}
|
||||
|
||||
this.hitchDetectorDown.Stop();
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@ namespace Dalamud.Game.Network;
|
|||
/// </summary>
|
||||
internal sealed class GameNetworkAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the ProcessZonePacketDown method.
|
||||
/// </summary>
|
||||
public IntPtr ProcessZonePacketDown { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ProcessZonePacketUp method.
|
||||
/// </summary>
|
||||
|
|
@ -18,9 +13,6 @@ internal sealed class GameNetworkAddressResolver : BaseAddressResolver
|
|||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
// ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
|
||||
this.ProcessZonePacketDown = sig.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? 8B F2");
|
||||
this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70");
|
||||
this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); // unnamed in cs
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ internal class MarketBoardItemRequest
|
|||
/// </summary>
|
||||
public uint AmountToArrive { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offered catalog id used in this listing.
|
||||
/// </summary>
|
||||
public uint CatalogId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offered item listings.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -48,4 +48,10 @@ internal class UniversalisTaxData
|
|||
/// </summary>
|
||||
[JsonProperty("sharlayan")]
|
||||
public uint Sharlayan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets Tuliyollal's current tax rate.
|
||||
/// </summary>
|
||||
[JsonProperty("tuliyollal")]
|
||||
public uint Tuliyollal { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
|||
|
||||
var uploadObject = new UniversalisItemUploadRequest
|
||||
{
|
||||
WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0,
|
||||
WorldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0,
|
||||
UploaderId = uploader.ToString(),
|
||||
ItemId = request.Listings.FirstOrDefault()?.CatalogId ?? 0,
|
||||
ItemId = request.CatalogId,
|
||||
Listings = [],
|
||||
Sales = [],
|
||||
};
|
||||
|
|
@ -106,7 +106,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
|||
|
||||
// ====================================================================================
|
||||
|
||||
Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.Listings.FirstOrDefault()?.CatalogId ?? 0);
|
||||
Log.Verbose("Universalis data upload for item#{CatalogId} completed", request.CatalogId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -120,7 +120,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
|||
|
||||
var taxUploadObject = new UniversalisTaxUploadRequest
|
||||
{
|
||||
WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0,
|
||||
WorldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0,
|
||||
UploaderId = clientState.LocalContentId.ToString(),
|
||||
TaxData = new UniversalisTaxData
|
||||
{
|
||||
|
|
@ -131,6 +131,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
|||
Kugane = taxRates.KuganeTax,
|
||||
Crystarium = taxRates.CrystariumTax,
|
||||
Sharlayan = taxRates.SharlayanTax,
|
||||
Tuliyollal = taxRates.TuliyollalTax,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -158,7 +159,7 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
|||
return;
|
||||
|
||||
var itemId = purchaseHandler.CatalogId;
|
||||
var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
||||
var worldId = clientState.LocalPlayer?.CurrentWorld.RowId ?? 0;
|
||||
|
||||
// ====================================================================================
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,11 @@ using Dalamud.Game.Text.SeStringHandling;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Networking.Http;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
|
||||
using FFXIVClientStructs.FFXIV.Client.Network;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Network.Internal;
|
||||
|
|
@ -35,13 +38,13 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
|
||||
private readonly NetworkHandlersAddressResolver addressResolver;
|
||||
|
||||
private readonly Hook<CfPopDelegate> cfPopHook;
|
||||
private readonly Hook<MarketBoardPurchasePacketHandler> mbPurchaseHook;
|
||||
private readonly Hook<MarketBoardHistoryPacketHandler> mbHistoryHook;
|
||||
private readonly Hook<PublicContentDirector.Delegates.HandleEnterContentInfoPacket> cfPopHook;
|
||||
private readonly Hook<PacketDispatcher.Delegates.HandleMarketBoardPurchasePacket> mbPurchaseHook;
|
||||
private readonly Hook<InfoProxyItemSearch.Delegates.ProcessItemHistory> mbHistoryHook;
|
||||
private readonly Hook<CustomTalkReceiveResponse> customTalkHook; // used for marketboard taxes
|
||||
private readonly Hook<MarketBoardItemRequestStartPacketHandler> mbItemRequestStartHook;
|
||||
private readonly Hook<InfoProxyItemSearchAddPage> mbOfferingsHook;
|
||||
private readonly Hook<MarketBoardSendPurchaseRequestPacket> mbSendPurchaseRequestHook;
|
||||
private readonly Hook<PacketDispatcher.Delegates.HandleMarketBoardItemRequestStartPacket> mbItemRequestStartHook;
|
||||
private readonly Hook<InfoProxyItemSearch.Delegates.AddPage> mbOfferingsHook;
|
||||
private readonly Hook<InfoProxyItemSearch.Delegates.SendPurchaseRequestPacket> mbSendPurchaseRequestHook;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -134,14 +137,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
this.handleMarketBoardPurchaseHandler = this.HandleMarketBoardPurchaseHandler();
|
||||
|
||||
this.mbPurchaseHook =
|
||||
Hook<MarketBoardPurchasePacketHandler>.FromAddress(
|
||||
this.addressResolver.MarketBoardPurchasePacketHandler,
|
||||
Hook<PacketDispatcher.Delegates.HandleMarketBoardPurchasePacket>.FromAddress(
|
||||
PacketDispatcher.Addresses.HandleMarketBoardPurchasePacket.Value,
|
||||
this.MarketPurchasePacketDetour);
|
||||
this.mbPurchaseHook.Enable();
|
||||
|
||||
this.mbHistoryHook =
|
||||
Hook<MarketBoardHistoryPacketHandler>.FromAddress(
|
||||
this.addressResolver.MarketBoardHistoryPacketHandler,
|
||||
Hook<InfoProxyItemSearch.Delegates.ProcessItemHistory>.FromAddress(
|
||||
InfoProxyItemSearch.Addresses.ProcessItemHistory.Value,
|
||||
this.MarketHistoryPacketDetour);
|
||||
this.mbHistoryHook.Enable();
|
||||
|
||||
|
|
@ -151,22 +154,22 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
this.CustomTalkReceiveResponseDetour);
|
||||
this.customTalkHook.Enable();
|
||||
|
||||
this.mbItemRequestStartHook = Hook<MarketBoardItemRequestStartPacketHandler>.FromAddress(
|
||||
this.addressResolver.MarketBoardItemRequestStartPacketHandler,
|
||||
this.mbItemRequestStartHook = Hook<PacketDispatcher.Delegates.HandleMarketBoardItemRequestStartPacket>.FromAddress(
|
||||
PacketDispatcher.Addresses.HandleMarketBoardItemRequestStartPacket.Value,
|
||||
this.MarketItemRequestStartDetour);
|
||||
this.mbItemRequestStartHook.Enable();
|
||||
|
||||
this.mbOfferingsHook = Hook<InfoProxyItemSearchAddPage>.FromAddress(
|
||||
this.addressResolver.InfoProxyItemSearchAddPage,
|
||||
this.mbOfferingsHook = Hook<InfoProxyItemSearch.Delegates.AddPage>.FromAddress(
|
||||
(nint)InfoProxyItemSearch.StaticVirtualTablePointer->AddPage,
|
||||
this.MarketBoardOfferingsDetour);
|
||||
this.mbOfferingsHook.Enable();
|
||||
|
||||
this.mbSendPurchaseRequestHook = Hook<MarketBoardSendPurchaseRequestPacket>.FromAddress(
|
||||
this.addressResolver.BuildMarketBoardPurchaseHandlerPacket,
|
||||
this.mbSendPurchaseRequestHook = Hook<InfoProxyItemSearch.Delegates.SendPurchaseRequestPacket>.FromAddress(
|
||||
InfoProxyItemSearch.Addresses.SendPurchaseRequestPacket.Value,
|
||||
this.MarketBoardSendPurchaseRequestDetour);
|
||||
this.mbSendPurchaseRequestHook.Enable();
|
||||
|
||||
this.cfPopHook = Hook<CfPopDelegate>.FromAddress(this.addressResolver.CfPopPacketHandler, this.CfPopDetour);
|
||||
this.cfPopHook = Hook<PublicContentDirector.Delegates.HandleEnterContentInfoPacket>.FromAddress(PublicContentDirector.Addresses.HandleEnterContentInfoPacket.Value, this.CfPopDetour);
|
||||
this.cfPopHook.Enable();
|
||||
}
|
||||
|
||||
|
|
@ -183,8 +186,6 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
|
||||
private delegate byte MarketBoardSendPurchaseRequestPacket(InfoProxyItemSearch* infoProxy);
|
||||
|
||||
private delegate nint CfPopDelegate(nint packetData);
|
||||
|
||||
/// <summary>
|
||||
/// Event which gets fired when a duty is ready.
|
||||
/// </summary>
|
||||
|
|
@ -263,7 +264,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
this.cfPopHook.Dispose();
|
||||
}
|
||||
|
||||
private unsafe nint CfPopDetour(nint packetData)
|
||||
private unsafe nint CfPopDetour(PublicContentDirector.EnterContentInfoPacket* packetData)
|
||||
{
|
||||
var result = this.cfPopHook.OriginalDisposeSafe(packetData);
|
||||
|
||||
|
|
@ -282,21 +283,17 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
if (this.configuration.DutyFinderTaskbarFlash)
|
||||
Util.FlashWindow();
|
||||
|
||||
var cfConditionSheet = Service<DataManager>.Get().GetExcelSheet<ContentFinderCondition>()!;
|
||||
var cfCondition = cfConditionSheet.GetRow(conditionId);
|
||||
var cfCondition = LuminaUtils.CreateRef<ContentFinderCondition>(conditionId);
|
||||
|
||||
if (cfCondition == null)
|
||||
if (!cfCondition.IsValid)
|
||||
{
|
||||
Log.Error("CFC key {ConditionId} not in Lumina data", conditionId);
|
||||
return result;
|
||||
}
|
||||
|
||||
var cfcName = cfCondition.Name.ToDalamudString();
|
||||
var cfcName = cfCondition.Value.Name.ToDalamudString();
|
||||
if (cfcName.Payloads.Count == 0)
|
||||
{
|
||||
cfcName = "Duty Roulette";
|
||||
cfCondition.Image = 112324;
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
|
|
@ -308,7 +305,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Service<ChatGui>.GetNullable()?.Print(b.Build());
|
||||
}
|
||||
|
||||
this.CfPop.InvokeSafely(cfCondition);
|
||||
this.CfPop.InvokeSafely(cfCondition.Value);
|
||||
}).ContinueWith(
|
||||
task => Log.Error(task.Exception, "CfPop.Invoke failed"),
|
||||
TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
|
@ -371,7 +368,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
}));
|
||||
}
|
||||
|
||||
private IObservable<List<MarketBoardHistory.MarketBoardHistoryListing>> OnMarketBoardSalesBatch(
|
||||
private IObservable<(uint CatalogId, List<MarketBoardHistory.MarketBoardHistoryListing> Sales)> OnMarketBoardSalesBatch(
|
||||
IObservable<MarketBoardItemRequest> start)
|
||||
{
|
||||
var historyObservable = this.MbHistoryObservable.Publish().RefCount();
|
||||
|
|
@ -394,6 +391,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
// When a start packet is observed, begin observing a window of history packets.
|
||||
// We should only get one packet, which the window closing function ensures.
|
||||
// This packet is flattened to its sale entries and emitted.
|
||||
uint catalogId = 0;
|
||||
return historyObservable
|
||||
.Do(LogHistoryObserved)
|
||||
.Window(start, UntilBatchEnd)
|
||||
|
|
@ -402,9 +400,12 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
new List<MarketBoardHistory.MarketBoardHistoryListing>(),
|
||||
(agg, next) =>
|
||||
{
|
||||
catalogId = next.CatalogId;
|
||||
|
||||
agg.AddRange(next.InternalHistoryListings);
|
||||
return agg;
|
||||
}));
|
||||
}))
|
||||
.Select(o => (catalogId, o));
|
||||
}
|
||||
|
||||
private IDisposable HandleMarketBoardItemRequest()
|
||||
|
|
@ -415,7 +416,8 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
}
|
||||
|
||||
var startObservable = this.MbItemRequestObservable
|
||||
.Where(request => request.Ok).Do(LogStartObserved)
|
||||
.Where(request => request.Ok)
|
||||
.Do(LogStartObserved)
|
||||
.Publish()
|
||||
.RefCount();
|
||||
return Observable.When(
|
||||
|
|
@ -436,10 +438,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
|
||||
private void UploadMarketBoardData(
|
||||
MarketBoardItemRequest request,
|
||||
ICollection<MarketBoardHistory.MarketBoardHistoryListing> sales,
|
||||
(uint CatalogId, ICollection<MarketBoardHistory.MarketBoardHistoryListing> Sales) sales,
|
||||
ICollection<MarketBoardCurrentOfferings.MarketBoardItemListing> listings)
|
||||
{
|
||||
var catalogId = listings.FirstOrDefault()?.CatalogId ?? 0;
|
||||
var catalogId = sales.CatalogId;
|
||||
if (listings.Count != request.AmountToArrive)
|
||||
{
|
||||
Log.Error(
|
||||
|
|
@ -452,10 +454,11 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
"Market Board request resolved, starting upload: item#{CatalogId} listings#{ListingsObserved} sales#{SalesObserved}",
|
||||
catalogId,
|
||||
listings.Count,
|
||||
sales.Count);
|
||||
sales.Sales.Count);
|
||||
|
||||
request.CatalogId = catalogId;
|
||||
request.Listings.AddRange(listings);
|
||||
request.History.AddRange(sales);
|
||||
request.History.AddRange(sales.Sales);
|
||||
|
||||
Task.Run(() => this.uploader.Upload(request))
|
||||
.ContinueWith(
|
||||
|
|
@ -527,7 +530,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
return this.configuration.IsMbCollect;
|
||||
}
|
||||
|
||||
private nint MarketPurchasePacketDetour(nint a1, nint packetData)
|
||||
private void MarketPurchasePacketDetour(PacketDispatcher* a1, nint packetData)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -538,10 +541,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Log.Error(ex, "MarketPurchasePacketHandler threw an exception");
|
||||
}
|
||||
|
||||
return this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData);
|
||||
this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData);
|
||||
}
|
||||
|
||||
private nint MarketHistoryPacketDetour(nint a1, nint packetData, uint a3, char a4)
|
||||
private void MarketHistoryPacketDetour(InfoProxyItemSearch* a1, nint packetData)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -552,7 +555,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Log.Error(ex, "MarketHistoryPacketDetour threw an exception");
|
||||
}
|
||||
|
||||
return this.mbHistoryHook.OriginalDisposeSafe(a1, packetData, a3, a4);
|
||||
this.mbHistoryHook.OriginalDisposeSafe(a1, packetData);
|
||||
}
|
||||
|
||||
private void CustomTalkReceiveResponseDetour(nuint a1, ushort eventId, byte responseId, uint* args, byte argCount)
|
||||
|
|
@ -571,7 +574,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount);
|
||||
}
|
||||
|
||||
private nint MarketItemRequestStartDetour(nint a1, nint packetRef)
|
||||
private void MarketItemRequestStartDetour(PacketDispatcher* a1, nint packetRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -582,10 +585,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Log.Error(ex, "MarketItemRequestStartDetour threw an exception");
|
||||
}
|
||||
|
||||
return this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef);
|
||||
this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef);
|
||||
}
|
||||
|
||||
private byte MarketBoardOfferingsDetour(nint a1, nint packetRef)
|
||||
private void MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetRef)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -596,10 +599,10 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Log.Error(ex, "MarketBoardOfferingsDetour threw an exception");
|
||||
}
|
||||
|
||||
return this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef);
|
||||
this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef);
|
||||
}
|
||||
|
||||
private byte MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch)
|
||||
private bool MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,62 +5,17 @@
|
|||
/// </summary>
|
||||
internal class NetworkHandlersAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the method responsible for handling CfPop packets.
|
||||
/// </summary>
|
||||
public nint CfPopPacketHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the method responsible for handling market board history. In this case, we are
|
||||
/// sigging the packet handler method directly.
|
||||
/// </summary>
|
||||
public nint MarketBoardHistoryPacketHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the method responsible for processing the market board purchase packet. In this
|
||||
/// case, we are sigging the packet handler method directly.
|
||||
/// </summary>
|
||||
public nint MarketBoardPurchasePacketHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the method responsible for custom talk events. Necessary for marketboard tax data,
|
||||
/// as this isn't really exposed anywhere else.
|
||||
/// </summary>
|
||||
public nint CustomTalkEventResponsePacketHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the method responsible for the marketboard ItemRequestStart packet.
|
||||
/// </summary>
|
||||
public nint MarketBoardItemRequestStartPacketHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the InfoProxyItemSearch.AddPage method, used to load market data.
|
||||
/// </summary>
|
||||
public nint InfoProxyItemSearchAddPage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pointer to the method inside InfoProxyItemSearch that is responsible for building and sending
|
||||
/// a purchase request packet.
|
||||
/// </summary>
|
||||
public nint BuildMarketBoardPurchaseHandlerPacket { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Setup64Bit(ISigScanner scanner)
|
||||
{
|
||||
this.CfPopPacketHandler = scanner.ScanText("40 53 57 48 83 EC 78 48 8B D9 48 8D 0D");
|
||||
|
||||
// TODO: I know this is a CC. I want things working for now. (KW)
|
||||
this.MarketBoardHistoryPacketHandler = scanner.ScanText(
|
||||
"40 53 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 85 C0 74 2F 4C 8B 00 48 8B C8 41 FF 90 18 01 00 00 48 8B C8 BA 0B 00 00 00 E8 ?? ?? ?? ?? 48 85 C0 74 10 48 8B D3 48 8B C8 48 83 C4 20 5B E9 ?? ?? ?? ?? 48 83 C4 20 5B C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 40 53");
|
||||
this.MarketBoardPurchasePacketHandler =
|
||||
scanner.ScanText("40 55 56 41 56 48 8B EC 48 83 EC ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 ?? 48 8B 0D ?? ?? ?? ?? 4C 8B F2");
|
||||
this.CustomTalkEventResponsePacketHandler =
|
||||
scanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24");
|
||||
this.MarketBoardItemRequestStartPacketHandler =
|
||||
scanner.ScanText("48 89 5C 24 08 57 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B FA E8 ?? ?? ?? ?? 48 8B D8 48 85 C0 74 4A");
|
||||
this.InfoProxyItemSearchAddPage =
|
||||
scanner.ScanText("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 82 ?? ?? ?? ?? 48 8B FA 48 8B D9 38 41 19 74 54");
|
||||
this.BuildMarketBoardPurchaseHandlerPacket =
|
||||
scanner.ScanText("40 53 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B D9 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8B D0 48 85 C0 0F 84 ?? ?? ?? ?? 8B 8B");
|
||||
scanner.ScanText(
|
||||
"48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); // unnamed in CS
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game.ClientState.Resolvers;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Data;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Game.Network.Structures.InfoProxy;
|
||||
|
||||
|
|
@ -92,15 +92,15 @@ public unsafe class CharacterData
|
|||
/// <summary>
|
||||
/// Gets the applicable statues of the character.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ExcelResolver<OnlineStatus>> Statuses
|
||||
public IReadOnlyList<RowRef<OnlineStatus>> Statuses
|
||||
{
|
||||
get
|
||||
{
|
||||
var statuses = new List<ExcelResolver<OnlineStatus>>();
|
||||
var statuses = new List<RowRef<OnlineStatus>>();
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
if ((this.StatusMask & (1UL << i)) != 0)
|
||||
statuses.Add(new((uint)i));
|
||||
statuses.Add(LuminaUtils.CreateRef<OnlineStatus>((uint)i));
|
||||
}
|
||||
|
||||
return statuses;
|
||||
|
|
@ -125,22 +125,22 @@ public unsafe class CharacterData
|
|||
/// <summary>
|
||||
/// Gets the current world of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<World> CurrentWorld => new(this.Struct->CurrentWorld);
|
||||
public RowRef<World> CurrentWorld => LuminaUtils.CreateRef<World>(this.Struct->CurrentWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the home world of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<World> HomeWorld => new(this.Struct->HomeWorld);
|
||||
public RowRef<World> HomeWorld => LuminaUtils.CreateRef<World>(this.Struct->HomeWorld);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<TerritoryType> Location => new(this.Struct->Location);
|
||||
public RowRef<TerritoryType> Location => LuminaUtils.CreateRef<TerritoryType>(this.Struct->Location);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the grand company of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<GrandCompany> GrandCompany => new((uint)this.Struct->GrandCompany);
|
||||
public RowRef<GrandCompany> GrandCompany => LuminaUtils.CreateRef<GrandCompany>((uint)this.Struct->GrandCompany);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the primary client language of the character.
|
||||
|
|
@ -178,7 +178,7 @@ public unsafe class CharacterData
|
|||
/// <summary>
|
||||
/// Gets the job of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<ClassJob> ClassJob => new(this.Struct->Job);
|
||||
public RowRef<ClassJob> ClassJob => LuminaUtils.CreateRef<ClassJob>(this.Struct->Job);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the character.
|
||||
|
|
|
|||
|
|
@ -38,13 +38,6 @@ public abstract partial class Payload
|
|||
/// </summary>
|
||||
public bool Dirty { get; protected set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lumina instance to use for any necessary data lookups.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
// TODO: We should refactor this. It should not be possible to get IDataManager through here.
|
||||
protected IDataManager DataResolver => Service<DataManager>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a binary representation of a payload into its corresponding nice object payload.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -106,28 +110,28 @@ public class AutoTranslatePayload : Payload, ITextProvider
|
|||
this.Key = GetInteger(reader);
|
||||
}
|
||||
|
||||
private static ReadOnlySeString ResolveTextCommand(TextCommand command)
|
||||
{
|
||||
// TextCommands prioritize the `Alias` field, if it not empty
|
||||
// Example for this is /rangerpose2l which becomes /blackrangerposeb in chat
|
||||
return !command.Alias.IsEmpty ? command.Alias : command.Command;
|
||||
}
|
||||
|
||||
private string Resolve()
|
||||
{
|
||||
string value = null;
|
||||
|
||||
var sheet = this.DataResolver.GetExcelSheet<Completion>();
|
||||
var excelModule = Service<DataManager>.Get().Excel;
|
||||
var completionSheet = excelModule.GetSheet<Completion>();
|
||||
|
||||
Completion row = null;
|
||||
try
|
||||
{
|
||||
// try to get the row in the Completion table itself, because this is 'easiest'
|
||||
// The row may not exist at all (if the Key is for another table), or it could be the wrong row
|
||||
// (again, if it's meant for another table)
|
||||
row = sheet.GetRow(this.Key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
} // don't care, row will be null
|
||||
// try to get the row in the Completion table itself, because this is 'easiest'
|
||||
// The row may not exist at all (if the Key is for another table), or it could be the wrong row
|
||||
// (again, if it's meant for another table)
|
||||
|
||||
if (row?.Group == this.Group)
|
||||
if (completionSheet.GetRowOrDefault(this.Key) is { } completion && completion.Group == this.Group)
|
||||
{
|
||||
// if the row exists in this table and the group matches, this is actually the correct data
|
||||
value = row.Text;
|
||||
value = completion.Text.ExtractText();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -135,34 +139,34 @@ public class AutoTranslatePayload : Payload, ITextProvider
|
|||
{
|
||||
// we need to get the linked table and do the lookup there instead
|
||||
// in this case, there will only be one entry for this group id
|
||||
row = sheet.First(r => r.Group == this.Group);
|
||||
var row = completionSheet.First(r => r.Group == this.Group);
|
||||
// many of the names contain valid id ranges after the table name, but we don't need those
|
||||
var actualTableName = row.LookupTable.RawString.Split('[')[0];
|
||||
var actualTableName = row.LookupTable.ExtractText().Split('[')[0];
|
||||
|
||||
var name = actualTableName switch
|
||||
{
|
||||
"Action" => this.DataResolver.GetExcelSheet<Lumina.Excel.GeneratedSheets.Action>().GetRow(this.Key).Name,
|
||||
"ActionComboRoute" => this.DataResolver.GetExcelSheet<ActionComboRoute>().GetRow(this.Key).Name,
|
||||
"BuddyAction" => this.DataResolver.GetExcelSheet<BuddyAction>().GetRow(this.Key).Name,
|
||||
"ClassJob" => this.DataResolver.GetExcelSheet<ClassJob>().GetRow(this.Key).Name,
|
||||
"Companion" => this.DataResolver.GetExcelSheet<Companion>().GetRow(this.Key).Singular,
|
||||
"CraftAction" => this.DataResolver.GetExcelSheet<CraftAction>().GetRow(this.Key).Name,
|
||||
"GeneralAction" => this.DataResolver.GetExcelSheet<GeneralAction>().GetRow(this.Key).Name,
|
||||
"GuardianDeity" => this.DataResolver.GetExcelSheet<GuardianDeity>().GetRow(this.Key).Name,
|
||||
"MainCommand" => this.DataResolver.GetExcelSheet<MainCommand>().GetRow(this.Key).Name,
|
||||
"Mount" => this.DataResolver.GetExcelSheet<Mount>().GetRow(this.Key).Singular,
|
||||
"Pet" => this.DataResolver.GetExcelSheet<Pet>().GetRow(this.Key).Name,
|
||||
"PetAction" => this.DataResolver.GetExcelSheet<PetAction>().GetRow(this.Key).Name,
|
||||
"PetMirage" => this.DataResolver.GetExcelSheet<PetMirage>().GetRow(this.Key).Name,
|
||||
"PlaceName" => this.DataResolver.GetExcelSheet<PlaceName>().GetRow(this.Key).Name,
|
||||
"Race" => this.DataResolver.GetExcelSheet<Race>().GetRow(this.Key).Masculine,
|
||||
"TextCommand" => this.ResolveTextCommand(),
|
||||
"Tribe" => this.DataResolver.GetExcelSheet<Tribe>().GetRow(this.Key).Masculine,
|
||||
"Weather" => this.DataResolver.GetExcelSheet<Weather>().GetRow(this.Key).Name,
|
||||
"Action" => excelModule.GetSheet<Lumina.Excel.Sheets.Action>().GetRow(this.Key).Name,
|
||||
"ActionComboRoute" => excelModule.GetSheet<ActionComboRoute>().GetRow(this.Key).Name,
|
||||
"BuddyAction" => excelModule.GetSheet<BuddyAction>().GetRow(this.Key).Name,
|
||||
"ClassJob" => excelModule.GetSheet<ClassJob>().GetRow(this.Key).Name,
|
||||
"Companion" => excelModule.GetSheet<Companion>().GetRow(this.Key).Singular,
|
||||
"CraftAction" => excelModule.GetSheet<CraftAction>().GetRow(this.Key).Name,
|
||||
"GeneralAction" => excelModule.GetSheet<GeneralAction>().GetRow(this.Key).Name,
|
||||
"GuardianDeity" => excelModule.GetSheet<GuardianDeity>().GetRow(this.Key).Name,
|
||||
"MainCommand" => excelModule.GetSheet<MainCommand>().GetRow(this.Key).Name,
|
||||
"Mount" => excelModule.GetSheet<Mount>().GetRow(this.Key).Singular,
|
||||
"Pet" => excelModule.GetSheet<Pet>().GetRow(this.Key).Name,
|
||||
"PetAction" => excelModule.GetSheet<PetAction>().GetRow(this.Key).Name,
|
||||
"PetMirage" => excelModule.GetSheet<PetMirage>().GetRow(this.Key).Name,
|
||||
"PlaceName" => excelModule.GetSheet<PlaceName>().GetRow(this.Key).Name,
|
||||
"Race" => excelModule.GetSheet<Race>().GetRow(this.Key).Masculine,
|
||||
"TextCommand" => AutoTranslatePayload.ResolveTextCommand(excelModule.GetSheet<TextCommand>().GetRow(this.Key)),
|
||||
"Tribe" => excelModule.GetSheet<Tribe>().GetRow(this.Key).Masculine,
|
||||
"Weather" => excelModule.GetSheet<Weather>().GetRow(this.Key).Name,
|
||||
_ => throw new Exception(actualTableName),
|
||||
};
|
||||
|
||||
value = name;
|
||||
value = name.ExtractText();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -172,12 +176,4 @@ public class AutoTranslatePayload : Payload, ITextProvider
|
|||
|
||||
return value;
|
||||
}
|
||||
|
||||
private Lumina.Text.SeString ResolveTextCommand()
|
||||
{
|
||||
// TextCommands prioritize the `Alias` field, if it not empty
|
||||
// Example for this is /rangerpose2l which becomes /blackrangerposeb in chat
|
||||
var result = this.DataResolver.GetExcelSheet<TextCommand>().GetRow(this.Key);
|
||||
return result.Alias.Payloads.Count > 0 ? result.Alias : result.Command;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
||||
|
|
@ -14,8 +15,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class ItemPayload : Payload
|
||||
{
|
||||
private Item? item;
|
||||
|
||||
// mainly to allow overriding the name (for things like owo)
|
||||
// TODO: even though this is present in some item links, it may not really have a use at all
|
||||
// For things like owo, changing the text payload is probably correct, whereas changing the
|
||||
|
|
@ -131,27 +130,13 @@ public class ItemPayload : Payload
|
|||
public uint RawItemId => this.rawItemId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying Lumina Item represented by this payload.
|
||||
/// Gets the underlying Lumina data represented by this payload. This is either a Item or EventItem <see cref="RowRef{T}"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Item? Item
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO(goat): This should be revamped/removed on an API level change.
|
||||
if (this.Kind == ItemKind.EventItem)
|
||||
{
|
||||
Log.Warning("Event items cannot be fetched from the ItemPayload");
|
||||
return null;
|
||||
}
|
||||
|
||||
this.item ??= this.DataResolver.GetExcelSheet<Item>()!.GetRow(this.ItemId);
|
||||
return this.item;
|
||||
}
|
||||
}
|
||||
public RowRef Item =>
|
||||
this.Kind == ItemKind.EventItem
|
||||
? (RowRef)LuminaUtils.CreateRef<EventItem>(this.ItemId)
|
||||
: (RowRef)LuminaUtils.CreateRef<Item>(this.ItemId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this item link is for a high-quality version of the item.
|
||||
|
|
@ -183,7 +168,8 @@ public class ItemPayload : Payload
|
|||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {this.displayName ?? this.Item?.Name}";
|
||||
var name = this.displayName ?? (this.Item.GetValueOrDefault<Item>()?.Name ?? this.Item.GetValueOrDefault<EventItem>()?.Name)?.ExtractText();
|
||||
return $"{this.Type} - ItemId: {this.ItemId}, Kind: {this.Kind}, Name: {name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -11,11 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class MapLinkPayload : Payload
|
||||
{
|
||||
private Map map;
|
||||
private TerritoryType territoryType;
|
||||
private string placeNameRegion;
|
||||
private string placeName;
|
||||
|
||||
[JsonProperty]
|
||||
private uint territoryTypeId;
|
||||
|
||||
|
|
@ -38,8 +36,8 @@ public class MapLinkPayload : Payload
|
|||
// this fudge is necessary basically to ensure we don't shift down a full tenth
|
||||
// because essentially values are truncated instead of rounded, so 3.09999f will become
|
||||
// 3.0f and not 3.1f
|
||||
this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetX);
|
||||
this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor, this.Map.OffsetY);
|
||||
this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.Value.SizeFactor, this.Map.Value.OffsetX);
|
||||
this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.Value.SizeFactor, this.Map.Value.OffsetY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -72,20 +70,14 @@ public class MapLinkPayload : Payload
|
|||
/// <summary>
|
||||
/// Gets the Map specified for this map link.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Map Map => this.map ??= this.DataResolver.GetExcelSheet<Map>().GetRow(this.mapId);
|
||||
public RowRef<Map> Map => LuminaUtils.CreateRef<Map>(this.mapId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TerritoryType specified for this map link.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet<TerritoryType>().GetRow(this.territoryTypeId);
|
||||
public RowRef<TerritoryType> TerritoryType => LuminaUtils.CreateRef<TerritoryType>(this.territoryTypeId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal x-coordinate for this map position.
|
||||
|
|
@ -102,13 +94,13 @@ public class MapLinkPayload : Payload
|
|||
/// <summary>
|
||||
/// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded.
|
||||
/// </summary>
|
||||
public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor, this.Map.OffsetX);
|
||||
public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.Value.SizeFactor, this.Map.Value.OffsetX);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor, this.Map.OffsetY);
|
||||
public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.Value.SizeFactor, this.Map.Value.OffsetY);
|
||||
|
||||
// there is no Z; it's purely in the text payload where applicable
|
||||
|
||||
|
|
@ -143,18 +135,18 @@ public class MapLinkPayload : Payload
|
|||
/// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea".
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name;
|
||||
public string PlaceNameRegion => this.TerritoryType.Value.PlaceNameRegion.Value.Name.ExtractText();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks".
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name;
|
||||
public string PlaceName => this.TerritoryType.Value.PlaceName.Value.Name.ExtractText();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload.
|
||||
/// </summary>
|
||||
public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}";
|
||||
public string DataString => $"m:{this.territoryTypeId},{this.mapId},{this.RawX},{this.RawY}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -12,8 +15,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class PlayerPayload : Payload
|
||||
{
|
||||
private World world;
|
||||
|
||||
[JsonProperty]
|
||||
private uint serverId;
|
||||
|
||||
|
|
@ -43,11 +44,8 @@ public class PlayerPayload : Payload
|
|||
/// <summary>
|
||||
/// Gets the Lumina object representing the player's home server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public World World => this.world ??= this.DataResolver.GetExcelSheet<World>().GetRow(this.serverId);
|
||||
public RowRef<World> World => LuminaUtils.CreateRef<World>(this.serverId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the player's displayed name. This does not contain the server name.
|
||||
|
|
@ -72,7 +70,7 @@ public class PlayerPayload : Payload
|
|||
/// The world name will always be present.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}";
|
||||
public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.ValueNullable?.Name}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override PayloadType Type => PayloadType.Player;
|
||||
|
|
@ -80,7 +78,7 @@ public class PlayerPayload : Payload
|
|||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}";
|
||||
return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.ValueNullable?.Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class QuestPayload : Payload
|
||||
{
|
||||
private Quest quest;
|
||||
|
||||
[JsonProperty]
|
||||
private uint questId;
|
||||
|
||||
|
|
@ -40,16 +41,13 @@ public class QuestPayload : Payload
|
|||
/// <summary>
|
||||
/// Gets the underlying Lumina Quest represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet<Quest>().GetRow(this.questId);
|
||||
public RowRef<Quest> Quest => LuminaUtils.CreateRef<Quest>(this.questId);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}";
|
||||
return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest.ValueNullable?.Name.ExtractText() ?? "QUEST NOT FOUND"}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class StatusPayload : Payload
|
||||
{
|
||||
private Status status;
|
||||
|
||||
[JsonProperty]
|
||||
private uint statusId;
|
||||
|
||||
|
|
@ -40,16 +41,13 @@ public class StatusPayload : Payload
|
|||
/// <summary>
|
||||
/// Gets the Lumina Status object represented by this payload.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Status Status => this.status ??= this.DataResolver.GetExcelSheet<Status>().GetRow(this.statusId);
|
||||
public RowRef<Status> Status => LuminaUtils.CreateRef<Status>(this.statusId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}";
|
||||
return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.ValueNullable?.Name}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class UIForegroundPayload : Payload
|
||||
{
|
||||
private UIColor color;
|
||||
|
||||
[JsonProperty]
|
||||
private ushort colorKey;
|
||||
|
||||
|
|
@ -51,11 +52,8 @@ public class UIForegroundPayload : Payload
|
|||
/// <summary>
|
||||
/// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
|
||||
public RowRef<UIColor> UIColor => LuminaUtils.CreateRef<UIColor>(this.colorKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color key used as a lookup in the UIColor table for this foreground color.
|
||||
|
|
@ -63,15 +61,11 @@ public class UIForegroundPayload : Payload
|
|||
[JsonIgnore]
|
||||
public ushort ColorKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.colorKey;
|
||||
}
|
||||
get => this.colorKey;
|
||||
|
||||
set
|
||||
{
|
||||
this.colorKey = value;
|
||||
this.color = null;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -80,13 +74,13 @@ public class UIForegroundPayload : Payload
|
|||
/// Gets the Red/Green/Blue/Alpha values for this foreground color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGBA => this.UIColor.UIForeground;
|
||||
public uint RGBA => this.UIColor.Value.UIForeground;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ABGR value for this foreground color, as ImGui requires it in PushColor.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.UIForeground);
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIForeground);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Data;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -11,8 +14,6 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
/// </summary>
|
||||
public class UIGlowPayload : Payload
|
||||
{
|
||||
private UIColor color;
|
||||
|
||||
[JsonProperty]
|
||||
private ushort colorKey;
|
||||
|
||||
|
|
@ -57,7 +58,6 @@ public class UIGlowPayload : Payload
|
|||
set
|
||||
{
|
||||
this.colorKey = value;
|
||||
this.color = null;
|
||||
this.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -71,22 +71,19 @@ public class UIGlowPayload : Payload
|
|||
/// Gets the Red/Green/Blue/Alpha values for this glow color, encoded as a typical hex color.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint RGBA => this.UIColor.UIGlow;
|
||||
public uint RGBA => this.UIColor.Value.UIGlow;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ABGR value for this glow color, as ImGui requires it in PushColor.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.UIGlow);
|
||||
public uint ABGR => Interface.ColorHelpers.SwapEndianness(this.UIColor.Value.UIGlow);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is evaluated lazily and cached.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
|
||||
public RowRef<UIColor> UIColor => LuminaUtils.CreateRef<UIColor>(this.colorKey);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using System.Text;
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Text.SeStringHandling;
|
||||
|
|
@ -200,12 +200,12 @@ public class SeString
|
|||
case ItemPayload.ItemKind.Normal:
|
||||
case ItemPayload.ItemKind.Collectible:
|
||||
case ItemPayload.ItemKind.Hq:
|
||||
var item = data.GetExcelSheet<Item>()?.GetRow(itemId);
|
||||
displayName = item?.Name;
|
||||
var item = data.GetExcelSheet<Item>()?.GetRowOrDefault(itemId);
|
||||
displayName = item?.Name.ExtractText();
|
||||
rarity = item?.Rarity ?? 1;
|
||||
break;
|
||||
case ItemPayload.ItemKind.EventItem:
|
||||
displayName = data.GetExcelSheet<EventItem>()?.GetRow(itemId)?.Name;
|
||||
displayName = data.GetExcelSheet<EventItem>()?.GetRowOrDefault(itemId)?.Name.ExtractText();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
|
||||
|
|
@ -251,7 +251,7 @@ public class SeString
|
|||
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
|
||||
public static SeString CreateItemLink(Item item, bool isHq, string? displayNameOverride = null)
|
||||
{
|
||||
return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name);
|
||||
return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name.ExtractText());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -360,15 +360,14 @@ public class SeString
|
|||
var mapSheet = data.GetExcelSheet<Map>();
|
||||
|
||||
var matches = data.GetExcelSheet<PlaceName>()
|
||||
.Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant())
|
||||
.ToArray();
|
||||
.Where(row => row.Name.ExtractText().Equals(placeName, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
foreach (var place in matches)
|
||||
{
|
||||
var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId);
|
||||
if (map != null && map.TerritoryType.Row != 0)
|
||||
var map = mapSheet.Cast<Map?>().FirstOrDefault(row => row!.Value.PlaceName.RowId == place.RowId);
|
||||
if (map.HasValue && map.Value.TerritoryType.RowId != 0)
|
||||
{
|
||||
return CreateMapLinkWithInstance(map.TerritoryType.Row, map.RowId, instance, xCoord, yCoord, fudgeFactor);
|
||||
return CreateMapLinkWithInstance(map.Value.TerritoryType.RowId, map.Value.RowId, instance, xCoord, yCoord, fudgeFactor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public sealed class XivChatEntry
|
|||
/// <summary>
|
||||
/// Gets or sets the type of entry.
|
||||
/// </summary>
|
||||
public XivChatType Type { get; set; } = XivChatType.Debug;
|
||||
public XivChatType? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message timestamp.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Components;
|
||||
|
|
@ -41,7 +43,9 @@ public static partial class ImGuiComponents
|
|||
ImGui.OpenPopup($"###ColorPickerPopup{id}");
|
||||
}
|
||||
|
||||
if (ImGui.BeginPopup($"###ColorPickerPopup{id}"))
|
||||
using var popup = ImRaii.Popup($"###ColorPickerPopup{id}");
|
||||
|
||||
if (popup)
|
||||
{
|
||||
if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags))
|
||||
{
|
||||
|
|
@ -61,8 +65,6 @@ public static partial class ImGuiComponents
|
|||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
return selectedColor;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Components;
|
||||
|
|
@ -21,17 +23,16 @@ public static partial class ImGuiComponents
|
|||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f)
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
var text = icon.ToIconString();
|
||||
if (id.HasValue)
|
||||
{
|
||||
text = $"{text}##{id}";
|
||||
}
|
||||
|
||||
var text = icon.ToIconString();
|
||||
if (id.HasValue)
|
||||
text = $"{text}##{id}";
|
||||
|
||||
var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult);
|
||||
|
||||
ImGui.PopFont();
|
||||
|
||||
return button;
|
||||
return DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -45,31 +46,28 @@ public static partial class ImGuiComponents
|
|||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f)
|
||||
{
|
||||
using var col = new ImRaii.Color();
|
||||
|
||||
if (defaultColor.HasValue)
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value);
|
||||
{
|
||||
col.Push(ImGuiCol.Button, defaultColor.Value);
|
||||
}
|
||||
|
||||
if (activeColor.HasValue)
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value);
|
||||
{
|
||||
col.Push(ImGuiCol.ButtonActive, activeColor.Value);
|
||||
}
|
||||
|
||||
if (hoveredColor.HasValue)
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value);
|
||||
{
|
||||
col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
|
||||
}
|
||||
|
||||
var style = ImGui.GetStyle();
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult);
|
||||
|
||||
var button = ImGui.Button(labelWithId);
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
if (defaultColor.HasValue)
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (activeColor.HasValue)
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
if (hoveredColor.HasValue)
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
return button;
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, style.Alpha * alphaMult))
|
||||
{
|
||||
return ImGui.Button(labelWithId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Components;
|
||||
|
|
@ -18,17 +22,32 @@ public static partial class ImGuiComponents
|
|||
/// </summary>
|
||||
/// <param name="helpText">The text to display on hover.</param>
|
||||
/// <param name="icon">The icon to use.</param>
|
||||
public static void HelpMarker(string helpText, FontAwesomeIcon icon)
|
||||
/// <param name="color">The color of the icon.</param>
|
||||
public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null)
|
||||
{
|
||||
using var col = new ImRaii.Color();
|
||||
|
||||
if (color.HasValue)
|
||||
{
|
||||
col.Push(ImGuiCol.TextDisabled, color.Value);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.TextDisabled(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
if (!ImGui.IsItemHovered()) return;
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f);
|
||||
ImGui.TextUnformatted(helpText);
|
||||
ImGui.PopTextWrapPos();
|
||||
ImGui.EndTooltip();
|
||||
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.TextDisabled(icon.ToIconString());
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using (ImRaii.Tooltip())
|
||||
{
|
||||
using (ImRaii.TextWrapPos(ImGui.GetFontSize() * 35.0f))
|
||||
{
|
||||
ImGui.TextUnformatted(helpText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Components;
|
||||
|
|
@ -15,8 +17,26 @@ public static partial class ImGuiComponents
|
|||
/// </summary>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(FontAwesomeIcon icon)
|
||||
=> IconButton(icon, null, null, null);
|
||||
public static bool IconButton(FontAwesomeIcon icon) => IconButton(icon, null);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
/// </summary>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(FontAwesomeIcon icon, Vector2 size) => IconButton(icon, null, null, null, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
/// </summary>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
|
|
@ -24,8 +44,28 @@ public static partial class ImGuiComponents
|
|||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(int id, FontAwesomeIcon icon)
|
||||
=> IconButton(id, icon, null, null, null);
|
||||
public static bool IconButton(int id, FontAwesomeIcon icon) => IconButton(id, icon, null);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(int id, FontAwesomeIcon icon, Vector2 size) => IconButton(id, icon, null, null, null, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null) => IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
|
|
@ -33,51 +73,45 @@ public static partial class ImGuiComponents
|
|||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(string id, FontAwesomeIcon icon)
|
||||
=> IconButton(id, icon, null, null, null);
|
||||
public static bool IconButton(string id, FontAwesomeIcon icon) => IconButton(id, icon, null);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(string id, FontAwesomeIcon icon, Vector2 size)
|
||||
=> IconButton(id, icon, null, null, null, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
|
||||
=> IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
/// </summary>
|
||||
/// <param name="iconText">Text already containing the icon string.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(string iconText)
|
||||
=> IconButton(iconText, null, null, null);
|
||||
public static bool IconButton(string iconText) => IconButton(iconText, null);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button.
|
||||
/// </summary>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <param name="iconText">Text already containing the icon string.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
=> IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
=> IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the button.</param>
|
||||
/// <param name="icon">The icon for the button.</param>
|
||||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(string id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
=> IconButton($"{icon.ToIconString()}##{id}", defaultColor, activeColor, hoveredColor);
|
||||
public static bool IconButton(string iconText, Vector2 size) => IconButton(iconText, null, null, null, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
|
|
@ -86,62 +120,72 @@ public static partial class ImGuiComponents
|
|||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
public static bool IconButton(string iconText, Vector4? defaultColor, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
|
||||
{
|
||||
var numColors = 0;
|
||||
using var col = new ImRaii.Color();
|
||||
|
||||
if (defaultColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value);
|
||||
numColors++;
|
||||
col.Push(ImGuiCol.Button, defaultColor.Value);
|
||||
}
|
||||
|
||||
if (activeColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value);
|
||||
numColors++;
|
||||
col.Push(ImGuiCol.ButtonActive, activeColor.Value);
|
||||
}
|
||||
|
||||
if (hoveredColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value);
|
||||
numColors++;
|
||||
col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
|
||||
}
|
||||
|
||||
if (size.HasValue)
|
||||
{
|
||||
size *= ImGuiHelpers.GlobalScale;
|
||||
}
|
||||
|
||||
var icon = iconText;
|
||||
if (icon.Contains("#"))
|
||||
icon = icon[..icon.IndexOf("#", StringComparison.Ordinal)];
|
||||
if (icon.Contains('#'))
|
||||
{
|
||||
icon = icon[..icon.IndexOf('#', StringComparison.Ordinal)];
|
||||
}
|
||||
|
||||
ImGui.PushID(iconText);
|
||||
bool button;
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var iconSize = ImGui.CalcTextSize(icon);
|
||||
ImGui.PopFont();
|
||||
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
var cursor = ImGui.GetCursorScreenPos();
|
||||
|
||||
// Draw an ImGui button with the icon and text
|
||||
var buttonWidth = iconSize.X + (ImGui.GetStyle().FramePadding.X * 2);
|
||||
var buttonHeight = ImGui.GetFrameHeight();
|
||||
var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight));
|
||||
|
||||
// Draw the icon on the window drawlist
|
||||
var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y);
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon);
|
||||
ImGui.PopFont();
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
var iconSize = ImGui.CalcTextSize(icon);
|
||||
var cursor = ImGui.GetCursorScreenPos();
|
||||
|
||||
ImGui.PopID();
|
||||
var width = size is { X: not 0 } ? size.Value.X : iconSize.X + (ImGui.GetStyle().FramePadding.X * 2);
|
||||
var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight();
|
||||
|
||||
if (numColors > 0)
|
||||
ImGui.PopStyleColor(numColors);
|
||||
var buttonSize = new Vector2(width, height);
|
||||
|
||||
using (ImRaii.PushId(iconText))
|
||||
{
|
||||
button = ImGui.Button(string.Empty, buttonSize);
|
||||
}
|
||||
|
||||
var iconPos = cursor + ((buttonSize - iconSize) / 2f);
|
||||
|
||||
ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
/// </summary>
|
||||
/// <param name="icon">Icon to show.</param>
|
||||
/// <param name="text">Text to show.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector2 size) => IconButtonWithText(icon, text, null, null, null, size);
|
||||
|
||||
/// <summary>
|
||||
/// IconButton component to use an icon as a button with color options.
|
||||
/// </summary>
|
||||
|
|
@ -150,61 +194,72 @@ public static partial class ImGuiComponents
|
|||
/// <param name="defaultColor">The default color of the button.</param>
|
||||
/// <param name="activeColor">The color of the button when active.</param>
|
||||
/// <param name="hoveredColor">The color of the button when hovered.</param>
|
||||
/// <param name="size">Sets the size of the button. If either dimension is set to 0, that dimension will conform to the size of the icon & text.</param>
|
||||
/// <returns>Indicator if button is clicked.</returns>
|
||||
public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
public static bool IconButtonWithText(FontAwesomeIcon icon, string text, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, Vector2? size = null)
|
||||
{
|
||||
var numColors = 0;
|
||||
using var col = new ImRaii.Color();
|
||||
|
||||
if (defaultColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value);
|
||||
numColors++;
|
||||
col.Push(ImGuiCol.Button, defaultColor.Value);
|
||||
}
|
||||
|
||||
if (activeColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value);
|
||||
numColors++;
|
||||
col.Push(ImGuiCol.ButtonActive, activeColor.Value);
|
||||
}
|
||||
|
||||
if (hoveredColor.HasValue)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value);
|
||||
numColors++;
|
||||
col.Push(ImGuiCol.ButtonHovered, hoveredColor.Value);
|
||||
}
|
||||
|
||||
ImGui.PushID(text);
|
||||
if (size.HasValue)
|
||||
{
|
||||
size *= ImGuiHelpers.GlobalScale;
|
||||
}
|
||||
|
||||
bool button;
|
||||
|
||||
Vector2 iconSize;
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
||||
}
|
||||
|
||||
var textStr = text;
|
||||
if (textStr.Contains('#'))
|
||||
{
|
||||
textStr = textStr[..textStr.IndexOf('#', StringComparison.Ordinal)];
|
||||
}
|
||||
|
||||
var framePadding = ImGui.GetStyle().FramePadding;
|
||||
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
|
||||
var textSize = ImGui.CalcTextSize(text);
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
var cursor = ImGui.GetCursorScreenPos();
|
||||
|
||||
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
// Draw an ImGui button with the icon and text
|
||||
var buttonWidth = iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding;
|
||||
var buttonHeight = ImGui.GetFrameHeight();
|
||||
var button = ImGui.Button(string.Empty, new Vector2(buttonWidth, buttonHeight));
|
||||
|
||||
// Draw the icon on the window drawlist
|
||||
var iconPos = new Vector2(cursor.X + ImGui.GetStyle().FramePadding.X, cursor.Y + ImGui.GetStyle().FramePadding.Y);
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
|
||||
// Draw the text on the window drawlist
|
||||
var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + ImGui.GetStyle().FramePadding.Y);
|
||||
dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), text);
|
||||
using (ImRaii.PushId(text))
|
||||
{
|
||||
var textSize = ImGui.CalcTextSize(textStr);
|
||||
|
||||
ImGui.PopID();
|
||||
var width = size is { X: not 0 } ? size.Value.X : iconSize.X + textSize.X + (framePadding.X * 2) + iconPadding;
|
||||
var height = size is { Y: not 0 } ? size.Value.Y : ImGui.GetFrameHeight();
|
||||
|
||||
if (numColors > 0)
|
||||
ImGui.PopStyleColor(numColors);
|
||||
button = ImGui.Button(string.Empty, new Vector2(width, height));
|
||||
}
|
||||
|
||||
var iconPos = cursor + framePadding;
|
||||
var textPos = new Vector2(iconPos.X + iconSize.X + iconPadding, cursor.Y + framePadding.Y);
|
||||
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
dl.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
|
||||
}
|
||||
|
||||
dl.AddText(textPos, ImGui.GetColorU32(ImGuiCol.Text), textStr);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
|
@ -217,16 +272,15 @@ public static partial class ImGuiComponents
|
|||
/// <returns>Width.</returns>
|
||||
internal static float GetIconButtonWithTextWidth(FontAwesomeIcon icon, string text)
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
var iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
||||
ImGui.PopFont();
|
||||
|
||||
var textSize = ImGui.CalcTextSize(text);
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
var cursor = ImGui.GetCursorScreenPos();
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
var iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
||||
|
||||
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding;
|
||||
var textSize = ImGui.CalcTextSize(text);
|
||||
|
||||
var iconPadding = 3 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
return iconSize.X + textSize.X + (ImGui.GetStyle().FramePadding.X * 2) + iconPadding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Components;
|
||||
|
||||
public static partial class ImGuiComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// A radio-like input that uses icon buttons.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value being set.</typeparam>
|
||||
/// <param name="label">Text that will be used to generate individual labels for the buttons.</param>
|
||||
/// <param name="val">The value to set.</param>
|
||||
/// <param name="optionIcons">The icons that will be displayed on each button.</param>
|
||||
/// <param name="optionValues">The options that each button will apply.</param>
|
||||
/// <param name="columns">Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row).</param>
|
||||
/// <param name="buttonSize">Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <param name="defaultColor">The default color of the button range.</param>
|
||||
/// <param name="activeColor">The color of the actively-selected button.</param>
|
||||
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
|
||||
/// <returns>True if any button is clicked.</returns>
|
||||
internal static bool IconButtonSelect<T>(string label, ref T val, IEnumerable<FontAwesomeIcon> optionIcons, IEnumerable<T> optionValues, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
{
|
||||
var options = optionIcons.Zip(optionValues, static (icon, value) => new KeyValuePair<FontAwesomeIcon, T>(icon, value));
|
||||
return IconButtonSelect(label, ref val, options, columns, buttonSize, defaultColor, activeColor, hoveredColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A radio-like input that uses icon buttons.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value being set.</typeparam>
|
||||
/// <param name="label">Text that will be used to generate individual labels for the buttons.</param>
|
||||
/// <param name="val">The value to set.</param>
|
||||
/// <param name="options">A list of all icon/option pairs.</param>
|
||||
/// <param name="columns">Arranges the buttons in a grid with the given number of columns. 0 = ignored (all buttons drawn in one row).</param>
|
||||
/// <param name="buttonSize">Sets the size of all buttons. If either dimension is set to 0, that dimension will conform to the size of the icon.</param>
|
||||
/// <param name="defaultColor">The default color of the button range.</param>
|
||||
/// <param name="activeColor">The color of the actively-selected button.</param>
|
||||
/// <param name="hoveredColor">The color of the buttons when hovered.</param>
|
||||
/// <returns>True if any button is clicked.</returns>
|
||||
internal static unsafe bool IconButtonSelect<T>(string label, ref T val, IEnumerable<KeyValuePair<FontAwesomeIcon, T>> options, uint columns = 0, Vector2? buttonSize = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null)
|
||||
{
|
||||
defaultColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.Button);
|
||||
activeColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive);
|
||||
hoveredColor ??= *ImGui.GetStyleColorVec4(ImGuiCol.ButtonHovered);
|
||||
|
||||
var result = false;
|
||||
|
||||
var innerSpacing = ImGui.GetStyle().ItemInnerSpacing;
|
||||
var y = ImGui.GetCursorPosY();
|
||||
|
||||
var optArr = options.ToArray();
|
||||
for (var i = 0; i < optArr.Length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
if (columns == 0 || i % columns != 0)
|
||||
{
|
||||
ImGui.SameLine(0, innerSpacing.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
y += (buttonSize is { Y: not 0 } ? buttonSize.Value.Y * ImGuiHelpers.GlobalScale : ImGui.GetFrameHeight()) + innerSpacing.Y;
|
||||
|
||||
ImGui.SetCursorPosY(y);
|
||||
}
|
||||
}
|
||||
|
||||
optArr[i].Deconstruct(out var icon, out var option);
|
||||
|
||||
var selected = val is not null && val.Equals(option);
|
||||
|
||||
if (IconButton($"{label}{option}{i}", icon, selected ? activeColor : defaultColor, activeColor, hoveredColor, buttonSize))
|
||||
{
|
||||
val = option;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Components;
|
||||
|
|
@ -24,7 +26,13 @@ public static partial class ImGuiComponents
|
|||
else
|
||||
{
|
||||
ImGui.Text(value + "*");
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using (ImRaii.Tooltip())
|
||||
{
|
||||
ImGui.TextUnformatted(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,14 @@ public static partial class ImGuiComponents
|
|||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f);
|
||||
}
|
||||
|
||||
drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1)));
|
||||
|
||||
return changed;
|
||||
|
|
@ -62,7 +67,7 @@ public static partial class ImGuiComponents
|
|||
// TODO: animate
|
||||
ImGui.InvisibleButton(id, new Vector2(width, height));
|
||||
|
||||
var dimFactor = 0.5f;
|
||||
const float dimFactor = 0.5f;
|
||||
|
||||
drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f);
|
||||
drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor));
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
|||
using FFXIVClientStructs.FFXIV.Component.Text;
|
||||
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets2;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.Expressions;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
|||
|
||||
using ImGuiNET;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets2;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text.Parse;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
|
|||
using CheapLoc;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Console;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
|
|
@ -57,6 +58,7 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
private readonly Dalamud dalamud;
|
||||
private readonly DalamudConfiguration configuration;
|
||||
private readonly InterfaceManager interfaceManager;
|
||||
private readonly DataManager dataManager;
|
||||
|
||||
private readonly ChangelogWindow changelogWindow;
|
||||
private readonly ColorDemoWindow colorDemoWindow;
|
||||
|
|
@ -98,6 +100,7 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
DalamudConfiguration configuration,
|
||||
FontAtlasFactory fontAtlasFactory,
|
||||
InterfaceManager interfaceManager,
|
||||
DataManager dataManager,
|
||||
PluginImageCache pluginImageCache,
|
||||
DalamudAssetManager dalamudAssetManager,
|
||||
Game.Framework framework,
|
||||
|
|
@ -110,6 +113,7 @@ internal class DalamudInterface : IInternalDisposableService
|
|||
this.dalamud = dalamud;
|
||||
this.configuration = configuration;
|
||||
this.interfaceManager = interfaceManager;
|
||||
this.dataManager = dataManager;
|
||||
|
||||
this.WindowSystem = new WindowSystem("DalamudCore");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
using Dalamud.Interface.Internal.UiDebug2.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
|
||||
|
||||
public unsafe partial class AddonTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Prints a table of AtkValues associated with a given addon.
|
||||
/// </summary>
|
||||
/// <param name="addon">The addon to look up.</param>
|
||||
internal static void PrintAtkValues(AtkUnitBase* addon)
|
||||
{
|
||||
var atkValue = addon->AtkValues;
|
||||
if (addon->AtkValuesCount > 0 && atkValue != null)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode($"Atk Values [{addon->AtkValuesCount}]###atkValues_{addon->NameString}");
|
||||
if (tree)
|
||||
{
|
||||
using (ImRaii.Table(
|
||||
"atkUnitBase_atkValueTable",
|
||||
3,
|
||||
ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg))
|
||||
{
|
||||
ImGui.TableSetupColumn("Index");
|
||||
ImGui.TableSetupColumn("Type");
|
||||
ImGui.TableSetupColumn("Value");
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < addon->AtkValuesCount; i++)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
if (atkValue->Type == 0)
|
||||
{
|
||||
ImGui.TextDisabled($"#{i}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text($"#{i}");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (atkValue->Type == 0)
|
||||
{
|
||||
ImGui.TextDisabled("Not Set");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text($"{atkValue->Type}");
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
switch (atkValue->Type)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case ValueType.Int:
|
||||
case ValueType.UInt:
|
||||
{
|
||||
ImGui.TextUnformatted($"{atkValue->Int}");
|
||||
break;
|
||||
}
|
||||
|
||||
case ValueType.ManagedString:
|
||||
case ValueType.String8:
|
||||
case ValueType.String:
|
||||
{
|
||||
if (atkValue->String == null)
|
||||
{
|
||||
ImGui.TextDisabled("null");
|
||||
}
|
||||
else
|
||||
{
|
||||
var str = MemoryHelper.ReadSeStringNullTerminated(new nint(atkValue->String));
|
||||
Util.ShowStruct(str, (ulong)atkValue);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ValueType.Bool:
|
||||
{
|
||||
ImGui.TextUnformatted($"{atkValue->Byte != 0}");
|
||||
break;
|
||||
}
|
||||
|
||||
case ValueType.Pointer:
|
||||
ImGui.TextUnformatted($"{(nint)atkValue->Pointer}");
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
ImGui.TextDisabled("Unhandled Type");
|
||||
ImGui.SameLine();
|
||||
Util.ShowStruct(atkValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
atkValue++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ImGui.TextColored(new(1, 0, 0, 1), $"{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gui.PaddedSeparator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,13 +16,18 @@ public unsafe partial class AddonTree
|
|||
{
|
||||
private static readonly Dictionary<string, Type?> AddonTypeDict = [];
|
||||
|
||||
private static readonly Assembly? ClientStructsAssembly = typeof(Addon).Assembly;
|
||||
private static readonly Assembly? ClientStructsAssembly = typeof(AddonAttribute).Assembly;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of names for field offsets that have been documented in FFXIVClientStructs.
|
||||
/// </summary>
|
||||
internal Dictionary<nint, List<string>> FieldNames { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the addon according to its Attributes in FFXIVClientStructs.
|
||||
/// </summary>
|
||||
internal int AddonSize { get; set; }
|
||||
|
||||
private object? GetAddonObj(AtkUnitBase* addon)
|
||||
{
|
||||
if (addon == null)
|
||||
|
|
@ -36,12 +41,19 @@ public unsafe partial class AddonTree
|
|||
{
|
||||
foreach (var t in from t in ClientStructsAssembly.GetTypes()
|
||||
where t.IsPublic
|
||||
let xivAddonAttr = (Addon?)t.GetCustomAttribute(typeof(Addon), false)
|
||||
let xivAddonAttr = (AddonAttribute?)t.GetCustomAttribute(typeof(AddonAttribute), false)
|
||||
where xivAddonAttr != null
|
||||
where xivAddonAttr.AddonIdentifiers.Contains(this.AddonName)
|
||||
select t)
|
||||
{
|
||||
AddonTypeDict[this.AddonName] = t;
|
||||
|
||||
var size = t.StructLayoutAttribute?.Size;
|
||||
if (size != null)
|
||||
{
|
||||
this.AddonSize = size.Value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Components;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -19,14 +20,12 @@ namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
|
|||
/// </summary>
|
||||
public unsafe partial class AddonTree : IDisposable
|
||||
{
|
||||
private readonly nint initialPtr;
|
||||
|
||||
private AddonPopoutWindow? window;
|
||||
|
||||
private AddonTree(string name, nint ptr)
|
||||
{
|
||||
this.AddonName = name;
|
||||
this.initialPtr = ptr;
|
||||
this.InitialPtr = ptr;
|
||||
this.PopulateFieldNames(ptr);
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +34,11 @@ public unsafe partial class AddonTree : IDisposable
|
|||
/// </summary>
|
||||
internal string AddonName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the addon's pointer at the time this <see cref="AddonTree"/> was created.
|
||||
/// </summary>
|
||||
internal nint InitialPtr { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a collection of trees representing nodes within this addon.
|
||||
/// </summary>
|
||||
|
|
@ -81,7 +85,7 @@ public unsafe partial class AddonTree : IDisposable
|
|||
{
|
||||
if (AddonTrees.TryGetValue(name, out var tree))
|
||||
{
|
||||
if (tree.initialPtr == ptr)
|
||||
if (tree.InitialPtr == ptr)
|
||||
{
|
||||
return tree;
|
||||
}
|
||||
|
|
@ -143,34 +147,47 @@ public unsafe partial class AddonTree : IDisposable
|
|||
ImGui.SetTooltip("Toggle Popout Window");
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
PrintFieldValuePair("Address", $"{(nint)addon:X}");
|
||||
PaddedSeparator(1);
|
||||
|
||||
var uldManager = addon->UldManager;
|
||||
|
||||
PrintFieldValuePair("Address", $"{(nint)addon:X}");
|
||||
PrintFieldValuePair("Agent", $"{GameGui.FindAgentInterface(addon):X}");
|
||||
|
||||
PrintFieldValuePairs(
|
||||
("X", $"{addon->X}"),
|
||||
("Y", $"{addon->X}"),
|
||||
("Y", $"{addon->Y}"),
|
||||
("Scale", $"{addon->Scale}"),
|
||||
("Widget Count", $"{uldManager.ObjectCount}"));
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var addonObj = this.GetAddonObj(addon);
|
||||
if (addonObj != null)
|
||||
{
|
||||
PaddedSeparator();
|
||||
ShowStruct(addonObj, (ulong)addon);
|
||||
}
|
||||
|
||||
ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale));
|
||||
ImGui.Separator();
|
||||
PaddedSeparator();
|
||||
|
||||
ResNodeTree.PrintNodeList(uldManager.NodeList, uldManager.NodeListCount, this);
|
||||
PrintAtkValues(addon);
|
||||
|
||||
ImGui.Dummy(new(25 * ImGui.GetIO().FontGlobalScale));
|
||||
ImGui.Separator();
|
||||
if (addon->RootNode != null)
|
||||
{
|
||||
ResNodeTree.GetOrCreate(addon->RootNode, this).Print(0);
|
||||
PaddedSeparator();
|
||||
}
|
||||
|
||||
ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F));
|
||||
if (uldManager.NodeList != null)
|
||||
{
|
||||
var count = uldManager.NodeListCount;
|
||||
ResNodeTree.PrintNodeListAsTree(uldManager.NodeList, count, $"Node List [{count}]:", this, new(0, 0.85F, 1, 1));
|
||||
PaddedSeparator();
|
||||
}
|
||||
|
||||
if (addon->CollisionNodeList != null)
|
||||
{
|
||||
ResNodeTree.PrintNodeListAsTree(addon->CollisionNodeList, (int)addon->CollisionNodeListCount, "Collision List", this, new(0.5F, 0.7F, 1F, 1F));
|
||||
}
|
||||
|
||||
if (SearchResults.Length > 0 && Countdown <= 0)
|
||||
{
|
||||
|
|
@ -218,7 +235,7 @@ public unsafe partial class AddonTree : IDisposable
|
|||
private bool ValidateAddon(out AtkUnitBase* addon)
|
||||
{
|
||||
addon = (AtkUnitBase*)GameGui.GetAddonByName(this.AddonName);
|
||||
if (addon == null || (nint)addon != this.initialPtr)
|
||||
if (addon == null || (nint)addon != this.InitialPtr)
|
||||
{
|
||||
this.Dispose();
|
||||
return false;
|
||||
|
|
@ -231,7 +248,7 @@ public unsafe partial class AddonTree : IDisposable
|
|||
{
|
||||
if (this.window == null)
|
||||
{
|
||||
this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.initialPtr}");
|
||||
this.window = new AddonPopoutWindow(this, $"{this.AddonName}###addonPopout{this.InitialPtr}");
|
||||
PopoutWindows.AddWindow(this.window);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Dalamud.Interface.Internal.UiDebug2.Utility;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
|
@ -36,7 +38,7 @@ public static class Events
|
|||
ImGui.TableSetupColumn("Type", WidthFixed);
|
||||
ImGui.TableSetupColumn("Param", WidthFixed);
|
||||
ImGui.TableSetupColumn("Flags", WidthFixed);
|
||||
ImGui.TableSetupColumn("Unk29", WidthFixed);
|
||||
ImGui.TableSetupColumn("StateFlags1", WidthFixed);
|
||||
ImGui.TableSetupColumn("Target", WidthFixed);
|
||||
ImGui.TableSetupColumn("Listener", WidthFixed);
|
||||
|
||||
|
|
@ -48,17 +50,17 @@ public static class Events
|
|||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"{i++}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"{evt->Type}");
|
||||
ImGui.TextUnformatted($"{evt->State.EventType}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"{evt->Param}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"{evt->Flags}");
|
||||
ImGui.TextUnformatted($"{evt->State.StateFlags}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted($"{evt->Unk29}");
|
||||
ImGui.TextUnformatted($"{evt->State.UnkFlags1}");
|
||||
ImGui.TableNextColumn();
|
||||
Gui.ClickToCopyText($"{(nint)evt->Target:X}");
|
||||
ImGuiHelpers.ClickToCopyText($"{(nint)evt->Target:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1));
|
||||
ImGui.TableNextColumn();
|
||||
Gui.ClickToCopyText($"{(nint)evt->Listener:X}");
|
||||
ImGuiHelpers.ClickToCopyText($"{(nint)evt->Listener:X}", null, new Vector4(0.6f, 0.6f, 0.6f, 1));
|
||||
evt = evt->NextEvent;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,11 +34,13 @@ internal unsafe class ComponentNodeTree : ResNodeTree
|
|||
|
||||
private AtkUldManager* UldManager => &this.Component->UldManager;
|
||||
|
||||
private int? ComponentFieldOffset { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
private protected override string GetHeaderText()
|
||||
{
|
||||
var childCount = (int)this.UldManager->NodeListCount;
|
||||
return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)} (Node: {(nint)this.Node:X} / Comp: {(nint)this.Component:X})";
|
||||
return $"{this.componentType} Component Node{(childCount > 0 ? $" [+{childCount}]" : string.Empty)}";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -62,10 +64,10 @@ internal unsafe class ComponentNodeTree : ResNodeTree
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
private protected override void PrintFieldNames()
|
||||
private protected override void PrintFieldLabels()
|
||||
{
|
||||
this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1));
|
||||
this.PrintFieldName((nint)this.Component, new(0f, 0.5f, 0.8f, 1f));
|
||||
this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset);
|
||||
this.PrintFieldLabel((nint)this.Component, new(0f, 0.5f, 0.8f, 1f), this.ComponentFieldOffset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -108,6 +110,34 @@ internal unsafe class ComponentNodeTree : ResNodeTree
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
private protected override void GetFieldOffset()
|
||||
{
|
||||
var nodeFound = false;
|
||||
var componentFound = false;
|
||||
for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8)
|
||||
{
|
||||
var readPtr = Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i);
|
||||
|
||||
if (readPtr == (nint)this.Node)
|
||||
{
|
||||
this.NodeFieldOffset = i;
|
||||
nodeFound = true;
|
||||
}
|
||||
|
||||
if (readPtr == (nint)this.Component)
|
||||
{
|
||||
this.ComponentFieldOffset = i;
|
||||
componentFound = true;
|
||||
}
|
||||
|
||||
if (nodeFound && componentFound)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintComponentObject()
|
||||
{
|
||||
PrintFieldValuePair("Component", $"{(nint)this.Component:X}");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Internal.UiDebug2.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
|
|
@ -370,8 +371,8 @@ internal unsafe partial class TextNodeTree
|
|||
var hAlign = (int)alignment % 3;
|
||||
var vAlign = ((int)alignment - hAlign) / 3;
|
||||
|
||||
var hAlignInput = IconButtonSelect($"{label}H", ref hAlign, [0, 1, 2], [AlignLeft, AlignCenter, AlignRight]);
|
||||
var vAlignInput = IconButtonSelect($"{label}V", ref vAlign, [0, 1, 2], [ArrowsUpToLine, GripLines, ArrowsDownToLine]);
|
||||
var hAlignInput = ImGuiComponents.IconButtonSelect($"{label}H", ref hAlign, [AlignLeft, AlignCenter, AlignRight], [0, 1, 2], 3u, new(25, 0));
|
||||
var vAlignInput = ImGuiComponents.IconButtonSelect($"{label}V", ref vAlign, [ArrowsUpToLine, GripLines, ArrowsDownToLine], [0, 1, 2], 3u, new(25, 0));
|
||||
|
||||
if (hAlignInput || vAlignInput)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Internal.UiDebug2.Utility;
|
||||
|
|
@ -60,6 +61,11 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
/// </summary>
|
||||
private protected NodeType NodeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the offset of this node within its parent Addon.
|
||||
/// </summary>
|
||||
private protected int? NodeFieldOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears this NodeTree's popout window, if it has one.
|
||||
/// </summary>
|
||||
|
|
@ -164,19 +170,26 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
internal void WriteTreeHeading()
|
||||
{
|
||||
ImGui.TextUnformatted(this.GetHeaderText());
|
||||
this.PrintFieldNames();
|
||||
this.PrintFieldLabels();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the given pointer has been identified as a field within the addon struct, this method prints that field's name.
|
||||
/// If the given pointer is referenced with the addon struct, the offset within the addon will be printed. If the given pointer has been identified as a field within the addon struct, this method also prints that field's name.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to check.</param>
|
||||
/// <param name="color">The text color to use.</param>
|
||||
private protected void PrintFieldName(nint ptr, Vector4 color)
|
||||
/// <param name="fieldOffset">The field offset of the pointer, if it was found in the addon.</param>
|
||||
private protected void PrintFieldLabel(nint ptr, Vector4 color, int? fieldOffset)
|
||||
{
|
||||
if (fieldOffset != null)
|
||||
{
|
||||
ImGui.SameLine(0, -1);
|
||||
ImGui.TextColored(color * 0.85f, $"[0x{fieldOffset:X}]");
|
||||
}
|
||||
|
||||
if (this.AddonTree.FieldNames.TryGetValue(ptr, out var result))
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.SameLine(0, -1);
|
||||
ImGui.TextColored(color, string.Join(".", result));
|
||||
}
|
||||
}
|
||||
|
|
@ -188,7 +201,15 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
private protected virtual string GetHeaderText()
|
||||
{
|
||||
var count = this.GetDirectChildCount();
|
||||
return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)} ({(nint)this.Node:X})";
|
||||
return $"{this.NodeType} Node{(count > 0 ? $" [+{count}]" : string.Empty)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints any field names for the node.
|
||||
/// </summary>
|
||||
private protected virtual void PrintFieldLabels()
|
||||
{
|
||||
this.PrintFieldLabel((nint)this.Node, new(0, 0.85F, 1, 1), this.NodeFieldOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -201,11 +222,6 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
ImGui.NewLine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints any field names for the node.
|
||||
/// </summary>
|
||||
private protected virtual void PrintFieldNames() => this.PrintFieldName((nint)this.Node, new(0, 0.85F, 1, 1));
|
||||
|
||||
/// <summary>
|
||||
/// Prints all direct children of this node.
|
||||
/// </summary>
|
||||
|
|
@ -227,6 +243,21 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the field offset of the given pointer within the parent addon.
|
||||
/// </summary>
|
||||
private protected virtual void GetFieldOffset()
|
||||
{
|
||||
for (var i = 0; i < this.AddonTree.AddonSize; i += 0x8)
|
||||
{
|
||||
if (Marshal.ReadIntPtr(this.AddonTree.InitialPtr + i) == (nint)this.Node)
|
||||
{
|
||||
this.NodeFieldOffset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDirectChildCount()
|
||||
{
|
||||
var count = 0;
|
||||
|
|
@ -273,6 +304,8 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
ImGui.SetNextItemOpen(true, ImGuiCond.Always);
|
||||
}
|
||||
|
||||
this.GetFieldOffset();
|
||||
|
||||
using var col = ImRaii.PushColor(Text, displayColor);
|
||||
using var tree = ImRaii.TreeNode(label, SpanFullWidth);
|
||||
|
||||
|
|
@ -281,7 +314,7 @@ internal unsafe partial class ResNodeTree : IDisposable
|
|||
new NodeBounds(this.Node).Draw(visible ? new(0.1f, 1f, 0.1f, 1f) : new(1f, 0f, 0.2f, 1f));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SameLine(0, -1);
|
||||
this.WriteTreeHeading();
|
||||
|
||||
col.Pop();
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ internal unsafe class ElementSelector : IDisposable
|
|||
/// </summary>
|
||||
internal void DrawInterface()
|
||||
{
|
||||
using (ImRaii.Child("###sidebar_elementSelector", new(250, 0), true))
|
||||
using (ImRaii.Child("###sidebar_elementSelector", new(250, -1), true))
|
||||
{
|
||||
using (ImRaii.PushFont(IconFont))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics;
|
||||
|
|
@ -17,40 +16,6 @@ namespace Dalamud.Interface.Internal.UiDebug2.Utility;
|
|||
/// </summary>
|
||||
internal static class Gui
|
||||
{
|
||||
/// <summary>
|
||||
/// A radio-button-esque input that uses Fontawesome icon buttons.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value being set.</typeparam>
|
||||
/// <param name="label">The label for the inputs.</param>
|
||||
/// <param name="val">The value being set.</param>
|
||||
/// <param name="options">A list of all options.</param>
|
||||
/// <param name="icons">A list of icons corresponding to the options.</param>
|
||||
/// <returns>true if a button is clicked.</returns>
|
||||
internal static unsafe bool IconButtonSelect<T>(string label, ref T val, List<T> options, List<FontAwesomeIcon> icons)
|
||||
{
|
||||
var ret = false;
|
||||
|
||||
for (var i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
}
|
||||
|
||||
var option = options[i];
|
||||
var icon = icons.Count > i ? icons[i] : FontAwesomeIcon.Question;
|
||||
var color = *ImGui.GetStyleColorVec4(val is not null && val.Equals(option) ? ButtonActive : Button);
|
||||
|
||||
if (ImGuiComponents.IconButton($"{label}{option}{i}", icon, color))
|
||||
{
|
||||
val = option;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints field name and its value.
|
||||
/// </summary>
|
||||
|
|
@ -61,13 +26,14 @@ internal static class Gui
|
|||
{
|
||||
ImGui.TextUnformatted($"{fieldName}:");
|
||||
ImGui.SameLine();
|
||||
var grey60 = new Vector4(0.6f, 0.6f, 0.6f, 1);
|
||||
if (copy)
|
||||
{
|
||||
ClickToCopyText(value);
|
||||
ImGuiHelpers.ClickToCopyText(value, null, grey60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextColored(new(0.6f, 0.6f, 0.6f, 1), value);
|
||||
ImGui.TextColored(grey60, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +68,10 @@ internal static class Gui
|
|||
/// <remarks>Colors the text itself either white or black, depending on the luminosity of the background color.</remarks>
|
||||
internal static void PrintColor(Vector4 color, string fmt)
|
||||
{
|
||||
using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1)).Push(Button, color).Push(ButtonActive, color).Push(ButtonHovered, color))
|
||||
using (new ImRaii.Color().Push(Text, Luminosity(color) < 0.5f ? new Vector4(1) : new(0, 0, 0, 1))
|
||||
.Push(Button, color)
|
||||
.Push(ButtonActive, color)
|
||||
.Push(ButtonHovered, color))
|
||||
{
|
||||
ImGui.SmallButton(fmt);
|
||||
}
|
||||
|
|
@ -117,39 +86,6 @@ internal static class Gui
|
|||
0.5f) * vector4.W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print out text that can be copied when clicked.
|
||||
/// </summary>
|
||||
/// <param name="text">The text to show.</param>
|
||||
/// <param name="textCopy">The text to copy when clicked.</param>
|
||||
internal static void ClickToCopyText(string text, string? textCopy = null)
|
||||
{
|
||||
using (ImRaii.PushColor(Text, new Vector4(0.6f, 0.6f, 0.6f, 1)))
|
||||
{
|
||||
textCopy ??= text;
|
||||
ImGui.TextUnformatted($"{text}");
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using (ImRaii.Tooltip())
|
||||
{
|
||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.Copy.ToIconString());
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"{textCopy}");
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
{
|
||||
ImGui.SetClipboardText($"{textCopy}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a tooltip that changes based on the cursor's x-position within the hovered item.
|
||||
/// </summary>
|
||||
|
|
@ -176,4 +112,23 @@ internal static class Gui
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a separator with some padding above and below.
|
||||
/// </summary>
|
||||
/// <param name="mask">Governs whether to pad above, below, or both.</param>
|
||||
/// <param name="padding">The amount of padding.</param>
|
||||
internal static void PaddedSeparator(uint mask = 0b11, float padding = 5f)
|
||||
{
|
||||
if ((mask & 0b10) > 0)
|
||||
{
|
||||
ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale));
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
if ((mask & 0b01) > 0)
|
||||
{
|
||||
ImGui.Dummy(new(padding * ImGui.GetIO().FontGlobalScale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Dalamud.Interface.Utility;
|
|||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
|
||||
using static System.Math;
|
||||
using static System.MathF;
|
||||
using static Dalamud.Interface.ColorHelpers;
|
||||
|
||||
namespace Dalamud.Interface.Internal.UiDebug2.Utility;
|
||||
|
|
@ -133,7 +133,7 @@ public unsafe struct NodeBounds
|
|||
if (p.Y > Min(p1.Y, p2.Y) &&
|
||||
p.Y <= Max(p1.Y, p2.Y) &&
|
||||
p.X <= Max(p1.X, p2.X) &&
|
||||
(p1.X.Equals(p2.X) || p.X <= ((p.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y)) + p1.X))
|
||||
(p1.X.Equals(p2.X) || p.X <= (((p.Y - p1.Y) * (p2.X - p1.X)) / (p2.Y - p1.Y)) + p1.X))
|
||||
{
|
||||
inside = !inside;
|
||||
}
|
||||
|
|
@ -144,12 +144,12 @@ public unsafe struct NodeBounds
|
|||
|
||||
private static Vector2 TransformPoint(Vector2 p, Vector2 o, float r, Vector2 s)
|
||||
{
|
||||
var cosR = (float)Cos(r);
|
||||
var sinR = (float)Sin(r);
|
||||
var cosR = Cos(r);
|
||||
var sinR = Sin(r);
|
||||
var d = (p - o) * s;
|
||||
|
||||
return new(
|
||||
o.X + (d.X * cosR) - (d.Y * sinR),
|
||||
(o.X + (d.X * cosR)) - (d.Y * sinR),
|
||||
o.Y + (d.X * sinR) + (d.Y * cosR));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Dalamud.Game.ClientState.Aetherytes;
|
||||
using Dalamud.Game.ClientState.Aetherytes;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ internal class AetherytesWidget : IDataWindowWidget
|
|||
ImGui.TextUnformatted($"{i}");
|
||||
|
||||
ImGui.TableNextColumn(); // Name
|
||||
ImGui.TextUnformatted($"{info.AetheryteData.GameData?.PlaceName.Value?.Name}");
|
||||
ImGui.TextUnformatted($"{info.AetheryteData.ValueNullable?.PlaceName.ValueNullable?.Name}");
|
||||
|
||||
ImGui.TableNextColumn(); // ID
|
||||
ImGui.TextUnformatted($"{info.AetheryteId}");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Dalamud.Game.ClientState.Buddy;
|
||||
using Dalamud.Game.ClientState.Buddy;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -32,8 +32,6 @@ internal class BuddyListWidget : IDataWindowWidget
|
|||
var buddyList = Service<BuddyList>.Get();
|
||||
|
||||
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
|
||||
|
||||
ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}");
|
||||
{
|
||||
var member = buddyList.CompanionBuddy;
|
||||
if (member == null)
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ internal class FateTableWidget : IDataWindowWidget
|
|||
}
|
||||
|
||||
ImGui.TableNextColumn(); // HasExpBonus
|
||||
ImGui.TextUnformatted(fate.HasExpBonus.ToString());
|
||||
ImGui.TextUnformatted(fate.HasBonus.ToString());
|
||||
|
||||
ImGui.TableNextColumn(); // Position
|
||||
DrawCopyableText(fate.Position.ToString(), "Click to copy Position");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.JobGauge;
|
||||
using Dalamud.Game.ClientState.JobGauge.Types;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -39,7 +39,7 @@ internal class GaugeWidget : IDataWindowWidget
|
|||
return;
|
||||
}
|
||||
|
||||
var jobID = player.ClassJob.Id;
|
||||
var jobID = player.ClassJob.RowId;
|
||||
JobGaugeBase? gauge = jobID switch
|
||||
{
|
||||
19 => jobGauges.Get<PLDGauge>(),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
/// </summary>
|
||||
public class IconBrowserWidget : IDataWindowWidget
|
||||
{
|
||||
private const int MaxIconId = 250_000;
|
||||
|
||||
private Vector2 iconSize = new(64.0f, 64.0f);
|
||||
private Vector2 editIconSize = new(64.0f, 64.0f);
|
||||
|
||||
|
|
@ -24,7 +26,7 @@ public class IconBrowserWidget : IDataWindowWidget
|
|||
private Task<List<(int ItemId, string Path)>>? iconIdsTask;
|
||||
|
||||
private int startRange;
|
||||
private int stopRange = 200000;
|
||||
private int stopRange = MaxIconId;
|
||||
private bool showTooltipImage;
|
||||
|
||||
private Vector2 mouseDragStart;
|
||||
|
|
@ -53,8 +55,8 @@ public class IconBrowserWidget : IDataWindowWidget
|
|||
{
|
||||
var texm = Service<TextureManager>.Get();
|
||||
|
||||
var result = new List<(int ItemId, string Path)>(200000);
|
||||
for (var iconId = 0; iconId < 200000; iconId++)
|
||||
var result = new List<(int ItemId, string Path)>(MaxIconId);
|
||||
for (var iconId = 0; iconId < MaxIconId; iconId++)
|
||||
{
|
||||
// // Remove range 170,000 -> 180,000 by default, this specific range causes exceptions.
|
||||
// if (iconId is >= 170000 and < 180000)
|
||||
|
|
@ -119,12 +121,19 @@ public class IconBrowserWidget : IDataWindowWidget
|
|||
|
||||
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.InputInt("##StartRange", ref this.startRange, 0, 0))
|
||||
{
|
||||
this.startRange = Math.Clamp(this.startRange, 0, MaxIconId);
|
||||
this.valueRange = null;
|
||||
}
|
||||
|
||||
|
||||
ImGui.NextColumn();
|
||||
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||
if (ImGui.InputInt("##StopRange", ref this.stopRange, 0, 0))
|
||||
{
|
||||
this.stopRange = Math.Clamp(this.stopRange, 0, MaxIconId);
|
||||
this.valueRange = null;
|
||||
}
|
||||
|
||||
ImGui.NextColumn();
|
||||
ImGui.Checkbox("Show Image in Tooltip", ref this.showTooltipImage);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
|
|
@ -56,8 +56,8 @@ internal class ObjectTableWidget : IDataWindowWidget
|
|||
{
|
||||
stateString += $"ObjectTableLen: {objectTable.Length}\n";
|
||||
stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n";
|
||||
stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData?.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n";
|
||||
stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData?.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n";
|
||||
stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.ValueNullable?.Name : clientState.LocalPlayer.CurrentWorld.RowId.ToString())}\n";
|
||||
stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.ValueNullable?.Name : clientState.LocalPlayer.HomeWorld.RowId.ToString())}\n";
|
||||
stateString += $"LocalCID: {clientState.LocalContentId:X}\n";
|
||||
stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n";
|
||||
stateString += $"TerritoryType: {clientState.TerritoryType}\n\n";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -16,7 +16,8 @@ using FFXIVClientStructs.FFXIV.Component.GUI;
|
|||
|
||||
using ImGuiNET;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets2;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Text;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
|
@ -33,7 +34,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"];
|
||||
private ImVectorWrapper<byte> testStringBuffer;
|
||||
private string testString = string.Empty;
|
||||
private Addon[]? addons;
|
||||
private ExcelSheet<Addon> addons;
|
||||
private ReadOnlySeString? logkind;
|
||||
private SeStringDrawParams style;
|
||||
private bool interactable;
|
||||
|
|
@ -53,7 +54,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
public void Load()
|
||||
{
|
||||
this.style = new() { GetEntity = this.GetEntity };
|
||||
this.addons = null;
|
||||
this.addons = Service<DataManager>.Get().GetExcelSheet<Addon>();
|
||||
this.logkind = null;
|
||||
this.testString = string.Empty;
|
||||
this.interactable = this.useEntity = true;
|
||||
|
|
@ -155,9 +156,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
if (this.logkind is null)
|
||||
{
|
||||
var tt = new SeStringBuilder();
|
||||
foreach (var uc in Service<DataManager>.Get().GetExcelSheet<LogKind>()!)
|
||||
foreach (var uc in Service<DataManager>.Get().GetExcelSheet<LogKind>())
|
||||
{
|
||||
var ucsp = uc.Format.AsReadOnly().AsSpan();
|
||||
var ucsp = uc.Format.AsSpan();
|
||||
if (ucsp.IsEmpty)
|
||||
continue;
|
||||
|
||||
|
|
@ -184,7 +185,6 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
|
||||
if (ImGui.CollapsingHeader("Addon Table"))
|
||||
{
|
||||
this.addons ??= Service<DataManager>.Get().GetExcelSheet<Addon>()!.ToArray();
|
||||
if (ImGui.BeginTable("Addon Sheet", 3))
|
||||
{
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
|
|
@ -197,25 +197,27 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
|||
ImGui.TableHeadersRow();
|
||||
|
||||
var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||
clipper.Begin(this.addons.Length);
|
||||
clipper.Begin(this.addons.Count);
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||
{
|
||||
var row = this.addons.GetRowAt(i);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.PushID(i);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted($"{this.addons[i].RowId}");
|
||||
ImGui.TextUnformatted($"{row.RowId}");
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGuiHelpers.SeStringWrapped(this.addons[i].Text.AsReadOnly(), this.style);
|
||||
ImGuiHelpers.SeStringWrapped(row.Text, this.style);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Button("Print to Chat"))
|
||||
Service<ChatGui>.Get().Print(this.addons[i].Text.ToDalamudString());
|
||||
Service<ChatGui>.Get().Print(row.Text.ToDalamudString());
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Buffers.Binary;
|
||||
using System.Buffers.Binary;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
|
@ -12,7 +12,8 @@ using Dalamud.Storage.Assets;
|
|||
|
||||
using ImGuiNET;
|
||||
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
/// </summary>
|
||||
internal class UiColorWidget : IDataWindowWidget
|
||||
{
|
||||
private UIColor[]? colors;
|
||||
private ExcelSheet<UIColor> colors;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[]? CommandShortcuts { get; init; } = ["uicolor"];
|
||||
|
|
@ -36,15 +37,12 @@ internal class UiColorWidget : IDataWindowWidget
|
|||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
this.colors = null;
|
||||
this.colors = Service<DataManager>.Get().GetExcelSheet<UIColor>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void Draw()
|
||||
{
|
||||
this.colors ??= Service<DataManager>.Get().GetExcelSheet<UIColor>()?.ToArray();
|
||||
if (this.colors is null) return;
|
||||
|
||||
Service<SeStringRenderer>.Get().CompileAndDrawWrapped(
|
||||
"· Color notation is #" +
|
||||
"<edgecolor(0xFFEEEE)><color(0xFF0000)>RR<color(stackcolor)><edgecolor(stackcolor)>" +
|
||||
|
|
@ -73,12 +71,24 @@ internal class UiColorWidget : IDataWindowWidget
|
|||
ImGui.TableHeadersRow();
|
||||
|
||||
var clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||
clipper.Begin(this.colors.Length, ImGui.GetFrameHeightWithSpacing());
|
||||
clipper.Begin(this.colors.Count, ImGui.GetFrameHeightWithSpacing());
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||
{
|
||||
var id = this.colors[i].RowId;
|
||||
var row = this.colors.GetRowAt(i);
|
||||
UIColor? adjacentRow = null;
|
||||
if (i + 1 < this.colors.Count)
|
||||
{
|
||||
var adjRow = this.colors.GetRowAt(i + 1);
|
||||
if (adjRow.RowId == row.RowId + 1)
|
||||
{
|
||||
adjacentRow = adjRow;
|
||||
}
|
||||
}
|
||||
|
||||
var id = row.RowId;
|
||||
|
||||
ImGui.TableNextRow();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -88,33 +98,33 @@ internal class UiColorWidget : IDataWindowWidget
|
|||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.PushID($"row{id}_col1");
|
||||
if (this.DrawColorColumn(this.colors[i].UIForeground) &&
|
||||
i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1)
|
||||
DrawEdgePreview(id, this.colors[i].UIForeground, this.colors[i + 1].UIForeground);
|
||||
if (this.DrawColorColumn(row.UIForeground) &&
|
||||
adjacentRow.HasValue)
|
||||
DrawEdgePreview(id, row.UIForeground, adjacentRow.Value.UIForeground);
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.PushID($"row{id}_col2");
|
||||
if (this.DrawColorColumn(this.colors[i].UIGlow) &&
|
||||
i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1)
|
||||
DrawEdgePreview(id, this.colors[i].UIGlow, this.colors[i + 1].UIGlow);
|
||||
if (this.DrawColorColumn(row.UIGlow) &&
|
||||
adjacentRow.HasValue)
|
||||
DrawEdgePreview(id, row.UIGlow, adjacentRow.Value.UIGlow);
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.PushID($"row{id}_col3");
|
||||
if (this.DrawColorColumn(this.colors[i].Unknown2) &&
|
||||
i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1)
|
||||
DrawEdgePreview(id, this.colors[i].Unknown2, this.colors[i + 1].Unknown2);
|
||||
if (this.DrawColorColumn(row.Unknown0) &&
|
||||
adjacentRow.HasValue)
|
||||
DrawEdgePreview(id, row.Unknown0, adjacentRow.Value.Unknown0);
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.PushID($"row{id}_col4");
|
||||
if (this.DrawColorColumn(this.colors[i].Unknown3) &&
|
||||
i + 1 < this.colors.Length && this.colors[i + 1].RowId == id + 1)
|
||||
DrawEdgePreview(id, this.colors[i].Unknown3, this.colors[i + 1].Unknown3);
|
||||
if (this.DrawColorColumn(row.Unknown1) &&
|
||||
adjacentRow.HasValue)
|
||||
DrawEdgePreview(id, row.Unknown1, adjacentRow.Value.Unknown1);
|
||||
ImGui.PopID();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
|||
using Dalamud.Game.Gui.ContextMenu;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
|
||||
|
|
@ -45,9 +44,9 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
{
|
||||
var contextMenu = Service<ContextMenu>.Get();
|
||||
var dataMgr = Service<DataManager>.Get();
|
||||
this.itemSheet = dataMgr.GetExcelSheet<Item>()!;
|
||||
this.materiaSheet = dataMgr.GetExcelSheet<Materia>()!;
|
||||
this.stainSheet = dataMgr.GetExcelSheet<Stain>()!;
|
||||
this.itemSheet = dataMgr.GetExcelSheet<Item>();
|
||||
this.materiaSheet = dataMgr.GetExcelSheet<Materia>();
|
||||
this.stainSheet = dataMgr.GetExcelSheet<Stain>();
|
||||
|
||||
ImGui.Text(this.currentSubStep.ToString());
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
case SubStep.TestDefault:
|
||||
if (this.targetCharacter is { } character)
|
||||
{
|
||||
ImGui.Text($"Did you click \"{character.Name}\" ({character.ClassJob.GameData!.Abbreviation.ToDalamudString()})?");
|
||||
ImGui.Text($"Did you click \"{character.Name}\" ({character.ClassJob.Value.Abbreviation.ExtractText()})?");
|
||||
|
||||
if (ImGui.Button("Yes"))
|
||||
this.currentSubStep++;
|
||||
|
|
@ -142,11 +141,11 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
OnClicked = (IMenuItemClickedArgs a) =>
|
||||
{
|
||||
SeString name;
|
||||
uint count;
|
||||
int count;
|
||||
var targetItem = (a.Target as MenuTargetInventory)!.TargetItem;
|
||||
if (targetItem is { } item)
|
||||
{
|
||||
name = (this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty);
|
||||
name = (this.itemSheet.GetRowOrDefault(item.ItemId)?.Name.ExtractText() ?? $"Unknown ({item.ItemId})") + (item.IsHq ? $" {SeIconChar.HighQuality.ToIconString()}" : string.Empty);
|
||||
count = item.Quantity;
|
||||
}
|
||||
else
|
||||
|
|
@ -194,7 +193,7 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
{
|
||||
var b = new StringBuilder();
|
||||
b.AppendLine($"Target: {targetDefault.TargetName}");
|
||||
b.AppendLine($"Home World: {targetDefault.TargetHomeWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({targetDefault.TargetHomeWorld.Id})");
|
||||
b.AppendLine($"Home World: {targetDefault.TargetHomeWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({targetDefault.TargetHomeWorld.RowId})");
|
||||
b.AppendLine($"Content Id: 0x{targetDefault.TargetContentId:X8}");
|
||||
b.AppendLine($"Object Id: 0x{targetDefault.TargetObjectId:X8}");
|
||||
Log.Verbose(b.ToString());
|
||||
|
|
@ -209,20 +208,20 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
b.AppendLine($"Content Id: 0x{character.ContentId:X8}");
|
||||
b.AppendLine($"FC Tag: {character.FCTag}");
|
||||
|
||||
b.AppendLine($"Job: {character.ClassJob.GameData?.Abbreviation.ToDalamudString() ?? "Unknown"} ({character.ClassJob.Id})");
|
||||
b.AppendLine($"Statuses: {string.Join(", ", character.Statuses.Select(s => s.GameData?.Name.ToDalamudString() ?? s.Id.ToString()))}");
|
||||
b.AppendLine($"Home World: {character.HomeWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.HomeWorld.Id})");
|
||||
b.AppendLine($"Current World: {character.CurrentWorld.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.CurrentWorld.Id})");
|
||||
b.AppendLine($"Job: {character.ClassJob.ValueNullable?.Abbreviation.ExtractText() ?? "Unknown"} ({character.ClassJob.RowId})");
|
||||
b.AppendLine($"Statuses: {string.Join(", ", character.Statuses.Select(s => s.ValueNullable?.Name.ExtractText() ?? s.RowId.ToString()))}");
|
||||
b.AppendLine($"Home World: {character.HomeWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.HomeWorld.RowId})");
|
||||
b.AppendLine($"Current World: {character.CurrentWorld.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.CurrentWorld.RowId})");
|
||||
b.AppendLine($"Is From Other Server: {character.IsFromOtherServer}");
|
||||
|
||||
b.Append("Location: ");
|
||||
if (character.Location.GameData is { } location)
|
||||
b.Append($"{location.PlaceNameRegion.Value?.Name.ToDalamudString() ?? "Unknown"}/{location.PlaceNameZone.Value?.Name.ToDalamudString() ?? "Unknown"}/{location.PlaceName.Value?.Name.ToDalamudString() ?? "Unknown"}");
|
||||
if (character.Location.ValueNullable is { } location)
|
||||
b.Append($"{location.PlaceNameRegion.ValueNullable?.Name.ExtractText() ?? "Unknown"}/{location.PlaceNameZone.ValueNullable?.Name.ExtractText() ?? "Unknown"}/{location.PlaceName.ValueNullable?.Name.ExtractText() ?? "Unknown"}");
|
||||
else
|
||||
b.Append("Unknown");
|
||||
b.AppendLine($" ({character.Location.Id})");
|
||||
b.AppendLine($" ({character.Location.RowId})");
|
||||
|
||||
b.AppendLine($"Grand Company: {character.GrandCompany.GameData?.Name.ToDalamudString() ?? "Unknown"} ({character.GrandCompany.Id})");
|
||||
b.AppendLine($"Grand Company: {character.GrandCompany.ValueNullable?.Name.ExtractText() ?? "Unknown"} ({character.GrandCompany.RowId})");
|
||||
b.AppendLine($"Client Language: {character.ClientLanguage}");
|
||||
b.AppendLine($"Languages: {string.Join(", ", character.Languages)}");
|
||||
b.AppendLine($"Gender: {character.Gender}");
|
||||
|
|
@ -241,7 +240,7 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
if (targetInventory.TargetItem is { } item)
|
||||
{
|
||||
var b = new StringBuilder();
|
||||
b.AppendLine($"Item: {(item.IsEmpty ? "None" : this.itemSheet.GetRow(item.ItemId)?.Name.ToDalamudString())} ({item.ItemId})");
|
||||
b.AppendLine($"Item: {(item.IsEmpty ? "None" : this.itemSheet.GetRowOrDefault(item.ItemId)?.Name.ExtractText())} ({item.ItemId})");
|
||||
b.AppendLine($"Container: {item.ContainerType}");
|
||||
b.AppendLine($"Slot: {item.InventorySlot}");
|
||||
b.AppendLine($"Quantity: {item.Quantity}");
|
||||
|
|
@ -259,7 +258,7 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
Log.Verbose($"{materiaId} {materiaGrade}");
|
||||
if (this.materiaSheet.GetRow(materiaId) is { } materia &&
|
||||
materia.Item[materiaGrade].Value is { } materiaItem)
|
||||
materias.Add($"{materiaItem.Name.ToDalamudString()}");
|
||||
materias.Add($"{materiaItem.Name.ExtractText()}");
|
||||
else
|
||||
materias.Add($"Unknown (Id: {materiaId}, Grade: {materiaGrade})");
|
||||
}
|
||||
|
|
@ -275,7 +274,7 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
var stainId = item.Stains[i];
|
||||
if (stainId != 0)
|
||||
{
|
||||
var stainName = this.stainSheet.GetRow(stainId)?.Name.ToDalamudString().ToString() ?? "Unknown";
|
||||
var stainName = this.stainSheet.GetRowOrDefault(stainId)?.Name.ExtractText() ?? "Unknown";
|
||||
b.AppendLine($" Stain {i + 1}: {stainName} ({stainId})");
|
||||
}
|
||||
else
|
||||
|
|
@ -285,13 +284,13 @@ internal class ContextMenuAgingStep : IAgingStep
|
|||
}
|
||||
|
||||
if (item.Stains[0] != 0)
|
||||
b.AppendLine($"{this.stainSheet.GetRow(item.Stains[0])?.Name.ToDalamudString() ?? "Unknown"} ({item.Stains[0]})");
|
||||
b.AppendLine($"{this.stainSheet.GetRowOrDefault(item.Stains[0])?.Name.ExtractText() ?? "Unknown"} ({item.Stains[0]})");
|
||||
else
|
||||
b.AppendLine("None");
|
||||
|
||||
b.Append("Glamoured Item: ");
|
||||
if (item.GlamourId != 0)
|
||||
b.AppendLine($"{this.itemSheet.GetRow(item.GlamourId)?.Name.ToDalamudString() ?? "Unknown"} ({item.GlamourId})");
|
||||
b.AppendLine($"{this.itemSheet.GetRowOrDefault(item.GlamourId)?.Name.ExtractText() ?? "Unknown"} ({item.GlamourId})");
|
||||
else
|
||||
b.AppendLine("None");
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue