Merge branch 'master' into flytext

This commit is contained in:
goaaats 2021-08-11 16:37:33 +02:00 committed by GitHub
commit 4bf1e0f679
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
127 changed files with 5204 additions and 2348 deletions

2
.gitmodules vendored
View file

@ -3,4 +3,4 @@
url = https://github.com/goatcorp/ImGuiScene url = https://github.com/goatcorp/ImGuiScene
[submodule "lib/FFXIVClientStructs"] [submodule "lib/FFXIVClientStructs"]
path = lib/FFXIVClientStructs path = lib/FFXIVClientStructs
url = https://github.com/goatcorp/FFXIVClientStructs.git url = https://github.com/aers/FFXIVClientStructs.git

View file

@ -11,6 +11,11 @@
<NoWarn>IDE0003</NoWarn> <NoWarn>IDE0003</NoWarn>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Documentation">
<DocumentationFile></DocumentationFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Label="Build"> <PropertyGroup Label="Build">
<OutputPath>$(AppData)\XIVLauncher\devPlugins\Dalamud.CorePlugin</OutputPath> <OutputPath>$(AppData)\XIVLauncher\devPlugins\Dalamud.CorePlugin</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>

View file

@ -35,8 +35,8 @@ namespace Dalamud.CorePlugin
this.windowSystem.AddWindow(new PluginWindow(Dalamud.Instance)); this.windowSystem.AddWindow(new PluginWindow(Dalamud.Instance));
this.Interface.UiBuilder.OnBuildUi += this.OnDraw; this.Interface.UiBuilder.Draw += this.OnDraw;
this.Interface.UiBuilder.OnOpenConfigUi += this.OnOpenConfigUi; this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
this.Interface.CommandManager.AddHandler("/di", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." }); this.Interface.CommandManager.AddHandler("/di", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." });
} }
@ -51,7 +51,7 @@ namespace Dalamud.CorePlugin
{ {
this.Interface.CommandManager.RemoveHandler("/di"); this.Interface.CommandManager.RemoveHandler("/di");
this.Interface.UiBuilder.OnBuildUi -= this.OnDraw; this.Interface.UiBuilder.Draw -= this.OnDraw;
this.windowSystem.RemoveAllWindows(); this.windowSystem.RemoveAllWindows();

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
@ -11,6 +12,7 @@ namespace Dalamud.CorePlugin
/// </summary> /// </summary>
internal class PluginWindow : Window, IDisposable internal class PluginWindow : Window, IDisposable
{ {
[SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "This is a placeholder.")]
private readonly Dalamud dalamud; private readonly Dalamud dalamud;
/// <summary> /// <summary>

View file

@ -9,6 +9,7 @@ using Dalamud.Data;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState; using Dalamud.Game.ClientState;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Game.Gui.Internal;
using Dalamud.Game.Internal; using Dalamud.Game.Internal;
using Dalamud.Game.Network.Internal; using Dalamud.Game.Network.Internal;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
@ -100,6 +101,11 @@ namespace Dalamud
/// </summary> /// </summary>
internal InterfaceManager InterfaceManager { get; private set; } internal InterfaceManager InterfaceManager { get; private set; }
/// <summary>
/// Gets the Input Method subsystem.
/// </summary>
internal DalamudIME IME { get; private set; }
/// <summary> /// <summary>
/// Gets ClientState subsystem. /// Gets ClientState subsystem.
/// </summary> /// </summary>
@ -293,6 +299,16 @@ namespace Dalamud
} }
} }
try
{
this.IME = new DalamudIME(this);
Log.Information("[T2] IME OK!");
}
catch (Exception e)
{
Log.Information(e, "Could not init IME.");
}
this.Data = new DataManager(this.StartInfo.Language, this.InterfaceManager); this.Data = new DataManager(this.StartInfo.Language, this.InterfaceManager);
try try
{ {
@ -415,6 +431,11 @@ namespace Dalamud
{ {
this.hasDisposedPlugins = true; this.hasDisposedPlugins = true;
// this must be done before unloading interface manager, in order to do rebuild
// the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game
// will not receive any windows messages
this.IME?.Dispose();
// this must be done before unloading plugins, or it can cause a race condition // this must be done before unloading plugins, or it can cause a race condition
// due to rendering happening on another thread, where a plugin might receive // due to rendering happening on another thread, where a plugin might receive
// a render call after it has been disposed, which can crash if it attempts to // a render call after it has been disposed, which can crash if it attempts to

View file

@ -51,8 +51,9 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Warnings"> <PropertyGroup Label="Warnings">
<NoWarn>IDE0003;IDE0044;IDE1006;CS1591;CS1701;CS1702</NoWarn> <NoWarn>IDE0003;IDE0044;IDE1006;CA1822;CS1591;CS1701;CS1702</NoWarn>
<!-- IDE1006 - Naming violation --> <!-- IDE1006 - Naming violation -->
<!-- CA1822 - Can be marked as static -->
<!-- CS1591 - Missing XML comment for publicly visible type or member --> <!-- CS1591 - Missing XML comment for publicly visible type or member -->
<!-- CS1701 - Runtime policy may be needed --> <!-- CS1701 - Runtime policy may be needed -->
<!-- CS1702 - Runtime policy may be needed --> <!-- CS1702 - Runtime policy may be needed -->
@ -67,9 +68,6 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.10.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.10.0" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Serilog" Version="2.10.0" /> <PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" /> <PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

View file

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="EventInvokerNames" type="xs:string">
<xs:annotation>
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEquality" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressWarnings" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
<xs:annotation>
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -105,8 +104,8 @@ namespace Dalamud.Game
{ {
this.dalamud = dalamud; this.dalamud = dalamud;
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += this.OnCheckMessageHandled; dalamud.Framework.Gui.Chat.CheckMessageHandled += this.OnCheckMessageHandled;
dalamud.Framework.Gui.Chat.OnChatMessage += this.OnChatMessage; dalamud.Framework.Gui.Chat.ChatMessage += this.OnChatMessage;
this.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) => this.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
{ {

View file

@ -1,149 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public sealed partial class ActorTable
{
private const int ActorTableLength = 424;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="ActorTable"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Actor table address 0x{this.address.ActorTable.ToInt64():X}");
}
/// <summary>
/// Gets the amount of currently spawned actors.
/// </summary>
public int Length
{
get
{
var count = 0;
for (var i = 0; i < ActorTableLength; i++)
{
var ptr = this.GetActorAddress(i);
if (ptr != IntPtr.Zero)
{
count++;
}
}
return count;
}
}
/// <summary>
/// Get an actor at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>An <see cref="Actor"/> at the specified spawn index.</returns>
[CanBeNull]
public Actor this[int index]
{
get
{
var address = this.GetActorAddress(index);
return this.CreateActorReference(address);
}
}
/// <summary>
/// Gets the address of the actor at the specified index of the actor table.
/// </summary>
/// <param name="index">The index of the actor.</param>
/// <returns>The memory address of the actor.</returns>
public unsafe IntPtr GetActorAddress(int index)
{
if (index >= ActorTableLength)
return IntPtr.Zero;
return *(IntPtr*)(this.address.ActorTable + (8 * index));
}
/// <summary>
/// Create a reference to a FFXIV actor.
/// </summary>
/// <param name="address">The address of the actor in memory.</param>
/// <returns><see cref="Actor"/> object or inheritor containing requested data.</returns>
[CanBeNull]
public unsafe Actor CreateActorReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
var objKind = *(ObjectKind*)(address + ActorOffsets.ObjectKind);
return objKind switch
{
ObjectKind.Player => new PlayerCharacter(address, this.dalamud),
ObjectKind.BattleNpc => new BattleNpc(address, this.dalamud),
ObjectKind.EventObj => new EventObj(address, this.dalamud),
ObjectKind.Companion => new Npc(address, this.dalamud),
_ => new Actor(address, this.dalamud),
};
}
}
/// <summary>
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection
{
/// <inheritdoc/>
int IReadOnlyCollection<Actor>.Count => this.Length;
/// <inheritdoc/>
int ICollection.Count => this.Length;
/// <inheritdoc/>
bool ICollection.IsSynchronized => false;
/// <inheritdoc/>
object ICollection.SyncRoot => this;
/// <inheritdoc/>
public IEnumerator<Actor> GetEnumerator()
{
for (var i = 0; i < ActorTableLength; i++)
{
yield return this[i];
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -1,125 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// Get and set various kinds of targets for the player.
/// </summary>
public sealed class Targets
{
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="Targets"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
}
/// <summary>
/// Gets the current target.
/// </summary>
[CanBeNull]
public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
/// <summary>
/// Gets the mouseover target.
/// </summary>
[CanBeNull]
public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
/// <summary>
/// Gets the focus target.
/// </summary>
[CanBeNull]
public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
/// <summary>
/// Gets the previous target.
/// </summary>
[CanBeNull]
public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget);
/// <summary>
/// Gets the soft target.
/// </summary>
[CanBeNull]
public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetCurrentTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetCurrentTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.CurrentTarget);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actor">Actor to focus.</param>
public void SetFocusTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actorAddress">Actor (address) to focus.</param>
public void SetFocusTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.FocusTarget);
/// <summary>
/// Clears the current target.
/// </summary>
public void ClearCurrentTarget() => this.SetCurrentTarget(IntPtr.Zero);
/// <summary>
/// Clears the focus target.
/// </summary>
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
private void SetTarget(IntPtr actorAddress, int offset)
{
if (this.address.TargetManager == IntPtr.Zero)
return;
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
}
[CanBeNull]
private Actor GetActorByOffset(int offset)
{
if (this.address.TargetManager == IntPtr.Zero)
return null;
var actorAddress = Marshal.ReadIntPtr(this.address.TargetManager + offset);
if (actorAddress == IntPtr.Zero)
return null;
return this.dalamud.ClientState.Actors.CreateActorReference(actorAddress);
}
}
/// <summary>
/// Memory offsets for the <see cref="Targets"/> type.
/// </summary>
public static class TargetOffsets
{
public const int CurrentTarget = 0x80;
public const int SoftTarget = 0x88;
public const int MouseOverTarget = 0xD0;
public const int FocusTarget = 0xF8;
public const int PreviousTarget = 0x110;
}
}

View file

@ -1,165 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Structs;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public unsafe partial class Actor : IEquatable<Actor>
{
/// <summary>
/// Initializes a new instance of the <see cref="Actor"/> class.
/// </summary>
/// <param name="address">The address of this actor in memory.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal Actor(IntPtr address, Dalamud dalamud)
{
this.Dalamud = dalamud;
this.Address = address;
}
/// <summary>
/// Gets the address of the actor in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets Dalamud itself.
/// </summary>
private protected Dalamud Dalamud { get; }
/// <summary>
/// This allows you to <c>if (actor) {...}</c> to check for validity.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static implicit operator bool(Actor actor) => IsValid(actor);
public static bool operator ==(Actor actor1, Actor actor2)
{
if (actor1 is null || actor2 is null)
return Equals(actor1, actor2);
return actor1.Equals(actor2);
}
public static bool operator !=(Actor actor1, Actor actor2) => !(actor1 == actor2);
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(Actor actor)
{
if (actor == null)
return false;
if (actor.Dalamud.ClientState.LocalContentId == 0)
return false;
return true;
}
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid() => IsValid(this);
/// <inheritdoc/>
bool IEquatable<Actor>.Equals(Actor other) => this.ActorId == other?.ActorId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<Actor>)this).Equals(obj as Actor);
/// <inheritdoc/>
public override int GetHashCode() => this.ActorId.GetHashCode();
}
/// <summary>
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public unsafe partial class Actor
{
/// <summary>
/// Gets the displayname of this <see cref="Actor" />.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString(this.Address + ActorOffsets.Name, 32);
/// <summary>
/// Gets the actor ID of this <see cref="Actor" />.
/// </summary>
public uint ActorId => *(uint*)(this.Address + ActorOffsets.ActorId);
/// <summary>
/// Gets the data ID for linking to other respective game data.
/// </summary>
public uint DataId => *(uint*)(this.Address + ActorOffsets.DataId);
/// <summary>
/// Gets the ID of this GameObject's owner.
/// </summary>
public uint OwnerId => *(uint*)(this.Address + ActorOffsets.OwnerId);
/// <summary>
/// Gets the entity kind of this <see cref="Actor" />.
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
/// </summary>
public ObjectKind ObjectKind => *(ObjectKind*)(this.Address + ActorOffsets.ObjectKind);
/// <summary>
/// Gets the sub kind of this Actor.
/// </summary>
public byte SubKind => *(byte*)(this.Address + ActorOffsets.SubKind);
/// <summary>
/// Gets a value indicating whether the actor is friendly.
/// </summary>
public bool IsFriendly => *(int*)(this.Address + ActorOffsets.IsFriendly) > 0;
/// <summary>
/// Gets the X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX => *(byte*)(this.Address + ActorOffsets.YalmDistanceFromObjectX);
/// <summary>
/// Gets the target status.
/// </summary>
/// <remarks>
/// This is some kind of enum. It may be <see cref="StatusEffect"/>.
/// </remarks>
public byte TargetStatus => *(byte*)(this.Address + ActorOffsets.TargetStatus);
/// <summary>
/// Gets the Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceY => *(byte*)(this.Address + ActorOffsets.YalmDistanceFromObjectY);
/// <summary>
/// Gets the position of this <see cref="Actor" />.
/// </summary>
public Position3 Position => *(Position3*)(this.Address + ActorOffsets.Position);
/// <summary>
/// Gets the rotation of this <see cref="Actor" />.
/// This ranges from -pi to pi radians.
/// </summary>
public float Rotation => *(float*)(this.Address + ActorOffsets.Rotation);
/// <summary>
/// Gets the hitbox radius of this <see cref="Actor" />.
/// </summary>
public float HitboxRadius => *(float*)(this.Address + ActorOffsets.HitboxRadius);
/// <summary>
/// Gets the current target of the Actor.
/// </summary>
public virtual uint TargetActorID => 0;
}
}

View file

@ -1,61 +0,0 @@
using System.Diagnostics.CodeAnalysis;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// Memory offsets for the <see cref="Actor"/> type and all that inherit from it.
/// </summary>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.")]
public static class ActorOffsets
{
// GameObject(Actor)
// GameObject :: Character
// GameObject :: Character :: BattleChara
// GameObject :: Character :: Companion
public const int Name = 0x30;
public const int ActorId = 0x74;
public const int DataId = 0x80;
public const int OwnerId = 0x84;
public const int ObjectKind = 0x8C;
public const int SubKind = 0x8D;
public const int IsFriendly = 0x8E;
public const int YalmDistanceFromObjectX = 0x90;
public const int TargetStatus = 0x91;
public const int YalmDistanceFromObjectY = 0x92;
public const int Position = 0xA0;
public const int Rotation = 0xB0;
public const int HitboxRadius = 0xC0;
// End GameObject 0x1A0
public const int CurrentHp = 0x1C4;
public const int MaxHp = 0x1C8;
public const int CurrentMp = 0x1CC;
public const int MaxMp = 0x1D0;
public const int CurrentGp = 0x1D4;
public const int MaxGp = 0x1D6;
public const int CurrentCp = 0x1D8;
public const int MaxCp = 0x1DA;
public const int ClassJob = 0x1E2;
public const int Level = 0x1E3;
public const int PlayerCharacterTargetActorId = 0x230;
public const int Customize = 0x1898;
public const int CompanyTag = 0x18B2;
public const int BattleNpcTargetActorId = 0x18D8;
public const int NameId = 0x1940;
public const int CurrentWorld = 0x195C;
public const int HomeWorld = 0x195E;
public const int StatusFlags = 0x19A0;
// End Character 0x19B0
// End Companion 0x19C0
public const int UIStatusEffects = 0x19F8;
public const int IsCasting = 0x1B80;
public const int IsCasting2 = 0x1B82;
public const int CurrentCastSpellActionId = 0x1B84;
public const int CurrentCastTargetActorId = 0x1B90;
public const int CurrentCastTime = 0x1BB4;
public const int TotalCastTime = 0x1BB8;
// End BattleChara 0x2C00
}
}

View file

@ -1,124 +0,0 @@
using System;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.ClientState.Structs;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents the base for non-static entities.
/// </summary>
public unsafe class Chara : Actor
{
/// <summary>
/// Initializes a new instance of the <see cref="Chara"/> class.
/// This represents a non-static entity.
/// </summary>
/// <param name="address">The address of this actor in memory.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal Chara(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the current HP of this Chara.
/// </summary>
public uint CurrentHp => *(uint*)(this.Address + ActorOffsets.CurrentHp);
/// <summary>
/// Gets the maximum HP of this Chara.
/// </summary>
public uint MaxHp => *(uint*)(this.Address + ActorOffsets.MaxHp);
/// <summary>
/// Gets the current MP of this Chara.
/// </summary>
public uint CurrentMp => *(uint*)(this.Address + ActorOffsets.CurrentMp);
/// <summary>
/// Gets the maximum MP of this Chara.
/// </summary>
public uint MaxMp => *(uint*)(this.Address + ActorOffsets.MaxMp);
/// <summary>
/// Gets the current GP of this Chara.
/// </summary>
public uint CurrentGp => *(uint*)(this.Address + ActorOffsets.CurrentGp);
/// <summary>
/// Gets the maximum GP of this Chara.
/// </summary>
public uint MaxGp => *(uint*)(this.Address + ActorOffsets.MaxGp);
/// <summary>
/// Gets the current CP of this Chara.
/// </summary>
public uint CurrentCp => *(uint*)(this.Address + ActorOffsets.CurrentCp);
/// <summary>
/// Gets the maximum CP of this Chara.
/// </summary>
public uint MaxCp => *(uint*)(this.Address + ActorOffsets.MaxCp);
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(*(byte*)(this.Address + ActorOffsets.ClassJob), this.Dalamud);
/// <summary>
/// Gets the level of this Chara.
/// </summary>
public byte Level => *(byte*)(this.Address + ActorOffsets.Level);
/// <summary>
/// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize => MemoryHelper.Read<byte>(this.Address + ActorOffsets.Customize, 28);
/// <summary>
/// Gets the status flags.
/// </summary>
public StatusFlags StatusFlags => *(StatusFlags*)(this.Address + ActorOffsets.StatusFlags);
/// <summary>
/// Gets the current status effects.
/// </summary>
/// <remarks>
/// This copies every time it is invoked, so make sure to only grab it once.
/// </remarks>
public StatusEffect[] StatusEffects => MemoryHelper.Read<StatusEffect>(this.Address + ActorOffsets.UIStatusEffects, 30, true);
/// <summary>
/// Gets a value indicating whether the actor is currently casting.
/// </summary>
public bool IsCasting => *(int*)(this.Address + ActorOffsets.IsCasting) > 0;
/// <summary>
/// Gets a value indicating whether the actor is currently casting (again?).
/// </summary>
public bool IsCasting2 => *(int*)(this.Address + ActorOffsets.IsCasting2) > 0;
/// <summary>
/// Gets the spell action ID currently being cast by the actor.
/// </summary>
public uint CurrentCastSpellActionId => *(uint*)(this.Address + ActorOffsets.CurrentCastSpellActionId);
/// <summary>
/// Gets the actor ID of the target currently being cast at by the actor.
/// </summary>
public uint CurrentCastTargetActorId => *(uint*)(this.Address + ActorOffsets.CurrentCastTargetActorId);
/// <summary>
/// Gets the current casting time of the spell being cast by the actor.
/// </summary>
public float CurrentCastTime => *(float*)(this.Address + ActorOffsets.CurrentCastTime);
/// <summary>
/// Gets the total casting time of the spell being cast by the actor.
/// </summary>
public float TotalCastTime => *(float*)(this.Address + ActorOffsets.TotalCastTime);
}
}

View file

@ -1,26 +0,0 @@
using System;
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
{
/// <summary>
/// This class represents an EventObj.
/// </summary>
public unsafe class EventObj : Actor
{
/// <summary>
/// Initializes a new instance of the <see cref="EventObj"/> class.
/// Set up a new EventObj with the provided memory representation.
/// </summary>
/// <param name="address">The address of this actor in memory.</param>
/// <param name="dalamud">A dalamud reference needed to access game data in Resolvers.</param>
internal EventObj(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the event object ID of the linking to their respective game data.
/// </summary>
public uint EventObjectId => *(uint*)(this.Address + ActorOffsets.DataId);
}
}

View file

@ -1,54 +0,0 @@
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a party member.
/// </summary>
public class PartyMember
{
/// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary>
/// <param name="table">The ActorTable instance.</param>
/// <param name="rawData">The interop data struct.</param>
public PartyMember(ActorTable table, Structs.PartyMember rawData)
{
this.CharacterName = MemoryHelper.ReadSeString(rawData.namePtr);
this.Unknown = rawData.unknown;
this.Actor = null;
for (var i = 0; i < table.Length; i++)
{
if (table[i] != null && table[i].ActorId == rawData.actorId)
{
this.Actor = table[i];
break;
}
}
this.ObjectKind = rawData.objectKind;
}
/// <summary>
/// Gets the name of the character.
/// </summary>
public SeString CharacterName { get; }
/// <summary>
/// Gets something unknown.
/// </summary>
public long Unknown { get; }
/// <summary>
/// Gets the actor object that corresponds to this party member.
/// </summary>
public Actor Actor { get; }
/// <summary>
/// Gets the kind or type of actor.
/// </summary>
public ObjectKind ObjectKind { get; }
}
}

View file

@ -0,0 +1,188 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Buddy
{
/// <summary>
/// This collection represents the buddies present in your squadron or trust party.
/// It does not include the local player.
/// </summary>
public sealed partial class BuddyList
{
private const uint InvalidObjectID = 0xE0000000;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="BuddyList"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal BuddyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
}
/// <summary>
/// Gets the amount of battle buddies the local player has.
/// </summary>
public int Length
{
get
{
var i = 0;
for (; i < 3; i++)
{
var addr = this.GetBattleBuddyMemberAddress(i);
var member = this.CreateBuddyMemberReference(addr);
if (member == null)
break;
}
return i;
}
}
/// <summary>
/// Gets a value indicating whether the local player's companion is present.
/// </summary>
public bool CompanionBuddyPresent => this.CompanionBuddy != null;
/// <summary>
/// Gets a value indicating whether the local player's pet is present.
/// </summary>
public bool PetBuddyPresent => this.PetBuddy != null;
/// <summary>
/// Gets the active companion buddy.
/// </summary>
[CanBeNull]
public BuddyMember CompanionBuddy
{
get
{
var addr = this.GetCompanionBuddyMemberAddress();
return this.CreateBuddyMemberReference(addr);
}
}
/// <summary>
/// Gets the active pet buddy.
/// </summary>
[CanBeNull]
public BuddyMember PetBuddy
{
get
{
var addr = this.GetPetBuddyMemberAddress();
return this.CreateBuddyMemberReference(addr);
}
}
/// <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>();
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
/// <summary>
/// Gets a battle buddy at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>A <see cref="BuddyMember"/> at the specified spawn index.</returns>
[CanBeNull]
public BuddyMember this[int index]
{
get
{
var address = this.GetBattleBuddyMemberAddress(index);
return this.CreateBuddyMemberReference(address);
}
}
/// <summary>
/// Gets the address of the companion buddy.
/// </summary>
/// <returns>The memory address of the companion buddy.</returns>
public unsafe IntPtr GetCompanionBuddyMemberAddress()
{
return (IntPtr)(&this.BuddyListStruct->Companion);
}
/// <summary>
/// Gets the address of the pet buddy.
/// </summary>
/// <returns>The memory address of the pet buddy.</returns>
public unsafe IntPtr GetPetBuddyMemberAddress()
{
return (IntPtr)(&this.BuddyListStruct->Pet);
}
/// <summary>
/// Gets the address of the battle buddy at the specified index of the buddy list.
/// </summary>
/// <param name="index">The index of the battle buddy.</param>
/// <returns>The memory address of the battle buddy.</returns>
public unsafe IntPtr GetBattleBuddyMemberAddress(int index)
{
if (index < 0 || index >= 3)
return IntPtr.Zero;
return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize));
}
/// <summary>
/// Create a reference to a buddy.
/// </summary>
/// <param name="address">The address of the buddy in memory.</param>
/// <returns><see cref="BuddyMember"/> object containing the requested data.</returns>
[CanBeNull]
public BuddyMember CreateBuddyMemberReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
var buddy = new BuddyMember(address, this.dalamud);
if (buddy.ObjectId == InvalidObjectID)
return null;
return buddy;
}
}
/// <summary>
/// This collection represents the buddies present in your squadron or trust party.
/// </summary>
public sealed partial class BuddyList : IReadOnlyCollection<BuddyMember>
{
/// <inheritdoc/>
int IReadOnlyCollection<BuddyMember>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<BuddyMember> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
yield return this[i];
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}

View file

@ -0,0 +1,78 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Buddy
{
/// <summary>
/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties.
/// </summary>
public unsafe class BuddyMember
{
private Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
/// </summary>
/// <param name="address">Buddy address.</param>
/// <param name="dalamud">Dalamud instance.</param>
internal BuddyMember(IntPtr address, Dalamud dalamud)
{
this.dalamud = dalamud;
this.Address = address;
}
/// <summary>
/// Gets the address of the buddy in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the object ID of this buddy.
/// </summary>
public uint ObjectId => this.Struct->ObjectID;
/// <summary>
/// Gets the actor associated with this buddy.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public GameObject Actor => this.dalamud.ClientState.Objects.SearchByID(this.ObjectId);
/// <summary>
/// Gets the current health of this buddy.
/// </summary>
public uint CurrentHP => this.Struct->CurrentHealth;
/// <summary>
/// Gets the maximum health of this buddy.
/// </summary>
public uint MaxHP => this.Struct->MaxHealth;
/// <summary>
/// Gets the data ID of this buddy.
/// </summary>
public uint DataID => this.Struct->DataID;
/// <summary>
/// Gets the Mount data related to this buddy. It should only be used with companion buddies.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Mount> MountData => new(this.DataID, this.dalamud);
/// <summary>
/// Gets the Pet data related to this buddy. It should only be used with pet buddies.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.Pet> PetData => new(this.DataID, this.dalamud);
/// <summary>
/// Gets the Trust data related to this buddy. It should only be used with battle buddies.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.DawnGrowMember> TrustData => new(this.DataID, this.dalamud);
private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address;
}
}

View file

@ -1,14 +1,15 @@
using System; using System;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors; using Dalamud.Game.ClientState.Buddy;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Fates; using Dalamud.Game.ClientState.Fates;
using Dalamud.Game.ClientState.GamePad; using Dalamud.Game.ClientState.GamePad;
using Dalamud.Game.ClientState.JobGauge;
using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Party;
using Dalamud.Hooking; using Dalamud.Hooking;
using JetBrains.Annotations; using JetBrains.Annotations;
using Serilog; using Serilog;
@ -18,7 +19,7 @@ namespace Dalamud.Game.ClientState
/// <summary> /// <summary>
/// This class represents the state of the game client at the time of access. /// This class represents the state of the game client at the time of access.
/// </summary> /// </summary>
public sealed class ClientState : INotifyPropertyChanged, IDisposable public sealed class ClientState : IDisposable
{ {
private readonly Dalamud dalamud; private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
@ -43,12 +44,14 @@ namespace Dalamud.Game.ClientState
this.ClientLanguage = startInfo.Language; this.ClientLanguage = startInfo.Language;
this.Actors = new ActorTable(dalamud, this.address); this.Objects = new ObjectTable(dalamud, this.address);
this.Fates = new FateTable(dalamud, this.address); this.Fates = new FateTable(dalamud, this.address);
this.PartyList = new PartyList(dalamud, this.address); this.PartyList = new PartyList(dalamud, this.address);
this.BuddyList = new BuddyList(dalamud, this.address);
this.JobGauges = new JobGauges(this.address); this.JobGauges = new JobGauges(this.address);
this.KeyState = new KeyState(this.address, scanner.Module.BaseAddress); this.KeyState = new KeyState(this.address, scanner.Module.BaseAddress);
@ -70,13 +73,6 @@ namespace Dalamud.Game.ClientState
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType); private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
/// <summary>
/// Event that fires when a property changes.
/// </summary>
#pragma warning disable CS0067
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore
/// <summary> /// <summary>
/// Event that gets fired when the current Territory changes. /// Event that gets fired when the current Territory changes.
/// </summary> /// </summary>
@ -85,12 +81,12 @@ namespace Dalamud.Game.ClientState
/// <summary> /// <summary>
/// Event that fires when a character is logging in. /// Event that fires when a character is logging in.
/// </summary> /// </summary>
public event EventHandler OnLogin; public event EventHandler Login;
/// <summary> /// <summary>
/// Event that fires when a character is logging out. /// Event that fires when a character is logging out.
/// </summary> /// </summary>
public event EventHandler OnLogout; public event EventHandler Logout;
/// <summary> /// <summary>
/// Event that gets fired when a duty is ready. /// Event that gets fired when a duty is ready.
@ -100,7 +96,7 @@ namespace Dalamud.Game.ClientState
/// <summary> /// <summary>
/// Gets the table of all present actors. /// Gets the table of all present actors.
/// </summary> /// </summary>
public ActorTable Actors { get; } public ObjectTable Objects { get; }
/// <summary> /// <summary>
/// Gets the table of all present fates. /// Gets the table of all present fates.
@ -122,6 +118,11 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public PartyList PartyList { get; } public PartyList PartyList { get; }
/// <summary>
/// Gets the class facilitating buddy list data access.
/// </summary>
public BuddyList BuddyList { get; }
/// <summary> /// <summary>
/// Gets access to the keypress state of keyboard keys in game. /// Gets access to the keypress state of keyboard keys in game.
/// </summary> /// </summary>
@ -151,7 +152,7 @@ namespace Dalamud.Game.ClientState
/// Gets the local player character, if one is present. /// Gets the local player character, if one is present.
/// </summary> /// </summary>
[CanBeNull] [CanBeNull]
public PlayerCharacter LocalPlayer => this.Actors[0] as PlayerCharacter; public PlayerCharacter LocalPlayer => this.Objects[0] as PlayerCharacter;
/// <summary> /// <summary>
/// Gets the content ID of the local character. /// Gets the content ID of the local character.
@ -169,7 +170,6 @@ namespace Dalamud.Game.ClientState
public void Enable() public void Enable()
{ {
this.GamepadState.Enable(); this.GamepadState.Enable();
this.PartyList.Enable();
this.setupTerritoryTypeHook.Enable(); this.setupTerritoryTypeHook.Enable();
} }
@ -178,7 +178,6 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
this.PartyList.Dispose();
this.setupTerritoryTypeHook.Dispose(); this.setupTerritoryTypeHook.Dispose();
this.GamepadState.Dispose(); this.GamepadState.Dispose();
@ -208,7 +207,7 @@ namespace Dalamud.Game.ClientState
Log.Debug("Is login"); Log.Debug("Is login");
this.lastConditionNone = false; this.lastConditionNone = false;
this.IsLoggedIn = true; this.IsLoggedIn = true;
this.OnLogin?.Invoke(this, null); this.Login?.Invoke(this, null);
} }
if (!this.Condition.Any() && this.lastConditionNone == false) if (!this.Condition.Any() && this.lastConditionNone == false)
@ -216,7 +215,7 @@ namespace Dalamud.Game.ClientState
Log.Debug("Is logout"); Log.Debug("Is logout");
this.lastConditionNone = true; this.lastConditionNone = true;
this.IsLoggedIn = false; this.IsLoggedIn = false;
this.OnLogout?.Invoke(this, null); this.Logout?.Invoke(this, null);
} }
} }
} }

View file

@ -1,7 +1,4 @@
using System; using System;
using System.Runtime.InteropServices;
using Dalamud.Game.Internal;
namespace Dalamud.Game.ClientState namespace Dalamud.Game.ClientState
{ {
@ -15,17 +12,25 @@ namespace Dalamud.Game.ClientState
/// <summary> /// <summary>
/// Gets the address of the actor table. /// Gets the address of the actor table.
/// </summary> /// </summary>
public IntPtr ActorTable { get; private set; } public IntPtr ObjectTable { get; private set; }
/// <summary> /// <summary>
/// Gets the address of the fate table pointer. /// 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> /// </summary>
/// <remarks> /// <remarks>
/// This is a static address to a pointer, not the address of the table itself. /// This is a static address to a pointer, not the address of the table itself.
/// </remarks> /// </remarks>
public IntPtr FateTablePtr { get; private set; } public IntPtr FateTablePtr { get; private set; }
// public IntPtr ViewportActorTable { get; private set; } /// <summary>
/// Gets the address of the Group Manager.
/// </summary>
public IntPtr GroupManager { get; private set; }
/// <summary> /// <summary>
/// Gets the address of the local content id. /// Gets the address of the local content id.
@ -75,12 +80,16 @@ namespace Dalamud.Game.ClientState
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148; // ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??"); // SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83"); this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 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 F9 44 0F B7 41 ??"); this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??");
this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50");
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07"); this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
this.JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10; this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8;
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??"); this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
@ -88,11 +97,9 @@ namespace Dalamud.Game.ClientState
// so GetStaticAddressFromSig() can't be used. lea rcx, ds:1DB9F74h[rax*4] // so GetStaticAddressFromSig() can't be used. lea rcx, ds:1DB9F74h[rax*4]
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
// PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30"); this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3); this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB");
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
} }

View file

@ -0,0 +1,23 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// DRG Blood of the Dragon state types.
/// </summary>
public enum BOTDState : byte
{
/// <summary>
/// Inactive type.
/// </summary>
NONE = 0,
/// <summary>
/// Blood of the Dragon is active.
/// </summary>
BOTD = 1,
/// <summary>
/// Life of the Dragon is active.
/// </summary>
LOTD = 2,
}
}

View file

@ -0,0 +1,53 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// AST Arcanum (card) types.
/// </summary>
public enum CardType : byte
{
/// <summary>
/// No card.
/// </summary>
NONE = 0,
/// <summary>
/// The Balance card.
/// </summary>
BALANCE = 1,
/// <summary>
/// The Bole card.
/// </summary>
BOLE = 2,
/// <summary>
/// The Arrow card.
/// </summary>
ARROW = 3,
/// <summary>
/// The Spear card.
/// </summary>
SPEAR = 4,
/// <summary>
/// The Ewer card.
/// </summary>
EWER = 5,
/// <summary>
/// The Spire card.
/// </summary>
SPIRE = 6,
/// <summary>
/// The Lord of Crowns card.
/// </summary>
LORD = 0x70,
/// <summary>
/// The Lady of Crowns card.
/// </summary>
LADY = 0x80,
}
}

View file

@ -0,0 +1,18 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// SCH Dismissed fairy types.
/// </summary>
public enum DismissedFairy : byte
{
/// <summary>
/// Dismissed fairy is Eos.
/// </summary>
EOS = 6,
/// <summary>
/// Dismissed fairy is Selene.
/// </summary>
SELENE = 7,
}
}

View file

@ -0,0 +1,23 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// NIN Mudra types.
/// </summary>
public enum Mudras : byte
{
/// <summary>
/// Ten mudra.
/// </summary>
TEN = 1,
/// <summary>
/// Chi mudra.
/// </summary>
CHI = 2,
/// <summary>
/// Jin mudra.
/// </summary>
JIN = 3,
}
}

View file

@ -0,0 +1,28 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// SMN summoned pet glam types.
/// </summary>
public enum PetGlam : byte
{
/// <summary>
/// No pet glam.
/// </summary>
NONE = 0,
/// <summary>
/// Emerald carbuncle pet glam.
/// </summary>
EMERALD = 1,
/// <summary>
/// Topaz carbuncle pet glam.
/// </summary>
TOPAZ = 2,
/// <summary>
/// Ruby carbuncle pet glam.
/// </summary>
RUBY = 3,
}
}

View file

@ -0,0 +1,28 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// AST Divination seal types.
/// </summary>
public enum SealType : byte
{
/// <summary>
/// No seal.
/// </summary>
NONE = 0,
/// <summary>
/// Sun seal.
/// </summary>
SUN = 1,
/// <summary>
/// Moon seal.
/// </summary>
MOON = 2,
/// <summary>
/// Celestial seal.
/// </summary>
CELESTIAL = 3,
}
}

View file

@ -0,0 +1,31 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// Samurai Sen types.
/// </summary>
[Flags]
public enum Sen : byte
{
/// <summary>
/// No Sen.
/// </summary>
NONE = 0,
/// <summary>
/// Setsu Sen type.
/// </summary>
SETSU = 1 << 0,
/// <summary>
/// Getsu Sen type.
/// </summary>
GETSU = 1 << 1,
/// <summary>
/// Ka Sen type.
/// </summary>
KA = 1 << 2,
}
}

View file

@ -0,0 +1,28 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// BRD Song types.
/// </summary>
public enum Song : byte
{
/// <summary>
/// No song is active type.
/// </summary>
NONE = 0,
/// <summary>
/// Mage's Ballad type.
/// </summary>
MAGE = 5,
/// <summary>
/// Army's Paeon type.
/// </summary>
ARMY = 10,
/// <summary>
/// The Wanderer's Minuet type.
/// </summary>
WANDERER = 15,
}
}

View file

@ -0,0 +1,28 @@
namespace Dalamud.Game.ClientState.JobGauge.Enums
{
/// <summary>
/// SMN summoned pet types.
/// </summary>
public enum SummonPet : byte
{
/// <summary>
/// No pet.
/// </summary>
NONE = 0,
/// <summary>
/// The summoned pet Ifrit.
/// </summary>
IFRIT = 3,
/// <summary>
/// The summoned pet Titan.
/// </summary>
TITAN = 4,
/// <summary>
/// The summoned pet Garuda.
/// </summary>
GARUDA = 5,
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Dalamud.Game.ClientState.JobGauge.Types;
using Serilog;
namespace Dalamud.Game.ClientState.JobGauge
{
/// <summary>
/// This class converts in-memory Job gauge data to structs.
/// </summary>
public class JobGauges
{
private Dictionary<Type, JobGaugeBase> cache = new();
/// <summary>
/// Initializes a new instance of the <see cref="JobGauges"/> class.
/// </summary>
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
public JobGauges(ClientStateAddressResolver addressResolver)
{
this.Address = addressResolver.JobGaugeData;
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
}
/// <summary>
/// Gets the address of the JobGauge data.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Get the JobGauge for a given job.
/// </summary>
/// <typeparam name="T">A JobGauge struct from ClientState.Structs.JobGauge.</typeparam>
/// <returns>A JobGauge.</returns>
public T Get<T>() where T : JobGaugeBase
{
// This is cached to mitigate the effects of using activator for instantiation.
// Since the gauge itself reads from live memory, there isn't much downside to doing this.
if (!this.cache.TryGetValue(typeof(T), out var gauge))
{
gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null);
}
return (T)gauge;
}
}
}

View file

@ -0,0 +1,40 @@
using System;
using Dalamud.Game.ClientState.JobGauge.Enums;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory AST job gauge.
/// </summary>
public unsafe class ASTGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.AstrologianGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="ASTGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal ASTGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the currently drawn <see cref="CardType"/>.
/// </summary>
/// <returns>Currently drawn <see cref="CardType"/>.</returns>
public CardType DrawnCard => (CardType)this.Struct->Card;
/// <summary>
/// Check if a <see cref="SealType"/> is currently active on the divination gauge.
/// </summary>
/// <param name="seal">The <see cref="SealType"/> to check for.</param>
/// <returns>If the given Seal is currently divined.</returns>
public unsafe bool ContainsSeal(SealType seal)
{
if (this.Struct->Seals[0] == (byte)seal) return true;
if (this.Struct->Seals[1] == (byte)seal) return true;
if (this.Struct->Seals[2] == (byte)seal) return true;
return false;
}
}
}

View file

@ -0,0 +1,67 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory BLM job gauge.
/// </summary>
public unsafe class BLMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BlackMageGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="BLMGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal BLMGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the time remaining for the Enochian time in milliseconds.
/// </summary>
public short EnochianTimer => this.Struct->EnochianTimer;
/// <summary>
/// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
/// </summary>
public short ElementTimeRemaining => this.Struct->ElementTimeRemaining;
/// <summary>
/// Gets the number of Polyglot stacks remaining.
/// </summary>
public byte PolyglotStacks => this.Struct->PolyglotStacks;
/// <summary>
/// Gets the number of Umbral Hearts remaining.
/// </summary>
public byte UmbralHearts => this.Struct->UmbralHearts;
/// <summary>
/// Gets the amount of Umbral Ice stacks.
/// </summary>
public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0);
/// <summary>
/// Gets the amount of Astral Fire stacks.
/// </summary>
public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0);
/// <summary>
/// Gets a value indicating whether if the player is in Umbral Ice.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool InUmbralIce => this.Struct->ElementStance < 0;
/// <summary>
/// Gets a value indicating whether if the player is in Astral fire.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool InAstralFire => this.Struct->ElementStance > 0;
/// <summary>
/// Gets a value indicating whether if Enochian is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsEnochianActive => this.Struct->Enochian != 0;
}
}

View file

@ -0,0 +1,41 @@
using System;
using Dalamud.Game.ClientState.JobGauge.Enums;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory BRD job gauge.
/// </summary>
public unsafe class BRDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.BardGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="BRDGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal BRDGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the current song timer in milliseconds.
/// </summary>
public short SongTimer => this.Struct->SongTimer;
/// <summary>
/// Gets the amount of Repertoire accumulated.
/// </summary>
public byte Repertoire => this.Struct->Repertoire;
/// <summary>
/// Gets the amount of Soul Voice accumulated.
/// </summary>
public byte SoulVoice => this.Struct->SoulVoice;
/// <summary>
/// Gets the type of song that is active.
/// </summary>
public Song Song => (Song)this.Struct->Song;
}
}

View file

@ -0,0 +1,46 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory DNC job gauge.
/// </summary>
public unsafe class DNCGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DancerGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="DNCGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal DNCGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the number of feathers available.
/// </summary>
public byte Feathers => this.Struct->Feathers;
/// <summary>
/// Gets the amount of Espirit available.
/// </summary>
public byte Esprit => this.Struct->Esprit;
/// <summary>
/// Gets the number of steps completed for the current dance.
/// </summary>
public byte CompletedSteps => this.Struct->StepIndex;
/// <summary>
/// Gets the next step in the current dance.
/// </summary>
/// <returns>The next dance step action ID.</returns>
public ulong NextStep => (ulong)(15999 + this.Struct->DanceSteps[this.Struct->StepIndex] - 1);
/// <summary>
/// Gets a value indicating whether the player is dancing or not.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsDancing => this.Struct->DanceSteps[0] != 0;
}
}

View file

@ -0,0 +1,36 @@
using System;
using Dalamud.Game.ClientState.JobGauge.Enums;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory DRG job gauge.
/// </summary>
public unsafe class DRGGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DragoonGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="DRGGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal DRGGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the time remaining for Blood of the Dragon in milliseconds.
/// </summary>
public short BOTDTimer => this.Struct->BotdTimer;
/// <summary>
/// Gets the current state of Blood of the Dragon.
/// </summary>
public BOTDState BOTDState => (BOTDState)this.Struct->BotdState;
/// <summary>
/// Gets the count of eyes opened during Blood of the Dragon.
/// </summary>
public byte EyeCount => this.Struct->EyeCount;
}
}

View file

@ -0,0 +1,40 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory DRK job gauge.
/// </summary>
public unsafe class DRKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.DarkKnightGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="DRKGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal DRKGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the amount of blood accumulated.
/// </summary>
public byte Blood => this.Struct->Blood;
/// <summary>
/// Gets the Darkside time remaining in milliseconds.
/// </summary>
public ushort DarksideTimeRemaining => this.Struct->DarksideTimer;
/// <summary>
/// Gets the Shadow time remaining in milliseconds.
/// </summary>
public ushort ShadowTimeRemaining => this.Struct->ShadowTimer;
/// <summary>
/// Gets a value indicating whether the player has Dark Arts or not.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasDarkArts => this.Struct->DarkArtsState > 0;
}
}

View file

@ -0,0 +1,34 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory GNB job gauge.
/// </summary>
public unsafe class GNBGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.GunbreakerGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="GNBGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal GNBGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the amount of ammo available.
/// </summary>
public byte Ammo => this.Struct->Ammo;
/// <summary>
/// Gets the max combo time of the Gnashing Fang combo.
/// </summary>
public short MaxTimerDuration => this.Struct->MaxTimerDuration;
/// <summary>
/// Gets the current step of the Gnashing Fang combo.
/// </summary>
public byte AmmoComboStep => this.Struct->AmmoComboStep;
}
}

View file

@ -0,0 +1,24 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// Base job gauge class.
/// </summary>
public abstract unsafe class JobGaugeBase
{
/// <summary>
/// Initializes a new instance of the <see cref="JobGaugeBase"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal JobGaugeBase(IntPtr address)
{
this.Address = address;
}
/// <summary>
/// Gets the address of this job gauge in memory.
/// </summary>
public IntPtr Address { get; }
}
}

View file

@ -0,0 +1,25 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// Base job gauge class.
/// </summary>
/// <typeparam name="T">The underlying FFXIVClientStructs type.</typeparam>
public unsafe class JobGaugeBase<T> : JobGaugeBase where T : unmanaged
{
/// <summary>
/// Initializes a new instance of the <see cref="JobGaugeBase{T}"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal JobGaugeBase(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets an unsafe struct pointer of this job gauge.
/// </summary>
private protected T* Struct => (T*)this.Address;
}
}

View file

@ -0,0 +1,56 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory MCH job gauge.
/// </summary>
public unsafe class MCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MachinistGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="MCHGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal MCHGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the time time remaining for Overheat in milliseconds.
/// </summary>
public short OverheatTimeRemaining => this.Struct->OverheatTimeRemaining;
/// <summary>
/// Gets the time remaining for the Rook or Queen in milliseconds.
/// </summary>
public short SummonTimeRemaining => this.Struct->SummonTimeRemaining;
/// <summary>
/// Gets the current Heat level.
/// </summary>
public byte Heat => this.Struct->Heat;
/// <summary>
/// Gets the current Battery level.
/// </summary>
public byte Battery => this.Struct->Battery;
/// <summary>
/// Gets the battery level of the last summon (robot).
/// </summary>
public byte LastSummonBatteryPower => this.Struct->LastSummonBatteryPower;
/// <summary>
/// Gets a value indicating whether the player is currently Overheated.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsOverheated => (this.Struct->TimerActive & 1) != 0;
/// <summary>
/// Gets a value indicating whether the player has an active Robot.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsRobotActive => (this.Struct->TimerActive & 2) != 0;
}
}

View file

@ -0,0 +1,24 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory MNK job gauge.
/// </summary>
public unsafe class MNKGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.MonkGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="MNKGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal MNKGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the number of Chakra available.
/// </summary>
public byte Chakra => this.Struct->Chakra;
}
}

View file

@ -0,0 +1,34 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory NIN job gauge.
/// </summary>
public unsafe class NINGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.NinjaGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="NINGauge"/> class.
/// </summary>
/// <param name="address">The address of the gauge.</param>
internal NINGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the time left on Huton in milliseconds.
/// </summary>
public int HutonTimer => this.Struct->HutonTimer;
/// <summary>
/// Gets the amount of Ninki available.
/// </summary>
public byte Ninki => this.Struct->Ninki;
/// <summary>
/// Gets the number of times Huton has been cast manually.
/// </summary>
public byte HutonManualCasts => this.Struct->HutonManualCasts;
}
}

View file

@ -0,0 +1,24 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory PLD job gauge.
/// </summary>
public unsafe class PLDGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.PaladinGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="PLDGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal PLDGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the current level of the Oath gauge.
/// </summary>
public byte OathGauge => this.Struct->OathGauge;
}
}

View file

@ -0,0 +1,29 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory RDM job gauge.
/// </summary>
public unsafe class RDMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.RedMageGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="RDMGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal RDMGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the level of the White gauge.
/// </summary>
public byte WhiteMana => this.Struct->WhiteMana;
/// <summary>
/// Gets the level of the Black gauge.
/// </summary>
public byte BlackMana => this.Struct->BlackMana;
}
}

View file

@ -0,0 +1,54 @@
using System;
using Dalamud.Game.ClientState.JobGauge.Enums;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory SAM job gauge.
/// </summary>
public unsafe class SAMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SamuraiGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="SAMGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal SAMGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the current amount of Kenki available.
/// </summary>
public byte Kenki => this.Struct->Kenki;
/// <summary>
/// Gets the amount of Meditation stacks.
/// </summary>
public byte MeditationStacks => this.Struct->MeditationStacks;
/// <summary>
/// Gets the active Sen.
/// </summary>
public Sen Sen => (Sen)this.Struct->SenFlags;
/// <summary>
/// Gets a value indicating whether the Setsu Sen is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasSetsu => (this.Sen & Sen.SETSU) != 0;
/// <summary>
/// Gets a value indicating whether the Getsu Sen is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasGetsu => (this.Sen & Sen.GETSU) != 0;
/// <summary>
/// Gets a value indicating whether the Ka Sen is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasKa => (this.Sen & Sen.KA) != 0;
}
}

View file

@ -0,0 +1,41 @@
using System;
using Dalamud.Game.ClientState.JobGauge.Enums;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory SCH job gauge.
/// </summary>
public unsafe class SCHGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.ScholarGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="SCHGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal SCHGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the amount of Aetherflow stacks available.
/// </summary>
public byte Aetherflow => this.Struct->Aetherflow;
/// <summary>
/// Gets the current level of the Fairy Gauge.
/// </summary>
public byte FairyGauge => this.Struct->FairyGauge;
/// <summary>
/// Gets the Seraph time remaiSCHg in milliseconds.
/// </summary>
public short SeraphTimer => this.Struct->SeraphTimer;
/// <summary>
/// Gets the last dismissed fairy.
/// </summary>
public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy;
}
}

View file

@ -0,0 +1,60 @@
using System;
using Dalamud.Game.ClientState.JobGauge.Enums;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory SMN job gauge.
/// </summary>
public unsafe class SMNGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.SummonerGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="SMNGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal SMNGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the time remaining for the current summon.
/// </summary>
public short TimerRemaining => this.Struct->TimerRemaining;
/// <summary>
/// Gets the summon that will return after the current summon expires.
/// </summary>
public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon;
/// <summary>
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
/// </summary>
public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam;
/// <summary>
/// Gets the current aether flags.
/// Use the summon accessors instead.
/// </summary>
public byte AetherFlags => this.Struct->AetherFlags;
/// <summary>
/// Gets a value indicating whether if Phoenix is ready to be summoned.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsPhoenixReady => (this.AetherFlags & 0x10) > 0;
/// <summary>
/// Gets a value indicating whether Bahamut is ready to be summoned.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsBahamutReady => (this.AetherFlags & 8) > 0;
/// <summary>
/// Gets a value indicating whether there are any Aetherflow stacks available.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasAetherflowStacks => (this.AetherFlags & 3) > 0;
}
}

View file

@ -0,0 +1,24 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory WAR job gauge.
/// </summary>
public unsafe class WARGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WarriorGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="WARGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal WARGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the amount of wrath in the Beast gauge.
/// </summary>
public byte BeastGauge => this.Struct->BeastGauge;
}
}

View file

@ -0,0 +1,34 @@
using System;
namespace Dalamud.Game.ClientState.JobGauge.Types
{
/// <summary>
/// In-memory WHM job gauge.
/// </summary>
public unsafe class WHMGauge : JobGaugeBase<FFXIVClientStructs.FFXIV.Client.Game.Gauge.WhiteMageGauge>
{
/// <summary>
/// Initializes a new instance of the <see cref="WHMGauge"/> class.
/// </summary>
/// <param name="address">Address of the job gauge.</param>
internal WHMGauge(IntPtr address)
: base(address)
{
}
/// <summary>
/// Gets the time to next lily in milliseconds.
/// </summary>
public short LilyTimer => this.Struct->LilyTimer;
/// <summary>
/// Gets the number of Lilies.
/// </summary>
public byte Lily => this.Struct->Lily;
/// <summary>
/// Gets the number of times the blood lily has been nourished.
/// </summary>
public byte BloodLily => this.Struct->BloodLily;
}
}

View file

@ -1,35 +0,0 @@
using System.Runtime.InteropServices;
using Serilog;
namespace Dalamud.Game.ClientState
{
/// <summary>
/// This class converts in-memory Job gauge data to structs.
/// </summary>
public class JobGauges
{
/// <summary>
/// Initializes a new instance of the <see cref="JobGauges"/> class.
/// </summary>
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
public JobGauges(ClientStateAddressResolver addressResolver)
{
this.Address = addressResolver;
Log.Verbose($"JobGaugeData address 0x{this.Address.JobGaugeData.ToInt64():X}");
}
private ClientStateAddressResolver Address { get; }
/// <summary>
/// Get the JobGauge for a given job.
/// </summary>
/// <typeparam name="T">A JobGauge struct from ClientState.Structs.JobGauge.</typeparam>
/// <returns>A JobGauge.</returns>
public T Get<T>()
{
return Marshal.PtrToStructure<T>(this.Address.JobGaugeData);
}
}
}

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer namespace Dalamud.Game.ClientState.Objects.Enums
{ {
/// <summary> /// <summary>
/// An Enum describing possible BattleNpc kinds. /// An Enum describing possible BattleNpc kinds.

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors.Types namespace Dalamud.Game.ClientState.Objects.Enums
{ {
/// <summary> /// <summary>
/// This enum describes the indices of the Customize array. /// This enum describes the indices of the Customize array.

View file

@ -1,4 +1,4 @@
namespace Dalamud.Game.ClientState.Actors.Types namespace Dalamud.Game.ClientState.Objects.Enums
{ {
/// <summary> /// <summary>
/// Enum describing possible entity kinds. /// Enum describing possible entity kinds.
@ -6,7 +6,7 @@ namespace Dalamud.Game.ClientState.Actors.Types
public enum ObjectKind : byte public enum ObjectKind : byte
{ {
/// <summary> /// <summary>
/// Invalid actor. /// Invalid character.
/// </summary> /// </summary>
None = 0x00, None = 0x00,

View file

@ -1,6 +1,6 @@
using System; using System;
namespace Dalamud.Game.ClientState.Actors.Types namespace Dalamud.Game.ClientState.Objects.Enums
{ {
/// <summary> /// <summary>
/// Enum describing possible status flags. /// Enum describing possible status flags.
@ -14,42 +14,42 @@ namespace Dalamud.Game.ClientState.Actors.Types
None = 0, None = 0,
/// <summary> /// <summary>
/// Hostile actor. /// Hostile character.
/// </summary> /// </summary>
Hostile = 1, Hostile = 1,
/// <summary> /// <summary>
/// Actor in combat. /// Character in combat.
/// </summary> /// </summary>
InCombat = 2, InCombat = 2,
/// <summary> /// <summary>
/// Actor weapon is out. /// Character weapon is out.
/// </summary> /// </summary>
WeaponOut = 4, WeaponOut = 4,
/// <summary> /// <summary>
/// Actor offhand is out. /// Character offhand is out.
/// </summary> /// </summary>
OffhandOut = 8, OffhandOut = 8,
/// <summary> /// <summary>
/// Actor is a party member. /// Character is a party member.
/// </summary> /// </summary>
PartyMember = 16, PartyMember = 16,
/// <summary> /// <summary>
/// Actor is a alliance member. /// Character is a alliance member.
/// </summary> /// </summary>
AllianceMember = 32, AllianceMember = 32,
/// <summary> /// <summary>
/// Actor is in friend list. /// Character is in friend list.
/// </summary> /// </summary>
Friend = 64, Friend = 64,
/// <summary> /// <summary>
/// Actor is casting. /// Character is casting.
/// </summary> /// </summary>
IsCasting = 128, IsCasting = 128,
} }

View file

@ -0,0 +1,146 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Objects
{
/// <summary>
/// This collection represents the currently spawned FFXIV game objects.
/// </summary>
public sealed partial class ObjectTable
{
private const int ObjectTableLength = 424;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="ObjectTable"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal ObjectTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
}
/// <summary>
/// Gets the address of the object table.
/// </summary>
public IntPtr Address => this.address.ObjectTable;
/// <summary>
/// Gets the length of the object table.
/// </summary>
public int Length => ObjectTableLength;
/// <summary>
/// Get an object at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>An <see cref="GameObject"/> at the specified spawn index.</returns>
[CanBeNull]
public GameObject this[int index]
{
get
{
var address = this.GetObjectAddress(index);
return this.CreateObjectReference(address);
}
}
/// <summary>
/// Search for a game object by their Object ID.
/// </summary>
/// <param name="objectID">Object ID to find.</param>
/// <returns>A game object or null.</returns>
[CanBeNull]
public GameObject SearchByID(uint objectID)
{
foreach (var obj in this)
{
if (obj == null)
continue;
if (obj.ObjectId == objectID)
return obj;
}
return null;
}
/// <summary>
/// Gets the address of the game object at the specified index of the object table.
/// </summary>
/// <param name="index">The index of the object.</param>
/// <returns>The memory address of the object.</returns>
public unsafe IntPtr GetObjectAddress(int index)
{
if (index < 0 || index >= ObjectTableLength)
return IntPtr.Zero;
return *(IntPtr*)(this.address.ObjectTable + (8 * index));
}
/// <summary>
/// Create a reference to an FFXIV game object.
/// </summary>
/// <param name="address">The address of the object in memory.</param>
/// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns>
[CanBeNull]
public unsafe GameObject CreateObjectReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address;
var objKind = (ObjectKind)obj->ObjectKind;
return objKind switch
{
ObjectKind.Player => new PlayerCharacter(address, this.dalamud),
ObjectKind.BattleNpc => new BattleNpc(address, this.dalamud),
ObjectKind.EventObj => new EventObj(address, this.dalamud),
ObjectKind.Companion => new Npc(address, this.dalamud),
_ => new GameObject(address, this.dalamud),
};
}
}
/// <summary>
/// This collection represents the currently spawned FFXIV game objects.
/// </summary>
public sealed partial class ObjectTable : IReadOnlyCollection<GameObject>
{
/// <inheritdoc/>
int IReadOnlyCollection<GameObject>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<GameObject> GetEnumerator()
{
for (var i = 0; i < ObjectTableLength; i++)
{
var obj = this[i];
if (obj == null)
continue;
yield return obj;
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}

View file

@ -1,11 +1,13 @@
using System; using System;
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer using Dalamud.Game.ClientState.Objects.Enums;
namespace Dalamud.Game.ClientState.Objects.Types
{ {
/// <summary> /// <summary>
/// This class represents a battle NPC. /// This class represents a battle NPC.
/// </summary> /// </summary>
public unsafe class BattleNpc : Npc public unsafe class BattleNpc : BattleChara
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BattleNpc"/> class. /// Initializes a new instance of the <see cref="BattleNpc"/> class.
@ -21,11 +23,6 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
/// <summary> /// <summary>
/// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc. /// Gets the BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
/// </summary> /// </summary>
public BattleNpcSubKind BattleNpcKind => *(BattleNpcSubKind*)(this.Address + ActorOffsets.SubKind); public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind;
/// <summary>
/// Gets the target of the Battle NPC.
/// </summary>
public override uint TargetActorID => *(uint*)(this.Address + ActorOffsets.BattleNpcTargetActorId);
} }
} }

View file

@ -0,0 +1,23 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
namespace Dalamud.Game.ClientState.Objects.SubKinds
{
/// <summary>
/// This class represents an EventObj.
/// </summary>
public unsafe class EventObj : GameObject
{
/// <summary>
/// Initializes a new instance of the <see cref="EventObj"/> class.
/// Set up a new EventObj with the provided memory representation.
/// </summary>
/// <param name="address">The address of this event object in memory.</param>
/// <param name="dalamud">A dalamud reference.</param>
internal EventObj(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
}
}

View file

@ -1,11 +1,13 @@
using System; using System;
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer using Dalamud.Game.ClientState.Objects.Types;
namespace Dalamud.Game.ClientState.Objects.SubKinds
{ {
/// <summary> /// <summary>
/// This class represents a NPC. /// This class represents a NPC.
/// </summary> /// </summary>
public unsafe class Npc : Chara public unsafe class Npc : Character
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Npc"/> class. /// Initializes a new instance of the <see cref="Npc"/> class.
@ -18,14 +20,9 @@ namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer
{ {
} }
/// <summary>
/// Gets the data ID of the NPC linking to their assoicated BNpcBase data.
/// </summary>
public uint BaseId => *(uint*)(this.Address + ActorOffsets.DataId);
/// <summary> /// <summary>
/// Gets the name ID of the NPC linking to their respective game data. /// Gets the name ID of the NPC linking to their respective game data.
/// </summary> /// </summary>
public uint NameId => *(uint*)(this.Address + ActorOffsets.NameId); public uint NameId => this.Struct->NameID;
} }
} }

View file

@ -1,15 +1,14 @@
using System; using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
namespace Dalamud.Game.ClientState.Actors.Types namespace Dalamud.Game.ClientState.Objects.SubKinds
{ {
/// <summary> /// <summary>
/// This class represents a player character. /// This class represents a player character.
/// </summary> /// </summary>
public unsafe class PlayerCharacter : Chara public unsafe class PlayerCharacter : BattleChara
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PlayerCharacter"/> class. /// Initializes a new instance of the <see cref="PlayerCharacter"/> class.
@ -25,21 +24,16 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// <summary> /// <summary>
/// Gets the current <see cref="ExcelResolver{T}">world</see> of the character. /// Gets the current <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary> /// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(*(ushort*)(this.Address + ActorOffsets.CurrentWorld), this.Dalamud); public ExcelResolver<Lumina.Excel.GeneratedSheets.World> CurrentWorld => new(this.Struct->Character.CurrentWorld, this.Dalamud);
/// <summary> /// <summary>
/// Gets the home <see cref="ExcelResolver{T}">world</see> of the character. /// Gets the home <see cref="ExcelResolver{T}">world</see> of the character.
/// </summary> /// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(*(ushort*)(this.Address + ActorOffsets.HomeWorld), this.Dalamud); public ExcelResolver<Lumina.Excel.GeneratedSheets.World> HomeWorld => new(this.Struct->Character.HomeWorld, this.Dalamud);
/// <summary>
/// Gets the Free Company tag of this player.
/// </summary>
public SeString CompanyTag => MemoryHelper.ReadSeString(this.Address + ActorOffsets.CompanyTag, 6);
/// <summary> /// <summary>
/// Gets the target actor ID of the PlayerCharacter. /// Gets the target actor ID of the PlayerCharacter.
/// </summary> /// </summary>
public override uint TargetActorID => *(uint*)(this.Address + ActorOffsets.PlayerCharacterTargetActorId); public override uint TargetObjectId => this.Struct->Character.GameObject.TargetObjectID;
} }
} }

View file

@ -0,0 +1,169 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Objects
{
/// <summary>
/// Get and set various kinds of targets for the player.
/// </summary>
public sealed unsafe class Targets
{
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="Targets"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
}
/// <summary>
/// Gets the address of the target manager.
/// </summary>
public IntPtr Address => this.address.TargetManager;
/// <summary>
/// Gets or sets the current target.
/// </summary>
[CanBeNull]
public GameObject Target
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->Target);
set => this.SetTarget(value);
}
/// <summary>
/// Gets or sets the mouseover target.
/// </summary>
[CanBeNull]
public GameObject MouseOverTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
set => this.SetMouseOverTarget(value);
}
/// <summary>
/// Gets or sets the focus target.
/// </summary>
[CanBeNull]
public GameObject FocusTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->FocusTarget);
set => this.SetFocusTarget(value);
}
/// <summary>
/// Gets or sets the previous target.
/// </summary>
[CanBeNull]
public GameObject PreviousTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->PreviousTarget);
set => this.SetPreviousTarget(value);
}
/// <summary>
/// Gets or sets the soft target.
/// </summary>
[CanBeNull]
public GameObject SoftTarget
{
get => this.dalamud.ClientState.Objects.CreateObjectReference((IntPtr)Struct->SoftTarget);
set => this.SetSoftTarget(value);
}
private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address;
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the mouseover target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetMouseOverTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetFocusTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the previous target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetPreviousTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the soft target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetSoftTarget(GameObject actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the mouseover target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the previous target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Sets the soft target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress;
/// <summary>
/// Clears the current target.
/// </summary>
public void ClearTarget() => this.SetTarget(IntPtr.Zero);
/// <summary>
/// Clears the mouseover target.
/// </summary>
public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero);
/// <summary>
/// Clears the focus target.
/// </summary>
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
/// <summary>
/// Clears the previous target.
/// </summary>
public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero);
/// <summary>
/// Clears the soft target.
/// </summary>
public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero);
}
}

View file

@ -0,0 +1,68 @@
using System;
using Dalamud.Game.ClientState.Statuses;
namespace Dalamud.Game.ClientState.Objects.Types
{
/// <summary>
/// This class represents the battle characters.
/// </summary>
public unsafe class BattleChara : Character
{
/// <summary>
/// Initializes a new instance of the <see cref="BattleChara"/> class.
/// This represents a battle character.
/// </summary>
/// <param name="address">The address of this character in memory.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal BattleChara(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the current status effects.
/// </summary>
public StatusList StatusList => new(&this.Struct->StatusManager, this.Dalamud);
/// <summary>
/// Gets a value indicating whether the chara is currently casting.
/// </summary>
public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0;
/// <summary>
/// Gets a value indicating whether the cast is interruptible.
/// </summary>
public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0;
/// <summary>
/// Gets the spell action type of the spell being cast by the actor.
/// </summary>
public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType;
/// <summary>
/// Gets the spell action ID of the spell being cast by the actor.
/// </summary>
public uint CastActionId => this.Struct->SpellCastInfo.ActionID;
/// <summary>
/// Gets the object ID of the target currently being cast at by the chara.
/// </summary>
public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID;
/// <summary>
/// Gets the current casting time of the spell being cast by the chara.
/// </summary>
public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime;
/// <summary>
/// Gets the total casting time of the spell being cast by the chara.
/// </summary>
public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime;
/// <summary>
/// Gets the underlying structure.
/// </summary>
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address;
}
}

View file

@ -0,0 +1,103 @@
using System;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Resolvers;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Objects.Types
{
/// <summary>
/// This class represents the base for non-static entities.
/// </summary>
public unsafe class Character : GameObject
{
/// <summary>
/// Initializes a new instance of the <see cref="Character"/> class.
/// This represents a non-static entity.
/// </summary>
/// <param name="address">The address of this character in memory.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal Character(IntPtr address, Dalamud dalamud)
: base(address, dalamud)
{
}
/// <summary>
/// Gets the current HP of this Chara.
/// </summary>
public uint CurrentHp => this.Struct->Health;
/// <summary>
/// Gets the maximum HP of this Chara.
/// </summary>
public uint MaxHp => this.Struct->MaxHealth;
/// <summary>
/// Gets the current MP of this Chara.
/// </summary>
public uint CurrentMp => this.Struct->Mana;
/// <summary>
/// Gets the maximum MP of this Chara.
/// </summary>
public uint MaxMp => this.Struct->MaxMana;
/// <summary>
/// Gets the current GP of this Chara.
/// </summary>
public uint CurrentGp => this.Struct->GatheringPoints;
/// <summary>
/// Gets the maximum GP of this Chara.
/// </summary>
public uint MaxGp => this.Struct->MaxGatheringPoints;
/// <summary>
/// Gets the current CP of this Chara.
/// </summary>
public uint CurrentCp => this.Struct->CraftingPoints;
/// <summary>
/// Gets the maximum CP of this Chara.
/// </summary>
public uint MaxCp => this.Struct->MaxCraftingPoints;
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob, this.Dalamud);
/// <summary>
/// Gets the level of this Chara.
/// </summary>
public byte Level => this.Struct->Level;
/// <summary>
/// Gets a byte array describing the visual appearance of this Chara.
/// Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize => MemoryHelper.Read<byte>((IntPtr)this.Struct->CustomizeData, 28);
/// <summary>
/// Gets the Free Company tag of this chara.
/// </summary>
public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6);
/// <summary>
/// Gets the target object ID of the character.
/// </summary>
public override uint TargetObjectId => this.Struct->GameObject.TargetObjectID;
/// <summary>
/// Gets the status flags.
/// </summary>
public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags;
/// <summary>
/// Gets the underlying structure.
/// </summary>
private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address;
}
}

View file

@ -0,0 +1,168 @@
using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Memory;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Objects.Types
{
/// <summary>
/// This class represents a GameObject in FFXIV.
/// </summary>
public unsafe partial class GameObject : IEquatable<GameObject>
{
/// <summary>
/// Initializes a new instance of the <see cref="GameObject"/> class.
/// </summary>
/// <param name="address">The address of this game object in memory.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal GameObject(IntPtr address, Dalamud dalamud)
{
this.Address = address;
this.Dalamud = dalamud;
}
/// <summary>
/// Gets the address of the game object in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the Dalamud instance.
/// </summary>
private protected Dalamud Dalamud { get; }
/// <summary>
/// This allows you to <c>if (obj) {...}</c> to check for validity.
/// </summary>
/// <param name="gameObject">The actor to check.</param>
/// <returns>True or false.</returns>
public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject);
public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2)
{
// Using == results in a stack overflow.
if (gameObject1 is null || gameObject2 is null)
return Equals(gameObject1, gameObject2);
return gameObject1.Equals(gameObject2);
}
public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2);
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <param name="actor">The actor to check.</param>
/// <returns>True or false.</returns>
public static bool IsValid(GameObject? actor)
{
if (actor is null)
return false;
if (actor.Dalamud.ClientState.LocalContentId == 0)
return false;
return true;
}
/// <summary>
/// Gets a value indicating whether this actor is still valid in memory.
/// </summary>
/// <returns>True or false.</returns>
public bool IsValid() => IsValid(this);
/// <inheritdoc/>
bool IEquatable<GameObject>.Equals(GameObject other) => this.ObjectId == other?.ObjectId;
/// <inheritdoc/>
public override bool Equals(object obj) => ((IEquatable<GameObject>)this).Equals(obj as GameObject);
/// <inheritdoc/>
public override int GetHashCode() => this.ObjectId.GetHashCode();
}
/// <summary>
/// This class represents a basic actor (GameObject) in FFXIV.
/// </summary>
public unsafe partial class GameObject
{
/// <summary>
/// Gets the name of this <see cref="GameObject" />.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 32);
/// <summary>
/// Gets the object ID of this <see cref="GameObject" />.
/// </summary>
public uint ObjectId => this.Struct->ObjectID;
/// <summary>
/// Gets the data ID for linking to other respective game data.
/// </summary>
public uint DataId => this.Struct->DataID;
/// <summary>
/// Gets the ID of this GameObject's owner.
/// </summary>
public uint OwnerId => this.Struct->OwnerID;
/// <summary>
/// Gets the entity kind of this <see cref="GameObject" />.
/// See <see cref="ObjectKind">the ObjectKind enum</see> for possible values.
/// </summary>
public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind;
/// <summary>
/// Gets the sub kind of this Actor.
/// </summary>
public byte SubKind => this.Struct->SubKind;
/// <summary>
/// Gets the X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX;
/// <summary>
/// Gets the Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ;
/// <summary>
/// Gets the position of this <see cref="GameObject" />.
/// </summary>
public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z);
/// <summary>
/// Gets the rotation of this <see cref="GameObject" />.
/// This ranges from -pi to pi radians.
/// </summary>
public float Rotation => this.Struct->Rotation;
/// <summary>
/// Gets the hitbox radius of this <see cref="GameObject" />.
/// </summary>
public float HitboxRadius => this.Struct->HitboxRadius;
/// <summary>
/// Gets the current target of the game object.
/// </summary>
public virtual uint TargetObjectId => 0;
/// <summary>
/// Gets the target object of the game object.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public virtual GameObject TargetObject => this.Dalamud.ClientState.Objects.SearchByID(this.TargetObjectId);
/// <summary>
/// Gets the underlying structure.
/// </summary>
private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address;
}
}

View file

@ -0,0 +1,183 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Party
{
/// <summary>
/// This collection represents the actors present in your party or alliance.
/// </summary>
public sealed unsafe partial class PartyList
{
private const int GroupLength = 8;
private const int AllianceLength = 20;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
/// <summary>
/// Initializes a new instance of the <see cref="PartyList"/> class.
/// </summary>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
internal PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
this.address = addressResolver;
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
}
/// <summary>
/// Gets the amount of party members the local player has.
/// </summary>
public int Length => this.GroupManagerStruct->MemberCount;
/// <summary>
/// Gets the index of the party leader.
/// </summary>
public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex;
/// <summary>
/// Gets a value indicating whether this group is an alliance.
/// </summary>
public bool IsAlliance => this.GroupManagerStruct->IsAlliance;
/// <summary>
/// Gets the address of the Group Manager.
/// </summary>
public IntPtr GroupManagerAddress => this.address.GroupManager;
/// <summary>
/// Gets the address of the party list within the group manager.
/// </summary>
public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers;
/// <summary>
/// Gets the address of the alliance member list within the group manager.
/// </summary>
public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers;
private static int PartyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember>();
private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress;
/// <summary>
/// Get a party member at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns>A <see cref="PartyMember"/> at the specified spawn index.</returns>
[CanBeNull]
public PartyMember this[int index]
{
get
{
// Normally using Length results in a recursion crash, however we know the party size via ptr.
if (index < 0 || index >= this.Length)
return null;
if (this.Length > GroupLength)
{
var addr = this.GetAllianceMemberAddress(index);
return this.CreateAllianceMemberReference(addr);
}
else
{
var addr = this.GetPartyMemberAddress(index);
return this.CreatePartyMemberReference(addr);
}
}
}
/// <summary>
/// Gets the address of the party member at the specified index of the party list.
/// </summary>
/// <param name="index">The index of the party member.</param>
/// <returns>The memory address of the party member.</returns>
public IntPtr GetPartyMemberAddress(int index)
{
if (index < 0 || index >= GroupLength)
return IntPtr.Zero;
return this.GroupListAddress + (index * PartyMemberSize);
}
/// <summary>
/// Create a reference to an FFXIV party member.
/// </summary>
/// <param name="address">The address of the party member in memory.</param>
/// <returns>The party member object containing the requested data.</returns>
[CanBeNull]
public PartyMember CreatePartyMemberReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
return new PartyMember(address, this.dalamud);
}
/// <summary>
/// Gets the address of the alliance member at the specified index of the alliance list.
/// </summary>
/// <param name="index">The index of the alliance member.</param>
/// <returns>The memory address of the alliance member.</returns>
public IntPtr GetAllianceMemberAddress(int index)
{
if (index < 0 || index >= AllianceLength)
return IntPtr.Zero;
return this.AllianceListAddress + (index * PartyMemberSize);
}
/// <summary>
/// Create a reference to an FFXIV alliance member.
/// </summary>
/// <param name="address">The address of the alliance member in memory.</param>
/// <returns>The party member object containing the requested data.</returns>
[CanBeNull]
public PartyMember CreateAllianceMemberReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
return new PartyMember(address, this.dalamud);
}
}
/// <summary>
/// This collection represents the party members present in your party or alliance.
/// </summary>
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
{
/// <inheritdoc/>
int IReadOnlyCollection<PartyMember>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<PartyMember> GetEnumerator()
{
// Normally using Length results in a recursion crash, however we know the party size via ptr.
for (var i = 0; i < this.Length; i++)
{
var member = this[i];
if (member == null)
break;
yield return member;
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}

View file

@ -0,0 +1,117 @@
using System;
using System.Numerics;
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 JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Party
{
/// <summary>
/// This class represents a party member in the group manager.
/// </summary>
public unsafe class PartyMember
{
private Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary>
/// <param name="address">Address of the party member.</param>
/// <param name="dalamud">Dalamud itself.</param>
internal PartyMember(IntPtr address, Dalamud dalamud)
{
this.Address = address;
this.dalamud = dalamud;
}
/// <summary>
/// Gets the address of this party member in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets a list of buffs or debuffs applied to this party member.
/// </summary>
public StatusList Statuses => new(&this.Struct->StatusManager, this.dalamud);
/// <summary>
/// Gets the position of the party member.
/// </summary>
public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z);
/// <summary>
/// Gets the content ID of the party member.
/// </summary>
public long ContentId => this.Struct->ContentID;
/// <summary>
/// Gets the actor ID of this party member.
/// </summary>
public uint ObjectId => this.Struct->ObjectID;
/// <summary>
/// Gets the actor associated with this buddy.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public GameObject GameObject => this.dalamud.ClientState.Objects.SearchByID(this.ObjectId);
/// <summary>
/// Gets the current HP of this party member.
/// </summary>
public uint CurrentHP => this.Struct->CurrentHP;
/// <summary>
/// Gets the maximum HP of this party member.
/// </summary>
public uint MaxHP => this.Struct->MaxHP;
/// <summary>
/// Gets the current MP of this party member.
/// </summary>
public ushort CurrentMP => this.Struct->CurrentMP;
/// <summary>
/// Gets the maximum MP of this party member.
/// </summary>
public ushort MaxMP => this.Struct->MaxMP;
/// <summary>
/// Gets the territory this party member is located in.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.TerritoryType> Territory => new(this.Struct->TerritoryType, this.dalamud);
/// <summary>
/// Gets the World this party member resides in.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.World> World => new(this.Struct->HomeWorld, this.dalamud);
/// <summary>
/// Gets the displayname of this party member.
/// </summary>
public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40);
/// <summary>
/// Gets the sex of this party member.
/// </summary>
public byte Sex => this.Struct->Sex;
/// <summary>
/// Gets the classjob of this party member.
/// </summary>
public ExcelResolver<Lumina.Excel.GeneratedSheets.ClassJob> ClassJob => new(this.Struct->ClassJob, this.dalamud);
/// <summary>
/// Gets the level of this party member.
/// </summary>
public byte Level => this.Struct->Level;
private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address;
}
}

View file

@ -1,139 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
// using Dalamud.Hooking;
namespace Dalamud.Game.ClientState
{
/// <summary>
/// This class represents the members of your party.
/// </summary>
public sealed partial class PartyList
{
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
// private bool isReady = false;
// private IntPtr partyListBegin;
// private Hook<PartyListUpdateDelegate> partyListUpdateHook;
/// <summary>
/// Initializes a new instance of the <see cref="PartyList"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.dalamud = dalamud;
// this.partyListUpdateHook = new Hook<PartyListUpdateDelegate>(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
}
private delegate long PartyListUpdateDelegate(IntPtr structBegin, long param2, char param3);
/// <summary>
/// Gets the length of the PartyList.
/// </summary>
public int Length => 0; // !this.isReady ? 0 : Marshal.ReadByte(this.partyListBegin + 0xF0);
/// <summary>
/// Get the nth party member.
/// </summary>
/// <param name="index">Index of the party member.</param>
/// <returns>The party member.</returns>
public PartyMember this[int index]
{
get
{
return null;
// if (!this.isReady)
// return null;
// if (index >= this.Length)
// return null;
// var tblIndex = this.partyListBegin + (index * 24);
// var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
// return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
}
}
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
// TODO Fix for 5.3
// this.partyListUpdateHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
// if (!this.isReady)
// this.partyListUpdateHook.Dispose();
// this.isReady = false;
}
// private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
// {
// var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
// this.partyListBegin = structBegin + 0xB48;
// this.partyListUpdateHook.Dispose();
// this.isReady = true;
// return result;
// }
}
/// <summary>
/// Implements IReadOnlyCollection, IEnumerable.
/// </summary>
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
{
/// <inheritdoc/>
int IReadOnlyCollection<PartyMember>.Count => this.Length;
/// <inheritdoc/>
public IEnumerator<PartyMember> GetEnumerator()
{
for (var i = 0; i < this.Length; i++)
{
if (this[i] != null)
{
yield return this[i];
}
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
/// <summary>
/// Implements ICollection.
/// </summary>
public sealed partial class PartyList : ICollection
{
/// <inheritdoc/>
public int Count => this.Length;
/// <inheritdoc/>
public object SyncRoot => this;
/// <inheritdoc/>
public bool IsSynchronized => false;
/// <inheritdoc/>
public void CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -0,0 +1,73 @@
using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Resolvers;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Statuses
{
/// <summary>
/// This class represents a status effect an actor is afflicted by.
/// </summary>
public unsafe class Status
{
private Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="Status"/> class.
/// </summary>
/// <param name="address">Status address.</param>
/// <param name="dalamud">Dalamud instance.</param>
internal Status(IntPtr address, Dalamud dalamud)
{
this.dalamud = dalamud;
this.Address = address;
}
/// <summary>
/// Gets the address of the status in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the status ID of this status.
/// </summary>
public uint StatusID => this.Struct->StatusID;
/// <summary>
/// Gets the GameData associated with this status.
/// </summary>
public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver<Lumina.Excel.GeneratedSheets.Status>(this.Struct->StatusID, this.dalamud).GameData;
/// <summary>
/// Gets the parameter value of the status.
/// </summary>
public byte Param => this.Struct->Param;
/// <summary>
/// Gets the stack count of this status.
/// </summary>
public byte StackCount => this.Struct->StackCount;
/// <summary>
/// Gets the time remaining of this status.
/// </summary>
public float RemainingTime => this.Struct->RemainingTime;
/// <summary>
/// Gets the source ID of this status.
/// </summary>
public uint SourceID => this.Struct->SourceID;
/// <summary>
/// Gets the source actor associated with this status.
/// </summary>
/// <remarks>
/// This iterates the actor table, it should be used with care.
/// </remarks>
[CanBeNull]
public GameObject SourceActor => this.dalamud.ClientState.Objects.SearchByID(this.SourceID);
private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address;
}
}

View file

@ -0,0 +1,161 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
namespace Dalamud.Game.ClientState.Statuses
{
/// <summary>
/// This collection represents the status effects an actor is afflicted by.
/// </summary>
public sealed unsafe partial class StatusList
{
private const int StatusListLength = 30;
private readonly Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="StatusList"/> class.
/// </summary>
/// <param name="address">Address of the status list.</param>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
internal StatusList(IntPtr address, Dalamud dalamud)
{
this.Address = address;
this.dalamud = dalamud;
}
/// <summary>
/// Initializes a new instance of the <see cref="StatusList"/> class.
/// </summary>
/// <param name="pointer">Pointer to the status list.</param>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
internal unsafe StatusList(void* pointer, Dalamud dalamud)
: this((IntPtr)pointer, dalamud)
{
}
/// <summary>
/// Gets the address of the status list in memory.
/// </summary>
public IntPtr Address { get; }
/// <summary>
/// Gets the amount of status effects the actor has.
/// </summary>
public int Length
{
get
{
var i = 0;
for (; i < StatusListLength; i++)
{
var status = this[i];
if (status == null || status.StatusID == 0)
break;
}
return i;
}
}
private static int StatusSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.Status>();
private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address;
/// <summary>
/// Get a status effect at the specified index.
/// </summary>
/// <param name="index">Status Index.</param>
/// <returns>The status at the specified index.</returns>
[CanBeNull]
public Status this[int index]
{
get
{
if (index < 0 || index > StatusListLength)
return null;
var addr = this.GetStatusAddress(index);
return this.CreateStatusReference(addr);
}
}
/// <summary>
/// Gets the address of the party member at the specified index of the party list.
/// </summary>
/// <param name="index">The index of the party member.</param>
/// <returns>The memory address of the party member.</returns>
public IntPtr GetStatusAddress(int index)
{
if (index < 0 || index >= StatusListLength)
return IntPtr.Zero;
return (IntPtr)(this.Struct->Status + (index * StatusSize));
}
/// <summary>
/// Create a reference to an FFXIV actor status.
/// </summary>
/// <param name="address">The address of the status effect in memory.</param>
/// <returns>The status object containing the requested data.</returns>
[CanBeNull]
public Status CreateStatusReference(IntPtr address)
{
if (this.dalamud.ClientState.LocalContentId == 0)
return null;
if (address == IntPtr.Zero)
return null;
return new Status(address, this.dalamud);
}
}
/// <summary>
/// This collection represents the status effects an actor is afflicted by.
/// </summary>
public sealed partial class StatusList : IReadOnlyCollection<Status>, ICollection
{
/// <inheritdoc/>
int IReadOnlyCollection<Status>.Count => this.Length;
/// <inheritdoc/>
int ICollection.Count => this.Length;
/// <inheritdoc/>
bool ICollection.IsSynchronized => false;
/// <inheritdoc/>
object ICollection.SyncRoot => this;
/// <inheritdoc/>
public IEnumerator<Status> GetEnumerator()
{
for (var i = 0; i < StatusListLength; i++)
{
var status = this[i];
if (status == null || status.StatusID == 0)
continue;
yield return status;
}
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -1,36 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory AST job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct ASTGauge
{
[FieldOffset(4)]
private CardType card;
[FieldOffset(5)]
private unsafe fixed byte seals[3];
/// <summary>
/// Gets the currently drawn <see cref="CardType"/>.
/// </summary>
/// <returns>Currently drawn <see cref="CardType"/>.</returns>
public CardType DrawnCard() => this.card;
/// <summary>
/// Check if a <see cref="SealType"/> is currently active on the divination gauge.
/// </summary>
/// <param name="seal">The <see cref="SealType"/> to check for.</param>
/// <returns>If the given Seal is currently divined.</returns>
public unsafe bool ContainsSeal(SealType seal)
{
if (this.seals[0] == (byte)seal) return true;
if (this.seals[1] == (byte)seal) return true;
if (this.seals[2] == (byte)seal) return true;
return false;
}
}
}

View file

@ -1,67 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory BLM job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct BLMGauge
{
[FieldOffset(0)]
private short timeUntilNextPolyglot; // enochian timer
[FieldOffset(2)]
private short elementTimeRemaining; // umbral ice and astral fire timer
[FieldOffset(4)]
private byte elementStance; // umbral ice or astral fire
[FieldOffset(5)]
private byte numUmbralHearts;
[FieldOffset(6)]
private byte numPolyglotStacks;
[FieldOffset(7)]
private byte enochianState;
/// <summary>
/// Gets the time until the next Polyglot stack in milliseconds.
/// </summary>
public short TimeUntilNextPolyglot => this.timeUntilNextPolyglot;
/// <summary>
/// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds.
/// </summary>
public short ElementTimeRemaining => this.elementTimeRemaining;
/// <summary>
/// Gets the number of Polyglot stacks remaining.
/// </summary>
public byte NumPolyglotStacks => this.numPolyglotStacks;
/// <summary>
/// Gets the number of Umbral Hearts remaining.
/// </summary>
public byte NumUmbralHearts => this.numUmbralHearts;
/// <summary>
/// Gets if the player is in Umbral Ice.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool InUmbralIce() => this.elementStance > 4;
/// <summary>
/// Gets if the player is in Astral fire.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool InAstralFire() => this.elementStance > 0 && this.elementStance < 4;
/// <summary>
/// Gets if Enochian is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsEnoActive() => this.enochianState > 0;
}
}

View file

@ -1,43 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory BRD job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct BRDGauge
{
[FieldOffset(0)]
private short songTimer;
[FieldOffset(2)]
private byte numSongStacks;
[FieldOffset(3)]
private byte soulVoiceValue;
[FieldOffset(4)]
private CurrentSong activeSong;
/// <summary>
/// Gets the current song timer in milliseconds.
/// </summary>
public short SongTimer => this.songTimer;
/// <summary>
/// Gets the number of stacks for the current song.
/// </summary>
public byte NumSongStacks => this.numSongStacks;
/// <summary>
/// Gets the amount of Soul Voice accumulated.
/// </summary>
public byte SoulVoiceValue => this.soulVoiceValue;
/// <summary>
/// Gets the type of song that is active.
/// </summary>
public CurrentSong ActiveSong => this.activeSong;
}
}

View file

@ -1,50 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory DNC job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DNCGauge
{
[FieldOffset(0)]
private byte numFeathers;
[FieldOffset(1)]
private byte esprit;
[FieldOffset(2)]
private fixed byte stepOrder[4];
[FieldOffset(6)]
private byte numCompleteSteps;
/// <summary>
/// Gets the number of feathers available.
/// </summary>
public byte NumFeathers => this.numFeathers;
/// <summary>
/// Gets the amount of Espirit available.
/// </summary>
public byte Esprit => this.esprit;
/// <summary>
/// Gets the number of steps completed for the current dance.
/// </summary>
public byte NumCompleteSteps => this.numCompleteSteps;
/// <summary>
/// Gets the next step in the current dance.
/// </summary>
/// <returns>The next dance step action ID.</returns>
public ulong NextStep() => (ulong)(15999 + this.stepOrder[this.NumCompleteSteps] - 1);
/// <summary>
/// Gets if the player is dancing or not.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsDancing() => this.stepOrder[0] != 0;
}
}

View file

@ -1,35 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory DRG job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct DRGGauge
{
[FieldOffset(0)]
private short botdTimer;
[FieldOffset(2)]
private BOTDState botdState;
[FieldOffset(3)]
private byte eyeCount;
/// <summary>
/// Gets the time remaining for Blood of the Dragon in milliseconds.
/// </summary>
public short BOTDTimer => this.botdTimer;
/// <summary>
/// Gets the current state of Blood of the Dragon.
/// </summary>
public BOTDState BOTDState => this.botdState;
/// <summary>
/// Gets the count of eyes opened during Blood of the Dragon.
/// </summary>
public byte EyeCount => this.eyeCount;
}
}

View file

@ -1,44 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory DRK job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct DRKGauge
{
[FieldOffset(0)]
private byte blood;
[FieldOffset(2)]
private ushort darksideTimeRemaining;
[FieldOffset(4)]
private byte darkArtsState;
[FieldOffset(6)]
private ushort shadowTimeRemaining;
/// <summary>
/// Gets the amount of blood accumulated.
/// </summary>
public byte Blood => this.blood;
/// <summary>
/// Gets the Darkside time remaining in milliseconds.
/// </summary>
public ushort DarksideTimeRemaining => this.darksideTimeRemaining;
/// <summary>
/// Gets the Shadow time remaining in milliseconds.
/// </summary>
public ushort ShadowTimeRemaining => this.shadowTimeRemaining;
/// <summary>
/// Gets if the player has Dark Arts or not.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasDarkArts() => this.darkArtsState > 0;
}
}

View file

@ -1,35 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory GNB job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct GNBGauge
{
[FieldOffset(0)]
private byte numAmmo;
[FieldOffset(2)]
private short maxTimerDuration;
[FieldOffset(4)]
private byte ammoComboStepNumber;
/// <summary>
/// Gets the amount of ammo available.
/// </summary>
public byte NumAmmo => this.numAmmo;
/// <summary>
/// Gets the max combo time of the Gnashing Fang combo.
/// </summary>
public short MaxTimerDuration => this.maxTimerDuration;
/// <summary>
/// Gets the current step of the Gnashing Fang combo.
/// </summary>
public byte AmmoComboStepNumber => this.ammoComboStepNumber;
}
}

View file

@ -1,276 +0,0 @@
using System;
#pragma warning disable SA1402 // File may only contain a single type
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
#region AST
/// <summary>
/// AST Divination seal types.
/// </summary>
public enum SealType : byte
{
/// <summary>
/// No seal.
/// </summary>
NONE = 0,
/// <summary>
/// Sun seal.
/// </summary>
SUN = 1,
/// <summary>
/// Moon seal.
/// </summary>
MOON = 2,
/// <summary>
/// Celestial seal.
/// </summary>
CELESTIAL = 3,
}
/// <summary>
/// AST Arcanum (card) types.
/// </summary>
public enum CardType : byte
{
/// <summary>
/// No card.
/// </summary>
NONE = 0,
/// <summary>
/// The Balance card.
/// </summary>
BALANCE = 1,
/// <summary>
/// The Bole card.
/// </summary>
BOLE = 2,
/// <summary>
/// The Arrow card.
/// </summary>
ARROW = 3,
/// <summary>
/// The Spear card.
/// </summary>
SPEAR = 4,
/// <summary>
/// The Ewer card.
/// </summary>
EWER = 5,
/// <summary>
/// The Spire card.
/// </summary>
SPIRE = 6,
/// <summary>
/// The Lord of Crowns card.
/// </summary>
LORD = 0x70,
/// <summary>
/// The Lady of Crowns card.
/// </summary>
LADY = 0x80,
}
#endregion
#region BRD
/// <summary>
/// BRD Current Song types.
/// </summary>
public enum CurrentSong : byte
{
/// <summary>
/// No song is active type.
/// </summary>
NONE = 0,
/// <summary>
/// Mage's Ballad type.
/// </summary>
MAGE = 5,
/// <summary>
/// Army's Paeon type.
/// </summary>
ARMY = 0xA,
/// <summary>
/// The Wanderer's Minuet type.
/// </summary>
WANDERER = 0xF,
}
#endregion
#region DRG
/// <summary>
/// DRG Blood of the Dragon state types.
/// </summary>
public enum BOTDState : byte
{
/// <summary>
/// Inactive type.
/// </summary>
NONE = 0,
/// <summary>
/// Blood of the Dragon is active.
/// </summary>
BOTD = 1,
/// <summary>
/// Life of the Dragon is active.
/// </summary>
LOTD = 2,
}
#endregion
#region NIN
/// <summary>
/// NIN Mudra types.
/// </summary>
public enum Mudras : byte
{
/// <summary>
/// Ten mudra.
/// </summary>
TEN = 1,
/// <summary>
/// Chi mudra.
/// </summary>
CHI = 2,
/// <summary>
/// Jin mudra.
/// </summary>
JIN = 3,
}
#endregion
#region SAM
/// <summary>
/// Samurai Sen types.
/// </summary>
[Flags]
public enum Sen : byte
{
/// <summary>
/// No Sen.
/// </summary>
NONE = 0,
/// <summary>
/// Setsu Sen type.
/// </summary>
SETSU = 1 << 0,
/// <summary>
/// Getsu Sen type.
/// </summary>
GETSU = 1 << 1,
/// <summary>
/// Ka Sen type.
/// </summary>
KA = 1 << 2,
}
#endregion
#region SCH
/// <summary>
/// SCH Dismissed fairy types.
/// </summary>
public enum DismissedFairy : byte
{
/// <summary>
/// Dismissed fairy is Eos.
/// </summary>
EOS = 6,
/// <summary>
/// Dismissed fairy is Selene.
/// </summary>
SELENE = 7,
}
#endregion
#region SMN
/// <summary>
/// SMN summoned pet types.
/// </summary>
public enum SummonPet : byte
{
/// <summary>
/// No pet.
/// </summary>
NONE = 0,
/// <summary>
/// The summoned pet Ifrit.
/// </summary>
IFRIT = 3,
/// <summary>
/// The summoned pet Titan.
/// </summary>
TITAN = 4,
/// <summary>
/// The summoned pet Garuda.
/// </summary>
GARUDA = 5,
}
/// <summary>
/// SMN summoned pet glam types.
/// </summary>
public enum PetGlam : byte
{
/// <summary>
/// No pet glam.
/// </summary>
NONE = 0,
/// <summary>
/// Emerald carbuncle pet glam.
/// </summary>
EMERALD = 1,
/// <summary>
/// Topaz carbuncle pet glam.
/// </summary>
TOPAZ = 2,
/// <summary>
/// Ruby carbuncle pet glam.
/// </summary>
RUBY = 3,
}
#endregion
}
#pragma warning restore SA1402 // File may only contain a single type

View file

@ -1,66 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory MCH job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct MCHGauge
{
[FieldOffset(0)]
private short overheatTimeRemaining;
[FieldOffset(2)]
private short robotTimeRemaining;
[FieldOffset(4)]
private byte heat;
[FieldOffset(5)]
private byte battery;
[FieldOffset(6)]
private byte lastRobotBatteryPower;
[FieldOffset(7)]
private byte timerActive;
/// <summary>
/// Gets the time time remaining for Overheat in milliseconds.
/// </summary>
public short OverheatTimeRemaining => this.overheatTimeRemaining;
/// <summary>
/// Gets the time remaining for the Rook or Queen in milliseconds.
/// </summary>
public short RobotTimeRemaining => this.robotTimeRemaining;
/// <summary>
/// Gets the current Heat level.
/// </summary>
public byte Heat => this.heat;
/// <summary>
/// Gets the current Battery level.
/// </summary>
public byte Battery => this.battery;
/// <summary>
/// Gets the battery level of the last Robot.
/// </summary>
public byte LastRobotBatteryPower => this.lastRobotBatteryPower;
/// <summary>
/// Gets if the player is currently Overheated.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsOverheated() => (this.timerActive & 1) != 0;
/// <summary>
/// Gets if the player has an active Robot.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsRobotActive() => (this.timerActive & 2) != 0;
}
}

View file

@ -1,19 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory MNK job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct MNKGauge
{
[FieldOffset(0)]
private byte numChakra;
/// <summary>
/// Gets the number of Chakra available.
/// </summary>
public byte NumChakra => this.numChakra;
}
}

View file

@ -1,36 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory NIN job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct NINGauge
{
[FieldOffset(0)]
private int hutonTimeLeft;
[FieldOffset(4)]
private byte ninki;
[FieldOffset(5)]
private byte numHutonManualCasts;
/// <summary>
/// Gets the time left on Huton in milliseconds.
/// </summary>
public int HutonTimeLeft => this.hutonTimeLeft;
/// <summary>
/// Gets the amount of Ninki available.
/// </summary>
public byte Ninki => this.ninki;
/// <summary>
/// Gets the number of times Huton has been cast manually.
/// </summary>
public byte NumHutonManualCasts => this.numHutonManualCasts;
}
}

View file

@ -1,19 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory PLD job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct PLDGauge
{
[FieldOffset(0)]
private byte gaugeAmount;
/// <summary>
/// Gets the current level of the Oath gauge.
/// </summary>
public byte GaugeAmount => this.gaugeAmount;
}
}

View file

@ -1,27 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory RDM job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct RDMGauge
{
[FieldOffset(0)]
private byte whiteGauge;
[FieldOffset(1)]
private byte blackGauge;
/// <summary>
/// Gets the level of the White gauge.
/// </summary>
public byte WhiteGauge => this.whiteGauge;
/// <summary>
/// Gets the level of the Black gauge.
/// </summary>
public byte BlackGauge => this.blackGauge;
}
}

View file

@ -1,53 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory SAM job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct SAMGauge
{
[FieldOffset(3)]
private byte kenki;
[FieldOffset(4)]
private byte meditationStacks;
[FieldOffset(5)]
private Sen sen;
/// <summary>
/// Gets the current amount of Kenki available.
/// </summary>
public byte Kenki => this.kenki;
/// <summary>
/// Gets the amount of Meditation stacks.
/// </summary>
public byte MeditationStacks => this.meditationStacks;
/// <summary>
/// Gets the active Sen.
/// </summary>
public Sen Sen => this.sen;
/// <summary>
/// Gets if the Setsu Sen is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasSetsu() => (this.Sen & Sen.SETSU) != 0;
/// <summary>
/// Gets if the Getsu Sen is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasGetsu() => (this.Sen & Sen.GETSU) != 0;
/// <summary>
/// Gets if the Ka Sen is active.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasKa() => (this.Sen & Sen.KA) != 0;
}
}

View file

@ -1,43 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory SCH job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct SCHGauge
{
[FieldOffset(2)]
private byte numAetherflowStacks;
[FieldOffset(3)]
private byte fairyGaugeAmount;
[FieldOffset(4)]
private short seraphTimer;
[FieldOffset(6)]
private DismissedFairy dismissedFairy;
/// <summary>
/// Gets the amount of Aetherflow stacks available.
/// </summary>
public byte NumAetherflowStacks => this.numAetherflowStacks;
/// <summary>
/// Gets the current level of the Fairy Gauge.
/// </summary>
public byte FairyGaugeAmount => this.fairyGaugeAmount;
/// <summary>
/// Gets the Seraph time remaining in milliseconds.
/// </summary>
public short SeraphTimer => this.seraphTimer;
/// <summary>
/// Gets the last dismissed fairy.
/// </summary>
public DismissedFairy DismissedFairy => this.dismissedFairy;
}
}

View file

@ -1,62 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory SMN job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct SMNGauge
{
[FieldOffset(0)]
private short timerRemaining;
[FieldOffset(2)]
private SummonPet returnSummon;
[FieldOffset(3)]
private PetGlam returnSummonGlam;
[FieldOffset(4)]
private byte numStacks;
/// <summary>
/// Gets the time remaining for the current summon.
/// </summary>
public short TimerRemaining => this.timerRemaining;
/// <summary>
/// Gets the summon that will return after the current summon expires.
/// </summary>
public SummonPet ReturnSummon => this.returnSummon;
/// <summary>
/// Gets the summon glam for the <see cref="ReturnSummon"/>.
/// </summary>
public PetGlam ReturnSummonGlam => this.returnSummonGlam;
/// <summary>
/// Gets the current stacks.
/// Use the summon accessors instead.
/// </summary>
public byte NumStacks => this.numStacks;
/// <summary>
/// Gets if Phoenix is ready to be summoned.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsPhoenixReady() => (this.NumStacks & 0x10) > 0;
/// <summary>
/// Gets if Bahamut is ready to be summoned.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool IsBahamutReady() => (this.NumStacks & 8) > 0;
/// <summary>
/// Gets if there are any Aetherflow stacks available.
/// </summary>
/// <returns><c>true</c> or <c>false</c>.</returns>
public bool HasAetherflowStacks() => (this.NumStacks & 3) > 0;
}
}

View file

@ -1,19 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory WAR job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct WARGauge
{
[FieldOffset(0)]
private byte beastGaugeAmount;
/// <summary>
/// Gets the amount of wrath in the Beast gauge.
/// </summary>
public byte BeastGaugeAmount => this.beastGaugeAmount;
}
}

View file

@ -1,35 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
/// <summary>
/// In-memory WHM job gauge.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct WHMGauge
{
[FieldOffset(2)]
private short lilyTimer;
[FieldOffset(4)]
private byte numLilies;
[FieldOffset(5)]
private byte numBloodLily;
/// <summary>
/// Gets the time to next lily in milliseconds.
/// </summary>
public short LilyTimer => this.lilyTimer;
/// <summary>
/// Gets the number of Lilies.
/// </summary>
public byte NumLilies => this.numLilies;
/// <summary>
/// Gets the number of times the blood lily has been nourished.
/// </summary>
public byte NumBloodLily => this.numBloodLily;
}
}

View file

@ -1,26 +0,0 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
namespace Dalamud.Game.ClientState.Structs
{
/// <summary>
/// This represents a native PartyMember class in memory.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct PartyMember
{
[FieldOffset(0x0)]
public IntPtr namePtr;
[FieldOffset(0x8)]
public long unknown;
[FieldOffset(0x10)]
public int actorId;
[FieldOffset(0x14)]
public ObjectKind objectKind;
}
}

View file

@ -47,7 +47,7 @@ namespace Dalamud.Game.Command
break; break;
} }
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += this.OnCheckMessageHandled; dalamud.Framework.Gui.Chat.CheckMessageHandled += this.OnCheckMessageHandled;
} }
/// <summary> /// <summary>

View file

@ -50,7 +50,7 @@ namespace Dalamud.Game.Gui
} }
/// <summary> /// <summary>
/// A delegate type used with the <see cref="OnChatMessage"/> event. /// A delegate type used with the <see cref="ChatGui.ChatMessage"/> event.
/// </summary> /// </summary>
/// <param name="type">The type of chat.</param> /// <param name="type">The type of chat.</param>
/// <param name="senderId">The sender ID.</param> /// <param name="senderId">The sender ID.</param>
@ -60,7 +60,7 @@ namespace Dalamud.Game.Gui
public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
/// <summary> /// <summary>
/// A delegate type used with the <see cref="OnCheckMessageHandled"/> event. /// A delegate type used with the <see cref="ChatGui.CheckMessageHandled"/> event.
/// </summary> /// </summary>
/// <param name="type">The type of chat.</param> /// <param name="type">The type of chat.</param>
/// <param name="senderId">The sender ID.</param> /// <param name="senderId">The sender ID.</param>
@ -70,7 +70,7 @@ namespace Dalamud.Game.Gui
public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled);
/// <summary> /// <summary>
/// A delegate type used with the <see cref="OnChatMessageHandled"/> event. /// A delegate type used with the <see cref="ChatGui.ChatMessageHandled"/> event.
/// </summary> /// </summary>
/// <param name="type">The type of chat.</param> /// <param name="type">The type of chat.</param>
/// <param name="senderId">The sender ID.</param> /// <param name="senderId">The sender ID.</param>
@ -79,7 +79,7 @@ namespace Dalamud.Game.Gui
public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message);
/// <summary> /// <summary>
/// A delegate type used with the <see cref="OnChatMessageUnhandled"/> event. /// A delegate type used with the <see cref="ChatGui.ChatMessageUnhandled"/> event.
/// </summary> /// </summary>
/// <param name="type">The type of chat.</param> /// <param name="type">The type of chat.</param>
/// <param name="senderId">The sender ID.</param> /// <param name="senderId">The sender ID.</param>
@ -99,22 +99,22 @@ namespace Dalamud.Game.Gui
/// <summary> /// <summary>
/// Event that will be fired when a chat message is sent to chat by the game. /// Event that will be fired when a chat message is sent to chat by the game.
/// </summary> /// </summary>
public event OnMessageDelegate OnChatMessage; public event OnMessageDelegate ChatMessage;
/// <summary> /// <summary>
/// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true. /// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true.
/// </summary> /// </summary>
public event OnCheckMessageHandledDelegate OnCheckMessageHandled; public event OnCheckMessageHandledDelegate CheckMessageHandled;
/// <summary> /// <summary>
/// Event that will be fired when a chat message is handled by Dalamud or a Plugin. /// Event that will be fired when a chat message is handled by Dalamud or a Plugin.
/// </summary> /// </summary>
public event OnMessageHandledDelegate OnChatMessageHandled; public event OnMessageHandledDelegate ChatMessageHandled;
/// <summary> /// <summary>
/// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin.
/// </summary> /// </summary>
public event OnMessageUnhandledDelegate OnChatMessageUnhandled; public event OnMessageUnhandledDelegate ChatMessageUnhandled;
/// <summary> /// <summary>
/// Gets the ID of the last linked item. /// Gets the ID of the last linked item.
@ -365,11 +365,11 @@ namespace Dalamud.Game.Gui
// Call events // Call events
var isHandled = false; var isHandled = false;
this.OnCheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); this.CheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
if (!isHandled) if (!isHandled)
{ {
this.OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); this.ChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled);
} }
var newEdited = parsedMessage.Encode(); var newEdited = parsedMessage.Encode();
@ -395,12 +395,12 @@ namespace Dalamud.Game.Gui
// Print the original chat if it's handled. // Print the original chat if it's handled.
if (isHandled) if (isHandled)
{ {
this.OnChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
} }
else else
{ {
retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter); retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter);
this.OnChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage);
} }
if (this.baseAddress == IntPtr.Zero) if (this.baseAddress == IntPtr.Zero)

View file

@ -8,6 +8,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Utility; using Dalamud.Utility;
using ImGuiNET;
using Serilog; using Serilog;
namespace Dalamud.Game.Gui namespace Dalamud.Game.Gui
@ -32,6 +33,7 @@ namespace Dalamud.Game.Gui
private readonly Hook<HandleItemOutDelegate> handleItemOutHook; private readonly Hook<HandleItemOutDelegate> handleItemOutHook;
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook; private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook;
private readonly Hook<HandleActionOutDelegate> handleActionOutHook; private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
private readonly Hook<HandleImmDelegate> handleImmHook;
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook; private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
private GetUIMapObjectDelegate getUIMapObject; private GetUIMapObjectDelegate getUIMapObject;
@ -57,6 +59,7 @@ namespace Dalamud.Game.Gui
Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}"); Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}");
Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}"); Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}");
Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}"); Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
Log.Verbose($"GetUIObject address 0x{this.address.GetUIObject.ToInt64():X}"); Log.Verbose($"GetUIObject address 0x{this.address.GetUIObject.ToInt64():X}");
Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}"); Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}");
@ -66,13 +69,15 @@ namespace Dalamud.Game.Gui
this.FlyText = new FlyTextGui(scanner, dalamud); this.FlyText = new FlyTextGui(scanner, dalamud);
this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
this.handleItemOutHook = new Hook<HandleItemOutDelegate>(this.address.HandleItemOut, this.HandleItemOutDetour); this.handleItemOutHook = new Hook<HandleItemOutDelegate>(this.address.HandleItemOut, this.HandleItemOutDetour);
this.handleActionHoverHook = new Hook<HandleActionHoverDelegate>(this.address.HandleActionHover, this.HandleActionHoverDetour); this.handleActionHoverHook = new Hook<HandleActionHoverDelegate>(this.address.HandleActionHover, this.HandleActionHoverDetour);
this.handleActionOutHook = new Hook<HandleActionOutDelegate>(this.address.HandleActionOut, this.HandleActionOutDetour); this.handleActionOutHook = new Hook<HandleActionOutDelegate>(this.address.HandleActionOut, this.HandleActionOutDetour);
this.handleImmHook = new Hook<HandleImmDelegate>(this.address.HandleImm, this.HandleImmDetour);
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(this.address.GetUIObject); this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(this.address.GetUIObject);
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton); this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton);
@ -136,6 +141,9 @@ namespace Dalamud.Game.Gui
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4); 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)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte); private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
@ -285,23 +293,6 @@ namespace Dalamud.Game.Gui
return result; return result;
} }
/// <summary>
/// Converts in-world coordinates to screen coordinates (upper left corner origin).
/// </summary>
/// <param name="worldPos">Coordinates in the world.</param>
/// <param name="screenPos">Converted coordinates.</param>
/// <returns>True if worldPos corresponds to a position in front of the camera.</returns>
/// <remarks>
/// This overload requires a conversion to SharpDX vectors, however the penalty should be negligible.
/// </remarks>
public bool WorldToScreen(Position3 worldPos, out Vector2 screenPos)
{
// This overload is necessary due to Positon3 implicit operators.
var result = this.WorldToScreen((SharpDX.Vector3)worldPos, out var sharpScreenPos);
screenPos = sharpScreenPos.ToSystem();
return result;
}
/// <summary> /// <summary>
/// Converts screen coordinates to in-world coordinates via raycasting. /// Converts screen coordinates to in-world coordinates via raycasting.
/// </summary> /// </summary>
@ -518,6 +509,7 @@ namespace Dalamud.Game.Gui
this.setGlobalBgmHook.Enable(); this.setGlobalBgmHook.Enable();
this.handleItemHoverHook.Enable(); this.handleItemHoverHook.Enable();
this.handleItemOutHook.Enable(); this.handleItemOutHook.Enable();
this.handleImmHook.Enable();
this.toggleUiHideHook.Enable(); this.toggleUiHideHook.Enable();
this.handleActionHoverHook.Enable(); this.handleActionHoverHook.Enable();
this.handleActionOutHook.Enable(); this.handleActionOutHook.Enable();
@ -535,6 +527,7 @@ namespace Dalamud.Game.Gui
this.setGlobalBgmHook.Dispose(); this.setGlobalBgmHook.Dispose();
this.handleItemHoverHook.Dispose(); this.handleItemHoverHook.Dispose();
this.handleItemOutHook.Dispose(); this.handleItemOutHook.Dispose();
this.handleImmHook.Dispose();
this.toggleUiHideHook.Dispose(); this.toggleUiHideHook.Dispose();
this.handleActionHoverHook.Dispose(); this.handleActionHoverHook.Dispose();
this.handleActionOutHook.Dispose(); this.handleActionOutHook.Dispose();
@ -666,5 +659,13 @@ namespace Dalamud.Game.Gui
return this.toggleUiHideHook.Original(thisPtr, unknownByte); return this.toggleUiHideHook.Original(thisPtr, unknownByte);
} }
private char HandleImmDetour(IntPtr framework, char a2, byte a3)
{
var result = this.handleImmHook.Original(framework, a2, a3);
return ImGui.GetIO().WantTextInput
? (char)0
: result;
}
} }
} }

View file

@ -52,6 +52,11 @@ namespace Dalamud.Game.Gui
/// </summary> /// </summary>
public IntPtr HandleActionOut { get; private set; } public IntPtr HandleActionOut { get; private set; }
/// <summary>
/// Gets the address of the native HandleImm method.
/// </summary>
public IntPtr HandleImm { get; private set; }
/// <summary> /// <summary>
/// Gets the address of the native GetUIObject method. /// Gets the address of the native GetUIObject method.
/// </summary> /// </summary>
@ -100,6 +105,7 @@ namespace Dalamud.Game.Gui
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D"); 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.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.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.GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9"); this.GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??"); this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1"); this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");

View file

@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Logging.Internal;
using ImGuiNET;
using static Dalamud.NativeFunctions;
namespace Dalamud.Game.Gui.Internal
{
/// <summary>
/// This class handles IME for non-English users.
/// </summary>
internal class DalamudIME : IDisposable
{
private static readonly ModuleLog Log = new("IME");
private readonly Dalamud dalamud;
private IntPtr interfaceHandle;
private IntPtr wndProcPtr;
private IntPtr oldWndProcPtr;
private WndProcDelegate wndProcDelegate;
/// <summary>
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
internal DalamudIME(Dalamud dalamud)
{
this.dalamud = dalamud;
}
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
/// <summary>
/// Gets a value indicating whether the module is enabled.
/// </summary>
internal bool IsEnabled { get; private set; }
/// <summary>
/// Gets the imm candidates.
/// </summary>
internal List<string> ImmCand { get; private set; } = new();
/// <summary>
/// Gets the imm component.
/// </summary>
internal string ImmComp { get; private set; } = string.Empty;
/// <inheritdoc/>
public void Dispose()
{
if (this.oldWndProcPtr != IntPtr.Zero)
{
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
this.oldWndProcPtr = IntPtr.Zero;
}
}
/// <summary>
/// Enables the IME module.
/// </summary>
internal void Enable()
{
try
{
this.wndProcDelegate = this.WndProcDetour;
this.interfaceHandle = this.dalamud.InterfaceManager.WindowHandlePtr;
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);
this.IsEnabled = true;
Log.Information("Enabled!");
}
catch (Exception ex)
{
Log.Information(ex, "Enable failed");
}
}
private void ToggleWindow(bool visible)
{
if (visible)
this.dalamud.DalamudUi.OpenIMEWindow();
else
this.dalamud.DalamudUi.CloseIMEWindow();
}
private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
{
try
{
if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
{
var io = ImGui.GetIO();
var wmsg = (WindowsMessage)msg;
switch (wmsg)
{
case WindowsMessage.WM_IME_NOTIFY:
switch ((IMECommand)wParam)
{
case IMECommand.ChangeCandidate:
this.ToggleWindow(true);
if (hWnd == IntPtr.Zero)
return 0;
var hIMC = ImmGetContext(hWnd);
if (hIMC == IntPtr.Zero)
return 0;
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
if (size > 0)
{
var candlistPtr = Marshal.AllocHGlobal((int)size);
size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size);
var candlist = Marshal.PtrToStructure<CandidateList>(candlistPtr);
var pageSize = candlist.PageSize;
var candCount = candlist.Count;
if (pageSize > 0 && candCount > 1)
{
var dwOffsets = new int[candCount];
for (var i = 0; i < candCount; i++)
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
var pageStart = candlist.PageStart;
// var pageEnd = pageStart + pageSize;
var cand = new string[pageSize];
this.ImmCand.Clear();
for (var i = 0; i < pageSize; i++)
{
var offStart = dwOffsets[i + pageStart];
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
var pStrStart = candlistPtr + (int)offStart;
var pStrEnd = candlistPtr + (int)offEnd;
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
if (len > 0)
{
var candBytes = new byte[len];
Marshal.Copy(pStrStart, candBytes, 0, len);
var candStr = Encoding.Unicode.GetString(candBytes);
cand[i] = candStr;
this.ImmCand.Add(candStr);
}
}
}
Marshal.FreeHGlobal(candlistPtr);
}
break;
case IMECommand.OpenCandidate:
this.ToggleWindow(true);
this.ImmCand.Clear();
break;
case IMECommand.CloseCandidate:
this.ToggleWindow(false);
this.ImmCand.Clear();
break;
default:
break;
}
break;
case WindowsMessage.WM_IME_COMPOSITION:
if ((lParam & (long)IMEComposition.ResultStr) > 0)
{
var hIMC = ImmGetContext(hWnd);
if (hIMC == IntPtr.Zero)
return 0;
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
var bytes = new byte[dwSize];
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
Marshal.FreeHGlobal(unmanagedPointer);
var lpstr = Encoding.Unicode.GetString(bytes);
io.AddInputCharactersUTF8(lpstr);
this.ImmComp = string.Empty;
this.ImmCand.Clear();
this.ToggleWindow(false);
}
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0)
{
var hIMC = ImmGetContext(hWnd);
if (hIMC == IntPtr.Zero)
return 0;
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
var bytes = new byte[dwSize];
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
Marshal.FreeHGlobal(unmanagedPointer);
var lpstr = Encoding.Unicode.GetString(bytes);
this.ImmComp = lpstr;
if (lpstr == string.Empty)
this.ToggleWindow(false);
}
break;
default:
break;
}
}
}
catch (Exception ex)
{
Log.Error(ex, "Prevented a crash in an IME hook");
}
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
}
}
}

View file

@ -85,17 +85,17 @@ namespace Dalamud.Game.Gui
/// <summary> /// <summary>
/// Event that will be fired when a toast is sent by the game or a plugin. /// Event that will be fired when a toast is sent by the game or a plugin.
/// </summary> /// </summary>
public event OnNormalToastDelegate OnToast; public event OnNormalToastDelegate Toast;
/// <summary> /// <summary>
/// Event that will be fired when a quest toast is sent by the game or a plugin. /// Event that will be fired when a quest toast is sent by the game or a plugin.
/// </summary> /// </summary>
public event OnQuestToastDelegate OnQuestToast; public event OnQuestToastDelegate QuestToast;
/// <summary> /// <summary>
/// Event that will be fired when an error toast is sent by the game or a plugin. /// Event that will be fired when an error toast is sent by the game or a plugin.
/// </summary> /// </summary>
public event OnErrorToastDelegate OnErrorToast; public event OnErrorToastDelegate ErrorToast;
#endregion #endregion
@ -231,7 +231,7 @@ namespace Dalamud.Game.Gui
Speed = (ToastSpeed)isFast, Speed = (ToastSpeed)isFast,
}; };
this.OnToast?.Invoke(ref str, ref options, ref isHandled); this.Toast?.Invoke(ref str, ref options, ref isHandled);
// do nothing if handled // do nothing if handled
if (isHandled) if (isHandled)
@ -323,7 +323,7 @@ namespace Dalamud.Game.Gui
PlaySound = playSound == 1, PlaySound = playSound == 1,
}; };
this.OnQuestToast?.Invoke(ref str, ref options, ref isHandled); this.QuestToast?.Invoke(ref str, ref options, ref isHandled);
// do nothing if handled // do nothing if handled
if (isHandled) if (isHandled)
@ -409,7 +409,7 @@ namespace Dalamud.Game.Gui
var isHandled = false; var isHandled = false;
var str = this.ParseString(text); var str = this.ParseString(text);
this.OnErrorToast?.Invoke(ref str, ref isHandled); this.ErrorToast?.Invoke(ref str, ref isHandled);
// do nothing if handled // do nothing if handled
if (isHandled) if (isHandled)

View file

@ -1,51 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game
{
/// <summary>
/// A game native equivalent of a Vector3.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Position3
{
/// <summary>
/// The X of (X,Z,Y).
/// </summary>
public float X;
/// <summary>
/// The Z of (X,Z,Y).
/// </summary>
public float Z;
/// <summary>
/// The Y of (X,Z,Y).
/// </summary>
public float Y;
/// <summary>
/// Initializes a new instance of the <see cref="Position3"/> struct.
/// </summary>
/// <param name="x">The X position.</param>
/// <param name="z">The Z position.</param>
/// <param name="y">The Y position.</param>
public Position3(float x, float z, float y)
{
this.X = x;
this.Z = z;
this.Y = y;
}
/// <summary>
/// Convert this Position3 to a System.Numerics.Vector3.
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator System.Numerics.Vector3(Position3 pos) => new(pos.X, pos.Y, pos.Z);
/// <summary>
/// Convert this Position3 to a SharpDX.Vector3.
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator SharpDX.Vector3(Position3 pos) => new(pos.X, pos.Z, pos.Y);
}
}

View file

@ -72,6 +72,12 @@ namespace Dalamud.Game.Text.SeStringHandling.Payloads
} }
} }
/// <summary>
/// Gets the raw item ID of this payload.
/// </summary>
[JsonIgnore]
public uint ItemId => this.itemId;
/// <summary> /// <summary>
/// Gets the underlying Lumina Item represented by this payload. /// Gets the underlying Lumina Item represented by this payload.
/// </summary> /// </summary>

View file

@ -14,70 +14,3 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] [assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:Single-line comment should be preceded by blank line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")] [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1127:Generic type constraints should be on their own line", Justification = "I like this better", Scope = "namespaceanddescendants", Target = "~N:Dalamud")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't do those yet")]
// Extensions
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Interface.FontAwesomeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Interface.FontAwesomeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlagsExt")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlagsExt")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group extensions with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Enum followed by an extension", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group attributes with the relevant class", Scope = "type", Target = "~T:Dalamud.Game.Text.XivChatTypeInfoAttribute")]
// DalamudStartInfo.cs
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Interop", Scope = "type", Target = "~T:Dalamud.DalamudStartInfo")]
// PartyFinder
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Explicit struct layout", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Packet")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Explicit struct layout", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Listing")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Packet")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.PartyFinder.Listing")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "TODO", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.PartyMember")]
// <type>Offsets.cs
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:Static elements should appear before instance elements", Justification = "Offset classes goto the end of file.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group offset classes with the relevant class.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Group offset classes with the relevant class.", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AddonOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.TargetOffsets")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the offset usage instead.", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.AddonOffsets")]
// Breaking api changes: these should be split into a PartyFinder subdirectory
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.SearchAreaFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.JobFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.ObjectiveFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.ConditionFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.DutyFinderSettingsFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.LootRuleFlags")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.Category")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.Structs.DutyType")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.Internal.Gui.PartyFinderListingEventArgs")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Structs.PartyMember")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "breaking api change", Scope = "member", Target = "~E:Dalamud.Game.Internal.Gui.PartyFinderGui.ReceiveListing")]
// Breaking api changes
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.BaseAddressResolver.DebugScannedValues")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.Addon.Addon.Address")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.Addon.Addon.addonStruct")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Gui.GameGui.GetBaseUIObject")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.DataResolver")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "type", Target = "~T:Dalamud.Game.ClientState.Actors.Types.PartyMember")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.START_BYTE")]
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Text.SeStringHandling.Payload.END_BYTE")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Unused, but eventually, maybe.", Scope = "member", Target = "~F:Dalamud.Game.ClientState.PartyList.address")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:Static elements should appear before instance elements", Justification = "breaking api change, move to util", Scope = "type", Target = "~T:Dalamud.Game.Text.EnumExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "breaking api change, move to util", Scope = "type", Target = "~T:Dalamud.Game.Text.EnumExtensions")]
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")]
[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Framework.StatsHistory")]
[assembly: SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly", Justification = "Appears to be a bug, it is being used correctly", Scope = "member", Target = "~M:Dalamud.Data.DataManager.Initialize(System.String)")]
// I mostly didnt care to do these.
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change", Scope = "member", Target = "~F:Dalamud.Game.Internal.Network.GameNetwork.OnNetworkMessage")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardCurrentOfferings")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardCurrentOfferings")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardHistory")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketBoardHistory")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketTaxRates")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.Structures.MarketTaxRates")]
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.MarketBoardItemRequest")]
[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "breaking api change.", Scope = "type", Target = "~T:Dalamud.Game.Network.MarketBoardItemRequest")]

View file

@ -84,6 +84,12 @@ namespace Dalamud.Interface.Internal
ShowInHelp = false, ShowInHelp = false,
}); });
this.dalamud.CommandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel)
{
HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"),
ShowInHelp = false,
});
this.dalamud.CommandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog) this.dalamud.CommandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog)
{ {
HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"), HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"),
@ -245,6 +251,11 @@ namespace Dalamud.Interface.Internal
this.dalamud.DalamudUi.OpenDataWindow(arguments); this.dalamud.DalamudUi.OpenDataWindow(arguments);
} }
private void OnDebugDrawIMEPanel(string command, string arguments)
{
this.dalamud.DalamudUi.OpenIMEWindow();
}
private void OnOpenLog(string command, string arguments) private void OnOpenLog(string command, string arguments)
{ {
this.dalamud.DalamudUi.ToggleLogWindow(); this.dalamud.DalamudUi.ToggleLogWindow();

View file

@ -5,6 +5,7 @@ using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Dalamud.Interface.Internal.Windows; using Dalamud.Interface.Internal.Windows;
using Dalamud.Interface.Internal.Windows.SelfTest;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
@ -31,11 +32,13 @@ namespace Dalamud.Interface.Internal
private readonly CreditsWindow creditsWindow; private readonly CreditsWindow creditsWindow;
private readonly DataWindow dataWindow; private readonly DataWindow dataWindow;
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
private readonly IMEWindow imeWindow;
private readonly ConsoleWindow consoleWindow; private readonly ConsoleWindow consoleWindow;
private readonly PluginStatWindow pluginStatWindow; private readonly PluginStatWindow pluginStatWindow;
private readonly PluginInstallerWindow pluginWindow; private readonly PluginInstallerWindow pluginWindow;
private readonly ScratchpadWindow scratchpadWindow; private readonly ScratchpadWindow scratchpadWindow;
private readonly SettingsWindow settingsWindow; private readonly SettingsWindow settingsWindow;
private readonly SelfTestWindow selfTestWindow;
private ulong frameCount = 0; private ulong frameCount = 0;
@ -62,11 +65,13 @@ namespace Dalamud.Interface.Internal
this.creditsWindow = new CreditsWindow(dalamud) { IsOpen = false }; this.creditsWindow = new CreditsWindow(dalamud) { IsOpen = false };
this.dataWindow = new DataWindow(dalamud) { IsOpen = false }; this.dataWindow = new DataWindow(dalamud) { IsOpen = false };
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow(); this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow();
this.imeWindow = new IMEWindow(dalamud);
this.consoleWindow = new ConsoleWindow(dalamud) { IsOpen = this.dalamud.Configuration.LogOpenAtStartup }; this.consoleWindow = new ConsoleWindow(dalamud) { IsOpen = this.dalamud.Configuration.LogOpenAtStartup };
this.pluginStatWindow = new PluginStatWindow(dalamud) { IsOpen = false }; this.pluginStatWindow = new PluginStatWindow(dalamud) { IsOpen = false };
this.pluginWindow = new PluginInstallerWindow(dalamud) { IsOpen = false }; this.pluginWindow = new PluginInstallerWindow(dalamud) { IsOpen = false };
this.scratchpadWindow = new ScratchpadWindow(dalamud) { IsOpen = false }; this.scratchpadWindow = new ScratchpadWindow(dalamud) { IsOpen = false };
this.settingsWindow = new SettingsWindow(dalamud) { IsOpen = false }; this.settingsWindow = new SettingsWindow(dalamud) { IsOpen = false };
this.selfTestWindow = new SelfTestWindow(dalamud) { IsOpen = false };
this.windowSystem.AddWindow(this.changelogWindow); this.windowSystem.AddWindow(this.changelogWindow);
this.windowSystem.AddWindow(this.colorDemoWindow); this.windowSystem.AddWindow(this.colorDemoWindow);
@ -74,13 +79,15 @@ namespace Dalamud.Interface.Internal
this.windowSystem.AddWindow(this.creditsWindow); this.windowSystem.AddWindow(this.creditsWindow);
this.windowSystem.AddWindow(this.dataWindow); this.windowSystem.AddWindow(this.dataWindow);
this.windowSystem.AddWindow(this.gamepadModeNotifierWindow); this.windowSystem.AddWindow(this.gamepadModeNotifierWindow);
this.windowSystem.AddWindow(this.imeWindow);
this.windowSystem.AddWindow(this.consoleWindow); this.windowSystem.AddWindow(this.consoleWindow);
this.windowSystem.AddWindow(this.pluginStatWindow); this.windowSystem.AddWindow(this.pluginStatWindow);
this.windowSystem.AddWindow(this.pluginWindow); this.windowSystem.AddWindow(this.pluginWindow);
this.windowSystem.AddWindow(this.scratchpadWindow); this.windowSystem.AddWindow(this.scratchpadWindow);
this.windowSystem.AddWindow(this.settingsWindow); this.windowSystem.AddWindow(this.settingsWindow);
this.windowSystem.AddWindow(this.selfTestWindow);
this.dalamud.InterfaceManager.OnDraw += this.OnDraw; this.dalamud.InterfaceManager.Draw += this.OnDraw;
Log.Information("Windows added"); Log.Information("Windows added");
} }
@ -102,7 +109,7 @@ namespace Dalamud.Interface.Internal
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
this.dalamud.InterfaceManager.OnDraw -= this.OnDraw; this.dalamud.InterfaceManager.Draw -= this.OnDraw;
this.windowSystem.RemoveAllWindows(); this.windowSystem.RemoveAllWindows();
@ -156,6 +163,11 @@ namespace Dalamud.Interface.Internal
/// </summary> /// </summary>
public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="IMEWindow"/>.
/// </summary>
public void OpenIMEWindow() => this.imeWindow.IsOpen = true;
/// <summary> /// <summary>
/// Opens the <see cref="ConsoleWindow"/>. /// Opens the <see cref="ConsoleWindow"/>.
/// </summary> /// </summary>
@ -181,6 +193,20 @@ namespace Dalamud.Interface.Internal
/// </summary> /// </summary>
public void OpenSettings() => this.settingsWindow.IsOpen = true; public void OpenSettings() => this.settingsWindow.IsOpen = true;
/// <summary>
/// Opens the <see cref="SelfTestWindow"/>.
/// </summary>
public void OpenSelfTest() => this.selfTestWindow.IsOpen = true;
#endregion
#region Close
/// <summary>
/// Closes the <see cref="IMEWindow"/>.
/// </summary>
public void CloseIMEWindow() => this.imeWindow.IsOpen = false;
#endregion #endregion
#region Toggle #region Toggle
@ -228,6 +254,11 @@ namespace Dalamud.Interface.Internal
/// </summary> /// </summary>
public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle(); public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle();
/// <summary>
/// Toggles the <see cref="IMEWindow"/>.
/// </summary>
public void ToggleIMEWindow() => this.imeWindow.Toggle();
/// <summary> /// <summary>
/// Toggles the <see cref="ConsoleWindow"/>. /// Toggles the <see cref="ConsoleWindow"/>.
/// </summary> /// </summary>
@ -253,6 +284,11 @@ namespace Dalamud.Interface.Internal
/// </summary> /// </summary>
public void ToggleSettingsWindow() => this.settingsWindow.Toggle(); public void ToggleSettingsWindow() => this.settingsWindow.Toggle();
/// <summary>
/// Toggles the <see cref="SelfTestWindow"/>.
/// </summary>
public void ToggleSelfTestWindow() => this.selfTestWindow.Toggle();
#endregion #endregion
private void OnDraw() private void OnDraw()
@ -383,6 +419,13 @@ namespace Dalamud.Interface.Internal
this.OpenColorsDemoWindow(); this.OpenColorsDemoWindow();
} }
if (ImGui.MenuItem("Open Self-Test"))
{
this.OpenSelfTest();
}
ImGui.Separator();
ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow); ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow);
ImGui.Separator(); ImGui.Separator();

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
@ -7,7 +8,6 @@ using System.Text;
using System.Threading; using System.Threading;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.GamePad; using Dalamud.Game.ClientState.GamePad;
using Dalamud.Game.Internal.DXGI; using Dalamud.Game.Internal.DXGI;
using Dalamud.Hooking; using Dalamud.Hooking;
@ -71,10 +71,10 @@ namespace Dalamud.Interface.Internal
this.address = sigResolver; this.address = sigResolver;
} }
catch (Exception ex) catch (KeyNotFoundException)
{ {
// The SigScanner method fails on wine/proton since DXGI is not a real DLL. We fall back to vtable to detect our Present function address. // The SigScanner method fails on wine/proton since DXGI is not a real DLL. We fall back to vtable to detect our Present function address.
Log.Debug(ex, "Could not get SwapChain address via sig method, falling back to vtable..."); Log.Debug("Could not get SwapChain address via sig method, falling back to vtable");
var vtableResolver = new SwapChainVtableResolver(); var vtableResolver = new SwapChainVtableResolver();
vtableResolver.Setup(scanner); vtableResolver.Setup(scanner);
@ -93,7 +93,7 @@ namespace Dalamud.Interface.Internal
var fileName = new StringBuilder(255); var fileName = new StringBuilder(255);
_ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity); _ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity);
this.rtssPath = fileName.ToString(); this.rtssPath = fileName.ToString();
Log.Verbose("RTSS at {0}", this.rtssPath); Log.Verbose($"RTSS at {this.rtssPath}");
if (!NativeFunctions.FreeLibrary(rtss)) if (!NativeFunctions.FreeLibrary(rtss))
throw new Win32Exception(); throw new Win32Exception();
@ -131,7 +131,7 @@ namespace Dalamud.Interface.Internal
/// <summary> /// <summary>
/// This event gets called by a plugin UiBuilder when read /// This event gets called by a plugin UiBuilder when read
/// </summary> /// </summary>
public event RawDX11Scene.BuildUIDelegate OnDraw; public event RawDX11Scene.BuildUIDelegate Draw;
/// <summary> /// <summary>
/// Gets the default ImGui font. /// Gets the default ImGui font.
@ -397,6 +397,8 @@ namespace Dalamud.Interface.Internal
ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); ImGuiHelpers.MainViewport = ImGui.GetMainViewport();
Log.Information("[IM] Scene & ImGui setup OK!"); Log.Information("[IM] Scene & ImGui setup OK!");
this.dalamud.IME.Enable();
} }
// Process information needed by ImGuiHelpers each frame. // Process information needed by ImGuiHelpers each frame.
@ -581,8 +583,7 @@ namespace Dalamud.Interface.Internal
this.dalamud.DalamudUi.ToggleGamepadModeNotifierWindow(); this.dalamud.DalamudUi.ToggleGamepadModeNotifierWindow();
} }
if (gamepadEnabled if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
&& (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0)
{ {
ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.South); ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.South);
ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.East); ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.East);
@ -621,7 +622,7 @@ namespace Dalamud.Interface.Internal
this.LastImGuiIoPtr = ImGui.GetIO(); this.LastImGuiIoPtr = ImGui.GetIO();
this.lastWantCapture = this.LastImGuiIoPtr.WantCaptureMouse; this.lastWantCapture = this.LastImGuiIoPtr.WantCaptureMouse;
this.OnDraw?.Invoke(); this.Draw?.Invoke();
} }
} }
} }

View file

@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using ImGuiNET; using ImGuiNET;
@ -158,26 +159,38 @@ namespace Dalamud.Interface.Internal.Windows
} }
ImGui.SameLine(); ImGui.SameLine();
ImGui.PushFont(InterfaceManager.IconFont);
if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString())) if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog))
ImGui.OpenPopup("Options"); ImGui.OpenPopup("Options");
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Options");
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button(FontAwesomeIcon.Search.ToIconString())) if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
ImGui.OpenPopup("Filters"); ImGui.OpenPopup("Filters");
ImGui.SameLine(); if (ImGui.IsItemHovered())
var clear = ImGui.Button(FontAwesomeIcon.Trash.ToIconString()); ImGui.SetTooltip("Filters");
ImGui.SameLine(); ImGui.SameLine();
var copy = ImGui.Button(FontAwesomeIcon.Copy.ToIconString()); var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Clear Log");
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button(FontAwesomeIcon.Skull.ToIconString())) var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy Log");
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull))
Process.GetCurrentProcess().Kill(); Process.GetCurrentProcess().Kill();
ImGui.PopFont(); if (ImGui.IsItemHovered())
ImGui.SetTooltip("Kill game");
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.HorizontalScrollbar); ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.HorizontalScrollbar);

Some files were not shown because too many files have changed in this diff Show more